aboutsummaryrefslogtreecommitdiffstats
path: root/web/src/vendor/qunit/qunit.js
diff options
context:
space:
mode:
Diffstat (limited to 'web/src/vendor/qunit/qunit.js')
-rw-r--r--web/src/vendor/qunit/qunit.js1364
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 ? "&nbsp;" : " ";
+ return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
},
// extra can be a number, shortcut for increasing-calling-decreasing
indent: function( extra ) {
@@ -1460,7 +1782,7 @@ QUnit.dump = (function() {
}
var chr = this.indentChar;
if ( this.HTML ) {
- chr = chr.replace( /\t/g, " " ).replace( / /g, "&nbsp;" );
+ chr = chr.replace( /\t/g, " " ).replace( / /g, "&#160;" );
}
return new Array( this.depth + ( extra || 0 ) ).join( chr );
},
@@ -1479,6 +1801,8 @@ QUnit.dump = (function() {
join: join,
//
depth: 1,
+ maxDepth: 5,
+
// This is the list of parsers, to modify them, use dump.setParser
parsers: {
window: "[Window]",
@@ -1491,6 +1815,7 @@ QUnit.dump = (function() {
"undefined": "undefined",
"function": function( fn ) {
var ret = "function",
+
// functions never have name in IE
name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
@@ -1506,8 +1831,13 @@ QUnit.dump = (function() {
nodelist: array,
"arguments": array,
object: function( map, stack ) {
- /*jshint forin:false */
- var ret = [], keys, key, val, i, nonEnumerableProperties;
+ var keys, key, val, i, nonEnumerableProperties,
+ ret = [];
+
+ if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
+ return "[object Object]";
+ }
+
dump.up();
keys = [];
for ( key in map ) {
@@ -1526,7 +1856,8 @@ QUnit.dump = (function() {
for ( i = 0; i < keys.length; i++ ) {
key = keys[ i ];
val = map[ key ];
- ret.push( dump.parse( key, "key" ) + ": " + dump.parse( val, undefined, stack ) );
+ ret.push( dump.parse( key, "key" ) + ": " +
+ dump.parse( val, undefined, stack ) );
}
dump.down();
return join( "{", ret, "}" );
@@ -1543,10 +1874,12 @@ QUnit.dump = (function() {
for ( i = 0, len = attrs.length; i < len; i++ ) {
val = attrs[ i ].nodeValue;
- // IE6 includes all attributes in .attributes, even ones not explicitly set.
- // Those have values like undefined, null, 0, false, "" or "inherit".
+ // IE6 includes all attributes in .attributes, even ones not explicitly
+ // set. Those have values like undefined, null, 0, false, "" or
+ // "inherit".
if ( val && val !== "inherit" ) {
- ret += " " + attrs[ i ].nodeName + "=" + dump.parse( val, "attribute" );
+ ret += " " + attrs[ i ].nodeName + "=" +
+ dump.parse( val, "attribute" );
}
}
}
@@ -1653,17 +1986,23 @@ if ( typeof window !== "undefined" ) {
window.QUnit = QUnit;
}
-// For CommonJS environments, export everything
+// For nodejs
if ( typeof module !== "undefined" && module.exports ) {
module.exports = QUnit;
}
+// For CommonJS with exports, but without module.exports, like Rhino
+if ( typeof exports !== "undefined" ) {
+ exports.QUnit = QUnit;
+}
+
// Get a reference to the global object, like window in browsers
}( (function() {
return this;
})() ));
/*istanbul ignore next */
+// jscs:disable maximumLineLength
/*
* Javascript Diff Algorithm
* By John Resig (http://ejohn.org/)
@@ -1810,6 +2149,7 @@ QUnit.diff = (function() {
return str;
};
}());
+// jscs:enable
(function() {
@@ -1828,7 +2168,6 @@ QUnit.init = function() {
config.autorun = false;
config.filter = "";
config.queue = [];
- config.semaphore = 1;
// Return on non-browser environments
// This is necessary to not break on node tests
@@ -1867,27 +2206,7 @@ QUnit.init = function() {
result.id = "qunit-testresult";
result.className = "result";
tests.parentNode.insertBefore( result, tests );
- result.innerHTML = "Running...<br/>&nbsp;";
- }
-};
-
-// Resets the test setup. Useful for tests that modify the DOM.
-/*
-DEPRECATED: Use multiple tests instead of resetting inside a test.
-Use testStart or testDone for custom cleanup.
-This method will throw an error in 2.0, and will be removed in 2.1
-*/
-QUnit.reset = function() {
-
- // Return on non-browser environments
- // This is necessary to not break on node tests
- if ( typeof window === "undefined" ) {
- return;
- }
-
- var fixture = id( "qunit-fixture" );
- if ( fixture ) {
- fixture.innerHTML = config.fixture;
+ result.innerHTML = "Running...<br />&#160;";
}
};
@@ -1899,7 +2218,7 @@ if ( typeof window === "undefined" ) {
var config = QUnit.config,
hasOwn = Object.prototype.hasOwnProperty,
defined = {
- document: typeof window.document !== "undefined",
+ document: window.document !== undefined,
sessionStorage: (function() {
var x = "qunit-test-string";
try {
@@ -1910,7 +2229,8 @@ var config = QUnit.config,
return false;
}
}())
- };
+ },
+ modulesList = [];
/**
* Escape text for attribute or text content.
@@ -2026,7 +2346,7 @@ function getUrlConfigHtml() {
"' name='" + escaped + "' type='checkbox'" +
( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
( config[ val.id ] ? " checked='checked'" : "" ) +
- " title='" + escapedTooltip + "'><label for='qunit-urlconfig-" + escaped +
+ " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
"' title='" + escapedTooltip + "'>" + val.label + "</label>";
} else {
urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
@@ -2064,6 +2384,38 @@ function getUrlConfigHtml() {
return urlConfigHtml;
}
+// Handle "click" events on toolbar checkboxes and "change" for select menus.
+// Updates the URL with the new state of `config.urlConfig` values.
+function toolbarChanged() {
+ var updatedUrl, value,
+ field = this,
+ params = {};
+
+ // Detect if field is a select menu or a checkbox
+ if ( "selectedIndex" in field ) {
+ value = field.options[ field.selectedIndex ].value || undefined;
+ } else {
+ value = field.checked ? ( field.defaultValue || true ) : undefined;
+ }
+
+ params[ field.name ] = value;
+ updatedUrl = QUnit.url( params );
+
+ if ( "hidepassed" === field.name && "replaceState" in window.history ) {
+ config[ field.name ] = value || false;
+ if ( value ) {
+ addClass( id( "qunit-tests" ), "hidepass" );
+ } else {
+ removeClass( id( "qunit-tests" ), "hidepass" );
+ }
+
+ // It is not necessary to refresh the whole page
+ window.history.replaceState( null, "", updatedUrl );
+ } else {
+ window.location = updatedUrl;
+ }
+}
+
function toolbarUrlConfigContainer() {
var urlConfigContainer = document.createElement( "span" );
@@ -2072,61 +2424,34 @@ function toolbarUrlConfigContainer() {
// For oldIE support:
// * Add handlers to the individual elements instead of the container
// * Use "click" instead of "change" for checkboxes
- // * Fallback from event.target to event.srcElement
- addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", function( event ) {
- var params = {},
- target = event.target || event.srcElement;
- params[ target.name ] = target.checked ?
- target.defaultValue || true :
- undefined;
- window.location = QUnit.url( params );
- });
- addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", function( event ) {
- var params = {},
- target = event.target || event.srcElement;
- params[ target.name ] = target.options[ target.selectedIndex ].value || undefined;
- window.location = QUnit.url( params );
- });
+ addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
+ addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
return urlConfigContainer;
}
-function getModuleNames() {
+function toolbarModuleFilterHtml() {
var i,
- moduleNames = [];
+ moduleFilterHtml = "";
- for ( i in config.modules ) {
- if ( config.modules.hasOwnProperty( i ) ) {
- moduleNames.push( i );
- }
+ if ( !modulesList.length ) {
+ return false;
}
- moduleNames.sort(function( a, b ) {
+ modulesList.sort(function( a, b ) {
return a.localeCompare( b );
});
- return moduleNames;
-}
-
-function toolbarModuleFilterHtml() {
- var i,
- moduleFilterHtml = "",
- moduleNames = getModuleNames();
-
- if ( moduleNames.length <= 1 ) {
- return false;
- }
-
moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
"<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
- ( config.module === undefined ? "selected='selected'" : "" ) +
+ ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
">< All Modules ></option>";
- for ( i = 0; i < moduleNames.length; i++ ) {
+ for ( i = 0; i < modulesList.length; i++ ) {
moduleFilterHtml += "<option value='" +
- escapeText( encodeURIComponent( moduleNames[ i ] ) ) + "' " +
- ( config.module === moduleNames[ i ] ? "selected='selected'" : "" ) +
- ">" + escapeText( moduleNames[ i ] ) + "</option>";
+ escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
+ ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
+ ">" + escapeText( modulesList[ i ] ) + "</option>";
}
moduleFilterHtml += "</select>";
@@ -2134,7 +2459,8 @@ function toolbarModuleFilterHtml() {
}
function toolbarModuleFilter() {
- var moduleFilter = document.createElement( "span" ),
+ var toolbar = id( "qunit-testrunner-toolbar" ),
+ moduleFilter = document.createElement( "span" ),
moduleFilterHtml = toolbarModuleFilterHtml();
if ( !moduleFilterHtml ) {
@@ -2146,73 +2472,25 @@ function toolbarModuleFilter() {
addEvent( moduleFilter.lastChild, "change", function() {
var selectBox = moduleFilter.getElementsByTagName( "select" )[ 0 ],
- selectedModule = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value );
+ selection = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value );
window.location = QUnit.url({
- module: ( selectedModule === "" ) ? undefined : selectedModule,
+ module: ( selection === "" ) ? undefined : selection,
// Remove any existing filters
filter: undefined,
- testNumber: undefined
+ testId: undefined
});
});
- return moduleFilter;
-}
-
-function toolbarFilter() {
- var testList = id( "qunit-tests" ),
- filter = document.createElement( "input" );
-
- filter.type = "checkbox";
- filter.id = "qunit-filter-pass";
-
- addEvent( filter, "click", function() {
- if ( filter.checked ) {
- addClass( testList, "hidepass" );
- if ( defined.sessionStorage ) {
- sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
- }
- } else {
- removeClass( testList, "hidepass" );
- if ( defined.sessionStorage ) {
- sessionStorage.removeItem( "qunit-filter-passed-tests" );
- }
- }
- });
-
- if ( config.hidepassed || defined.sessionStorage &&
- sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
- filter.checked = true;
-
- addClass( testList, "hidepass" );
- }
-
- return filter;
-}
-
-function toolbarLabel() {
- var label = document.createElement( "label" );
- label.setAttribute( "for", "qunit-filter-pass" );
- label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." );
- label.innerHTML = "Hide passed tests";
-
- return label;
+ toolbar.appendChild( moduleFilter );
}
function appendToolbar() {
- var moduleFilter,
- toolbar = id( "qunit-testrunner-toolbar" );
+ var toolbar = id( "qunit-testrunner-toolbar" );
if ( toolbar ) {
- toolbar.appendChild( toolbarFilter() );
- toolbar.appendChild( toolbarLabel() );
toolbar.appendChild( toolbarUrlConfigContainer() );
-
- moduleFilter = toolbarModuleFilter();
- if ( moduleFilter ) {
- toolbar.appendChild( moduleFilter );
- }
}
}
@@ -2222,7 +2500,7 @@ function appendBanner() {
if ( banner ) {
banner.className = "";
banner.innerHTML = "<a href='" +
- QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) +
+ QUnit.url({ filter: undefined, module: undefined, testId: undefined }) +
"'>" + banner.innerHTML + "</a> ";
}
}
@@ -2241,7 +2519,7 @@ function appendTestResults() {
result.id = "qunit-testresult";
result.className = "result";
tests.parentNode.insertBefore( result, tests );
- result.innerHTML = "Running...<br>&nbsp;";
+ result.innerHTML = "Running...<br />&#160;";
}
}
@@ -2259,24 +2537,80 @@ function appendUserAgent() {
}
}
+function appendTestsList( modules ) {
+ var i, l, x, z, test, moduleObj;
+
+ for ( i = 0, l = modules.length; i < l; i++ ) {
+ moduleObj = modules[ i ];
+
+ if ( moduleObj.name ) {
+ modulesList.push( moduleObj.name );
+ }
+
+ for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
+ test = moduleObj.tests[ x ];
+
+ appendTest( test.name, test.testId, moduleObj.name );
+ }
+ }
+}
+
+function appendTest( name, testId, moduleName ) {
+ var title, rerunTrigger, testBlock, assertList,
+ tests = id( "qunit-tests" );
+
+ if ( !tests ) {
+ return;
+ }
+
+ title = document.createElement( "strong" );
+ title.innerHTML = getNameHtml( name, moduleName );
+
+ rerunTrigger = document.createElement( "a" );
+ rerunTrigger.innerHTML = "Rerun";
+ rerunTrigger.href = QUnit.url({ testId: testId });
+
+ testBlock = document.createElement( "li" );
+ testBlock.appendChild( title );
+ testBlock.appendChild( rerunTrigger );
+ testBlock.id = "qunit-test-output-" + testId;
+
+ assertList = document.createElement( "ol" );
+ assertList.className = "qunit-assert-list";
+
+ testBlock.appendChild( assertList );
+
+ tests.appendChild( testBlock );
+}
+
// HTML Reporter initialization and load
-QUnit.begin(function() {
+QUnit.begin(function( details ) {
var qunit = id( "qunit" );
- if ( qunit ) {
- qunit.innerHTML =
+ // Fixture is the only one necessary to run without the #qunit element
+ storeFixture();
+
+ if ( !qunit ) {
+ return;
+ }
+
+ qunit.innerHTML =
"<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
"<h2 id='qunit-banner'></h2>" +
"<div id='qunit-testrunner-toolbar'></div>" +
"<h2 id='qunit-userAgent'></h2>" +
"<ol id='qunit-tests'></ol>";
- }
appendBanner();
appendTestResults();
appendUserAgent();
appendToolbar();
- storeFixture();
+ appendTestsList( details.modules );
+ toolbarModuleFilter();
+
+ if ( config.hidepassed ) {
+ addClass( qunit.lastChild, "hidepass" );
+ }
});
QUnit.done(function( details ) {
@@ -2286,7 +2620,7 @@ QUnit.done(function( details ) {
html = [
"Tests completed in ",
details.runtime,
- " milliseconds.<br>",
+ " milliseconds.<br />",
"<span class='passed'>",
details.passed,
"</span> assertions of <span class='total'>",
@@ -2343,35 +2677,20 @@ function getNameHtml( name, module ) {
}
QUnit.testStart(function( details ) {
- var a, b, li, running, assertList,
- name = getNameHtml( details.name, details.module ),
- tests = id( "qunit-tests" );
-
- if ( tests ) {
- b = document.createElement( "strong" );
- b.innerHTML = name;
+ var running, testBlock;
- a = document.createElement( "a" );
- a.innerHTML = "Rerun";
- a.href = QUnit.url({ testNumber: details.testNumber });
-
- li = document.createElement( "li" );
- li.appendChild( b );
- li.appendChild( a );
- li.className = "running";
- li.id = "qunit-test-output" + details.testNumber;
-
- assertList = document.createElement( "ol" );
- assertList.className = "qunit-assert-list";
-
- li.appendChild( assertList );
+ testBlock = id( "qunit-test-output-" + details.testId );
+ if ( testBlock ) {
+ testBlock.className = "running";
+ } else {
- tests.appendChild( li );
+ // Report later registered tests
+ appendTest( details.name, details.testId, details.module );
}
running = id( "qunit-testresult" );
if ( running ) {
- running.innerHTML = "Running: <br>" + name;
+ running.innerHTML = "Running: <br />" + getNameHtml( details.name, details.module );
}
});
@@ -2379,7 +2698,7 @@ QUnit.testStart(function( details ) {
QUnit.log(function( details ) {
var assertList, assertLi,
message, expected, actual,
- testItem = id( "qunit-test-output" + details.testNumber );
+ testItem = id( "qunit-test-output-" + details.testId );
if ( !testItem ) {
return;
@@ -2387,6 +2706,7 @@ QUnit.log(function( details ) {
message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
message = "<span class='test-message'>" + message + "</span>";
+ message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
// pushFailure doesn't provide details.expected
// when it calls, it's implicit to also not show expected and diff stuff
@@ -2430,19 +2750,15 @@ QUnit.log(function( details ) {
QUnit.testDone(function( details ) {
var testTitle, time, testItem, assertList,
- good, bad, testCounts,
+ good, bad, testCounts, skipped,
tests = id( "qunit-tests" );
- // QUnit.reset() is deprecated and will be replaced for a new
- // fixture reset function on QUnit 2.0/2.1.
- // It's still called here for backwards compatibility handling
- QUnit.reset();
-
if ( !tests ) {
return;
}
- testItem = id( "qunit-test-output" + details.testNumber );
+ testItem = id( "qunit-test-output-" + details.testId );
+
assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
good = details.passed;
@@ -2471,20 +2787,28 @@ QUnit.testDone(function( details ) {
testTitle.innerHTML += " <b class='counts'>(" + testCounts +
details.assertions.length + ")</b>";
- addEvent( testTitle, "click", function() {
- toggleClass( assertList, "qunit-collapsed" );
- });
-
- time = document.createElement( "span" );
- time.className = "runtime";
- time.innerHTML = details.runtime + " ms";
+ if ( details.skipped ) {
+ addClass( testItem, "skipped" );
+ skipped = document.createElement( "em" );
+ skipped.className = "qunit-skipped-label";
+ skipped.innerHTML = "skipped";
+ testItem.insertBefore( skipped, testTitle );
+ } else {
+ addEvent( testTitle, "click", function() {
+ toggleClass( assertList, "qunit-collapsed" );
+ });
- testItem.className = bad ? "fail" : "pass";
+ testItem.className = bad ? "fail" : "pass";
- testItem.insertBefore( time, assertList );
+ time = document.createElement( "span" );
+ time.className = "runtime";
+ time.innerHTML = details.runtime + " ms";
+ testItem.insertBefore( time, assertList );
+ }
});
if ( !defined.document || document.readyState === "complete" ) {
+ config.pageLoaded = true;
config.autorun = true;
}