]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
Update QUnit to v1.17.0.
authorXhmikosR <xhmikosr@gmail.com>
Mon, 19 Jan 2015 17:05:24 +0000 (19:05 +0200)
committerXhmikosR <xhmikosr@gmail.com>
Mon, 19 Jan 2015 18:10:34 +0000 (20:10 +0200)
js/tests/vendor/qunit.css
js/tests/vendor/qunit.js

index 9437b4b60c6b0a5ab719bcc825293e80781a98ee..bfcdca411ccc89acaa5833ffde104417d7b3e14e 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * QUnit 1.15.0
+ * QUnit 1.17.0
  * http://qunitjs.com/
  *
- * Copyright 2014 jQuery Foundation and other contributors
+ * Copyright jQuery Foundation and other contributors
  * Released under the MIT license
  * http://jquery.org/license
  *
- * Date: 2014-08-08T16:00Z
+ * Date: 2015-01-19T11:58Z
  */
 
 /** Font Family and Sizes */
 
 #qunit-modulefilter-container {
        float: right;
+       padding: 0.2em;
+}
+
+.qunit-url-config {
+       display: inline-block;
+       padding: 0.1em;
+}
+
+.qunit-filter {
+       display: block;
+       float: right;
+       margin-left: 1em;
 }
 
 /** Tests: Pass/Fail */
        list-style-position: inside;
 }
 
-#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running  {
+#qunit-tests > li {
+       display: none;
+}
+
+#qunit-tests li.running,
+#qunit-tests li.pass,
+#qunit-tests li.fail,
+#qunit-tests li.skipped {
+       display: list-item;
+}
+
+#qunit-tests.hidepass li.running,
+#qunit-tests.hidepass li.pass {
        display: none;
 }
 
        cursor: pointer;
 }
 
+#qunit-tests li.skipped strong {
+       cursor: default;
+}
+
 #qunit-tests li a {
        padding: 0.5em;
        color: #C2CCD1;
 
 #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 */
 
index 474cfe55f39c91feaee4915afc27740c0a9d8701..46d404157e855ffe3edeec90ddb230a262a0cba2 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * QUnit 1.15.0
+ * QUnit 1.17.0
  * http://qunitjs.com/
  *
- * Copyright 2014 jQuery Foundation and other contributors
+ * Copyright jQuery Foundation and other contributors
  * Released under the MIT license
  * http://jquery.org/license
  *
