You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by mg...@apache.org on 2014/12/11 10:20:31 UTC

wicket git commit: Update QUnit to 1.16

Repository: wicket
Updated Branches:
  refs/heads/master 6009b060e -> 25f7d433e


Update QUnit to 1.16


Project: http://git-wip-us.apache.org/repos/asf/wicket/repo
Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/25f7d433
Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/25f7d433
Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/25f7d433

Branch: refs/heads/master
Commit: 25f7d433e1258f0d322a69a482a959245e66f844
Parents: 6009b06
Author: Martin Tzvetanov Grigorov <mg...@apache.org>
Authored: Thu Dec 11 11:20:04 2014 +0200
Committer: Martin Tzvetanov Grigorov <mg...@apache.org>
Committed: Thu Dec 11 11:20:04 2014 +0200

----------------------------------------------------------------------
 wicket-core/src/test/js/all.html        |   10 +-
 wicket-core/src/test/js/amd.html        |   15 +-
 wicket-core/src/test/js/qunit/qunit.css |   33 +-
 wicket-core/src/test/js/qunit/qunit.js  | 1364 ++++++++++++++++----------
 4 files changed, 878 insertions(+), 544 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/wicket/blob/25f7d433/wicket-core/src/test/js/all.html
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/js/all.html b/wicket-core/src/test/js/all.html
index 9e80d3e..366b510 100644
--- a/wicket-core/src/test/js/all.html
+++ b/wicket-core/src/test/js/all.html
@@ -27,15 +27,7 @@
 </head>
 
 <body>
-	<h1 id="qunit-header">Wicket Ajax tests</h1>
-
-	<h2 id="qunit-banner"></h2>
-
-	<div id="qunit-testrunner-toolbar"></div>
-
-	<h2 id="qunit-userAgent"></h2>
-
-	<ol id="qunit-tests"></ol>
+	<div id="qunit"></div>
 
 	<div id="qunit-fixture">
 

http://git-wip-us.apache.org/repos/asf/wicket/blob/25f7d433/wicket-core/src/test/js/amd.html
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/js/amd.html b/wicket-core/src/test/js/amd.html
index 3f54d55..094e545 100644
--- a/wicket-core/src/test/js/amd.html
+++ b/wicket-core/src/test/js/amd.html
@@ -49,20 +49,11 @@
 </head>
 
 <body>
-	<h1 id="qunit-header">Wicket AMD tests</h1>
+    <div id="qunit"></div>
 
-	<h2 id="qunit-banner"></h2>
-
-	<div id="qunit-testrunner-toolbar"></div>
-
-	<h2 id="qunit-userAgent"></h2>
-
-	<ol id="qunit-tests"></ol>
-
-	<div id="qunit-fixture">
+    <div id="qunit-fixture">
 
         <div id="amdElement"></div>
-
-	</div>
+    </div>
 </body>
 </html>

http://git-wip-us.apache.org/repos/asf/wicket/blob/25f7d433/wicket-core/src/test/js/qunit/qunit.css
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/js/qunit/qunit.css b/wicket-core/src/test/js/qunit/qunit.css
index 9437b4b..385a1ce 100644
--- a/wicket-core/src/test/js/qunit/qunit.css
+++ b/wicket-core/src/test/js/qunit/qunit.css
@@ -1,12 +1,12 @@
 /*!
- * QUnit 1.15.0
+ * QUnit 1.16.0
  * http://qunitjs.com/
  *
- * Copyright 2014 jQuery Foundation and other contributors
+ * Copyright 2006, 2014 jQuery Foundation and other contributors
  * Released under the MIT license
  * http://jquery.org/license
  *
- * Date: 2014-08-08T16:00Z
+ * Date: 2014-12-03T16:32Z
  */
 
 /** Font Family and Sizes */
@@ -91,6 +91,14 @@
 	list-style-position: inside;
 }
 
+#qunit-tests > li {
+	display: none;
+}
+
+#qunit-tests li.pass, #qunit-tests li.running, #qunit-tests li.fail {
+	display: list-item;
+}
+
 #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running  {
 	display: none;
 }
@@ -99,6 +107,10 @@
 	cursor: pointer;
 }
 
+#qunit-tests li.skipped strong {
+	cursor: default;
+}
+
 #qunit-tests li a {
 	padding: 0.5em;
 	color: #C2CCD1;
@@ -211,6 +223,21 @@
 
 #qunit-banner.qunit-fail                    { background-color: #EE5757; }
 
+/*** Skipped tests */
+
+#qunit-tests .skipped {
+	background-color: #EBECE9;
+}
+
+#qunit-tests .qunit-skipped-label {
+	background-color: #F4FF77;
+	display: inline-block;
+	font-style: normal;
+	color: #366097;
+	line-height: 1.8em;
+	padding: 0 0.5em;
+	margin: -0.4em 0.4em -0.4em 0;
+}
 
 /** Result */
 

