5 * Copyright jQuery Foundation and other contributors
6 * Released under the MIT license
7 * http://jquery.org/license
9 * Date: 2015-01-20T19:39Z
17 loggingCallbacks = {},
18 fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ),
19 toString = Object.prototype.toString,
20 hasOwn = Object.prototype.hasOwnProperty,
21 // Keep a local reference to Date (GH-283)
23 now = Date.now || function() {
24 return new Date().getTime();
26 globalStartCalled = false,
28 setTimeout = window.setTimeout,
29 clearTimeout = window.clearTimeout,
31 document: window.document !== undefined,
32 setTimeout: window.setTimeout !== undefined,
33 sessionStorage: (function() {
34 var x = "qunit-test-string";
36 sessionStorage.setItem( x, x );
37 sessionStorage.removeItem( x );
45 * Provides a normalized error string, correcting an issue
46 * with IE 7 (and prior) where Error.prototype.toString is
47 * not properly implemented
49 * Based on http://es5.github.com/#x15.11.4.4
51 * @param {String|Error} error
52 * @return {String} error message
54 errorString = function( error ) {
56 errorString = error.toString();
57 if ( errorString.substring( 0, 7 ) === "[object" ) {
58 name = error.name ? error.name.toString() : "Error";
59 message = error.message ? error.message.toString() : "";
60 if ( name && message ) {
61 return name + ": " + message;
64 } else if ( message ) {
74 * Makes a clone of an object using only Array or Object as base,
75 * and copies over the own enumerable properties.
78 * @return {Object} New object with only the own properties (recursively).
80 objectValues = function( obj ) {
82 vals = QUnit.is( "array", obj ) ? [] : {};
84 if ( hasOwn.call( obj, key ) ) {
86 vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
95 * Config object: Maintain internal state
96 * Later exposed as QUnit.config
97 * `config` initialized at top of scope
100 // The queue of tests to run
103 // block until document ready
106 // by default, run previously failed tests first
107 // very useful in combination with "Hide passed tests" checked
110 // by default, modify document.title when suite is done
113 // by default, scroll to top of the page when suite is done
116 // when enabled, all tests must call expect()
117 requireExpects: false,
119 // add checkboxes that are persisted in the query-string
120 // when enabled, the id is set to `true` as a `QUnit.config` property
124 label: "Hide passed tests",
125 tooltip: "Only show tests and assertions that fail. Stored as query-strings."
129 label: "Check for Globals",
130 tooltip: "Enabling this will test if any test introduces new properties on the " +
131 "`window` object. Stored as query-strings."
135 label: "No try-catch",
136 tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
137 "exceptions in IE reasonable. Stored as query-strings."
141 // Set of all modules.
144 // The first unnamed module
153 // Push a loose unnamed module to the modules collection
154 config.modules.push( config.currentModule );
156 // Initialize more QUnit.config and QUnit.urlParams
159 location = window.location || { search: "", protocol: "file:" },
160 params = location.search.slice( 1 ).split( "&" ),
161 length = params.length,
165 for ( i = 0; i < length; i++ ) {
166 current = params[ i ].split( "=" );
167 current[ 0 ] = decodeURIComponent( current[ 0 ] );
169 // allow just a key to turn on a flag, e.g., test.html?noglobals
170 current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
171 if ( urlParams[ current[ 0 ] ] ) {
172 urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );
174 urlParams[ current[ 0 ] ] = current[ 1 ];
179 if ( urlParams.filter === true ) {
180 delete urlParams.filter;
183 QUnit.urlParams = urlParams;
185 // String search anywhere in moduleName+testName
186 config.filter = urlParams.filter;
189 if ( urlParams.testId ) {
191 // Ensure that urlParams.testId is an array
192 urlParams.testId = [].concat( urlParams.testId );
193 for ( i = 0; i < urlParams.testId.length; i++ ) {
194 config.testId.push( urlParams.testId[ i ] );
198 // Figure out if we're running the tests from a server or not
199 QUnit.isLocal = location.protocol === "file:";
202 // Root QUnit object.
203 // `QUnit` initialized at top of scope
206 // call on start of module test to prepend name to all tests
207 module: function( name, testEnvironment ) {
208 var currentModule = {
210 testEnvironment: testEnvironment,
214 // DEPRECATED: handles setup/teardown functions,
215 // beforeEach and afterEach should be used instead
216 if ( testEnvironment && testEnvironment.setup ) {
217 testEnvironment.beforeEach = testEnvironment.setup;
218 delete testEnvironment.setup;
220 if ( testEnvironment && testEnvironment.teardown ) {
221 testEnvironment.afterEach = testEnvironment.teardown;
222 delete testEnvironment.teardown;
225 config.modules.push( currentModule );
226 config.currentModule = currentModule;
229 // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
230 asyncTest: function( testName, expected, callback ) {
231 if ( arguments.length === 2 ) {
236 QUnit.test( testName, expected, callback, true );
239 test: function( testName, expected, callback, async ) {
242 if ( arguments.length === 2 ) {
257 skip: function( testName ) {
258 var test = new Test({
266 // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
267 // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
268 start: function( count ) {
269 var globalStartAlreadyCalled = globalStartCalled;
271 if ( !config.current ) {
272 globalStartCalled = true;
275 throw new Error( "Called start() outside of a test context while already started" );
276 } else if ( globalStartAlreadyCalled || count > 1 ) {
277 throw new Error( "Called start() outside of a test context too many times" );
278 } else if ( config.autostart ) {
279 throw new Error( "Called start() outside of a test context when " +
280 "QUnit.config.autostart was true" );
281 } else if ( !config.pageLoaded ) {
283 // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
284 config.autostart = true;
289 // If a test is running, adjust its semaphore
290 config.current.semaphore -= count || 1;
292 // Don't start until equal number of stop-calls
293 if ( config.current.semaphore > 0 ) {
297 // throw an Error if start is called more often than stop
298 if ( config.current.semaphore < 0 ) {
299 config.current.semaphore = 0;
302 "Called start() while already started (test's semaphore was 0 already)",
303 sourceFromStacktrace( 2 )
312 // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
313 stop: function( count ) {
315 // If there isn't a test running, don't allow QUnit.stop() to be called
316 if ( !config.current ) {
317 throw new Error( "Called stop() outside of a test context" );
320 // If a test is running, adjust its semaphore
321 config.current.semaphore += count || 1;
328 // Safe object type checking
329 is: function( type, obj ) {
330 return QUnit.objectType( obj ) === type;
333 objectType: function( obj ) {
334 if ( typeof obj === "undefined" ) {
338 // Consider: typeof null === object
339 if ( obj === null ) {
343 var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
344 type = match && match[ 1 ] || "";
348 if ( isNaN( obj ) ) {
358 return type.toLowerCase();
360 if ( typeof obj === "object" ) {
369 config.pageLoaded = true;
371 // Initialize the configuration options
373 stats: { all: 0, bad: 0 },
374 moduleStats: { all: 0, bad: 0 },
381 config.blocking = false;
383 if ( config.autostart ) {
389 // Register logging callbacks
392 callbacks = [ "begin", "done", "log", "testStart", "testDone",
393 "moduleStart", "moduleDone" ];
395 function registerLoggingCallback( key ) {
396 var loggingCallback = function( callback ) {
397 if ( QUnit.objectType( callback ) !== "function" ) {
399 "QUnit logging methods require a callback function as their first parameters."
403 config.callbacks[ key ].push( callback );
406 // DEPRECATED: This will be removed on QUnit 2.0.0+
407 // Stores the registered functions allowing restoring
408 // at verifyLoggingCallbacks() if modified
409 loggingCallbacks[ key ] = loggingCallback;
411 return loggingCallback;
414 for ( i = 0, l = callbacks.length; i < l; i++ ) {
415 key = callbacks[ i ];
417 // Initialize key collection of logging callback
418 if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
419 config.callbacks[ key ] = [];
422 QUnit[ key ] = registerLoggingCallback( key );
426 // `onErrorFnPrev` initialized at top of scope
427 // Preserve other handlers
428 onErrorFnPrev = window.onerror;
430 // Cover uncaught exceptions
431 // Returning true will suppress the default browser handler,
432 // returning false will let it run.
433 window.onerror = function( error, filePath, linerNr ) {
435 if ( onErrorFnPrev ) {
436 ret = onErrorFnPrev( error, filePath, linerNr );
439 // Treat return value as window.onerror itself does,
440 // Only do our handling if not suppressed.
441 if ( ret !== true ) {
442 if ( QUnit.config.current ) {
443 if ( QUnit.config.current.ignoreGlobalErrors ) {
446 QUnit.pushFailure( error, filePath + ":" + linerNr );
448 QUnit.test( "global failure", extend(function() {
449 QUnit.pushFailure( error, filePath + ":" + linerNr );
450 }, { validTest: true } ) );
461 config.autorun = true;
463 // Log the last module results
464 if ( config.previousModule ) {
465 runLoggingCallbacks( "moduleDone", {
466 name: config.previousModule.name,
467 tests: config.previousModule.tests,
468 failed: config.moduleStats.bad,
469 passed: config.moduleStats.all - config.moduleStats.bad,
470 total: config.moduleStats.all,
471 runtime: now() - config.moduleStats.started
474 delete config.previousModule;
476 runtime = now() - config.started;
477 passed = config.stats.all - config.stats.bad;
479 runLoggingCallbacks( "done", {
480 failed: config.stats.bad,
482 total: config.stats.all,
487 // Doesn't support IE6 to IE9
488 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
489 function extractStacktrace( e, offset ) {
490 offset = offset === undefined ? 4 : offset;
492 var stack, include, i;
494 if ( e.stacktrace ) {
497 return e.stacktrace.split( "\n" )[ offset + 3 ];
498 } else if ( e.stack ) {
500 // Firefox, Chrome, Safari 6+, IE10+, PhantomJS and Node
501 stack = e.stack.split( "\n" );
502 if ( /^error$/i.test( stack[ 0 ] ) ) {
507 for ( i = offset; i < stack.length; i++ ) {
508 if ( stack[ i ].indexOf( fileName ) !== -1 ) {
511 include.push( stack[ i ] );
513 if ( include.length ) {
514 return include.join( "\n" );
517 return stack[ offset ];
518 } else if ( e.sourceURL ) {
521 // exclude useless self-reference for generated Error objects
522 if ( /qunit.js$/.test( e.sourceURL ) ) {
526 // for actual exceptions, this is useful
527 return e.sourceURL + ":" + e.line;
531 function sourceFromStacktrace( offset ) {
537 // This should already be true in most browsers
541 return extractStacktrace( e, offset );
544 function synchronize( callback, last ) {
545 if ( QUnit.objectType( callback ) === "array" ) {
546 while ( callback.length ) {
547 synchronize( callback.shift() );
551 config.queue.push( callback );
553 if ( config.autorun && !config.blocking ) {
558 function process( last ) {
563 config.depth = ( config.depth || 0 ) + 1;
565 while ( config.queue.length && !config.blocking ) {
566 if ( !defined.setTimeout || config.updateRate <= 0 ||
567 ( ( now() - start ) < config.updateRate ) ) {
568 if ( config.current ) {
570 // Reset async tracking for each phase of the Test lifecycle
571 config.current.usedAsync = false;
573 config.queue.shift()();
575 setTimeout( next, 13 );
580 if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
589 // If the test run hasn't officially begun yet
590 if ( !config.started ) {
592 // Record the time of the test run's beginning
593 config.started = now();
595 verifyLoggingCallbacks();
597 // Delete the loose unnamed module if unused.
598 if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
599 config.modules.shift();
602 // Avoid unnecessary information by not logging modules' test environments
603 for ( i = 0, l = config.modules.length; i < l; i++ ) {
605 name: config.modules[ i ].name,
606 tests: config.modules[ i ].tests
610 // The test run is officially beginning now
611 runLoggingCallbacks( "begin", {
612 totalTests: Test.count,
617 config.blocking = false;
621 function resumeProcessing() {
624 // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
625 if ( defined.setTimeout ) {
626 setTimeout(function() {
627 if ( config.current && config.current.semaphore > 0 ) {
630 if ( config.timeout ) {
631 clearTimeout( config.timeout );
641 function pauseProcessing() {
642 config.blocking = true;
644 if ( config.testTimeout && defined.setTimeout ) {
645 clearTimeout( config.timeout );
646 config.timeout = setTimeout(function() {
647 if ( config.current ) {
648 config.current.semaphore = 0;
649 QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
651 throw new Error( "Test timed out" );
654 }, config.testTimeout );
658 function saveGlobal() {
659 config.pollution = [];
661 if ( config.noglobals ) {
662 for ( var key in window ) {
663 if ( hasOwn.call( window, key ) ) {
664 // in Opera sometimes DOM element ids show up here, ignore them
665 if ( /^qunit-test-output/.test( key ) ) {
668 config.pollution.push( key );
674 function checkPollution() {
677 old = config.pollution;
681 newGlobals = diff( config.pollution, old );
682 if ( newGlobals.length > 0 ) {
683 QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
686 deletedGlobals = diff( old, config.pollution );
687 if ( deletedGlobals.length > 0 ) {
688 QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
692 // returns a new Array with the elements that are in a but not in b
693 function diff( a, b ) {
697 for ( i = 0; i < result.length; i++ ) {
698 for ( j = 0; j < b.length; j++ ) {
699 if ( result[ i ] === b[ j ] ) {
700 result.splice( i, 1 );
709 function extend( a, b, undefOnly ) {
710 for ( var prop in b ) {
711 if ( hasOwn.call( b, prop ) ) {
713 // Avoid "Member not found" error in IE8 caused by messing with window.constructor
714 if ( !( prop === "constructor" && a === window ) ) {
715 if ( b[ prop ] === undefined ) {
717 } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
718 a[ prop ] = b[ prop ];
727 function runLoggingCallbacks( key, args ) {
730 callbacks = config.callbacks[ key ];
731 for ( i = 0, l = callbacks.length; i < l; i++ ) {
732 callbacks[ i ]( args );
736 // DEPRECATED: This will be removed on 2.0.0+
737 // This function verifies if the loggingCallbacks were modified by the user
738 // If so, it will restore it, assign the given callback and print a console warning
739 function verifyLoggingCallbacks() {
740 var loggingCallback, userCallback;
742 for ( loggingCallback in loggingCallbacks ) {
743 if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
745 userCallback = QUnit[ loggingCallback ];
747 // Restore the callback function
748 QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
750 // Assign the deprecated given callback
751 QUnit[ loggingCallback ]( userCallback );
753 if ( window.console && window.console.warn ) {
755 "QUnit." + loggingCallback + " was replaced with a new value.\n" +
756 "Please, check out the documentation on how to apply logging callbacks.\n" +
757 "Reference: http://api.qunitjs.com/category/callbacks/"
765 function inArray( elem, array ) {
766 if ( array.indexOf ) {
767 return array.indexOf( elem );
770 for ( var i = 0, length = array.length; i < length; i++ ) {
771 if ( array[ i ] === elem ) {
779 function Test( settings ) {
784 extend( this, settings );
785 this.assertions = [];
787 this.usedAsync = false;
788 this.module = config.currentModule;
789 this.stack = sourceFromStacktrace( 3 );
791 // Register unique strings
792 for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
793 if ( this.module.tests[ i ].name === this.testName ) {
794 this.testName += " ";
798 this.testId = generateHash( this.module.name, this.testName );
800 this.module.tests.push({
805 if ( settings.skip ) {
807 // Skipped tests will fully ignore any sent callback
808 this.callback = function() {};
812 this.assert = new Assert( this );
822 // Emit moduleStart when we're switching from one module to another
823 this.module !== config.previousModule ||
825 // They could be equal (both undefined) but if the previousModule property doesn't
826 // yet exist it means this is the first test in a suite that isn't wrapped in a
827 // module, in which case we'll just emit a moduleStart event for 'undefined'.
828 // Without this, reporters can get testStart before moduleStart which is a problem.
829 !hasOwn.call( config, "previousModule" )
831 if ( hasOwn.call( config, "previousModule" ) ) {
832 runLoggingCallbacks( "moduleDone", {
833 name: config.previousModule.name,
834 tests: config.previousModule.tests,
835 failed: config.moduleStats.bad,
836 passed: config.moduleStats.all - config.moduleStats.bad,
837 total: config.moduleStats.all,
838 runtime: now() - config.moduleStats.started
841 config.previousModule = this.module;
842 config.moduleStats = { all: 0, bad: 0, started: now() };
843 runLoggingCallbacks( "moduleStart", {
844 name: this.module.name,
845 tests: this.module.tests
849 config.current = this;
851 this.testEnvironment = extend( {}, this.module.testEnvironment );
852 delete this.testEnvironment.beforeEach;
853 delete this.testEnvironment.afterEach;
855 this.started = now();
856 runLoggingCallbacks( "testStart", {
858 module: this.module.name,
862 if ( !config.pollution ) {
870 config.current = this;
876 this.callbackStarted = now();
878 if ( config.notrycatch ) {
879 promise = this.callback.call( this.testEnvironment, this.assert );
880 this.resolvePromise( promise );
885 promise = this.callback.call( this.testEnvironment, this.assert );
886 this.resolvePromise( promise );
888 this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
889 this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
891 // else next test will carry the responsibility
894 // Restart the tests if they're blocking
895 if ( config.blocking ) {
905 queueHook: function( hook, hookName ) {
908 return function runHook() {
909 config.current = test;
910 if ( config.notrycatch ) {
911 promise = hook.call( test.testEnvironment, test.assert );
912 test.resolvePromise( promise, hookName );
916 promise = hook.call( test.testEnvironment, test.assert );
917 test.resolvePromise( promise, hookName );
919 test.pushFailure( hookName + " failed on " + test.testName + ": " +
920 ( error.message || error ), extractStacktrace( error, 0 ) );
925 // Currently only used for module level hooks, can be used to add global level ones
926 hooks: function( handler ) {
929 // Hooks are ignored on skipped tests
934 if ( this.module.testEnvironment &&
935 QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
936 hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
943 config.current = this;
944 if ( config.requireExpects && this.expected === null ) {
945 this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
946 "not called.", this.stack );
947 } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
948 this.pushFailure( "Expected " + this.expected + " assertions, but " +
949 this.assertions.length + " were run", this.stack );
950 } else if ( this.expected === null && !this.assertions.length ) {
951 this.pushFailure( "Expected at least one assertion, but none were run - call " +
952 "expect(0) to accept zero assertions.", this.stack );
958 this.runtime = now() - this.started;
959 config.stats.all += this.assertions.length;
960 config.moduleStats.all += this.assertions.length;
962 for ( i = 0; i < this.assertions.length; i++ ) {
963 if ( !this.assertions[ i ].result ) {
966 config.moduleStats.bad++;
970 runLoggingCallbacks( "testDone", {
972 module: this.module.name,
973 skipped: !!this.skip,
975 passed: this.assertions.length - bad,
976 total: this.assertions.length,
977 runtime: this.runtime,
980 assertions: this.assertions,
983 // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
984 duration: this.runtime
987 // QUnit.reset() is deprecated and will be replaced for a new
988 // fixture reset function on QUnit 2.0/2.1.
989 // It's still called here for backwards compatibility handling
992 config.current = undefined;
999 if ( !this.valid() ) {
1005 // each of these can by async
1011 test.hooks( "beforeEach" ),
1017 test.hooks( "afterEach" ).reverse(),
1028 // `bad` initialized at top of scope
1029 // defer when previous test run passed, if storage is available
1030 bad = QUnit.config.reorder && defined.sessionStorage &&
1031 +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
1036 synchronize( run, true );
1040 push: function( result, actual, expected, message ) {
1043 module: this.module.name,
1044 name: this.testName,
1049 testId: this.testId,
1050 runtime: now() - this.started
1054 source = sourceFromStacktrace();
1057 details.source = source;
1061 runLoggingCallbacks( "log", details );
1063 this.assertions.push({
1069 pushFailure: function( message, source, actual ) {
1070 if ( !this instanceof Test ) {
1071 throw new Error( "pushFailure() assertion outside test context, was " +
1072 sourceFromStacktrace( 2 ) );
1076 module: this.module.name,
1077 name: this.testName,
1079 message: message || "error",
1080 actual: actual || null,
1081 testId: this.testId,
1082 runtime: now() - this.started
1086 details.source = source;
1089 runLoggingCallbacks( "log", details );
1091 this.assertions.push({
1097 resolvePromise: function( promise, phase ) {
1100 if ( promise != null ) {
1101 then = promise.then;
1102 if ( QUnit.objectType( then ) === "function" ) {
1108 message = "Promise rejected " +
1109 ( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
1110 " " + test.testName + ": " + ( error.message || error );
1111 test.pushFailure( message, extractStacktrace( error, 0 ) );
1113 // else next test will carry the responsibility
1126 filter = config.filter,
1127 module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
1128 fullName = ( this.module.name + ": " + this.testName ).toLowerCase();
1130 // Internally-generated tests are always valid
1131 if ( this.callback && this.callback.validTest ) {
1135 if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
1139 if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {
1147 include = filter.charAt( 0 ) !== "!";
1149 filter = filter.toLowerCase().slice( 1 );
1152 // If the filter matches, we need to honour include
1153 if ( fullName.indexOf( filter ) !== -1 ) {
1157 // Otherwise, do the opposite
1163 // Resets the test setup. Useful for tests that modify the DOM.
1165 DEPRECATED: Use multiple tests instead of resetting inside a test.
1166 Use testStart or testDone for custom cleanup.
1167 This method will throw an error in 2.0, and will be removed in 2.1
1169 QUnit.reset = function() {
1171 // Return on non-browser environments
1172 // This is necessary to not break on node tests
1173 if ( typeof window === "undefined" ) {
1177 var fixture = defined.document && document.getElementById &&
1178 document.getElementById( "qunit-fixture" );
1181 fixture.innerHTML = config.fixture;
1185 QUnit.pushFailure = function() {
1186 if ( !QUnit.config.current ) {
1187 throw new Error( "pushFailure() assertion outside test context, in " +
1188 sourceFromStacktrace( 2 ) );
1191 // Gets current test obj
1192 var currentTest = QUnit.config.current;
1194 return currentTest.pushFailure.apply( currentTest, arguments );
1197 // Based on Java's String.hashCode, a simple but not
1198 // rigorously collision resistant hashing function
1199 function generateHash( module, testName ) {
1203 str = module + "\x1C" + testName,
1206 for ( ; i < len; i++ ) {
1207 hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
1211 // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
1212 // strictly necessary but increases user understanding that the id is a SHA-like hash
1213 hex = ( 0x100000000 + hash ).toString( 16 );
1214 if ( hex.length < 8 ) {
1215 hex = "0000000" + hex;
1218 return hex.slice( -8 );
1221 function Assert( testContext ) {
1222 this.test = testContext;
1226 QUnit.assert = Assert.prototype = {
1228 // Specify the number of expected assertions to guarantee that failed test
1229 // (no assertions are run at all) don't slip through.
1230 expect: function( asserts ) {
1231 if ( arguments.length === 1 ) {
1232 this.test.expected = asserts;
1234 return this.test.expected;
1238 // Increment this Test's semaphore counter, then return a single-use function that
1239 // decrements that counter a maximum of once.
1241 var test = this.test,
1244 test.semaphore += 1;
1245 test.usedAsync = true;
1248 return function done() {
1250 test.semaphore -= 1;
1254 test.pushFailure( "Called the callback returned from `assert.async` more than once",
1255 sourceFromStacktrace( 2 ) );
1260 // Exports test.push() to the user API
1261 push: function( /* result, actual, expected, message */ ) {
1263 currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
1265 // Backwards compatibility fix.
1266 // Allows the direct use of global exported assertions and QUnit.assert.*
1267 // Although, it's use is not recommended as it can leak assertions
1268 // to other tests from async tests, because we only get a reference to the current test,
1269 // not exactly the test where assertion were intended to be called.
1270 if ( !currentTest ) {
1271 throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
1274 if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
1275 currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
1276 sourceFromStacktrace( 2 ) );
1278 // Allow this assertion to continue running anyway...
1281 if ( !( assert instanceof Assert ) ) {
1282 assert = currentTest.assert;
1284 return assert.test.push.apply( assert.test, arguments );
1288 * Asserts rough true-ish result.
1291 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
1293 ok: function( result, message ) {
1294 message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
1295 QUnit.dump.parse( result ) );
1296 this.push( !!result, result, true, message );
1300 * Assert that the first two arguments are equal, with an optional message.
1301 * Prints out both actual and expected values.
1304 * @example equal( format( "{0} bytes.", 2), "2 bytes.", "replaces {0} with next argument" );
1306 equal: function( actual, expected, message ) {
1307 /*jshint eqeqeq:false */
1308 this.push( expected == actual, actual, expected, message );
1315 notEqual: function( actual, expected, message ) {
1316 /*jshint eqeqeq:false */
1317 this.push( expected != actual, actual, expected, message );
1324 propEqual: function( actual, expected, message ) {
1325 actual = objectValues( actual );
1326 expected = objectValues( expected );
1327 this.push( QUnit.equiv( actual, expected ), actual, expected, message );
1331 * @name notPropEqual
1334 notPropEqual: function( actual, expected, message ) {
1335 actual = objectValues( actual );
1336 expected = objectValues( expected );
1337 this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
1344 deepEqual: function( actual, expected, message ) {
1345 this.push( QUnit.equiv( actual, expected ), actual, expected, message );
1349 * @name notDeepEqual
1352 notDeepEqual: function( actual, expected, message ) {
1353 this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
1360 strictEqual: function( actual, expected, message ) {
1361 this.push( expected === actual, actual, expected, message );
1365 * @name notStrictEqual
1368 notStrictEqual: function( actual, expected, message ) {
1369 this.push( expected !== actual, actual, expected, message );
1372 "throws": function( block, expected, message ) {
1373 var actual, expectedType,
1374 expectedOutput = expected,
1377 // 'expected' is optional unless doing string comparison
1378 if ( message == null && typeof expected === "string" ) {
1383 this.test.ignoreGlobalErrors = true;
1385 block.call( this.test.testEnvironment );
1389 this.test.ignoreGlobalErrors = false;
1392 expectedType = QUnit.objectType( expected );
1394 // we don't want to validate thrown error
1397 expectedOutput = null;
1399 // expected is a regexp
1400 } else if ( expectedType === "regexp" ) {
1401 ok = expected.test( errorString( actual ) );
1403 // expected is a string
1404 } else if ( expectedType === "string" ) {
1405 ok = expected === errorString( actual );
1407 // expected is a constructor, maybe an Error constructor
1408 } else if ( expectedType === "function" && actual instanceof expected ) {
1411 // expected is an Error object
1412 } else if ( expectedType === "object" ) {
1413 ok = actual instanceof expected.constructor &&
1414 actual.name === expected.name &&
1415 actual.message === expected.message;
1417 // expected is a validation function which returns true if validation passed
1418 } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
1419 expectedOutput = null;
1423 this.push( ok, actual, expectedOutput, message );
1425 this.test.pushFailure( message, null, "No exception was thrown." );
1430 // Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word
1431 // Known to us are: Closure Compiler, Narwhal
1433 /*jshint sub:true */
1434 Assert.prototype.raises = Assert.prototype[ "throws" ];
1437 // Test for equality any JavaScript type.
1438 // Author: Philippe Rathé <prathe@gmail.com>
1439 QUnit.equiv = (function() {
1441 // Call the o related callback with the given arguments.
1442 function bindCallbacks( o, callbacks, args ) {
1443 var prop = QUnit.objectType( o );
1445 if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
1446 return callbacks[ prop ].apply( callbacks, args );
1448 return callbacks[ prop ]; // or undefined
1453 // the real equiv function
1456 // stack to decide between skip/abort functions
1459 // stack to avoiding loops from circular referencing
1463 getProto = Object.getPrototypeOf || function( obj ) {
1464 /* jshint camelcase: false, proto: true */
1465 return obj.__proto__;
1467 callbacks = (function() {
1469 // for string, boolean, number and null
1470 function useStrictEquality( b, a ) {
1472 /*jshint eqeqeq:false */
1473 if ( b instanceof a.constructor || a instanceof b.constructor ) {
1475 // to catch short annotation VS 'new' annotation of a
1478 // var j = new Number(1);
1486 "string": useStrictEquality,
1487 "boolean": useStrictEquality,
1488 "number": useStrictEquality,
1489 "null": useStrictEquality,
1490 "undefined": useStrictEquality,
1492 "nan": function( b ) {
1496 "date": function( b, a ) {
1497 return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
1500 "regexp": function( b, a ) {
1501 return QUnit.objectType( b ) === "regexp" &&
1504 a.source === b.source &&
1506 // and its modifiers
1507 a.global === b.global &&
1510 a.ignoreCase === b.ignoreCase &&
1511 a.multiline === b.multiline &&
1512 a.sticky === b.sticky;
1515 // - skip when the property is a method of an instance (OOP)
1516 // - abort otherwise,
1517 // initial === would have catch identical references anyway
1518 "function": function() {
1519 var caller = callers[ callers.length - 1 ];
1520 return caller !== Object && typeof caller !== "undefined";
1523 "array": function( b, a ) {
1524 var i, j, len, loop, aCircular, bCircular;
1526 // b could be an object literal here
1527 if ( QUnit.objectType( b ) !== "array" ) {
1532 if ( len !== b.length ) {
1537 // track reference to avoid circular references
1540 for ( i = 0; i < len; i++ ) {
1542 for ( j = 0; j < parents.length; j++ ) {
1543 aCircular = parents[ j ] === a[ i ];
1544 bCircular = parentsB[ j ] === b[ i ];
1545 if ( aCircular || bCircular ) {
1546 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1555 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1566 "object": function( b, a ) {
1568 /*jshint forin:false */
1569 var i, j, loop, aCircular, bCircular,
1575 // comparing constructors is more strict than using
1577 if ( a.constructor !== b.constructor ) {
1579 // Allow objects with no prototype to be equivalent to
1580 // objects with Object as their constructor.
1581 if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
1582 ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
1587 // stack constructor before traversing properties
1588 callers.push( a.constructor );
1590 // track reference to avoid circular references
1594 // be strict: don't ensure hasOwnProperty and go deep
1597 for ( j = 0; j < parents.length; j++ ) {
1598 aCircular = parents[ j ] === a[ i ];
1599 bCircular = parentsB[ j ] === b[ i ];
1600 if ( aCircular || bCircular ) {
1601 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1609 aProperties.push( i );
1610 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1618 callers.pop(); // unstack, we are done
1621 bProperties.push( i ); // collect b's properties
1624 // Ensures identical properties name
1625 return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
1630 innerEquiv = function() { // can take multiple arguments
1631 var args = [].slice.apply( arguments );
1632 if ( args.length < 2 ) {
1633 return true; // end transition
1636 return ( (function( a, b ) {
1638 return true; // catch the most you can
1639 } else if ( a === null || b === null || typeof a === "undefined" ||
1640 typeof b === "undefined" ||
1641 QUnit.objectType( a ) !== QUnit.objectType( b ) ) {
1643 // don't lose time with error prone cases
1646 return bindCallbacks( a, callbacks, [ b, a ] );
1649 // apply transition with (1..n) arguments
1650 }( args[ 0 ], args[ 1 ] ) ) &&
1651 innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
1657 // Based on jsDump by Ariel Flesler
1658 // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
1659 QUnit.dump = (function() {
1660 function quote( str ) {
1661 return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
1663 function literal( o ) {
1666 function join( pre, arr, post ) {
1667 var s = dump.separator(),
1668 base = dump.indent(),
1669 inner = dump.indent( 1 );
1671 arr = arr.join( "," + s + inner );
1676 return [ pre, inner + arr, base + post ].join( s );
1678 function array( arr, stack ) {
1680 ret = new Array( i );
1682 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1683 return "[object Array]";
1688 ret[ i ] = this.parse( arr[ i ], undefined, stack );
1691 return join( "[", ret, "]" );
1694 var reName = /^function (\w+)/,
1697 // objType is used mostly internally, you can fix a (custom) type in advance
1698 parse: function( obj, objType, stack ) {
1699 stack = stack || [];
1700 var res, parser, parserType,
1701 inStack = inArray( obj, stack );
1703 if ( inStack !== -1 ) {
1704 return "recursion(" + ( inStack - stack.length ) + ")";
1707 objType = objType || this.typeOf( obj );
1708 parser = this.parsers[ objType ];
1709 parserType = typeof parser;
1711 if ( parserType === "function" ) {
1713 res = parser.call( this, obj, stack );
1717 return ( parserType === "string" ) ? parser : this.parsers.error;
1719 typeOf: function( obj ) {
1721 if ( obj === null ) {
1723 } else if ( typeof obj === "undefined" ) {
1725 } else if ( QUnit.is( "regexp", obj ) ) {
1727 } else if ( QUnit.is( "date", obj ) ) {
1729 } else if ( QUnit.is( "function", obj ) ) {
1731 } else if ( obj.setInterval !== undefined &&
1732 obj.document !== undefined &&
1733 obj.nodeType === undefined ) {
1735 } else if ( obj.nodeType === 9 ) {
1737 } else if ( obj.nodeType ) {
1742 toString.call( obj ) === "[object Array]" ||
1745 ( typeof obj.length === "number" && obj.item !== undefined &&
1746 ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
1747 obj[ 0 ] === undefined ) ) )
1750 } else if ( obj.constructor === Error.prototype.constructor ) {
1757 separator: function() {
1758 return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? " " : " ";
1760 // extra can be a number, shortcut for increasing-calling-decreasing
1761 indent: function( extra ) {
1762 if ( !this.multiline ) {
1765 var chr = this.indentChar;
1767 chr = chr.replace( /\t/g, " " ).replace( / /g, " " );
1769 return new Array( this.depth + ( extra || 0 ) ).join( chr );
1772 this.depth += a || 1;
1774 down: function( a ) {
1775 this.depth -= a || 1;
1777 setParser: function( name, parser ) {
1778 this.parsers[ name ] = parser;
1780 // The next 3 are exposed so you can use them
1788 // This is the list of parsers, to modify them, use dump.setParser
1791 document: "[Document]",
1792 error: function( error ) {
1793 return "Error(\"" + error.message + "\")";
1795 unknown: "[Unknown]",
1797 "undefined": "undefined",
1798 "function": function( fn ) {
1799 var ret = "function",
1801 // functions never have name in IE
1802 name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
1809 ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
1810 return join( ret, dump.parse( fn, "functionCode" ), "}" );
1815 object: function( map, stack ) {
1816 var keys, key, val, i, nonEnumerableProperties,
1819 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1820 return "[object Object]";
1825 for ( key in map ) {
1829 // Some properties are not always enumerable on Error objects.
1830 nonEnumerableProperties = [ "message", "name" ];
1831 for ( i in nonEnumerableProperties ) {
1832 key = nonEnumerableProperties[ i ];
1833 if ( key in map && !( key in keys ) ) {
1838 for ( i = 0; i < keys.length; i++ ) {
1841 ret.push( dump.parse( key, "key" ) + ": " +
1842 dump.parse( val, undefined, stack ) );
1845 return join( "{", ret, "}" );
1847 node: function( node ) {
1849 open = dump.HTML ? "<" : "<",
1850 close = dump.HTML ? ">" : ">",
1851 tag = node.nodeName.toLowerCase(),
1853 attrs = node.attributes;
1856 for ( i = 0, len = attrs.length; i < len; i++ ) {
1857 val = attrs[ i ].nodeValue;
1859 // IE6 includes all attributes in .attributes, even ones not explicitly
1860 // set. Those have values like undefined, null, 0, false, "" or
1862 if ( val && val !== "inherit" ) {
1863 ret += " " + attrs[ i ].nodeName + "=" +
1864 dump.parse( val, "attribute" );
1870 // Show content of TextNode or CDATASection
1871 if ( node.nodeType === 3 || node.nodeType === 4 ) {
1872 ret += node.nodeValue;
1875 return ret + open + "/" + tag + close;
1878 // function calls it internally, it's the arguments part of the function
1879 functionArgs: function( fn ) {
1887 args = new Array( l );
1891 args[ l ] = String.fromCharCode( 97 + l );
1893 return " " + args.join( ", " ) + " ";
1895 // object calls it internally, the key part of an item in a map
1897 // function calls it internally, it's the content of the function
1898 functionCode: "[code]",
1899 // node calls it internally, it's an html attribute value
1907 // if true, entities are escaped ( <, >, \t, space and \n )
1911 // if true, items in a collection, are separated by a \n, else just a space.
1919 QUnit.jsDump = QUnit.dump;
1921 // For browser, export only select globals
1922 if ( typeof window !== "undefined" ) {
1925 // Extend assert methods to QUnit and Global scope through Backwards compatibility
1928 assertions = Assert.prototype;
1930 function applyCurrent( current ) {
1932 var assert = new Assert( QUnit.config.current );
1933 current.apply( assert, arguments );
1937 for ( i in assertions ) {
1938 QUnit[ i ] = applyCurrent( assertions[ i ] );
1963 for ( i = 0, l = keys.length; i < l; i++ ) {
1964 window[ keys[ i ] ] = QUnit[ keys[ i ] ];
1968 window.QUnit = QUnit;
1972 if ( typeof module !== "undefined" && module && module.exports ) {
1973 module.exports = QUnit;
1975 // For consistency with CommonJS environments' exports
1976 module.exports.QUnit = QUnit;
1979 // For CommonJS with exports, but without module.exports, like Rhino
1980 if ( typeof exports !== "undefined" && exports ) {
1981 exports.QUnit = QUnit;
1984 // Get a reference to the global object, like window in browsers
1989 /*istanbul ignore next */
1990 // jscs:disable maximumLineLength
1992 * Javascript Diff Algorithm
1993 * By John Resig (http://ejohn.org/)
1994 * Modified by Chu Alan "sprite"
1996 * Released under the MIT license.
1999 * http://ejohn.org/projects/javascript-diff-algorithm/
2001 * Usage: QUnit.diff(expected, actual)
2003 * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
2005 QUnit.diff = (function() {
2006 var hasOwn = Object.prototype.hasOwnProperty;
2008 /*jshint eqeqeq:false, eqnull:true */
2009 function diff( o, n ) {
2014 for ( i = 0; i < n.length; i++ ) {
2015 if ( !hasOwn.call( ns, n[ i ] ) ) {
2021 ns[ n[ i ] ].rows.push( i );
2024 for ( i = 0; i < o.length; i++ ) {
2025 if ( !hasOwn.call( os, o[ i ] ) ) {
2031 os[ o[ i ] ].rows.push( i );
2035 if ( hasOwn.call( ns, i ) ) {
2036 if ( ns[ i ].rows.length === 1 && hasOwn.call( os, i ) && os[ i ].rows.length === 1 ) {
2037 n[ ns[ i ].rows[ 0 ] ] = {
2038 text: n[ ns[ i ].rows[ 0 ] ],
2039 row: os[ i ].rows[ 0 ]
2041 o[ os[ i ].rows[ 0 ] ] = {
2042 text: o[ os[ i ].rows[ 0 ] ],
2043 row: ns[ i ].rows[ 0 ]
2049 for ( i = 0; i < n.length - 1; i++ ) {
2050 if ( n[ i ].text != null && n[ i + 1 ].text == null && n[ i ].row + 1 < o.length && o[ n[ i ].row + 1 ].text == null &&
2051 n[ i + 1 ] == o[ n[ i ].row + 1 ] ) {
2057 o[ n[ i ].row + 1 ] = {
2058 text: o[ n[ i ].row + 1 ],
2064 for ( i = n.length - 1; i > 0; i-- ) {
2065 if ( n[ i ].text != null && n[ i - 1 ].text == null && n[ i ].row > 0 && o[ n[ i ].row - 1 ].text == null &&
2066 n[ i - 1 ] == o[ n[ i ].row - 1 ] ) {
2072 o[ n[ i ].row - 1 ] = {
2073 text: o[ n[ i ].row - 1 ],
2085 return function( o, n ) {
2086 o = o.replace( /\s+$/, "" );
2087 n = n.replace( /\s+$/, "" );
2091 out = diff( o === "" ? [] : o.split( /\s+/ ), n === "" ? [] : n.split( /\s+/ ) ),
2092 oSpace = o.match( /\s+/g ),
2093 nSpace = n.match( /\s+/g );
2095 if ( oSpace == null ) {
2101 if ( nSpace == null ) {
2107 if ( out.n.length === 0 ) {
2108 for ( i = 0; i < out.o.length; i++ ) {
2109 str += "<del>" + out.o[ i ] + oSpace[ i ] + "</del>";
2112 if ( out.n[ 0 ].text == null ) {
2113 for ( n = 0; n < out.o.length && out.o[ n ].text == null; n++ ) {
2114 str += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>";
2118 for ( i = 0; i < out.n.length; i++ ) {
2119 if ( out.n[ i ].text == null ) {
2120 str += "<ins>" + out.n[ i ] + nSpace[ i ] + "</ins>";
2123 // `pre` initialized at top of scope
2126 for ( n = out.n[ i ].row + 1; n < out.o.length && out.o[ n ].text == null; n++ ) {
2127 pre += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>";
2129 str += " " + out.n[ i ].text + nSpace[ i ] + pre;
2141 // Deprecated QUnit.init - Ref #530
2142 // Re-initialize the configuration options
2143 QUnit.init = function() {
2144 var tests, banner, result, qunit,
2145 config = QUnit.config;
2147 config.stats = { all: 0, bad: 0 };
2148 config.moduleStats = { all: 0, bad: 0 };
2150 config.updateRate = 1000;
2151 config.blocking = false;
2152 config.autostart = true;
2153 config.autorun = false;
2157 // Return on non-browser environments
2158 // This is necessary to not break on node tests
2159 if ( typeof window === "undefined" ) {
2163 qunit = id( "qunit" );
2166 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
2167 "<h2 id='qunit-banner'></h2>" +
2168 "<div id='qunit-testrunner-toolbar'></div>" +
2169 "<h2 id='qunit-userAgent'></h2>" +
2170 "<ol id='qunit-tests'></ol>";
2173 tests = id( "qunit-tests" );
2174 banner = id( "qunit-banner" );
2175 result = id( "qunit-testresult" );
2178 tests.innerHTML = "";
2182 banner.className = "";
2186 result.parentNode.removeChild( result );
2190 result = document.createElement( "p" );
2191 result.id = "qunit-testresult";
2192 result.className = "result";
2193 tests.parentNode.insertBefore( result, tests );
2194 result.innerHTML = "Running...<br /> ";
2198 // Don't load the HTML Reporter on non-Browser environments
2199 if ( typeof window === "undefined" ) {
2203 var config = QUnit.config,
2204 hasOwn = Object.prototype.hasOwnProperty,
2206 document: window.document !== undefined,
2207 sessionStorage: (function() {
2208 var x = "qunit-test-string";
2210 sessionStorage.setItem( x, x );
2211 sessionStorage.removeItem( x );
2221 * Escape text for attribute or text content.
2223 function escapeText( s ) {
2229 // Both single quotes and double quotes (for attributes)
2230 return s.replace( /['"<>&]/g, function( s ) {
2247 * @param {HTMLElement} elem
2248 * @param {string} type
2249 * @param {Function} fn
2251 function addEvent( elem, type, fn ) {
2252 if ( elem.addEventListener ) {
2254 // Standards-based browsers
2255 elem.addEventListener( type, fn, false );
2256 } else if ( elem.attachEvent ) {
2259 elem.attachEvent( "on" + type, fn );
2264 * @param {Array|NodeList} elems
2265 * @param {string} type
2266 * @param {Function} fn
2268 function addEvents( elems, type, fn ) {
2269 var i = elems.length;
2271 addEvent( elems[ i ], type, fn );
2275 function hasClass( elem, name ) {
2276 return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
2279 function addClass( elem, name ) {
2280 if ( !hasClass( elem, name ) ) {
2281 elem.className += ( elem.className ? " " : "" ) + name;
2285 function toggleClass( elem, name ) {
2286 if ( hasClass( elem, name ) ) {
2287 removeClass( elem, name );
2289 addClass( elem, name );
2293 function removeClass( elem, name ) {
2294 var set = " " + elem.className + " ";
2296 // Class name may appear multiple times
2297 while ( set.indexOf( " " + name + " " ) >= 0 ) {
2298 set = set.replace( " " + name + " ", " " );
2301 // trim for prettiness
2302 elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
2305 function id( name ) {
2306 return defined.document && document.getElementById && document.getElementById( name );
2309 function getUrlConfigHtml() {
2311 escaped, escapedTooltip,
2313 len = config.urlConfig.length,
2316 for ( i = 0; i < len; i++ ) {
2317 val = config.urlConfig[ i ];
2318 if ( typeof val === "string" ) {
2325 escaped = escapeText( val.id );
2326 escapedTooltip = escapeText( val.tooltip );
2328 if ( config[ val.id ] === undefined ) {
2329 config[ val.id ] = QUnit.urlParams[ val.id ];
2332 if ( !val.value || typeof val.value === "string" ) {
2333 urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
2334 "' name='" + escaped + "' type='checkbox'" +
2335 ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
2336 ( config[ val.id ] ? " checked='checked'" : "" ) +
2337 " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
2338 "' title='" + escapedTooltip + "'>" + val.label + "</label>";
2340 urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
2341 "' title='" + escapedTooltip + "'>" + val.label +
2342 ": </label><select id='qunit-urlconfig-" + escaped +
2343 "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
2345 if ( QUnit.is( "array", val.value ) ) {
2346 for ( j = 0; j < val.value.length; j++ ) {
2347 escaped = escapeText( val.value[ j ] );
2348 urlConfigHtml += "<option value='" + escaped + "'" +
2349 ( config[ val.id ] === val.value[ j ] ?
2350 ( selection = true ) && " selected='selected'" : "" ) +
2351 ">" + escaped + "</option>";
2354 for ( j in val.value ) {
2355 if ( hasOwn.call( val.value, j ) ) {
2356 urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
2357 ( config[ val.id ] === j ?
2358 ( selection = true ) && " selected='selected'" : "" ) +
2359 ">" + escapeText( val.value[ j ] ) + "</option>";
2363 if ( config[ val.id ] && !selection ) {
2364 escaped = escapeText( config[ val.id ] );
2365 urlConfigHtml += "<option value='" + escaped +
2366 "' selected='selected' disabled='disabled'>" + escaped + "</option>";
2368 urlConfigHtml += "</select>";
2372 return urlConfigHtml;
2375 // Handle "click" events on toolbar checkboxes and "change" for select menus.
2376 // Updates the URL with the new state of `config.urlConfig` values.
2377 function toolbarChanged() {
2378 var updatedUrl, value,
2382 // Detect if field is a select menu or a checkbox
2383 if ( "selectedIndex" in field ) {
2384 value = field.options[ field.selectedIndex ].value || undefined;
2386 value = field.checked ? ( field.defaultValue || true ) : undefined;
2389 params[ field.name ] = value;
2390 updatedUrl = setUrl( params );
2392 if ( "hidepassed" === field.name && "replaceState" in window.history ) {
2393 config[ field.name ] = value || false;
2395 addClass( id( "qunit-tests" ), "hidepass" );
2397 removeClass( id( "qunit-tests" ), "hidepass" );
2400 // It is not necessary to refresh the whole page
2401 window.history.replaceState( null, "", updatedUrl );
2403 window.location = updatedUrl;
2407 function setUrl( params ) {
2411 params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
2413 for ( key in params ) {
2414 if ( hasOwn.call( params, key ) ) {
2415 if ( params[ key ] === undefined ) {
2418 querystring += encodeURIComponent( key );
2419 if ( params[ key ] !== true ) {
2420 querystring += "=" + encodeURIComponent( params[ key ] );
2425 return location.protocol + "//" + location.host +
2426 location.pathname + querystring.slice( 0, -1 );
2429 function applyUrlParams() {
2430 var selectBox = id( "qunit-modulefilter" ),
2431 selection = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value ),
2432 filter = id( "qunit-filter-input" ).value;
2434 window.location = setUrl({
2435 module: ( selection === "" ) ? undefined : selection,
2436 filter: ( filter === "" ) ? undefined : filter,
2438 // Remove testId filter
2443 function toolbarUrlConfigContainer() {
2444 var urlConfigContainer = document.createElement( "span" );
2446 urlConfigContainer.innerHTML = getUrlConfigHtml();
2447 addClass( urlConfigContainer, "qunit-url-config" );
2449 // For oldIE support:
2450 // * Add handlers to the individual elements instead of the container
2451 // * Use "click" instead of "change" for checkboxes
2452 addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
2453 addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
2455 return urlConfigContainer;
2458 function toolbarLooseFilter() {
2459 var filter = document.createElement( "form" ),
2460 label = document.createElement( "label" ),
2461 input = document.createElement( "input" ),
2462 button = document.createElement( "button" );
2464 addClass( filter, "qunit-filter" );
2466 label.innerHTML = "Filter: ";
2468 input.type = "text";
2469 input.value = config.filter || "";
2470 input.name = "filter";
2471 input.id = "qunit-filter-input";
2473 button.innerHTML = "Go";
2475 label.appendChild( input );
2477 filter.appendChild( label );
2478 filter.appendChild( button );
2479 addEvent( filter, "submit", function( ev ) {
2482 if ( ev && ev.preventDefault ) {
2483 ev.preventDefault();
2492 function toolbarModuleFilterHtml() {
2494 moduleFilterHtml = "";
2496 if ( !modulesList.length ) {
2500 modulesList.sort(function( a, b ) {
2501 return a.localeCompare( b );
2504 moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
2505 "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
2506 ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
2507 ">< All Modules ></option>";
2509 for ( i = 0; i < modulesList.length; i++ ) {
2510 moduleFilterHtml += "<option value='" +
2511 escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
2512 ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
2513 ">" + escapeText( modulesList[ i ] ) + "</option>";
2515 moduleFilterHtml += "</select>";
2517 return moduleFilterHtml;
2520 function toolbarModuleFilter() {
2521 var toolbar = id( "qunit-testrunner-toolbar" ),
2522 moduleFilter = document.createElement( "span" ),
2523 moduleFilterHtml = toolbarModuleFilterHtml();
2525 if ( !toolbar || !moduleFilterHtml ) {
2529 moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
2530 moduleFilter.innerHTML = moduleFilterHtml;
2532 addEvent( moduleFilter.lastChild, "change", applyUrlParams );
2534 toolbar.appendChild( moduleFilter );
2537 function appendToolbar() {
2538 var toolbar = id( "qunit-testrunner-toolbar" );
2541 toolbar.appendChild( toolbarUrlConfigContainer() );
2542 toolbar.appendChild( toolbarLooseFilter() );
2546 function appendHeader() {
2547 var header = id( "qunit-header" );
2550 header.innerHTML = "<a href='" +
2551 setUrl({ filter: undefined, module: undefined, testId: undefined }) +
2552 "'>" + header.innerHTML + "</a> ";
2556 function appendBanner() {
2557 var banner = id( "qunit-banner" );
2560 banner.className = "";
2564 function appendTestResults() {
2565 var tests = id( "qunit-tests" ),
2566 result = id( "qunit-testresult" );
2569 result.parentNode.removeChild( result );
2573 tests.innerHTML = "";
2574 result = document.createElement( "p" );
2575 result.id = "qunit-testresult";
2576 result.className = "result";
2577 tests.parentNode.insertBefore( result, tests );
2578 result.innerHTML = "Running...<br /> ";
2582 function storeFixture() {
2583 var fixture = id( "qunit-fixture" );
2585 config.fixture = fixture.innerHTML;
2589 function appendUserAgent() {
2590 var userAgent = id( "qunit-userAgent" );
2592 userAgent.innerHTML = "";
2593 userAgent.appendChild( document.createTextNode( navigator.userAgent ) );
2597 function appendTestsList( modules ) {
2598 var i, l, x, z, test, moduleObj;
2600 for ( i = 0, l = modules.length; i < l; i++ ) {
2601 moduleObj = modules[ i ];
2603 if ( moduleObj.name ) {
2604 modulesList.push( moduleObj.name );
2607 for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
2608 test = moduleObj.tests[ x ];
2610 appendTest( test.name, test.testId, moduleObj.name );
2615 function appendTest( name, testId, moduleName ) {
2616 var title, rerunTrigger, testBlock, assertList,
2617 tests = id( "qunit-tests" );
2623 title = document.createElement( "strong" );
2624 title.innerHTML = getNameHtml( name, moduleName );
2626 rerunTrigger = document.createElement( "a" );
2627 rerunTrigger.innerHTML = "Rerun";
2628 rerunTrigger.href = setUrl({ testId: testId });
2630 testBlock = document.createElement( "li" );
2631 testBlock.appendChild( title );
2632 testBlock.appendChild( rerunTrigger );
2633 testBlock.id = "qunit-test-output-" + testId;
2635 assertList = document.createElement( "ol" );
2636 assertList.className = "qunit-assert-list";
2638 testBlock.appendChild( assertList );
2640 tests.appendChild( testBlock );
2643 // HTML Reporter initialization and load
2644 QUnit.begin(function( details ) {
2645 var qunit = id( "qunit" );
2647 // Fixture is the only one necessary to run without the #qunit element
2652 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
2653 "<h2 id='qunit-banner'></h2>" +
2654 "<div id='qunit-testrunner-toolbar'></div>" +
2655 "<h2 id='qunit-userAgent'></h2>" +
2656 "<ol id='qunit-tests'></ol>";
2661 appendTestResults();
2664 appendTestsList( details.modules );
2665 toolbarModuleFilter();
2667 if ( qunit && config.hidepassed ) {
2668 addClass( qunit.lastChild, "hidepass" );
2672 QUnit.done(function( details ) {
2674 banner = id( "qunit-banner" ),
2675 tests = id( "qunit-tests" ),
2677 "Tests completed in ",
2679 " milliseconds.<br />",
2680 "<span class='passed'>",
2682 "</span> assertions of <span class='total'>",
2684 "</span> passed, <span class='failed'>",
2690 banner.className = details.failed ? "qunit-fail" : "qunit-pass";
2694 id( "qunit-testresult" ).innerHTML = html;
2697 if ( config.altertitle && defined.document && document.title ) {
2699 // show ✖ for good, ✔ for bad suite result in title
2700 // use escape sequences in case file gets loaded with non-utf-8-charset
2702 ( details.failed ? "\u2716" : "\u2714" ),
2703 document.title.replace( /^[\u2714\u2716] /i, "" )
2707 // clear own sessionStorage items if all tests passed
2708 if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
2709 for ( i = 0; i < sessionStorage.length; i++ ) {
2710 key = sessionStorage.key( i++ );
2711 if ( key.indexOf( "qunit-test-" ) === 0 ) {
2712 sessionStorage.removeItem( key );
2717 // scroll back to top to show results
2718 if ( config.scrolltop && window.scrollTo ) {
2719 window.scrollTo( 0, 0 );
2723 function getNameHtml( name, module ) {
2727 nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
2730 nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
2735 QUnit.testStart(function( details ) {
2736 var running, testBlock;
2738 testBlock = id( "qunit-test-output-" + details.testId );
2740 testBlock.className = "running";
2743 // Report later registered tests
2744 appendTest( details.name, details.testId, details.module );
2747 running = id( "qunit-testresult" );
2749 running.innerHTML = "Running: <br />" + getNameHtml( details.name, details.module );
2754 QUnit.log(function( details ) {
2755 var assertList, assertLi,
2756 message, expected, actual,
2757 testItem = id( "qunit-test-output-" + details.testId );
2763 message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
2764 message = "<span class='test-message'>" + message + "</span>";
2765 message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
2767 // pushFailure doesn't provide details.expected
2768 // when it calls, it's implicit to also not show expected and diff stuff
2769 // Also, we need to check details.expected existence, as it can exist and be undefined
2770 if ( !details.result && hasOwn.call( details, "expected" ) ) {
2771 expected = escapeText( QUnit.dump.parse( details.expected ) );
2772 actual = escapeText( QUnit.dump.parse( details.actual ) );
2773 message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
2777 if ( actual !== expected ) {
2778 message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
2779 actual + "</pre></td></tr>" +
2780 "<tr class='test-diff'><th>Diff: </th><td><pre>" +
2781 QUnit.diff( expected, actual ) + "</pre></td></tr>";
2784 if ( details.source ) {
2785 message += "<tr class='test-source'><th>Source: </th><td><pre>" +
2786 escapeText( details.source ) + "</pre></td></tr>";
2789 message += "</table>";
2791 // this occours when pushFailure is set and we have an extracted stack trace
2792 } else if ( !details.result && details.source ) {
2793 message += "<table>" +
2794 "<tr class='test-source'><th>Source: </th><td><pre>" +
2795 escapeText( details.source ) + "</pre></td></tr>" +
2799 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
2801 assertLi = document.createElement( "li" );
2802 assertLi.className = details.result ? "pass" : "fail";
2803 assertLi.innerHTML = message;
2804 assertList.appendChild( assertLi );
2807 QUnit.testDone(function( details ) {
2808 var testTitle, time, testItem, assertList,
2809 good, bad, testCounts, skipped,
2810 tests = id( "qunit-tests" );
2816 testItem = id( "qunit-test-output-" + details.testId );
2818 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
2820 good = details.passed;
2821 bad = details.failed;
2823 // store result when possible
2824 if ( config.reorder && defined.sessionStorage ) {
2826 sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
2828 sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
2833 addClass( assertList, "qunit-collapsed" );
2836 // testItem.firstChild is the test name
2837 testTitle = testItem.firstChild;
2840 "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
2843 testTitle.innerHTML += " <b class='counts'>(" + testCounts +
2844 details.assertions.length + ")</b>";
2846 if ( details.skipped ) {
2847 testItem.className = "skipped";
2848 skipped = document.createElement( "em" );
2849 skipped.className = "qunit-skipped-label";
2850 skipped.innerHTML = "skipped";
2851 testItem.insertBefore( skipped, testTitle );
2853 addEvent( testTitle, "click", function() {
2854 toggleClass( assertList, "qunit-collapsed" );
2857 testItem.className = bad ? "fail" : "pass";
2859 time = document.createElement( "span" );
2860 time.className = "runtime";
2861 time.innerHTML = details.runtime + " ms";
2862 testItem.insertBefore( time, assertList );
2866 if ( !defined.document || document.readyState === "complete" ) {
2867 config.pageLoaded = true;
2868 config.autorun = true;
2871 if ( defined.document ) {
2872 addEvent( window, "load", QUnit.load );