- * Date: 2014-08-08T16:00Z
+ * Date: 2015-01-19T11:58Z
  */
 
 (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
@@ -225,10 +103,6 @@ config = {
        // block until document ready
        blocking: true,
 
-       // when enabled, show only failing tests
-       // gets persisted through sessionStorage and can be changed in UI via checkbox
-       hidepassed: false,
-
        // by default, run previously failed tests first
        // very useful in combination with "Hide passed tests" checked
        reorder: true,
@@ -245,24 +119,40 @@ config = {
        // add checkboxes that are persisted in the query-string
        // 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,
@@ -286,22 +176,22 @@ config = {
                }
        }
 
+       if ( urlParams.filter === true ) {
+               delete urlParams.filter;
+       }
+
        QUnit.urlParams = urlParams;
 
        // 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
@@ -351,78 +363,65 @@ extend( QUnit, {
                return undefined;
        },
 
-       url: function( params ) {
-               params = extend( extend( {}, QUnit.urlParams ), params );
-               var key,
-                       querystring = "?";
-
-               for ( key in params ) {
-                       if ( hasOwn.call( params, key ) ) {
-                               querystring += encodeURIComponent( key ) + "=" +
-                                       encodeURIComponent( params[ key ] ) + "&";
-                       }
-               }
-               return window.location.protocol + "//" + window.location.host +
-                       window.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 +447,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 +456,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 +484,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 +527,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 ) {
@@ -586,10 +560,16 @@ function process( last ) {
                process( last );
        }
        var start = now();
-       config.depth = config.depth ? config.depth + 1 : 1;
+       config.depth = ( config.depth || 0 ) + 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 +582,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 +724,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 +733,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 +777,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 +830,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 +876,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 +897,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 +969,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 +978,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 +996,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 +1040,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 +1068,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 +1092,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,
+                       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.toLowerCase().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 +1225,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 +1235,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 +1293,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 +1301,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 +1427,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é <prathe@gmail.com>
 QUnit.equiv = (function() {
@@ -1356,7 +1647,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 +1678,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 +1693,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 +1728,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 +1742,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 +1755,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 +1764,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 +1783,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 +1797,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 +1813,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 +1838,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 +1856,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,9 +1968,17 @@ if ( typeof window !== "undefined" ) {
        window.QUnit = QUnit;
 }
 
-// For CommonJS environments, export everything
-if ( typeof module !== "undefined" && module.exports ) {
+// For nodejs
+if ( typeof module !== "undefined" && module && module.exports ) {
        module.exports = QUnit;
+
+       // For consistency with CommonJS environments' exports
+       module.exports.QUnit = QUnit;
+}
+
+// For CommonJS with exports, but without module.exports, like Rhino
+if ( typeof exports !== "undefined" && exports ) {
+       exports.QUnit = QUnit;
 }
 
 // Get a reference to the global object, like window in browsers
@@ -1664,6 +1987,7 @@ if ( typeof module !== "undefined" && module.exports ) {
 })() ));
 
 /*istanbul ignore next */
+// jscs:disable maximumLineLength
 /*
  * Javascript Diff Algorithm
  *  By John Resig (http://ejohn.org/)
@@ -1810,6 +2134,7 @@ QUnit.diff = (function() {
                return str;
        };
 }());
+// jscs:enable
 
 (function() {
 
@@ -1828,7 +2153,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 +2191,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 +2203,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 +2214,8 @@ var config = QUnit.config,
                                return false;
                        }
                }())
-       };
+       },
+       modulesList = [];
 
 /**
 * Escape text for attribute or text content.
@@ -2020,13 +2325,16 @@ function getUrlConfigHtml() {
                escaped = escapeText( val.id );
                escapedTooltip = escapeText( val.tooltip );
 
-               config[ val.id ] = QUnit.urlParams[ val.id ];
+               if ( config[ val.id ] === undefined ) {
+                       config[ val.id ] = QUnit.urlParams[ val.id ];
+               }
+
                if ( !val.value || typeof val.value === "string" ) {
                        urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
                                "' 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,69 +2372,145 @@ 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 = setUrl( 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 setUrl( params ) {
+       var key,
+               querystring = "?";
+
+       params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
+
+       for ( key in params ) {
+               if ( hasOwn.call( params, key ) ) {
+                       if ( params[ key ] === undefined ) {
+                               continue;
+                       }
+                       querystring += encodeURIComponent( key );
+                       if ( params[ key ] !== true ) {
+                               querystring += "=" + encodeURIComponent( params[ key ] );
+                       }
+                       querystring += "&";
+               }
+       }
+       return location.protocol + "//" + location.host +
+               location.pathname + querystring.slice( 0, -1 );
+}
+
+function applyUrlParams() {
+       var selectBox = id( "qunit-modulefilter" ),
+               selection = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value ),
+               filter = id( "qunit-filter-input" ).value;
+
+       window.location = setUrl({
+               module: ( selection === "" ) ? undefined : selection,
+               filter: ( filter === "" ) ? undefined : filter,
+
+               // Remove testId filter
+               testId: undefined
+       });
+}
+
 function toolbarUrlConfigContainer() {
        var urlConfigContainer = document.createElement( "span" );
 
        urlConfigContainer.innerHTML = getUrlConfigHtml();
+       addClass( urlConfigContainer, "qunit-url-config" );
 
        // 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() {
-       var i,
-               moduleNames = [];
+function toolbarLooseFilter() {
+       var filter = document.createElement( "form" ),
+               label = document.createElement( "label" ),
+               input = document.createElement( "input" ),
+               button = document.createElement( "button" );
+
+       addClass( filter, "qunit-filter" );
+
+       label.innerHTML = "Filter: ";
+
+       input.type = "text";
+       input.value = config.filter || "";
+       input.name = "filter";
+       input.id = "qunit-filter-input";
+
+       button.innerHTML = "Go";
 
-       for ( i in config.modules ) {
-               if ( config.modules.hasOwnProperty( i ) ) {
-                       moduleNames.push( i );
+       label.appendChild( input );
+
+       filter.appendChild( label );
+       filter.appendChild( button );
+       addEvent( filter, "submit", function( ev ) {
+               applyUrlParams();
+
+               if ( ev && ev.preventDefault ) {
+                       ev.preventDefault();
                }
-       }
 
-       moduleNames.sort(function( a, b ) {
-               return a.localeCompare( b );
+               return false;
        });
 
-       return moduleNames;
+       return filter;
 }
 
 function toolbarModuleFilterHtml() {
        var i,
-               moduleFilterHtml = "",
-               moduleNames = getModuleNames();
+               moduleFilterHtml = "";
 
-       if ( moduleNames.length <= 1 ) {
+       if ( !modulesList.length ) {
                return false;
        }
 
+       modulesList.sort(function( a, b ) {
+               return a.localeCompare( b );
+       });
+
        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 +2518,8 @@ function toolbarModuleFilterHtml() {
 }
 
 function toolbarModuleFilter() {
-       var moduleFilter = document.createElement( "span" ),
+       var toolbar = id( "qunit-testrunner-toolbar" ),
+               moduleFilter = document.createElement( "span" ),
                moduleFilterHtml = toolbarModuleFilterHtml();
 
        if ( !moduleFilterHtml ) {
@@ -2144,75 +2529,27 @@ function toolbarModuleFilter() {
        moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
        moduleFilter.innerHTML = moduleFilterHtml;
 
-       addEvent( moduleFilter.lastChild, "change", function() {
-               var selectBox = moduleFilter.getElementsByTagName( "select" )[ 0 ],
-                       selectedModule = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value );
-
-               window.location = QUnit.url({
-                       module: ( selectedModule === "" ) ? undefined : selectedModule,
-
-                       // Remove any existing filters
-                       filter: undefined,
-                       testNumber: undefined
-               });
-       });
-
-       return moduleFilter;
-}
-
-function toolbarFilter() {
-       var testList = id( "qunit-tests" ),
-               filter = document.createElement( "input" );
+       addEvent( moduleFilter.lastChild, "change", applyUrlParams );
 
-       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() );
+               toolbar.appendChild( toolbarLooseFilter() );
+       }
+}
 
-               moduleFilter = toolbarModuleFilter();
-               if ( moduleFilter ) {
-                       toolbar.appendChild( moduleFilter );
-               }
+function appendHeader() {
+       var header = id( "qunit-header" );
+
+       if ( header ) {
+               header.innerHTML = "<a href='" +
+                       setUrl({ filter: undefined, module: undefined, testId: undefined }) +
+                       "'>" + header.innerHTML + "</a> ";
        }
 }
 
@@ -2221,9 +2558,6 @@ function appendBanner() {
 
        if ( banner ) {
                banner.className = "";
-               banner.innerHTML = "<a href='" +
-                       QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) +
-                       "'>" + banner.innerHTML + "</a> ";
        }
 }
 
@@ -2241,7 +2575,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;";
        }
 }
 
@@ -2255,28 +2589,84 @@ function storeFixture() {
 function appendUserAgent() {
        var userAgent = id( "qunit-userAgent" );
        if ( userAgent ) {
-               userAgent.innerHTML = navigator.userAgent;
+               userAgent.innerHTML = "";
+               userAgent.appendChild( document.createTextNode( navigator.userAgent ) );
+       }
+}
+
+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 = setUrl({ 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" );
 
+       // Fixture is the only one necessary to run without the #qunit element
+       storeFixture();
+
        if ( qunit ) {
                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>";
+                       "<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>";
        }
 
+       appendHeader();
        appendBanner();
        appendTestResults();
        appendUserAgent();
        appendToolbar();
-       storeFixture();
+       appendTestsList( details.modules );
+       toolbarModuleFilter();
+
+       if ( qunit && config.hidepassed ) {
+               addClass( qunit.lastChild, "hidepass" );
+       }
 });
 
 QUnit.done(function( details ) {
@@ -2286,7 +2676,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 +2733,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;
-
-               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";
+       var running, testBlock;
 
-               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 +2754,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 +2762,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 +2806,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 +2843,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 ) {
+               testItem.className = "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;
 }