http://git-wip-us.apache.org/repos/asf/wicket/blob/25f7d433/wicket-core/src/test/js/qunit/qunit.js
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/js/qunit/qunit.js b/wicket-core/src/test/js/qunit/qunit.js
index 474cfe5..82020d4 100644
--- a/wicket-core/src/test/js/qunit/qunit.js
+++ b/wicket-core/src/test/js/qunit/qunit.js
@@ -1,12 +1,12 @@
 /*!
- * QUnit 1.15.0
+ * QUnit 1.16.0
  * http://qunitjs.com/
  *
- * Copyright 2014 jQuery Foundation and other contributors
+ * Copyright 2006, 2014 jQuery Foundation and other contributors
  * Released under the MIT license
  * http://jquery.org/license
  *
- * Date: 2014-08-08T16:00Z
+ * Date: 2014-12-03T16:32Z
  */
 
 (function( window ) {
@@ -14,6 +14,7 @@
 var QUnit,
 	config,
 	onErrorFnPrev,
+	loggingCallbacks = {},
 	fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ),
 	toString = Object.prototype.toString,
 	hasOwn = Object.prototype.hasOwnProperty,
@@ -22,11 +23,13 @@ var QUnit,
 	now = Date.now || function() {
 		return new Date().getTime();
 	},
+	globalStartCalled = false,
+	runStarted = false,
 	setTimeout = window.setTimeout,
 	clearTimeout = window.clearTimeout,
 	defined = {
-		document: typeof window.document !== "undefined",
-		setTimeout: typeof window.setTimeout !== "undefined",
+		document: window.document !== undefined,
+		setTimeout: window.setTimeout !== undefined,
 		sessionStorage: (function() {
 			var x = "qunit-test-string";
 			try {
@@ -86,132 +89,7 @@ var QUnit,
 		return vals;
 	};
 
-// Root QUnit object.
-// `QUnit` initialized at top of scope
-QUnit = {
-
-	// call on start of module test to prepend name to all tests
-	module: function( name, testEnvironment ) {
-		config.currentModule = name;
-		config.currentModuleTestEnvironment = testEnvironment;
-		config.modules[ name ] = true;
-	},
-
-	asyncTest: function( testName, expected, callback ) {
-		if ( arguments.length === 2 ) {
-			callback = expected;
-			expected = null;
-		}
-
-		QUnit.test( testName, expected, callback, true );
-	},
-
-	test: function( testName, expected, callback, async ) {
-		var test;
-
-		if ( arguments.length === 2 ) {
-			callback = expected;
-			expected = null;
-		}
-
-		test = new Test({
-			testName: testName,
-			expected: expected,
-			async: async,
-			callback: callback,
-			module: config.currentModule,
-			moduleTestEnvironment: config.currentModuleTestEnvironment,
-			stack: sourceFromStacktrace( 2 )
-		});
-
-		if ( !validTest( test ) ) {
-			return;
-		}
-
-		test.queue();
-	},
-
-	start: function( count ) {
-		var message;
-
-		// QUnit hasn't been initialized yet.
-		// Note: RequireJS (et al) may delay onLoad
-		if ( config.semaphore === undefined ) {
-			QUnit.begin(function() {
-				// This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first
-				setTimeout(function() {
-					QUnit.start( count );
-				});
-			});
-			return;
-		}
-
-		config.semaphore -= count || 1;
-		// don't start until equal number of stop-calls
-		if ( config.semaphore > 0 ) {
-			return;
-		}
-
-		// Set the starting time when the first test is run
-		QUnit.config.started = QUnit.config.started || now();
-		// ignore if start is called more often then stop
-		if ( config.semaphore < 0 ) {
-			config.semaphore = 0;
-
-			message = "Called start() while already started (QUnit.config.semaphore was 0 already)";
-
-			if ( config.current ) {
-				QUnit.pushFailure( message, sourceFromStacktrace( 2 ) );
-			} else {
-				throw new Error( message );
-			}
-
-			return;
-		}
-		// A slight delay, to avoid any current callbacks
-		if ( defined.setTimeout ) {
-			setTimeout(function() {
-				if ( config.semaphore > 0 ) {
-					return;
-				}
-				if ( config.timeout ) {
-					clearTimeout( config.timeout );
-				}
-
-				config.blocking = false;
-				process( true );
-			}, 13 );
-		} else {
-			config.blocking = false;
-			process( true );
-		}
-	},
-
-	stop: function( count ) {
-		config.semaphore += count || 1;
-		config.blocking = true;
-
-		if ( config.testTimeout && defined.setTimeout ) {
-			clearTimeout( config.timeout );
-			config.timeout = setTimeout(function() {
-				QUnit.ok( false, "Test timed out" );
-				config.semaphore = 1;
-				QUnit.start();
-			}, config.testTimeout );
-		}
-	}
-};
-
-// We use the prototype to distinguish between properties that should
-// be exposed as globals (and in exports) and those that shouldn't
-(function() {
-	function F() {}
-	F.prototype = QUnit;
-	QUnit = new F();
-
-	// Make F QUnit's constructor so that we can add to the prototype later
-	QUnit.constructor = F;
-}());
+QUnit = {};
 
 /**
  * Config object: Maintain internal state
@@ -246,23 +124,39 @@ config = {
 	// when enabled, the id is set to `true` as a `QUnit.config` property
 	urlConfig: [
 		{
+			id: "hidepassed",
+			label: "Hide passed tests",
+			tooltip: "Only show tests and assertions that fail. Stored as query-strings."
+		},
+		{
 			id: "noglobals",
 			label: "Check for Globals",
-			tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings."
+			tooltip: "Enabling this will test if any test introduces new properties on the " +
+				"`window` object. Stored as query-strings."
 		},
 		{
 			id: "notrycatch",
 			label: "No try-catch",
-			tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings."
+			tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
+				"exceptions in IE reasonable. Stored as query-strings."
 		}
 	],
 
 	// Set of all modules.
-	modules: {},
+	modules: [],
+
+	// The first unnamed module
+	currentModule: {
+		name: "",
+		tests: []
+	},
 
 	callbacks: {}
 };
 
+// Push a loose unnamed module to the modules collection
+config.modules.push( config.currentModule );
+
 // Initialize more QUnit.config and QUnit.urlParams
 (function() {
 	var i, current,
@@ -291,17 +185,13 @@ config = {
 	// String search anywhere in moduleName+testName
 	config.filter = urlParams.filter;
 
-	// Exact match of the module name
-	config.module = urlParams.module;
-
-	config.testNumber = [];
-	if ( urlParams.testNumber ) {
+	config.testId = [];
+	if ( urlParams.testId ) {
 
-		// Ensure that urlParams.testNumber is an array
-		urlParams.testNumber = [].concat( urlParams.testNumber );
-		for ( i = 0; i < urlParams.testNumber.length; i++ ) {
-			current = urlParams.testNumber[ i ];
-			config.testNumber.push( parseInt( current, 10 ) );
+		// Ensure that urlParams.testId is an array
+		urlParams.testId = [].concat( urlParams.testId );
+		for ( i = 0; i < urlParams.testId.length; i++ ) {
+			config.testId.push( urlParams.testId[ i ] );
 		}
 	}
 
@@ -309,8 +199,130 @@ config = {
 	QUnit.isLocal = location.protocol === "file:";
 }());
 
+// Root QUnit object.
+// `QUnit` initialized at top of scope
 extend( QUnit, {
 
+	// call on start of module test to prepend name to all tests
+	module: function( name, testEnvironment ) {
+		var currentModule = {
+			name: name,
+			testEnvironment: testEnvironment,
+			tests: []
+		};
+
+		// DEPRECATED: handles setup/teardown functions,
+		// beforeEach and afterEach should be used instead
+		if ( testEnvironment && testEnvironment.setup ) {
+			testEnvironment.beforeEach = testEnvironment.setup;
+			delete testEnvironment.setup;
+		}
+		if ( testEnvironment && testEnvironment.teardown ) {
+			testEnvironment.afterEach = testEnvironment.teardown;
+			delete testEnvironment.teardown;
+		}
+
+		config.modules.push( currentModule );
+		config.currentModule = currentModule;
+	},
+
+	// DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
+	asyncTest: function( testName, expected, callback ) {
+		if ( arguments.length === 2 ) {
+			callback = expected;
+			expected = null;
+		}
+
+		QUnit.test( testName, expected, callback, true );
+	},
+
+	test: function( testName, expected, callback, async ) {
+		var test;
+
+		if ( arguments.length === 2 ) {
+			callback = expected;
+			expected = null;
+		}
+
+		test = new Test({
+			testName: testName,
+			expected: expected,
+			async: async,
+			callback: callback
+		});
+
+		test.queue();
+	},
+
+	skip: function( testName ) {
+		var test = new Test({
+			testName: testName,
+			skip: true
+		});
+
+		test.queue();
+	},
+
+	// DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
+	// In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
+	start: function( count ) {
+		var globalStartAlreadyCalled = globalStartCalled;
+
+		if ( !config.current ) {
+			globalStartCalled = true;
+
+			if ( runStarted ) {
+				throw new Error( "Called start() outside of a test context while already started" );
+			} else if ( globalStartAlreadyCalled || count > 1 ) {
+				throw new Error( "Called start() outside of a test context too many times" );
+			} else if ( config.autostart ) {
+				throw new Error( "Called start() outside of a test context when " +
+					"QUnit.config.autostart was true" );
+			} else if ( !config.pageLoaded ) {
+
+				// The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
+				config.autostart = true;
+				return;
+			}
+		} else {
+
+			// If a test is running, adjust its semaphore
+			config.current.semaphore -= count || 1;
+
+			// Don't start until equal number of stop-calls
+			if ( config.current.semaphore > 0 ) {
+				return;
+			}
+
+			// throw an Error if start is called more often than stop
+			if ( config.current.semaphore < 0 ) {
+				config.current.semaphore = 0;
+
+				QUnit.pushFailure(
+					"Called start() while already started (test's semaphore was 0 already)",
+					sourceFromStacktrace( 2 )
+				);
+				return;
+			}
+		}
+
+		resumeProcessing();
+	},
+
+	// DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
+	stop: function( count ) {
+
+		// If there isn't a test running, don't allow QUnit.stop() to be called
+		if ( !config.current ) {
+			throw new Error( "Called stop() outside of a test context" );
+		}
+
+		// If a test is running, adjust its semaphore
+		config.current.semaphore += count || 1;
+
+		pauseProcessing();
+	},
+
 	config: config,
 
 	// Safe object type checking
@@ -358,71 +370,76 @@ extend( QUnit, {
 
 		for ( key in params ) {
 			if ( hasOwn.call( params, key ) ) {
-				querystring += encodeURIComponent( key ) + "=" +
-					encodeURIComponent( params[ key ] ) + "&";
+				querystring += encodeURIComponent( key );
+				if ( params[ key ] !== true ) {
+					querystring += "=" + encodeURIComponent( params[ key ] );
+				}
+				querystring += "&";
 			}
 		}
-		return window.location.protocol + "//" + window.location.host +
-			window.location.pathname + querystring.slice( 0, -1 );
+		return location.protocol + "//" + location.host +
+			location.pathname + querystring.slice( 0, -1 );
 	},
 
-	extend: extend
-});
-
-/**
- * @deprecated: Created for backwards compatibility with test runner that set the hook function
- * into QUnit.{hook}, instead of invoking it and passing the hook function.
- * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here.
- * Doing this allows us to tell if the following methods have been overwritten on the actual
- * QUnit object.
- */
-extend( QUnit.constructor.prototype, {
+	extend: extend,
 
-	// Logging callbacks; all receive a single argument with the listed properties
-	// run test/logs.html for any related changes
-	begin: registerLoggingCallback( "begin" ),
+	load: function() {
+		config.pageLoaded = true;
 
-	// done: { failed, passed, total, runtime }
-	done: registerLoggingCallback( "done" ),
+		// Initialize the configuration options
+		extend( config, {
+			stats: { all: 0, bad: 0 },
+			moduleStats: { all: 0, bad: 0 },
+			started: 0,
+			updateRate: 1000,
+			autostart: true,
+			filter: ""
+		}, true );
 
-	// log: { result, actual, expected, message }
-	log: registerLoggingCallback( "log" ),
+		config.blocking = false;
 
-	// testStart: { name }
-	testStart: registerLoggingCallback( "testStart" ),
+		if ( config.autostart ) {
+			resumeProcessing();
+		}
+	}
+});
 
-	// testDone: { name, failed, passed, total, runtime }
-	testDone: registerLoggingCallback( "testDone" ),
+// Register logging callbacks
+(function() {
+	var i, l, key,
+		callbacks = [ "begin", "done", "log", "testStart", "testDone",
+			"moduleStart", "moduleDone" ];
+
+	function registerLoggingCallback( key ) {
+		var loggingCallback = function( callback ) {
+			if ( QUnit.objectType( callback ) !== "function" ) {
+				throw new Error(
+					"QUnit logging methods require a callback function as their first parameters."
+				);
+			}
 
-	// moduleStart: { name }
-	moduleStart: registerLoggingCallback( "moduleStart" ),
+			config.callbacks[ key ].push( callback );
+		};
 
-	// moduleDone: { name, failed, passed, total }
-	moduleDone: registerLoggingCallback( "moduleDone" )
-});
+		// DEPRECATED: This will be removed on QUnit 2.0.0+
+		// Stores the registered functions allowing restoring
+		// at verifyLoggingCallbacks() if modified
+		loggingCallbacks[ key ] = loggingCallback;
 
-QUnit.load = function() {
-	runLoggingCallbacks( "begin", {
-		totalTests: Test.count
-	});
+		return loggingCallback;
+	}
 
-	// Initialize the configuration options
-	extend( config, {
-		stats: { all: 0, bad: 0 },
-		moduleStats: { all: 0, bad: 0 },
-		started: 0,
-		updateRate: 1000,
-		autostart: true,
-		filter: "",
-		semaphore: 1
-	}, true );
+	for ( i = 0, l = callbacks.length; i < l; i++ ) {
+		key = callbacks[ i ];
 
-	config.blocking = false;
+		// Initialize key collection of logging callback
+		if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
+			config.callbacks[ key ] = [];
+		}
 
-	if ( config.autostart ) {
-		QUnit.start();
+		QUnit[ key ] = registerLoggingCallback( key );
 	}
-};
+})();
 
 // `onErrorFnPrev` initialized at top of scope
 // Preserve other handlers
@@ -448,7 +465,7 @@ window.onerror = function( error, filePath, linerNr ) {
 		} else {
 			QUnit.test( "global failure", extend(function() {
 				QUnit.pushFailure( error, filePath + ":" + linerNr );
-			}, { validTest: validTest } ) );
+			}, { validTest: true } ) );
 		}
 		return false;
 	}
