diff options
author | Maximilian Hils <git@maximilianhils.com> | 2014-12-09 18:18:14 +0100 |
---|---|---|
committer | Maximilian Hils <git@maximilianhils.com> | 2014-12-09 18:18:14 +0100 |
commit | 14a8d2f5b83a1ea28abbb490f6c94c43b4e1f960 (patch) | |
tree | c114643959a6223289e8287e9daaa1b65a377356 /web/src/vendor/qunit/qunit.js | |
parent | 096a3af273ccb309820351b466e62382f62a2c36 (diff) | |
download | mitmproxy-14a8d2f5b83a1ea28abbb490f6c94c43b4e1f960.tar.gz mitmproxy-14a8d2f5b83a1ea28abbb490f6c94c43b4e1f960.tar.bz2 mitmproxy-14a8d2f5b83a1ea28abbb490f6c94c43b4e1f960.zip |
always use the app dispatcher
Diffstat (limited to 'web/src/vendor/qunit/qunit.js')
-rw-r--r-- | web/src/vendor/qunit/qunit.js | 1364 |
1 files changed, 844 insertions, 520 deletions
diff --git a/web/src/vendor/qunit/qunit.js b/web/src/vendor/qunit/qunit.js index 474cfe55..82020d40 100644 --- a/web/src/vendor/qunit/qunit.js +++ b/web/src/vendor/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é <prathe@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 ? " " : " "; + return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? " " : " "; }, // 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, " " ); + chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); } 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/> "; - } -}; - -// 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 /> "; } }; @@ -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> "; + result.innerHTML = "Running...<br /> "; } } @@ -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; } |