@@ -457,21 +474,25 @@ window.onerror = function( error, filePath, linerNr ) {
 };
 
 function done() {
+	var runtime, passed;
+
 	config.autorun = true;
 
 	// Log the last module results
 	if ( config.previousModule ) {
 		runLoggingCallbacks( "moduleDone", {
-			name: config.previousModule,
+			name: config.previousModule.name,
+			tests: config.previousModule.tests,
 			failed: config.moduleStats.bad,
 			passed: config.moduleStats.all - config.moduleStats.bad,
-			total: config.moduleStats.all
+			total: config.moduleStats.all,
+			runtime: now() - config.moduleStats.started
 		});
 	}
 	delete config.previousModule;
 
-	var runtime = now() - config.started,
-		passed = config.stats.all - config.stats.bad;
+	runtime = now() - config.started;
+	passed = config.stats.all - config.stats.bad;
 
 	runLoggingCallbacks( "done", {
 		failed: config.stats.bad,
@@ -481,47 +502,6 @@ function done() {
 	});
 }
 
-/** @return Boolean: true if this test should be ran */
-function validTest( test ) {
-	var include,
-		filter = config.filter && config.filter.toLowerCase(),
-		module = config.module && config.module.toLowerCase(),
-		fullName = ( test.module + ": " + test.testName ).toLowerCase();
-
-	// Internally-generated tests are always valid
-	if ( test.callback && test.callback.validTest === validTest ) {
-		delete test.callback.validTest;
-		return true;
-	}
-
-	if ( config.testNumber.length > 0 ) {
-		if ( inArray( test.testNumber, config.testNumber ) < 0 ) {
-			return false;
-		}
-	}
-
-	if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
-		return false;
-	}
-
-	if ( !filter ) {
-		return true;
-	}
-
-	include = filter.charAt( 0 ) !== "!";
-	if ( !include ) {
-		filter = filter.slice( 1 );
-	}
-
-	// If the filter matches, we need to honour include
-	if ( fullName.indexOf( filter ) !== -1 ) {
-		return include;
-	}
-
-	// Otherwise, do the opposite
-	return !include;
-}
-
 // Doesn't support IE6 to IE9
 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
 function extractStacktrace( e, offset ) {
@@ -565,15 +545,27 @@ function extractStacktrace( e, offset ) {
 		return e.sourceURL + ":" + e.line;
 	}
 }
+
 function sourceFromStacktrace( offset ) {
-	try {
-		throw new Error();
-	} catch ( e ) {
-		return extractStacktrace( e, offset );
+	var e = new Error();
+	if ( !e.stack ) {
+		try {
+			throw e;
+		} catch ( err ) {
+			// This should already be true in most browsers
+			e = err;
+		}
 	}
+	return extractStacktrace( e, offset );
 }
 
 function synchronize( callback, last ) {
+	if ( QUnit.objectType( callback ) === "array" ) {
+		while ( callback.length ) {
+			synchronize( callback.shift() );
+		}
+		return;
+	}
 	config.queue.push( callback );
 
 	if ( config.autorun && !config.blocking ) {
@@ -589,7 +581,13 @@ function process( last ) {
 	config.depth = config.depth ? config.depth + 1 : 1;
 
 	while ( config.queue.length && !config.blocking ) {
-		if ( !defined.setTimeout || config.updateRate <= 0 || ( ( now() - start ) < config.updateRate ) ) {
+		if ( !defined.setTimeout || config.updateRate <= 0 ||
+				( ( now() - start ) < config.updateRate ) ) {
+			if ( config.current ) {
+
+				// Reset async tracking for each phase of the Test lifecycle
+				config.current.usedAsync = false;
+			}
 			config.queue.shift()();
 		} else {
 			setTimeout( next, 13 );
@@ -602,6 +600,79 @@ function process( last ) {
 	}
 }
 
+function begin() {
+	var i, l,
+		modulesLog = [];
+
+	// If the test run hasn't officially begun yet
+	if ( !config.started ) {
+
+		// Record the time of the test run's beginning
+		config.started = now();
+
+		verifyLoggingCallbacks();
+
+		// Delete the loose unnamed module if unused.
+		if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
+			config.modules.shift();
+		}
+
+		// Avoid unnecessary information by not logging modules' test environments
+		for ( i = 0, l = config.modules.length; i < l; i++ ) {
+			modulesLog.push({
+				name: config.modules[ i ].name,
+				tests: config.modules[ i ].tests
+			});
+		}
+
+		// The test run is officially beginning now
+		runLoggingCallbacks( "begin", {
+			totalTests: Test.count,
+			modules: modulesLog
+		});
+	}
+
+	config.blocking = false;
+	process( true );
+}
+
+function resumeProcessing() {
+	runStarted = true;
+
+	// A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
+	if ( defined.setTimeout ) {
+		setTimeout(function() {
+			if ( config.current && config.current.semaphore > 0 ) {
+				return;
+			}
+			if ( config.timeout ) {
+				clearTimeout( config.timeout );
+			}
+
+			begin();
+		}, 13 );
+	} else {
+		begin();
+	}
+}
+
+function pauseProcessing() {
+	config.blocking = true;
+
+	if ( config.testTimeout && defined.setTimeout ) {
+		clearTimeout( config.timeout );
+		config.timeout = setTimeout(function() {
+			if ( config.current ) {
+				config.current.semaphore = 0;
+				QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
+			} else {
+				throw new Error( "Test timed out" );
+			}
+			resumeProcessing();
+		}, config.testTimeout );
+	}
+}
+
 function saveGlobal() {
 	config.pollution = [];
 
@@ -671,18 +742,6 @@ function extend( a, b, undefOnly ) {
 	return a;
 }
 
-function registerLoggingCallback( key ) {
-
-	// Initialize key collection of logging callback
-	if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
-		config.callbacks[ key ] = [];
-	}
-
-	return function( callback ) {
-		config.callbacks[ key ].push( callback );
-	};
-}
-
 function runLoggingCallbacks( key, args ) {
 	var i, l, callbacks;
 
@@ -692,6 +751,34 @@ function runLoggingCallbacks( key, args ) {
 	}
 }
 
+// DEPRECATED: This will be removed on 2.0.0+
+// This function verifies if the loggingCallbacks were modified by the user
+// If so, it will restore it, assign the given callback and print a console warning
+function verifyLoggingCallbacks() {
+	var loggingCallback, userCallback;
+
+	for ( loggingCallback in loggingCallbacks ) {
+		if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
+
+			userCallback = QUnit[ loggingCallback ];
+
+			// Restore the callback function
+			QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
+
+			// Assign the deprecated given callback
+			QUnit[ loggingCallback ]( userCallback );
+
+			if ( window.console && window.console.warn ) {
+				window.console.warn(
+					"QUnit." + loggingCallback + " was replaced with a new value.\n" +
+					"Please, check out the documentation on how to apply logging callbacks.\n" +
+					"Reference: http://api.qunitjs.com/category/callbacks/"
+				);
+			}
+		}
+	}
+}
+
 // from jquery.js
 function inArray( elem, array ) {
 	if ( array.indexOf ) {
@@ -708,16 +795,46 @@ function inArray( elem, array ) {
 }
 
 function Test( settings ) {
+	var i, l;
+
+	++Test.count;
+
 	extend( this, settings );
-	this.assert = new Assert( this );
 	this.assertions = [];
-	this.testNumber = ++Test.count;
+	this.semaphore = 0;
+	this.usedAsync = false;
+	this.module = config.currentModule;
+	this.stack = sourceFromStacktrace( 3 );
+
+	// Register unique strings
+	for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
+		if ( this.module.tests[ i ].name === this.testName ) {
+			this.testName += " ";
+		}
+	}
+
+	this.testId = generateHash( this.module.name, this.testName );
+
+	this.module.tests.push({
+		name: this.testName,
+		testId: this.testId
+	});
+
+	if ( settings.skip ) {
+
+		// Skipped tests will fully ignore any sent callback
+		this.callback = function() {};
+		this.async = false;
+		this.expected = 0;
+	} else {
+		this.assert = new Assert( this );
+	}
 }
 
 Test.count = 0;
 
 Test.prototype = {
-	setup: function() {
+	before: function() {
 		if (
 
 			// Emit moduleStart when we're switching from one module to another
@@ -731,47 +848,43 @@ Test.prototype = {
 		) {
 			if ( hasOwn.call( config, "previousModule" ) ) {
 				runLoggingCallbacks( "moduleDone", {
-					name: config.previousModule,
+					name: config.previousModule.name,
+					tests: config.previousModule.tests,
 					failed: config.moduleStats.bad,
 					passed: config.moduleStats.all - config.moduleStats.bad,
-					total: config.moduleStats.all
+					total: config.moduleStats.all,
+					runtime: now() - config.moduleStats.started
 				});
 			}
 			config.previousModule = this.module;
-			config.moduleStats = { all: 0, bad: 0 };
+			config.moduleStats = { all: 0, bad: 0, started: now() };
 			runLoggingCallbacks( "moduleStart", {
-				name: this.module
+				name: this.module.name,
+				tests: this.module.tests
 			});
 		}
 
 		config.current = this;
 
-		this.testEnvironment = extend({
-			setup: function() {},
-			teardown: function() {}
-		}, this.moduleTestEnvironment );
+		this.testEnvironment = extend( {}, this.module.testEnvironment );
+		delete this.testEnvironment.beforeEach;
+		delete this.testEnvironment.afterEach;
 
 		this.started = now();
 		runLoggingCallbacks( "testStart", {
 			name: this.testName,
-			module: this.module,
-			testNumber: this.testNumber
+			module: this.module.name,
+			testId: this.testId
 		});
 
 		if ( !config.pollution ) {
 			saveGlobal();
 		}
-		if ( config.notrycatch ) {
-			this.testEnvironment.setup.call( this.testEnvironment, this.assert );
-			return;
-		}
-		try {
-			this.testEnvironment.setup.call( this.testEnvironment, this.assert );
-		} catch ( e ) {
-			this.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
-		}
 	},
+
 	run: function() {
+		var promise;
+
 		config.current = this;
 
 		if ( this.async ) {
@@ -781,18 +894,17 @@ Test.prototype = {
 		this.callbackStarted = now();
 
 		if ( config.notrycatch ) {
-			this.callback.call( this.testEnvironment, this.assert );
-			this.callbackRuntime = now() - this.callbackStarted;
+			promise = this.callback.call( this.testEnvironment, this.assert );
+			this.resolvePromise( promise );
 			return;
 		}
 
 		try {
-			this.callback.call( this.testEnvironment, this.assert );
-			this.callbackRuntime = now() - this.callbackStarted;
+			promise = this.callback.call( this.testEnvironment, this.assert );
+			this.resolvePromise( promise );
 		} catch ( e ) {
-			this.callbackRuntime = now() - this.callbackStarted;
-
-			this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
+			this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
+				this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
 
 			// else next test will carry the responsibility
 			saveGlobal();
@@ -803,31 +915,59 @@ Test.prototype = {
 			}
 		}
 	},
-	teardown: function() {
-		config.current = this;
-		if ( config.notrycatch ) {
-			if ( typeof this.callbackRuntime === "undefined" ) {
-				this.callbackRuntime = now() - this.callbackStarted;
+
+	after: function() {
+		checkPollution();
+	},
+
+	queueHook: function( hook, hookName ) {
+		var promise,
+			test = this;
+		return function runHook() {
+			config.current = test;
+			if ( config.notrycatch ) {
+				promise = hook.call( test.testEnvironment, test.assert );
+				test.resolvePromise( promise, hookName );
+				return;
 			}
-			this.testEnvironment.teardown.call( this.testEnvironment, this.assert );
-			return;
-		} else {
 			try {
-				this.testEnvironment.teardown.call( this.testEnvironment, this.assert );
-			} catch ( e ) {
-				this.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
+				promise = hook.call( test.testEnvironment, test.assert );
+				test.resolvePromise( promise, hookName );
+			} catch ( error ) {
+				test.pushFailure( hookName + " failed on " + test.testName + ": " +
+					( error.message || error ), extractStacktrace( error, 0 ) );
 			}
+		};
+	},
+
+	// Currently only used for module level hooks, can be used to add global level ones
+	hooks: function( handler ) {
+		var hooks = [];
+
+		// Hooks are ignored on skipped tests
+		if ( this.skip ) {
+			return hooks;
 		}
-		checkPollution();
+
+		if ( this.module.testEnvironment &&
+				QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
+			hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
+		}
+
+		return hooks;
 	},
+
 	finish: function() {
 		config.current = this;
 		if ( config.requireExpects && this.expected === null ) {
-			this.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
+			this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
+				"not called.", this.stack );
 		} else if ( this.expected !== null && this.expected !== this.assertions.length ) {
-			this.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
+			this.pushFailure( "Expected " + this.expected + " assertions, but " +
+				this.assertions.length + " were run", this.stack );
 		} else if ( this.expected === null && !this.assertions.length ) {
-			this.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
+			this.pushFailure( "Expected at least one assertion, but none were run - call " +
+				"expect(0) to accept zero assertions.", this.stack );
 		}
 
 		var i,
@@ -847,7 +987,8 @@ Test.prototype = {
 
 		runLoggingCallbacks( "testDone", {
 			name: this.testName,
-			module: this.module,
+			module: this.module.name,
+			skipped: !!this.skip,
 			failed: bad,
 			passed: this.assertions.length - bad,
 			total: this.assertions.length,
@@ -855,12 +996,17 @@ Test.prototype = {
 
 			// HTML Reporter use
 			assertions: this.assertions,
-			testNumber: this.testNumber,
+			testId: this.testId,
 
 			// DEPRECATED: this property will be removed in 2.0.0, use runtime instead
 			duration: this.runtime
 		});
 
+		// QUnit.reset() is deprecated and will be replaced for a new
+		// fixture reset function on QUnit 2.0/2.1.
+		// It's still called here for backwards compatibility handling
+		QUnit.reset();
+
 		config.current = undefined;
 	},
 
@@ -868,26 +1014,39 @@ Test.prototype = {
 		var bad,
 			test = this;
 
+		if ( !this.valid() ) {
+			return;
+		}
+
 		function run() {
+
 			// each of these can by async
-			synchronize(function() {
-				test.setup();
-			});
-			synchronize(function() {
-				test.run();
-			});
-			synchronize(function() {
-				test.teardown();
-			});
-			synchronize(function() {
-				test.finish();
-			});
+			synchronize([
+				function() {
+					test.before();
+				},
+
+				test.hooks( "beforeEach" ),
+
+				function() {
+					test.run();
+				},
+
+				test.hooks( "afterEach" ).reverse(),
+
+				function() {
+					test.after();
+				},
+				function() {
+					test.finish();
+				}
+			]);
 		}
 
 		// `bad` initialized at top of scope
 		// defer when previous test run passed, if storage is available
 		bad = QUnit.config.reorder && defined.sessionStorage &&
-				+sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName );
+				+sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
 
 		if ( bad ) {
 			run();
@@ -899,13 +1058,14 @@ Test.prototype = {
 	push: function( result, actual, expected, message ) {
 		var source,
 			details = {
-				module: this.module,
+				module: this.module.name,
 				name: this.testName,
 				result: result,
 				message: message,
 				actual: actual,
 				expected: expected,
-				testNumber: this.testNumber
+				testId: this.testId,
+				runtime: now() - this.started
 			};
 
 		if ( !result ) {
@@ -926,16 +1086,18 @@ Test.prototype = {
 
 	pushFailure: function( message, source, actual ) {
 		if ( !this instanceof Test ) {
-			throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace( 2 ) );
+			throw new Error( "pushFailure() assertion outside test context, was " +
+				sourceFromStacktrace( 2 ) );
 		}
 
 		var details = {
-				module: this.module,
+				module: this.module.name,
 				name: this.testName,
 				result: false,
 				message: message || "error",
 				actual: actual || null,
-				testNumber: this.testNumber
+				testId: this.testId,
+				runtime: now() - this.started
 			};
 
 		if ( source ) {
@@ -948,20 +1110,132 @@ Test.prototype = {
 			result: false,
 			message: message
 		});
+	},
+
+	resolvePromise: function( promise, phase ) {
+		var then, message,
+			test = this;
+		if ( promise != null ) {
+			then = promise.then;
+			if ( QUnit.objectType( then ) === "function" ) {
+				QUnit.stop();
+				then.call(
+					promise,
+					QUnit.start,
+					function( error ) {
+						message = "Promise rejected " +
+							( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
+							" " + test.testName + ": " + ( error.message || error );
+						test.pushFailure( message, extractStacktrace( error, 0 ) );
+
+						// else next test will carry the responsibility
+						saveGlobal();
+
+						// Unblock
+						QUnit.start();
+					}
+				);
+			}
+		}
+	},
+
+	valid: function() {
+		var include,
+			filter = config.filter && config.filter.toLowerCase(),
+			module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
+			fullName = ( this.module.name + ": " + this.testName ).toLowerCase();
+
+		// Internally-generated tests are always valid
+		if ( this.callback && this.callback.validTest ) {
+			return true;
+		}
+
+		if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
+			return false;
+		}
+
+		if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {
+			return false;
+		}
+
+		if ( !filter ) {
+			return true;
+		}
+
+		include = filter.charAt( 0 ) !== "!";
+		if ( !include ) {
+			filter = filter.slice( 1 );
+		}
+
+		// If the filter matches, we need to honour include
+		if ( fullName.indexOf( filter ) !== -1 ) {
+			return include;
+		}
+
+		// Otherwise, do the opposite
+		return !include;
+	}
+
+};
+
+// Resets the test setup. Useful for tests that modify the DOM.
+/*
+DEPRECATED: Use multiple tests instead of resetting inside a test.
+Use testStart or testDone for custom cleanup.
+This method will throw an error in 2.0, and will be removed in 2.1
+*/
+QUnit.reset = function() {
+
+	// Return on non-browser environments
+	// This is necessary to not break on node tests
+	if ( typeof window === "undefined" ) {
+		return;
+	}
+
+	var fixture = defined.document && document.getElementById &&
+			document.getElementById( "qunit-fixture" );
+
+	if ( fixture ) {
+		fixture.innerHTML = config.fixture;
 	}
 };
 
 QUnit.pushFailure = function() {
 	if ( !QUnit.config.current ) {
-		throw new Error( "pushFailure() assertion outside test context, in " + sourceFromStacktrace( 2 ) );
+		throw new Error( "pushFailure() assertion outside test context, in " +
+			sourceFromStacktrace( 2 ) );
 	}
 
 	// Gets current test obj
-	var currentTest = QUnit.config.current.assert.test;
+	var currentTest = QUnit.config.current;
 
 	return currentTest.pushFailure.apply( currentTest, arguments );
 };
 
+// Based on Java's String.hashCode, a simple but not
+// rigorously collision resistant hashing function
+function generateHash( module, testName ) {
+	var hex,
+		i = 0,
+		hash = 0,
+		str = module + "\x1C" + testName,
+		len = str.length;
+
+	for ( ; i < len; i++ ) {
+		hash  = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
+		hash |= 0;
+	}
+
+	// Convert the possibly negative integer hash code into an 8 character hex string, which isn't
+	// strictly necessary but increases user understanding that the id is a SHA-like hash
+	hex = ( 0x100000000 + hash ).toString( 16 );
+	if ( hex.length < 8 ) {
+		hex = "0000000" + hex;
+	}
+
+	return hex.slice( -8 );
+}
+
 function Assert( testContext ) {
 	this.test = testContext;
 }
@@ -969,7 +1243,8 @@ function Assert( testContext ) {
 // Assert helpers
 QUnit.assert = Assert.prototype = {
 
-	// Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through.
+	// Specify the number of expected assertions to guarantee that failed test
+	// (no assertions are run at all) don't slip through.
 	expect: function( asserts ) {
 		if ( arguments.length === 1 ) {
 			this.test.expected = asserts;
@@ -978,20 +1253,51 @@ QUnit.assert = Assert.prototype = {
 		}
 	},
 
+	// Increment this Test's semaphore counter, then return a single-use function that
+	// decrements that counter a maximum of once.
+	async: function() {
+		var test = this.test,
+			popped = false;
+
+		test.semaphore += 1;
+		test.usedAsync = true;
+		pauseProcessing();
+
+		return function done() {
+			if ( !popped ) {
+				test.semaphore -= 1;
+				popped = true;
+				resumeProcessing();
+			} else {
+				test.pushFailure( "Called the callback returned from `assert.async` more than once",
+					sourceFromStacktrace( 2 ) );
+			}
+		};
+	},
+
 	// Exports test.push() to the user API
-	push: function() {
-		var assert = this;
+	push: function( /* result, actual, expected, message */ ) {
+		var assert = this,
+			currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
 
 		// Backwards compatibility fix.
 		// Allows the direct use of global exported assertions and QUnit.assert.*
 		// Although, it's use is not recommended as it can leak assertions
 		// to other tests from async tests, because we only get a reference to the current test,
 		// not exactly the test where assertion were intended to be called.
-		if ( !QUnit.config.current ) {
+		if ( !currentTest ) {
 			throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
 		}
+
+		if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
+			currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
+				sourceFromStacktrace( 2 ) );
+
+			// Allow this assertion to continue running anyway...
+		}
+
 		if ( !( assert instanceof Assert ) ) {
-			assert = QUnit.config.current.assert;
+			assert = currentTest.assert;
 		}
 		return assert.test.push.apply( assert.test, arguments );
 	},
@@ -1005,11 +1311,7 @@ QUnit.assert = Assert.prototype = {
 	ok: function( result, message ) {
 		message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
 			QUnit.dump.parse( result ) );
-		if ( !!result ) {
-			this.push( true, result, true, message );
-		} else {
-			this.test.pushFailure( message, null, result );
-		}
+		this.push( !!result, result, true, message );
 	},
 
 	/**
@@ -1017,7 +1319,7 @@ QUnit.assert = Assert.prototype = {
 	 * Prints out both actual and expected values.
 	 * @name equal
 	 * @function
-	 * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
+	 * @example equal( format( "{0} bytes.", 2), "2 bytes.", "replaces {0} with next argument" );
 	 */
 	equal: function( actual, expected, message ) {
 		/*jshint eqeqeq:false */
@@ -1143,6 +1445,13 @@ QUnit.assert = Assert.prototype = {
 	}
 };
 
+// Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word
+// Known to us are: Closure Compiler, Narwhal
+(function() {
+	/*jshint sub:true */
+	Assert.prototype.raises = Assert.prototype[ "throws" ];
+}());
+
 // Test for equality any JavaScript type.
 // Author: Philippe Rathé <pr...@gmail.com>
 QUnit.equiv = (function() {
@@ -1356,7 +1665,8 @@ QUnit.equiv = (function() {
 			}
 
 			// apply transition with (1..n) arguments
-		}( args[ 0 ], args[ 1 ] ) ) && innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
+		}( args[ 0 ], args[ 1 ] ) ) &&
+			innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
 	};
 
 	return innerEquiv;
@@ -1386,6 +1696,11 @@ QUnit.dump = (function() {
 	function array( arr, stack ) {
 		var i = arr.length,
 			ret = new Array( i );
+
+		if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
+			return "[object Array]";
+		}
+
 		this.up();
 		while ( i-- ) {
 			ret[ i ] = this.parse( arr[ i ], undefined, stack );
@@ -1396,25 +1711,28 @@ QUnit.dump = (function() {
 
 	var reName = /^function (\w+)/,
 		dump = {
-			// type is used mostly internally, you can fix a (custom)type in advance
-			parse: function( obj, type, stack ) {
-				stack = stack || [];
-				var inStack, res,
-					parser = this.parsers[ type || this.typeOf( obj ) ];
 
-				type = typeof parser;
-				inStack = inArray( obj, stack );
+			// objType is used mostly internally, you can fix a (custom) type in advance
+			parse: function( obj, objType, stack ) {
+				stack = stack || [];
+				var res, parser, parserType,
+					inStack = inArray( obj, stack );
 
 				if ( inStack !== -1 ) {
 					return "recursion(" + ( inStack - stack.length ) + ")";
 				}
-				if ( type === "function" ) {
+
+				objType = objType || this.typeOf( obj  );
+				parser = this.parsers[ objType ];
+				parserType = typeof parser;
+
+				if ( parserType === "function" ) {
 					stack.push( obj );
 					res = parser.call( this, obj, stack );
 					stack.pop();
 					return res;
 				}
-				return ( type === "string" ) ? parser : this.parsers.error;
+				return ( parserType === "string" ) ? parser : this.parsers.error;
 			},
 			typeOf: function( obj ) {
 				var type;
@@ -1428,7 +1746,9 @@ QUnit.dump = (function() {
 					type = "date";
 				} else if ( QUnit.is( "function", obj ) ) {
 					type = "function";
-				} else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) {
+				} else if ( obj.setInterval !== undefined &&
+						obj.document !== undefined &&
+						obj.nodeType === undefined ) {
 					type = "window";
 				} else if ( obj.nodeType === 9 ) {
 					type = "document";
@@ -1440,7 +1760,9 @@ QUnit.dump = (function() {
 					toString.call( obj ) === "[object Array]" ||
 
 					// NodeList objects
-					( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null && typeof obj[ 0 ] === "undefined" ) ) )
+					( typeof obj.length === "number" && obj.item !== undefined &&
+					( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
+					obj[ 0 ] === undefined ) ) )
 				) {
 					type = "array";
 				} else if ( obj.constructor === Error.prototype.constructor ) {
@@ -1451,7 +1773,7 @@ QUnit.dump = (function() {
 				return type;
 			},
 			separator: function() {
-				return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&nbsp;" : " ";
+				return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
 			},
 			// extra can be a number, shortcut for increasing-calling-decreasing
 			indent: function( extra ) {
@@ -1460,7 +1782,7 @@ QUnit.dump = (function() {
 				}
 				var chr = this.indentChar;
 				if ( this.HTML ) {
-					chr = chr.replace( /\t/g, "   " ).replace( / /g, "&nbsp;" );
+					chr = chr.replace( /\t/g, "   " ).replace( / /g, "&#160;" );
 				}
 				return new Array( this.depth + ( extra || 0 ) ).join( chr );
 			},
@@ -1479,6 +1801,8 @@ QUnit.dump = (function() {
 			join: join,
 			//
 			depth: 1,
+			maxDepth: 5,
+
 			// This is the list of parsers, to modify them, use dump.setParser
 			parsers: {
 				window: "[Window]",
@@ -1491,6 +1815,7 @@ QUnit.dump = (function() {
 				"undefined": "undefined",
 				"function": function( fn ) {
 					var ret = "function",
+
 						// functions never have name in IE
 						name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
 
@@ -1506,8 +1831,13 @@ QUnit.dump = (function() {
 				nodelist: array,
 				"arguments": array,
 				object: function( map, stack ) {
-					/*jshint forin:false */
-					var ret = [], keys, key, val, i, nonEnumerableProperties;
+					var keys, key, val, i, nonEnumerableProperties,
+						ret = [];
+
+					if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
+						return "[object Object]";
+					}
+
 					dump.up();
 					keys = [];
 					for ( key in map ) {
@@ -1526,7 +1856,8 @@ QUnit.dump = (function() {
 					for ( i = 0; i < keys.length; i++ ) {
 						key = keys[ i ];
 						val = map[ key ];
-						ret.push( dump.parse( key, "key" ) + ": " + dump.parse( val, undefined, stack ) );
+						ret.push( dump.parse( key, "key" ) + ": " +
+							dump.parse( val, undefined, stack ) );
 					}
 					dump.down();
 					return join( "{", ret, "}" );
@@ -1543,10 +1874,12 @@ QUnit.dump = (function() {
 						for ( i = 0, len = attrs.length; i < len; i++ ) {
 							val = attrs[ i ].nodeValue;
 
-							// IE6 includes all attributes in .attributes, even ones not explicitly set.
-							// Those have values like undefined, null, 0, false, "" or "inherit".
+							// IE6 includes all attributes in .attributes, even ones not explicitly
+							// set. Those have values like undefined, null, 0, false, "" or
+							// "inherit".
 							if ( val && val !== "inherit" ) {
-								ret += " " + attrs[ i ].nodeName + "=" + dump.parse( val, "attribute" );
+								ret += " " + attrs[ i ].nodeName + "=" +
+									dump.parse( val, "attribute" );
 							}
 						}
 					}
@@ -1653,17 +1986,23 @@ if ( typeof window !== "undefined" ) {
 	window.QUnit = QUnit;
 }
 
-// For CommonJS environments, export everything
+// For nodejs
 if ( typeof module !== "undefined" && module.exports ) {
 	module.exports = QUnit;
 }
 
+// For CommonJS with exports, but without module.exports, like Rhino
+if ( typeof exports !== "undefined" ) {
+	exports.QUnit = QUnit;
+}
+
 // Get a reference to the global object, like window in browsers
 }( (function() {
 	return this;
 })() ));
 
 /*istanbul ignore next */
+// jscs:disable maximumLineLength
 /*
  * Javascript Diff Algorithm
  *  By John Resig (http://ejohn.org/)
@@ -1810,6 +2149,7 @@ QUnit.diff = (function() {
 		return str;
 	};
 }());
+// jscs:enable
 
 (function() {
 
@@ -1828,7 +2168,6 @@ QUnit.init = function() {
 	config.autorun = false;
 	config.filter = "";
 	config.queue = [];
-	config.semaphore = 1;
 
 	// Return on non-browser environments
 	// This is necessary to not break on node tests
@@ -1867,27 +2206,7 @@ QUnit.init = function() {
 		result.id = "qunit-testresult";
 		result.className = "result";
 		tests.parentNode.insertBefore( result, tests );
-		result.innerHTML = "Running...<br/>&nbsp;";
-	}
-};
-
-// Resets the test setup. Useful for tests that modify the DOM.
-/*
-DEPRECATED: Use multiple tests instead of resetting inside a test.
-Use testStart or testDone for custom cleanup.
-This method will throw an error in 2.0, and will be removed in 2.1
-*/
-QUnit.reset = function() {
-
-	// Return on non-browser environments
-	// This is necessary to not break on node tests
-	if ( typeof window === "undefined" ) {
-		return;
-	}
-
-	var fixture = id( "qunit-fixture" );
-	if ( fixture ) {
-		fixture.innerHTML = config.fixture;
+		result.innerHTML = "Running...<br />&#160;";
 	}
 };
 
@@ -1899,7 +2218,7 @@ if ( typeof window === "undefined" ) {
 var config = QUnit.config,
 	hasOwn = Object.prototype.hasOwnProperty,
 	defined = {
-		document: typeof window.document !== "undefined",
+		document: window.document !== undefined,
 		sessionStorage: (function() {
 			var x = "qunit-test-string";
 			try {
@@ -1910,7 +2229,8 @@ var config = QUnit.config,
 				return false;
 			}
 		}())
-	};
+	},
+	modulesList = [];
 
 /**
 * Escape text for attribute or text content.
@@ -2026,7 +2346,7 @@ function getUrlConfigHtml() {
 				"' name='" + escaped + "' type='checkbox'" +
 				( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
 				( config[ val.id ] ? " checked='checked'" : "" ) +
-				" title='" + escapedTooltip + "'><label for='qunit-urlconfig-" + escaped +
+				" title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
 				"' title='" + escapedTooltip + "'>" + val.label + "</label>";
 		} else {
 			urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
@@ -2064,6 +2384,38 @@ function getUrlConfigHtml() {
 	return urlConfigHtml;
 }
 
+// Handle "click" events on toolbar checkboxes and "change" for select menus.
+// Updates the URL with the new state of `config.urlConfig` values.
+function toolbarChanged() {
+	var updatedUrl, value,
+		field = this,
+		params = {};
+
+	// Detect if field is a select menu or a checkbox
+	if ( "selectedIndex" in field ) {
+		value = field.options[ field.selectedIndex ].value || undefined;
+	} else {
+		value = field.checked ? ( field.defaultValue || true ) : undefined;
+	}
+
+	params[ field.name ] = value;
+	updatedUrl = QUnit.url( params );
+
+	if ( "hidepassed" === field.name && "replaceState" in window.history ) {
+		config[ field.name ] = value || false;
+		if ( value ) {
+			addClass( id( "qunit-tests" ), "hidepass" );
+		} else {
+			removeClass( id( "qunit-tests" ), "hidepass" );
+		}
+
+		// It is not necessary to refresh the whole page
+		window.history.replaceState( null, "", updatedUrl );
+	} else {
+		window.location = updatedUrl;
+	}
+}
+
 function toolbarUrlConfigContainer() {
 	var urlConfigContainer = document.createElement( "span" );
 
@@ -2072,61 +2424,34 @@ function toolbarUrlConfigContainer() {
 	// For oldIE support:
 	// * Add handlers to the individual elements instead of the container
 	// * Use "click" instead of "change" for checkboxes
-	// * Fallback from event.target to event.srcElement
-	addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", function( event ) {
-		var params = {},
-			target = event.target || event.srcElement;
-		params[ target.name ] = target.checked ?
-			target.defaultValue || true :
-			undefined;
-		window.location = QUnit.url( params );
-	});
-	addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", function( event ) {
-		var params = {},
-			target = event.target || event.srcElement;
-		params[ target.name ] = target.options[ target.selectedIndex ].value || undefined;
-		window.location = QUnit.url( params );
-	});
+	addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
+	addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
 
 	return urlConfigContainer;
 }
 
-function getModuleNames() {
+function toolbarModuleFilterHtml() {
 	var i,
-		moduleNames = [];
+		moduleFilterHtml = "";
 
-	for ( i in config.modules ) {
-		if ( config.modules.hasOwnProperty( i ) ) {
-			moduleNames.push( i );
-		}
+	if ( !modulesList.length ) {
+		return false;
 	}
 
-	moduleNames.sort(function( a, b ) {
+	modulesList.sort(function( a, b ) {
 		return a.localeCompare( b );
 	});
 
-	return moduleNames;
-}
-
-function toolbarModuleFilterHtml() {
-	var i,
-		moduleFilterHtml = "",
-		moduleNames = getModuleNames();
-
-	if ( moduleNames.length <= 1 ) {
-		return false;
-	}
-
 	moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
 		"<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
-		( config.module === undefined ? "selected='selected'" : "" ) +
+		( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
 		">< All Modules ></option>";
 
-	for ( i = 0; i < moduleNames.length; i++ ) {
+	for ( i = 0; i < modulesList.length; i++ ) {
 		moduleFilterHtml += "<option value='" +
-			escapeText( encodeURIComponent( moduleNames[ i ] ) ) + "' " +
-			( config.module === moduleNames[ i ] ? "selected='selected'" : "" ) +
-			">" + escapeText( moduleNames[ i ] ) + "</option>";
+			escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
+			( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
+			">" + escapeText( modulesList[ i ] ) + "</option>";
 	}
 	moduleFilterHtml += "</select>";
 
@@ -2134,7 +2459,8 @@ function toolbarModuleFilterHtml() {
 }
 
 function toolbarModuleFilter() {
-	var moduleFilter = document.createElement( "span" ),
+	var toolbar = id( "qunit-testrunner-toolbar" ),
+		moduleFilter = document.createElement( "span" ),
 		moduleFilterHtml = toolbarModuleFilterHtml();
 
 	if ( !moduleFilterHtml ) {
@@ -2146,73 +2472,25 @@ function toolbarModuleFilter() {
 
 	addEvent( moduleFilter.lastChild, "change", function() {
 		var selectBox = moduleFilter.getElementsByTagName( "select" )[ 0 ],
-			selectedModule = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value );
+			selection = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value );
 
 		window.location = QUnit.url({
-			module: ( selectedModule === "" ) ? undefined : selectedModule,
+			module: ( selection === "" ) ? undefined : selection,
 
 			// Remove any existing filters
 			filter: undefined,
-			testNumber: undefined
+			testId: undefined
 		});
 	});
 
-	return moduleFilter;
-}
-
-function toolbarFilter() {
-	var testList = id( "qunit-tests" ),
-		filter = document.createElement( "input" );
-
-	filter.type = "checkbox";
-	filter.id = "qunit-filter-pass";
-
-	addEvent( filter, "click", function() {
-		if ( filter.checked ) {
-			addClass( testList, "hidepass" );
-			if ( defined.sessionStorage ) {
-				sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
-			}
-		} else {
-			removeClass( testList, "hidepass" );
-			if ( defined.sessionStorage ) {
-				sessionStorage.removeItem( "qunit-filter-passed-tests" );
-			}
-		}
-	});
-
-	if ( config.hidepassed || defined.sessionStorage &&
-			sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
-		filter.checked = true;
-
-		addClass( testList, "hidepass" );
-	}
-
-	return filter;
-}
-
-function toolbarLabel() {
-	var label = document.createElement( "label" );
-	label.setAttribute( "for", "qunit-filter-pass" );
-	label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." );
-	label.innerHTML = "Hide passed tests";
-
-	return label;
+	toolbar.appendChild( moduleFilter );
 }
 
 function appendToolbar() {
-	var moduleFilter,
-		toolbar = id( "qunit-testrunner-toolbar" );
+	var toolbar = id( "qunit-testrunner-toolbar" );
 
 	if ( toolbar ) {
-		toolbar.appendChild( toolbarFilter() );
-		toolbar.appendChild( toolbarLabel() );
 		toolbar.appendChild( toolbarUrlConfigContainer() );
-
-		moduleFilter = toolbarModuleFilter();
-		if ( moduleFilter ) {
-			toolbar.appendChild( moduleFilter );
-		}
 	}
 }
 
@@ -2222,7 +2500,7 @@ function appendBanner() {
 	if ( banner ) {
 		banner.className = "";
 		banner.innerHTML = "<a href='" +
-			QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) +
+			QUnit.url({ filter: undefined, module: undefined, testId: undefined }) +
 			"'>" + banner.innerHTML + "</a> ";
 	}
 }
@@ -2241,7 +2519,7 @@ function appendTestResults() {
 		result.id = "qunit-testresult";
 		result.className = "result";
 		tests.parentNode.insertBefore( result, tests );
-		result.innerHTML = "Running...<br>&nbsp;";
+		result.innerHTML = "Running...<br />&#160;";
 	}
 }
 
@@ -2259,24 +2537,80 @@ function appendUserAgent() {
 	}
 }
 
+function appendTestsList( modules ) {
+	var i, l, x, z, test, moduleObj;
+
+	for ( i = 0, l = modules.length; i < l; i++ ) {
+		moduleObj = modules[ i ];
+
+		if ( moduleObj.name ) {
+			modulesList.push( moduleObj.name );
+		}
+
+		for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
+			test = moduleObj.tests[ x ];
+
+			appendTest( test.name, test.testId, moduleObj.name );
+		}
+	}
+}
+
+function appendTest( name, testId, moduleName ) {
+	var title, rerunTrigger, testBlock, assertList,
+		tests = id( "qunit-tests" );
+
+	if ( !tests ) {
+		return;
+	}
+
+	title = document.createElement( "strong" );
+	title.innerHTML = getNameHtml( name, moduleName );
+
+	rerunTrigger = document.createElement( "a" );
+	rerunTrigger.innerHTML = "Rerun";
+	rerunTrigger.href = QUnit.url({ testId: testId });
+
+	testBlock = document.createElement( "li" );
+	testBlock.appendChild( title );
+	testBlock.appendChild( rerunTrigger );
+	testBlock.id = "qunit-test-output-" + testId;
+
+	assertList = document.createElement( "ol" );
+	assertList.className = "qunit-assert-list";
+
+	testBlock.appendChild( assertList );
+
+	tests.appendChild( testBlock );
+}
+
 // HTML Reporter initialization and load
-QUnit.begin(function() {
+QUnit.begin(function( details ) {
 	var qunit = id( "qunit" );
 
-	if ( qunit ) {
-		qunit.innerHTML =
+	// Fixture is the only one necessary to run without the #qunit element
+	storeFixture();
+
+	if ( !qunit ) {
+		return;
+	}
+
+	qunit.innerHTML =
 		"<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
 		"<h2 id='qunit-banner'></h2>" +
 		"<div id='qunit-testrunner-toolbar'></div>" +
 		"<h2 id='qunit-userAgent'></h2>" +
 		"<ol id='qunit-tests'></ol>";
-	}
 
 	appendBanner();
 	appendTestResults();
 	appendUserAgent();
 	appendToolbar();
-	storeFixture();
+	appendTestsList( details.modules );
+	toolbarModuleFilter();
+
+	if ( config.hidepassed ) {
+		addClass( qunit.lastChild, "hidepass" );
+	}
 });
 
 QUnit.done(function( details ) {
@@ -2286,7 +2620,7 @@ QUnit.done(function( details ) {
 		html = [
 			"Tests completed in ",
 			details.runtime,
-			" milliseconds.<br>",
+			" milliseconds.<br />",
 			"<span class='passed'>",
 			details.passed,
 			"</span> assertions of <span class='total'>",
@@ -2343,35 +2677,20 @@ function getNameHtml( name, module ) {
 }
 
 QUnit.testStart(function( details ) {
-	var a, b, li, running, assertList,
-		name = getNameHtml( details.name, details.module ),
-		tests = id( "qunit-tests" );
-
-	if ( tests ) {
-		b = document.createElement( "strong" );
-		b.innerHTML = name;
+	var running, testBlock;
 
-		a = document.createElement( "a" );
-		a.innerHTML = "Rerun";
-		a.href = QUnit.url({ testNumber: details.testNumber });
-
-		li = document.createElement( "li" );
-		li.appendChild( b );
-		li.appendChild( a );
-		li.className = "running";
-		li.id = "qunit-test-output" + details.testNumber;
-
-		assertList = document.createElement( "ol" );
-		assertList.className = "qunit-assert-list";
-
-		li.appendChild( assertList );
+	testBlock = id( "qunit-test-output-" + details.testId );
+	if ( testBlock ) {
+		testBlock.className = "running";
+	} else {
 
-		tests.appendChild( li );
+		// Report later registered tests
+		appendTest( details.name, details.testId, details.module );
 	}
 
 	running = id( "qunit-testresult" );
 	if ( running ) {
-		running.innerHTML = "Running: <br>" + name;
+		running.innerHTML = "Running: <br />" + getNameHtml( details.name, details.module );
 	}
 
 });
@@ -2379,7 +2698,7 @@ QUnit.testStart(function( details ) {
 QUnit.log(function( details ) {
 	var assertList, assertLi,
 		message, expected, actual,
-		testItem = id( "qunit-test-output" + details.testNumber );
+		testItem = id( "qunit-test-output-" + details.testId );
 
 	if ( !testItem ) {
 		return;
@@ -2387,6 +2706,7 @@ QUnit.log(function( details ) {
 
 	message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
 	message = "<span class='test-message'>" + message + "</span>";
+	message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
 
 	// pushFailure doesn't provide details.expected
 	// when it calls, it's implicit to also not show expected and diff stuff
@@ -2430,19 +2750,15 @@ QUnit.log(function( details ) {
 
 QUnit.testDone(function( details ) {
 	var testTitle, time, testItem, assertList,
-		good, bad, testCounts,
+		good, bad, testCounts, skipped,
 		tests = id( "qunit-tests" );
 
-	// QUnit.reset() is deprecated and will be replaced for a new
-	// fixture reset function on QUnit 2.0/2.1.
-	// It's still called here for backwards compatibility handling
-	QUnit.reset();
-
 	if ( !tests ) {
 		return;
 	}
 
-	testItem = id( "qunit-test-output" + details.testNumber );
+	testItem = id( "qunit-test-output-" + details.testId );
+
 	assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
 
 	good = details.passed;
@@ -2471,20 +2787,28 @@ QUnit.testDone(function( details ) {
 	testTitle.innerHTML += " <b class='counts'>(" + testCounts +
 		details.assertions.length + ")</b>";
 
-	addEvent( testTitle, "click", function() {
-		toggleClass( assertList, "qunit-collapsed" );
-	});
-
-	time = document.createElement( "span" );
-	time.className = "runtime";
-	time.innerHTML = details.runtime + " ms";
+	if ( details.skipped ) {
+		addClass( testItem, "skipped" );
+		skipped = document.createElement( "em" );
+		skipped.className = "qunit-skipped-label";
+		skipped.innerHTML = "skipped";
+		testItem.insertBefore( skipped, testTitle );
+	} else {
+		addEvent( testTitle, "click", function() {
+			toggleClass( assertList, "qunit-collapsed" );
+		});
 
-	testItem.className = bad ? "fail" : "pass";
+		testItem.className = bad ? "fail" : "pass";
 
-	testItem.insertBefore( time, assertList );
+		time = document.createElement( "span" );
+		time.className = "runtime";
+		time.innerHTML = details.runtime + " ms";
+		testItem.insertBefore( time, assertList );
+	}
 });
 
 if ( !defined.document || document.readyState === "complete" ) {
+	config.pageLoaded = true;
 	config.autorun = true;
 }