5 * Copyright 2006, 2014 jQuery Foundation and other contributors
6 * Released under the MIT license
7 * http://jquery.org/license
9 * Date: 2014-12-03T16:32Z
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 // when enabled, show only failing tests
107 // gets persisted through sessionStorage and can be changed in UI via checkbox
110 // by default, run previously failed tests first
111 // very useful in combination with "Hide passed tests" checked
114 // by default, modify document.title when suite is done
117 // by default, scroll to top of the page when suite is done
120 // when enabled, all tests must call expect()
121 requireExpects: false,
123 // add checkboxes that are persisted in the query-string
124 // when enabled, the id is set to `true` as a `QUnit.config` property
128 label: "Hide passed tests",
129 tooltip: "Only show tests and assertions that fail. Stored as query-strings."
133 label: "Check for Globals",
134 tooltip: "Enabling this will test if any test introduces new properties on the " +
135 "`window` object. Stored as query-strings."
139 label: "No try-catch",
140 tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
141 "exceptions in IE reasonable. Stored as query-strings."
145 // Set of all modules.
148 // The first unnamed module
157 // Push a loose unnamed module to the modules collection
158 config.modules.push( config.currentModule );
160 // Initialize more QUnit.config and QUnit.urlParams
163 location = window.location || { search: "", protocol: "file:" },
164 params = location.search.slice( 1 ).split( "&" ),
165 length = params.length,
169 for ( i = 0; i < length; i++ ) {
170 current = params[ i ].split( "=" );
171 current[ 0 ] = decodeURIComponent( current[ 0 ] );
173 // allow just a key to turn on a flag, e.g., test.html?noglobals
174 current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
175 if ( urlParams[ current[ 0 ] ] ) {
176 urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );
178 urlParams[ current[ 0 ] ] = current[ 1 ];
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" ) {
366 url: function( params ) {
367 params = extend( extend( {}, QUnit.urlParams ), params );
371 for ( key in params ) {
372 if ( hasOwn.call( params, key ) ) {
373 querystring += encodeURIComponent( key );
374 if ( params[ key ] !== true ) {
375 querystring += "=" + encodeURIComponent( params[ key ] );
380 return location.protocol + "//" + location.host +
381 location.pathname + querystring.slice( 0, -1 );
387 config.pageLoaded = true;
389 // Initialize the configuration options
391 stats: { all: 0, bad: 0 },
392 moduleStats: { all: 0, bad: 0 },
399 config.blocking = false;
401 if ( config.autostart ) {
407 // Register logging callbacks
410 callbacks = [ "begin", "done", "log", "testStart", "testDone",
411 "moduleStart", "moduleDone" ];
413 function registerLoggingCallback( key ) {
414 var loggingCallback = function( callback ) {
415 if ( QUnit.objectType( callback ) !== "function" ) {
417 "QUnit logging methods require a callback function as their first parameters."
421 config.callbacks[ key ].push( callback );
424 // DEPRECATED: This will be removed on QUnit 2.0.0+
425 // Stores the registered functions allowing restoring
426 // at verifyLoggingCallbacks() if modified
427 loggingCallbacks[ key ] = loggingCallback;
429 return loggingCallback;
432 for ( i = 0, l = callbacks.length; i < l; i++ ) {
433 key = callbacks[ i ];
435 // Initialize key collection of logging callback
436 if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
437 config.callbacks[ key ] = [];
440 QUnit[ key ] = registerLoggingCallback( key );
444 // `onErrorFnPrev` initialized at top of scope
445 // Preserve other handlers
446 onErrorFnPrev = window.onerror;
448 // Cover uncaught exceptions
449 // Returning true will suppress the default browser handler,
450 // returning false will let it run.
451 window.onerror = function( error, filePath, linerNr ) {
453 if ( onErrorFnPrev ) {
454 ret = onErrorFnPrev( error, filePath, linerNr );
457 // Treat return value as window.onerror itself does,
458 // Only do our handling if not suppressed.
459 if ( ret !== true ) {
460 if ( QUnit.config.current ) {
461 if ( QUnit.config.current.ignoreGlobalErrors ) {
464 QUnit.pushFailure( error, filePath + ":" + linerNr );
466 QUnit.test( "global failure", extend(function() {
467 QUnit.pushFailure( error, filePath + ":" + linerNr );
468 }, { validTest: true } ) );
479 config.autorun = true;
481 // Log the last module results
482 if ( config.previousModule ) {
483 runLoggingCallbacks( "moduleDone", {
484 name: config.previousModule.name,
485 tests: config.previousModule.tests,
486 failed: config.moduleStats.bad,
487 passed: config.moduleStats.all - config.moduleStats.bad,
488 total: config.moduleStats.all,
489 runtime: now() - config.moduleStats.started
492 delete config.previousModule;
494 runtime = now() - config.started;
495 passed = config.stats.all - config.stats.bad;
497 runLoggingCallbacks( "done", {
498 failed: config.stats.bad,
500 total: config.stats.all,
505 // Doesn't support IE6 to IE9
506 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
507 function extractStacktrace( e, offset ) {
508 offset = offset === undefined ? 4 : offset;
510 var stack, include, i;
512 if ( e.stacktrace ) {
515 return e.stacktrace.split( "\n" )[ offset + 3 ];
516 } else if ( e.stack ) {
518 // Firefox, Chrome, Safari 6+, IE10+, PhantomJS and Node
519 stack = e.stack.split( "\n" );
520 if ( /^error$/i.test( stack[ 0 ] ) ) {
525 for ( i = offset; i < stack.length; i++ ) {
526 if ( stack[ i ].indexOf( fileName ) !== -1 ) {
529 include.push( stack[ i ] );
531 if ( include.length ) {
532 return include.join( "\n" );
535 return stack[ offset ];
536 } else if ( e.sourceURL ) {
539 // exclude useless self-reference for generated Error objects
540 if ( /qunit.js$/.test( e.sourceURL ) ) {
544 // for actual exceptions, this is useful
545 return e.sourceURL + ":" + e.line;
549 function sourceFromStacktrace( offset ) {
555 // This should already be true in most browsers
559 return extractStacktrace( e, offset );
562 function synchronize( callback, last ) {
563 if ( QUnit.objectType( callback ) === "array" ) {
564 while ( callback.length ) {
565 synchronize( callback.shift() );
569 config.queue.push( callback );
571 if ( config.autorun && !config.blocking ) {
576 function process( last ) {
581 config.depth = config.depth ? config.depth + 1 : 1;
583 while ( config.queue.length && !config.blocking ) {
584 if ( !defined.setTimeout || config.updateRate <= 0 ||
585 ( ( now() - start ) < config.updateRate ) ) {
586 if ( config.current ) {
588 // Reset async tracking for each phase of the Test lifecycle
589 config.current.usedAsync = false;
591 config.queue.shift()();
593 setTimeout( next, 13 );
598 if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
607 // If the test run hasn't officially begun yet
608 if ( !config.started ) {
610 // Record the time of the test run's beginning
611 config.started = now();
613 verifyLoggingCallbacks();
615 // Delete the loose unnamed module if unused.
616 if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
617 config.modules.shift();
620 // Avoid unnecessary information by not logging modules' test environments
621 for ( i = 0, l = config.modules.length; i < l; i++ ) {
623 name: config.modules[ i ].name,
624 tests: config.modules[ i ].tests
628 // The test run is officially beginning now
629 runLoggingCallbacks( "begin", {
630 totalTests: Test.count,
635 config.blocking = false;
639 function resumeProcessing() {
642 // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
643 if ( defined.setTimeout ) {
644 setTimeout(function() {
645 if ( config.current && config.current.semaphore > 0 ) {
648 if ( config.timeout ) {
649 clearTimeout( config.timeout );
659 function pauseProcessing() {
660 config.blocking = true;
662 if ( config.testTimeout && defined.setTimeout ) {
663 clearTimeout( config.timeout );
664 config.timeout = setTimeout(function() {
665 if ( config.current ) {
666 config.current.semaphore = 0;
667 QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
669 throw new Error( "Test timed out" );
672 }, config.testTimeout );
676 function saveGlobal() {
677 config.pollution = [];
679 if ( config.noglobals ) {
680 for ( var key in window ) {
681 if ( hasOwn.call( window, key ) ) {
682 // in Opera sometimes DOM element ids show up here, ignore them
683 if ( /^qunit-test-output/.test( key ) ) {
686 config.pollution.push( key );
692 function checkPollution() {
695 old = config.pollution;
699 newGlobals = diff( config.pollution, old );
700 if ( newGlobals.length > 0 ) {
701 QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
704 deletedGlobals = diff( old, config.pollution );
705 if ( deletedGlobals.length > 0 ) {
706 QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
710 // returns a new Array with the elements that are in a but not in b
711 function diff( a, b ) {
715 for ( i = 0; i < result.length; i++ ) {
716 for ( j = 0; j < b.length; j++ ) {
717 if ( result[ i ] === b[ j ] ) {
718 result.splice( i, 1 );
727 function extend( a, b, undefOnly ) {
728 for ( var prop in b ) {
729 if ( hasOwn.call( b, prop ) ) {
731 // Avoid "Member not found" error in IE8 caused by messing with window.constructor
732 if ( !( prop === "constructor" && a === window ) ) {
733 if ( b[ prop ] === undefined ) {
735 } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
736 a[ prop ] = b[ prop ];
745 function runLoggingCallbacks( key, args ) {
748 callbacks = config.callbacks[ key ];
749 for ( i = 0, l = callbacks.length; i < l; i++ ) {
750 callbacks[ i ]( args );
754 // DEPRECATED: This will be removed on 2.0.0+
755 // This function verifies if the loggingCallbacks were modified by the user
756 // If so, it will restore it, assign the given callback and print a console warning
757 function verifyLoggingCallbacks() {
758 var loggingCallback, userCallback;
760 for ( loggingCallback in loggingCallbacks ) {
761 if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
763 userCallback = QUnit[ loggingCallback ];
765 // Restore the callback function
766 QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
768 // Assign the deprecated given callback
769 QUnit[ loggingCallback ]( userCallback );
771 if ( window.console && window.console.warn ) {
773 "QUnit." + loggingCallback + " was replaced with a new value.\n" +
774 "Please, check out the documentation on how to apply logging callbacks.\n" +
775 "Reference: http://api.qunitjs.com/category/callbacks/"
783 function inArray( elem, array ) {
784 if ( array.indexOf ) {
785 return array.indexOf( elem );
788 for ( var i = 0, length = array.length; i < length; i++ ) {
789 if ( array[ i ] === elem ) {
797 function Test( settings ) {
802 extend( this, settings );
803 this.assertions = [];
805 this.usedAsync = false;
806 this.module = config.currentModule;
807 this.stack = sourceFromStacktrace( 3 );
809 // Register unique strings
810 for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
811 if ( this.module.tests[ i ].name === this.testName ) {
812 this.testName += " ";
816 this.testId = generateHash( this.module.name, this.testName );
818 this.module.tests.push({
823 if ( settings.skip ) {
825 // Skipped tests will fully ignore any sent callback
826 this.callback = function() {};
830 this.assert = new Assert( this );
840 // Emit moduleStart when we're switching from one module to another
841 this.module !== config.previousModule ||
843 // They could be equal (both undefined) but if the previousModule property doesn't
844 // yet exist it means this is the first test in a suite that isn't wrapped in a
845 // module, in which case we'll just emit a moduleStart event for 'undefined'.
846 // Without this, reporters can get testStart before moduleStart which is a problem.
847 !hasOwn.call( config, "previousModule" )
849 if ( hasOwn.call( config, "previousModule" ) ) {
850 runLoggingCallbacks( "moduleDone", {
851 name: config.previousModule.name,
852 tests: config.previousModule.tests,
853 failed: config.moduleStats.bad,
854 passed: config.moduleStats.all - config.moduleStats.bad,
855 total: config.moduleStats.all,
856 runtime: now() - config.moduleStats.started
859 config.previousModule = this.module;
860 config.moduleStats = { all: 0, bad: 0, started: now() };
861 runLoggingCallbacks( "moduleStart", {
862 name: this.module.name,
863 tests: this.module.tests
867 config.current = this;
869 this.testEnvironment = extend( {}, this.module.testEnvironment );
870 delete this.testEnvironment.beforeEach;
871 delete this.testEnvironment.afterEach;
873 this.started = now();
874 runLoggingCallbacks( "testStart", {
876 module: this.module.name,
880 if ( !config.pollution ) {
888 config.current = this;
894 this.callbackStarted = now();
896 if ( config.notrycatch ) {
897 promise = this.callback.call( this.testEnvironment, this.assert );
898 this.resolvePromise( promise );
903 promise = this.callback.call( this.testEnvironment, this.assert );
904 this.resolvePromise( promise );
906 this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
907 this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
909 // else next test will carry the responsibility
912 // Restart the tests if they're blocking
913 if ( config.blocking ) {
923 queueHook: function( hook, hookName ) {
926 return function runHook() {
927 config.current = test;
928 if ( config.notrycatch ) {
929 promise = hook.call( test.testEnvironment, test.assert );
930 test.resolvePromise( promise, hookName );
934 promise = hook.call( test.testEnvironment, test.assert );
935 test.resolvePromise( promise, hookName );
937 test.pushFailure( hookName + " failed on " + test.testName + ": " +
938 ( error.message || error ), extractStacktrace( error, 0 ) );
943 // Currently only used for module level hooks, can be used to add global level ones
944 hooks: function( handler ) {
947 // Hooks are ignored on skipped tests
952 if ( this.module.testEnvironment &&
953 QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
954 hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
961 config.current = this;
962 if ( config.requireExpects && this.expected === null ) {
963 this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
964 "not called.", this.stack );
965 } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
966 this.pushFailure( "Expected " + this.expected + " assertions, but " +
967 this.assertions.length + " were run", this.stack );
968 } else if ( this.expected === null && !this.assertions.length ) {
969 this.pushFailure( "Expected at least one assertion, but none were run - call " +
970 "expect(0) to accept zero assertions.", this.stack );
976 this.runtime = now() - this.started;
977 config.stats.all += this.assertions.length;
978 config.moduleStats.all += this.assertions.length;
980 for ( i = 0; i < this.assertions.length; i++ ) {
981 if ( !this.assertions[ i ].result ) {
984 config.moduleStats.bad++;
988 runLoggingCallbacks( "testDone", {
990 module: this.module.name,
991 skipped: !!this.skip,
993 passed: this.assertions.length - bad,
994 total: this.assertions.length,
995 runtime: this.runtime,
998 assertions: this.assertions,
1001 // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
1002 duration: this.runtime
1005 // QUnit.reset() is deprecated and will be replaced for a new
1006 // fixture reset function on QUnit 2.0/2.1.
1007 // It's still called here for backwards compatibility handling
1010 config.current = undefined;
1017 if ( !this.valid() ) {
1023 // each of these can by async
1029 test.hooks( "beforeEach" ),
1035 test.hooks( "afterEach" ).reverse(),
1046 // `bad` initialized at top of scope
1047 // defer when previous test run passed, if storage is available
1048 bad = QUnit.config.reorder && defined.sessionStorage &&
1049 +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
1054 synchronize( run, true );
1058 push: function( result, actual, expected, message ) {
1061 module: this.module.name,
1062 name: this.testName,
1067 testId: this.testId,
1068 runtime: now() - this.started
1072 source = sourceFromStacktrace();
1075 details.source = source;
1079 runLoggingCallbacks( "log", details );
1081 this.assertions.push({
1087 pushFailure: function( message, source, actual ) {
1088 if ( !this instanceof Test ) {
1089 throw new Error( "pushFailure() assertion outside test context, was " +
1090 sourceFromStacktrace( 2 ) );
1094 module: this.module.name,
1095 name: this.testName,
1097 message: message || "error",
1098 actual: actual || null,
1099 testId: this.testId,
1100 runtime: now() - this.started
1104 details.source = source;
1107 runLoggingCallbacks( "log", details );
1109 this.assertions.push({
1115 resolvePromise: function( promise, phase ) {
1118 if ( promise != null ) {
1119 then = promise.then;
1120 if ( QUnit.objectType( then ) === "function" ) {
1126 message = "Promise rejected " +
1127 ( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
1128 " " + test.testName + ": " + ( error.message || error );
1129 test.pushFailure( message, extractStacktrace( error, 0 ) );
1131 // else next test will carry the responsibility
1144 filter = config.filter && config.filter.toLowerCase(),
1145 module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
1146 fullName = ( this.module.name + ": " + this.testName ).toLowerCase();
1148 // Internally-generated tests are always valid
1149 if ( this.callback && this.callback.validTest ) {
1153 if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
1157 if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {
1165 include = filter.charAt( 0 ) !== "!";
1167 filter = filter.slice( 1 );
1170 // If the filter matches, we need to honour include
1171 if ( fullName.indexOf( filter ) !== -1 ) {
1175 // Otherwise, do the opposite
1181 // Resets the test setup. Useful for tests that modify the DOM.
1183 DEPRECATED: Use multiple tests instead of resetting inside a test.
1184 Use testStart or testDone for custom cleanup.
1185 This method will throw an error in 2.0, and will be removed in 2.1
1187 QUnit.reset = function() {
1189 // Return on non-browser environments
1190 // This is necessary to not break on node tests
1191 if ( typeof window === "undefined" ) {
1195 var fixture = defined.document && document.getElementById &&
1196 document.getElementById( "qunit-fixture" );
1199 fixture.innerHTML = config.fixture;
1203 QUnit.pushFailure = function() {
1204 if ( !QUnit.config.current ) {
1205 throw new Error( "pushFailure() assertion outside test context, in " +
1206 sourceFromStacktrace( 2 ) );
1209 // Gets current test obj
1210 var currentTest = QUnit.config.current;
1212 return currentTest.pushFailure.apply( currentTest, arguments );
1215 // Based on Java's String.hashCode, a simple but not
1216 // rigorously collision resistant hashing function
1217 function generateHash( module, testName ) {
1221 str = module + "\x1C" + testName,
1224 for ( ; i < len; i++ ) {
1225 hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
1229 // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
1230 // strictly necessary but increases user understanding that the id is a SHA-like hash
1231 hex = ( 0x100000000 + hash ).toString( 16 );
1232 if ( hex.length < 8 ) {
1233 hex = "0000000" + hex;
1236 return hex.slice( -8 );
1239 function Assert( testContext ) {
1240 this.test = testContext;
1244 QUnit.assert = Assert.prototype = {
1246 // Specify the number of expected assertions to guarantee that failed test
1247 // (no assertions are run at all) don't slip through.
1248 expect: function( asserts ) {
1249 if ( arguments.length === 1 ) {
1250 this.test.expected = asserts;
1252 return this.test.expected;
1256 // Increment this Test's semaphore counter, then return a single-use function that
1257 // decrements that counter a maximum of once.
1259 var test = this.test,
1262 test.semaphore += 1;
1263 test.usedAsync = true;
1266 return function done() {
1268 test.semaphore -= 1;
1272 test.pushFailure( "Called the callback returned from `assert.async` more than once",
1273 sourceFromStacktrace( 2 ) );
1278 // Exports test.push() to the user API
1279 push: function( /* result, actual, expected, message */ ) {
1281 currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
1283 // Backwards compatibility fix.
1284 // Allows the direct use of global exported assertions and QUnit.assert.*
1285 // Although, it's use is not recommended as it can leak assertions
1286 // to other tests from async tests, because we only get a reference to the current test,
1287 // not exactly the test where assertion were intended to be called.
1288 if ( !currentTest ) {
1289 throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
1292 if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
1293 currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
1294 sourceFromStacktrace( 2 ) );
1296 // Allow this assertion to continue running anyway...
1299 if ( !( assert instanceof Assert ) ) {
1300 assert = currentTest.assert;
1302 return assert.test.push.apply( assert.test, arguments );
1306 * Asserts rough true-ish result.
1309 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
1311 ok: function( result, message ) {
1312 message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
1313 QUnit.dump.parse( result ) );
1314 this.push( !!result, result, true, message );
1318 * Assert that the first two arguments are equal, with an optional message.
1319 * Prints out both actual and expected values.
1322 * @example equal( format( "{0} bytes.", 2), "2 bytes.", "replaces {0} with next argument" );
1324 equal: function( actual, expected, message ) {
1325 /*jshint eqeqeq:false */
1326 this.push( expected == actual, actual, expected, message );
1333 notEqual: function( actual, expected, message ) {
1334 /*jshint eqeqeq:false */
1335 this.push( expected != actual, actual, expected, message );
1342 propEqual: function( actual, expected, message ) {
1343 actual = objectValues( actual );
1344 expected = objectValues( expected );
1345 this.push( QUnit.equiv( actual, expected ), actual, expected, message );
1349 * @name notPropEqual
1352 notPropEqual: function( actual, expected, message ) {
1353 actual = objectValues( actual );
1354 expected = objectValues( expected );
1355 this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
1362 deepEqual: function( actual, expected, message ) {
1363 this.push( QUnit.equiv( actual, expected ), actual, expected, message );
1367 * @name notDeepEqual
1370 notDeepEqual: function( actual, expected, message ) {
1371 this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
1378 strictEqual: function( actual, expected, message ) {
1379 this.push( expected === actual, actual, expected, message );
1383 * @name notStrictEqual
1386 notStrictEqual: function( actual, expected, message ) {
1387 this.push( expected !== actual, actual, expected, message );
1390 "throws": function( block, expected, message ) {
1391 var actual, expectedType,
1392 expectedOutput = expected,
1395 // 'expected' is optional unless doing string comparison
1396 if ( message == null && typeof expected === "string" ) {
1401 this.test.ignoreGlobalErrors = true;
1403 block.call( this.test.testEnvironment );
1407 this.test.ignoreGlobalErrors = false;
1410 expectedType = QUnit.objectType( expected );
1412 // we don't want to validate thrown error
1415 expectedOutput = null;
1417 // expected is a regexp
1418 } else if ( expectedType === "regexp" ) {
1419 ok = expected.test( errorString( actual ) );
1421 // expected is a string
1422 } else if ( expectedType === "string" ) {
1423 ok = expected === errorString( actual );
1425 // expected is a constructor, maybe an Error constructor
1426 } else if ( expectedType === "function" && actual instanceof expected ) {
1429 // expected is an Error object
1430 } else if ( expectedType === "object" ) {
1431 ok = actual instanceof expected.constructor &&
1432 actual.name === expected.name &&
1433 actual.message === expected.message;
1435 // expected is a validation function which returns true if validation passed
1436 } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
1437 expectedOutput = null;
1441 this.push( ok, actual, expectedOutput, message );
1443 this.test.pushFailure( message, null, "No exception was thrown." );
1448 // Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word
1449 // Known to us are: Closure Compiler, Narwhal
1451 /*jshint sub:true */
1452 Assert.prototype.raises = Assert.prototype[ "throws" ];
1455 // Test for equality any JavaScript type.
1456 // Author: Philippe Rathé <prathe@gmail.com>
1457 QUnit.equiv = (function() {
1459 // Call the o related callback with the given arguments.
1460 function bindCallbacks( o, callbacks, args ) {
1461 var prop = QUnit.objectType( o );
1463 if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
1464 return callbacks[ prop ].apply( callbacks, args );
1466 return callbacks[ prop ]; // or undefined
1471 // the real equiv function
1474 // stack to decide between skip/abort functions
1477 // stack to avoiding loops from circular referencing
1481 getProto = Object.getPrototypeOf || function( obj ) {
1482 /* jshint camelcase: false, proto: true */
1483 return obj.__proto__;
1485 callbacks = (function() {
1487 // for string, boolean, number and null
1488 function useStrictEquality( b, a ) {
1490 /*jshint eqeqeq:false */
1491 if ( b instanceof a.constructor || a instanceof b.constructor ) {
1493 // to catch short annotation VS 'new' annotation of a
1496 // var j = new Number(1);
1504 "string": useStrictEquality,
1505 "boolean": useStrictEquality,
1506 "number": useStrictEquality,
1507 "null": useStrictEquality,
1508 "undefined": useStrictEquality,
1510 "nan": function( b ) {
1514 "date": function( b, a ) {
1515 return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
1518 "regexp": function( b, a ) {
1519 return QUnit.objectType( b ) === "regexp" &&
1522 a.source === b.source &&
1524 // and its modifiers
1525 a.global === b.global &&
1528 a.ignoreCase === b.ignoreCase &&
1529 a.multiline === b.multiline &&
1530 a.sticky === b.sticky;
1533 // - skip when the property is a method of an instance (OOP)
1534 // - abort otherwise,
1535 // initial === would have catch identical references anyway
1536 "function": function() {
1537 var caller = callers[ callers.length - 1 ];
1538 return caller !== Object && typeof caller !== "undefined";
1541 "array": function( b, a ) {
1542 var i, j, len, loop, aCircular, bCircular;
1544 // b could be an object literal here
1545 if ( QUnit.objectType( b ) !== "array" ) {
1550 if ( len !== b.length ) {
1555 // track reference to avoid circular references
1558 for ( i = 0; i < len; i++ ) {
1560 for ( j = 0; j < parents.length; j++ ) {
1561 aCircular = parents[ j ] === a[ i ];
1562 bCircular = parentsB[ j ] === b[ i ];
1563 if ( aCircular || bCircular ) {
1564 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1573 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1584 "object": function( b, a ) {
1586 /*jshint forin:false */
1587 var i, j, loop, aCircular, bCircular,
1593 // comparing constructors is more strict than using
1595 if ( a.constructor !== b.constructor ) {
1597 // Allow objects with no prototype to be equivalent to
1598 // objects with Object as their constructor.
1599 if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
1600 ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
1605 // stack constructor before traversing properties
1606 callers.push( a.constructor );
1608 // track reference to avoid circular references
1612 // be strict: don't ensure hasOwnProperty and go deep
1615 for ( j = 0; j < parents.length; j++ ) {
1616 aCircular = parents[ j ] === a[ i ];
1617 bCircular = parentsB[ j ] === b[ i ];
1618 if ( aCircular || bCircular ) {
1619 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1627 aProperties.push( i );
1628 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1636 callers.pop(); // unstack, we are done
1639 bProperties.push( i ); // collect b's properties
1642 // Ensures identical properties name
1643 return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
1648 innerEquiv = function() { // can take multiple arguments
1649 var args = [].slice.apply( arguments );
1650 if ( args.length < 2 ) {
1651 return true; // end transition
1654 return ( (function( a, b ) {
1656 return true; // catch the most you can
1657 } else if ( a === null || b === null || typeof a === "undefined" ||
1658 typeof b === "undefined" ||
1659 QUnit.objectType( a ) !== QUnit.objectType( b ) ) {
1661 // don't lose time with error prone cases
1664 return bindCallbacks( a, callbacks, [ b, a ] );
1667 // apply transition with (1..n) arguments
1668 }( args[ 0 ], args[ 1 ] ) ) &&
1669 innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
1675 // Based on jsDump by Ariel Flesler
1676 // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
1677 QUnit.dump = (function() {
1678 function quote( str ) {
1679 return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
1681 function literal( o ) {
1684 function join( pre, arr, post ) {
1685 var s = dump.separator(),
1686 base = dump.indent(),
1687 inner = dump.indent( 1 );
1689 arr = arr.join( "," + s + inner );
1694 return [ pre, inner + arr, base + post ].join( s );
1696 function array( arr, stack ) {
1698 ret = new Array( i );
1700 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1701 return "[object Array]";
1706 ret[ i ] = this.parse( arr[ i ], undefined, stack );
1709 return join( "[", ret, "]" );
1712 var reName = /^function (\w+)/,
1715 // objType is used mostly internally, you can fix a (custom) type in advance
1716 parse: function( obj, objType, stack ) {
1717 stack = stack || [];
1718 var res, parser, parserType,
1719 inStack = inArray( obj, stack );
1721 if ( inStack !== -1 ) {
1722 return "recursion(" + ( inStack - stack.length ) + ")";
1725 objType = objType || this.typeOf( obj );
1726 parser = this.parsers[ objType ];
1727 parserType = typeof parser;
1729 if ( parserType === "function" ) {
1731 res = parser.call( this, obj, stack );
1735 return ( parserType === "string" ) ? parser : this.parsers.error;
1737 typeOf: function( obj ) {
1739 if ( obj === null ) {
1741 } else if ( typeof obj === "undefined" ) {
1743 } else if ( QUnit.is( "regexp", obj ) ) {
1745 } else if ( QUnit.is( "date", obj ) ) {
1747 } else if ( QUnit.is( "function", obj ) ) {
1749 } else if ( obj.setInterval !== undefined &&
1750 obj.document !== undefined &&
1751 obj.nodeType === undefined ) {
1753 } else if ( obj.nodeType === 9 ) {
1755 } else if ( obj.nodeType ) {
1760 toString.call( obj ) === "[object Array]" ||
1763 ( typeof obj.length === "number" && obj.item !== undefined &&
1764 ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
1765 obj[ 0 ] === undefined ) ) )
1768 } else if ( obj.constructor === Error.prototype.constructor ) {
1775 separator: function() {
1776 return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? " " : " ";
1778 // extra can be a number, shortcut for increasing-calling-decreasing
1779 indent: function( extra ) {
1780 if ( !this.multiline ) {
1783 var chr = this.indentChar;
1785 chr = chr.replace( /\t/g, " " ).replace( / /g, " " );
1787 return new Array( this.depth + ( extra || 0 ) ).join( chr );
1790 this.depth += a || 1;
1792 down: function( a ) {
1793 this.depth -= a || 1;
1795 setParser: function( name, parser ) {
1796 this.parsers[ name ] = parser;
1798 // The next 3 are exposed so you can use them
1806 // This is the list of parsers, to modify them, use dump.setParser
1809 document: "[Document]",
1810 error: function( error ) {
1811 return "Error(\"" + error.message + "\")";
1813 unknown: "[Unknown]",
1815 "undefined": "undefined",
1816 "function": function( fn ) {
1817 var ret = "function",
1819 // functions never have name in IE
1820 name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
1827 ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
1828 return join( ret, dump.parse( fn, "functionCode" ), "}" );
1833 object: function( map, stack ) {
1834 var keys, key, val, i, nonEnumerableProperties,
1837 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1838 return "[object Object]";
1843 for ( key in map ) {
1847 // Some properties are not always enumerable on Error objects.
1848 nonEnumerableProperties = [ "message", "name" ];
1849 for ( i in nonEnumerableProperties ) {
1850 key = nonEnumerableProperties[ i ];
1851 if ( key in map && !( key in keys ) ) {
1856 for ( i = 0; i < keys.length; i++ ) {
1859 ret.push( dump.parse( key, "key" ) + ": " +
1860 dump.parse( val, undefined, stack ) );
1863 return join( "{", ret, "}" );
1865 node: function( node ) {
1867 open = dump.HTML ? "<" : "<",
1868 close = dump.HTML ? ">" : ">",
1869 tag = node.nodeName.toLowerCase(),
1871 attrs = node.attributes;
1874 for ( i = 0, len = attrs.length; i < len; i++ ) {
1875 val = attrs[ i ].nodeValue;
1877 // IE6 includes all attributes in .attributes, even ones not explicitly
1878 // set. Those have values like undefined, null, 0, false, "" or
1880 if ( val && val !== "inherit" ) {
1881 ret += " " + attrs[ i ].nodeName + "=" +
1882 dump.parse( val, "attribute" );
1888 // Show content of TextNode or CDATASection
1889 if ( node.nodeType === 3 || node.nodeType === 4 ) {
1890 ret += node.nodeValue;
1893 return ret + open + "/" + tag + close;
1896 // function calls it internally, it's the arguments part of the function
1897 functionArgs: function( fn ) {
1905 args = new Array( l );
1909 args[ l ] = String.fromCharCode( 97 + l );
1911 return " " + args.join( ", " ) + " ";
1913 // object calls it internally, the key part of an item in a map
1915 // function calls it internally, it's the content of the function
1916 functionCode: "[code]",
1917 // node calls it internally, it's an html attribute value
1925 // if true, entities are escaped ( <, >, \t, space and \n )
1929 // if true, items in a collection, are separated by a \n, else just a space.
1937 QUnit.jsDump = QUnit.dump;
1939 // For browser, export only select globals
1940 if ( typeof window !== "undefined" ) {
1943 // Extend assert methods to QUnit and Global scope through Backwards compatibility
1946 assertions = Assert.prototype;
1948 function applyCurrent( current ) {
1950 var assert = new Assert( QUnit.config.current );
1951 current.apply( assert, arguments );
1955 for ( i in assertions ) {
1956 QUnit[ i ] = applyCurrent( assertions[ i ] );
1981 for ( i = 0, l = keys.length; i < l; i++ ) {
1982 window[ keys[ i ] ] = QUnit[ keys[ i ] ];
1986 window.QUnit = QUnit;
1990 if ( typeof module !== "undefined" && module.exports ) {
1991 module.exports = QUnit;
1994 // For CommonJS with exports, but without module.exports, like Rhino
1995 if ( typeof exports !== "undefined" ) {
1996 exports.QUnit = QUnit;
1999 // Get a reference to the global object, like window in browsers
2004 /*istanbul ignore next */
2005 // jscs:disable maximumLineLength
2007 * Javascript Diff Algorithm
2008 * By John Resig (http://ejohn.org/)
2009 * Modified by Chu Alan "sprite"
2011 * Released under the MIT license.
2014 * http://ejohn.org/projects/javascript-diff-algorithm/
2016 * Usage: QUnit.diff(expected, actual)
2018 * 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"
2020 QUnit.diff = (function() {
2021 var hasOwn = Object.prototype.hasOwnProperty;
2023 /*jshint eqeqeq:false, eqnull:true */
2024 function diff( o, n ) {
2029 for ( i = 0; i < n.length; i++ ) {
2030 if ( !hasOwn.call( ns, n[ i ] ) ) {
2036 ns[ n[ i ] ].rows.push( i );
2039 for ( i = 0; i < o.length; i++ ) {
2040 if ( !hasOwn.call( os, o[ i ] ) ) {
2046 os[ o[ i ] ].rows.push( i );
2050 if ( hasOwn.call( ns, i ) ) {
2051 if ( ns[ i ].rows.length === 1 && hasOwn.call( os, i ) && os[ i ].rows.length === 1 ) {
2052 n[ ns[ i ].rows[ 0 ] ] = {
2053 text: n[ ns[ i ].rows[ 0 ] ],
2054 row: os[ i ].rows[ 0 ]
2056 o[ os[ i ].rows[ 0 ] ] = {
2057 text: o[ os[ i ].rows[ 0 ] ],
2058 row: ns[ i ].rows[ 0 ]
2064 for ( i = 0; i < n.length - 1; i++ ) {
2065 if ( n[ i ].text != null && n[ i + 1 ].text == null && n[ i ].row + 1 < o.length && 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 ],
2079 for ( i = n.length - 1; i > 0; i-- ) {
2080 if ( n[ i ].text != null && n[ i - 1 ].text == null && n[ i ].row > 0 && o[ n[ i ].row - 1 ].text == null &&
2081 n[ i - 1 ] == o[ n[ i ].row - 1 ] ) {
2087 o[ n[ i ].row - 1 ] = {
2088 text: o[ n[ i ].row - 1 ],
2100 return function( o, n ) {
2101 o = o.replace( /\s+$/, "" );
2102 n = n.replace( /\s+$/, "" );
2106 out = diff( o === "" ? [] : o.split( /\s+/ ), n === "" ? [] : n.split( /\s+/ ) ),
2107 oSpace = o.match( /\s+/g ),
2108 nSpace = n.match( /\s+/g );
2110 if ( oSpace == null ) {
2116 if ( nSpace == null ) {
2122 if ( out.n.length === 0 ) {
2123 for ( i = 0; i < out.o.length; i++ ) {
2124 str += "<del>" + out.o[ i ] + oSpace[ i ] + "</del>";
2127 if ( out.n[ 0 ].text == null ) {
2128 for ( n = 0; n < out.o.length && out.o[ n ].text == null; n++ ) {
2129 str += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>";
2133 for ( i = 0; i < out.n.length; i++ ) {
2134 if ( out.n[ i ].text == null ) {
2135 str += "<ins>" + out.n[ i ] + nSpace[ i ] + "</ins>";
2138 // `pre` initialized at top of scope
2141 for ( n = out.n[ i ].row + 1; n < out.o.length && out.o[ n ].text == null; n++ ) {
2142 pre += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>";
2144 str += " " + out.n[ i ].text + nSpace[ i ] + pre;
2156 // Deprecated QUnit.init - Ref #530
2157 // Re-initialize the configuration options
2158 QUnit.init = function() {
2159 var tests, banner, result, qunit,
2160 config = QUnit.config;
2162 config.stats = { all: 0, bad: 0 };
2163 config.moduleStats = { all: 0, bad: 0 };
2165 config.updateRate = 1000;
2166 config.blocking = false;
2167 config.autostart = true;
2168 config.autorun = false;
2172 // Return on non-browser environments
2173 // This is necessary to not break on node tests
2174 if ( typeof window === "undefined" ) {
2178 qunit = id( "qunit" );
2181 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
2182 "<h2 id='qunit-banner'></h2>" +
2183 "<div id='qunit-testrunner-toolbar'></div>" +
2184 "<h2 id='qunit-userAgent'></h2>" +
2185 "<ol id='qunit-tests'></ol>";
2188 tests = id( "qunit-tests" );
2189 banner = id( "qunit-banner" );
2190 result = id( "qunit-testresult" );
2193 tests.innerHTML = "";
2197 banner.className = "";
2201 result.parentNode.removeChild( result );
2205 result = document.createElement( "p" );
2206 result.id = "qunit-testresult";
2207 result.className = "result";
2208 tests.parentNode.insertBefore( result, tests );
2209 result.innerHTML = "Running...<br /> ";
2213 // Don't load the HTML Reporter on non-Browser environments
2214 if ( typeof window === "undefined" ) {
2218 var config = QUnit.config,
2219 hasOwn = Object.prototype.hasOwnProperty,
2221 document: window.document !== undefined,
2222 sessionStorage: (function() {
2223 var x = "qunit-test-string";
2225 sessionStorage.setItem( x, x );
2226 sessionStorage.removeItem( x );
2236 * Escape text for attribute or text content.
2238 function escapeText( s ) {
2244 // Both single quotes and double quotes (for attributes)
2245 return s.replace( /['"<>&]/g, function( s ) {
2262 * @param {HTMLElement} elem
2263 * @param {string} type
2264 * @param {Function} fn
2266 function addEvent( elem, type, fn ) {
2267 if ( elem.addEventListener ) {
2269 // Standards-based browsers
2270 elem.addEventListener( type, fn, false );
2271 } else if ( elem.attachEvent ) {
2274 elem.attachEvent( "on" + type, fn );
2279 * @param {Array|NodeList} elems
2280 * @param {string} type
2281 * @param {Function} fn
2283 function addEvents( elems, type, fn ) {
2284 var i = elems.length;
2286 addEvent( elems[ i ], type, fn );
2290 function hasClass( elem, name ) {
2291 return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
2294 function addClass( elem, name ) {
2295 if ( !hasClass( elem, name ) ) {
2296 elem.className += ( elem.className ? " " : "" ) + name;
2300 function toggleClass( elem, name ) {
2301 if ( hasClass( elem, name ) ) {
2302 removeClass( elem, name );
2304 addClass( elem, name );
2308 function removeClass( elem, name ) {
2309 var set = " " + elem.className + " ";
2311 // Class name may appear multiple times
2312 while ( set.indexOf( " " + name + " " ) >= 0 ) {
2313 set = set.replace( " " + name + " ", " " );
2316 // trim for prettiness
2317 elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
2320 function id( name ) {
2321 return defined.document && document.getElementById && document.getElementById( name );
2324 function getUrlConfigHtml() {
2326 escaped, escapedTooltip,
2328 len = config.urlConfig.length,
2331 for ( i = 0; i < len; i++ ) {
2332 val = config.urlConfig[ i ];
2333 if ( typeof val === "string" ) {
2340 escaped = escapeText( val.id );
2341 escapedTooltip = escapeText( val.tooltip );
2343 config[ val.id ] = QUnit.urlParams[ val.id ];
2344 if ( !val.value || typeof val.value === "string" ) {
2345 urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
2346 "' name='" + escaped + "' type='checkbox'" +
2347 ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
2348 ( config[ val.id ] ? " checked='checked'" : "" ) +
2349 " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
2350 "' title='" + escapedTooltip + "'>" + val.label + "</label>";
2352 urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
2353 "' title='" + escapedTooltip + "'>" + val.label +
2354 ": </label><select id='qunit-urlconfig-" + escaped +
2355 "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
2357 if ( QUnit.is( "array", val.value ) ) {
2358 for ( j = 0; j < val.value.length; j++ ) {
2359 escaped = escapeText( val.value[ j ] );
2360 urlConfigHtml += "<option value='" + escaped + "'" +
2361 ( config[ val.id ] === val.value[ j ] ?
2362 ( selection = true ) && " selected='selected'" : "" ) +
2363 ">" + escaped + "</option>";
2366 for ( j in val.value ) {
2367 if ( hasOwn.call( val.value, j ) ) {
2368 urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
2369 ( config[ val.id ] === j ?
2370 ( selection = true ) && " selected='selected'" : "" ) +
2371 ">" + escapeText( val.value[ j ] ) + "</option>";
2375 if ( config[ val.id ] && !selection ) {
2376 escaped = escapeText( config[ val.id ] );
2377 urlConfigHtml += "<option value='" + escaped +
2378 "' selected='selected' disabled='disabled'>" + escaped + "</option>";
2380 urlConfigHtml += "</select>";
2384 return urlConfigHtml;
2387 // Handle "click" events on toolbar checkboxes and "change" for select menus.
2388 // Updates the URL with the new state of `config.urlConfig` values.
2389 function toolbarChanged() {
2390 var updatedUrl, value,
2394 // Detect if field is a select menu or a checkbox
2395 if ( "selectedIndex" in field ) {
2396 value = field.options[ field.selectedIndex ].value || undefined;
2398 value = field.checked ? ( field.defaultValue || true ) : undefined;
2401 params[ field.name ] = value;
2402 updatedUrl = QUnit.url( params );
2404 if ( "hidepassed" === field.name && "replaceState" in window.history ) {
2405 config[ field.name ] = value || false;
2407 addClass( id( "qunit-tests" ), "hidepass" );
2409 removeClass( id( "qunit-tests" ), "hidepass" );
2412 // It is not necessary to refresh the whole page
2413 window.history.replaceState( null, "", updatedUrl );
2415 window.location = updatedUrl;
2419 function toolbarUrlConfigContainer() {
2420 var urlConfigContainer = document.createElement( "span" );
2422 urlConfigContainer.innerHTML = getUrlConfigHtml();
2424 // For oldIE support:
2425 // * Add handlers to the individual elements instead of the container
2426 // * Use "click" instead of "change" for checkboxes
2427 addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
2428 addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
2430 return urlConfigContainer;
2433 function toolbarModuleFilterHtml() {
2435 moduleFilterHtml = "";
2437 if ( !modulesList.length ) {
2441 modulesList.sort(function( a, b ) {
2442 return a.localeCompare( b );
2445 moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
2446 "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
2447 ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
2448 ">< All Modules ></option>";
2450 for ( i = 0; i < modulesList.length; i++ ) {
2451 moduleFilterHtml += "<option value='" +
2452 escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
2453 ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
2454 ">" + escapeText( modulesList[ i ] ) + "</option>";
2456 moduleFilterHtml += "</select>";
2458 return moduleFilterHtml;
2461 function toolbarModuleFilter() {
2462 var toolbar = id( "qunit-testrunner-toolbar" ),
2463 moduleFilter = document.createElement( "span" ),
2464 moduleFilterHtml = toolbarModuleFilterHtml();
2466 if ( !moduleFilterHtml ) {
2470 moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
2471 moduleFilter.innerHTML = moduleFilterHtml;
2473 addEvent( moduleFilter.lastChild, "change", function() {
2474 var selectBox = moduleFilter.getElementsByTagName( "select" )[ 0 ],
2475 selection = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value );
2477 window.location = QUnit.url({
2478 module: ( selection === "" ) ? undefined : selection,
2480 // Remove any existing filters
2486 toolbar.appendChild( moduleFilter );
2489 function appendToolbar() {
2490 var toolbar = id( "qunit-testrunner-toolbar" );
2493 toolbar.appendChild( toolbarUrlConfigContainer() );
2497 function appendBanner() {
2498 var banner = id( "qunit-banner" );
2501 banner.className = "";
2502 banner.innerHTML = "<a href='" +
2503 QUnit.url({ filter: undefined, module: undefined, testId: undefined }) +
2504 "'>" + banner.innerHTML + "</a> ";
2508 function appendTestResults() {
2509 var tests = id( "qunit-tests" ),
2510 result = id( "qunit-testresult" );
2513 result.parentNode.removeChild( result );
2517 tests.innerHTML = "";
2518 result = document.createElement( "p" );
2519 result.id = "qunit-testresult";
2520 result.className = "result";
2521 tests.parentNode.insertBefore( result, tests );
2522 result.innerHTML = "Running...<br /> ";
2526 function storeFixture() {
2527 var fixture = id( "qunit-fixture" );
2529 config.fixture = fixture.innerHTML;
2533 function appendUserAgent() {
2534 var userAgent = id( "qunit-userAgent" );
2536 userAgent.innerHTML = navigator.userAgent;
2540 function appendTestsList( modules ) {
2541 var i, l, x, z, test, moduleObj;
2543 for ( i = 0, l = modules.length; i < l; i++ ) {
2544 moduleObj = modules[ i ];
2546 if ( moduleObj.name ) {
2547 modulesList.push( moduleObj.name );
2550 for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
2551 test = moduleObj.tests[ x ];
2553 appendTest( test.name, test.testId, moduleObj.name );
2558 function appendTest( name, testId, moduleName ) {
2559 var title, rerunTrigger, testBlock, assertList,
2560 tests = id( "qunit-tests" );
2566 title = document.createElement( "strong" );
2567 title.innerHTML = getNameHtml( name, moduleName );
2569 rerunTrigger = document.createElement( "a" );
2570 rerunTrigger.innerHTML = "Rerun";
2571 rerunTrigger.href = QUnit.url({ testId: testId });
2573 testBlock = document.createElement( "li" );
2574 testBlock.appendChild( title );
2575 testBlock.appendChild( rerunTrigger );
2576 testBlock.id = "qunit-test-output-" + testId;
2578 assertList = document.createElement( "ol" );
2579 assertList.className = "qunit-assert-list";
2581 testBlock.appendChild( assertList );
2583 tests.appendChild( testBlock );
2586 // HTML Reporter initialization and load
2587 QUnit.begin(function( details ) {
2588 var qunit = id( "qunit" );
2590 // Fixture is the only one necessary to run without the #qunit element
2598 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
2599 "<h2 id='qunit-banner'></h2>" +
2600 "<div id='qunit-testrunner-toolbar'></div>" +
2601 "<h2 id='qunit-userAgent'></h2>" +
2602 "<ol id='qunit-tests'></ol>";
2605 appendTestResults();
2608 appendTestsList( details.modules );
2609 toolbarModuleFilter();
2611 if ( config.hidepassed ) {
2612 addClass( qunit.lastChild, "hidepass" );
2616 QUnit.done(function( details ) {
2618 banner = id( "qunit-banner" ),
2619 tests = id( "qunit-tests" ),
2621 "Tests completed in ",
2623 " milliseconds.<br />",
2624 "<span class='passed'>",
2626 "</span> assertions of <span class='total'>",
2628 "</span> passed, <span class='failed'>",
2634 banner.className = details.failed ? "qunit-fail" : "qunit-pass";
2638 id( "qunit-testresult" ).innerHTML = html;
2641 if ( config.altertitle && defined.document && document.title ) {
2643 // show ✖ for good, ✔ for bad suite result in title
2644 // use escape sequences in case file gets loaded with non-utf-8-charset
2646 ( details.failed ? "\u2716" : "\u2714" ),
2647 document.title.replace( /^[\u2714\u2716] /i, "" )
2651 // clear own sessionStorage items if all tests passed
2652 if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
2653 for ( i = 0; i < sessionStorage.length; i++ ) {
2654 key = sessionStorage.key( i++ );
2655 if ( key.indexOf( "qunit-test-" ) === 0 ) {
2656 sessionStorage.removeItem( key );
2661 // scroll back to top to show results
2662 if ( config.scrolltop && window.scrollTo ) {
2663 window.scrollTo( 0, 0 );
2667 function getNameHtml( name, module ) {
2671 nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
2674 nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
2679 QUnit.testStart(function( details ) {
2680 var running, testBlock;
2682 testBlock = id( "qunit-test-output-" + details.testId );
2684 testBlock.className = "running";
2687 // Report later registered tests
2688 appendTest( details.name, details.testId, details.module );
2691 running = id( "qunit-testresult" );
2693 running.innerHTML = "Running: <br />" + getNameHtml( details.name, details.module );
2698 QUnit.log(function( details ) {
2699 var assertList, assertLi,
2700 message, expected, actual,
2701 testItem = id( "qunit-test-output-" + details.testId );
2707 message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
2708 message = "<span class='test-message'>" + message + "</span>";
2709 message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
2711 // pushFailure doesn't provide details.expected
2712 // when it calls, it's implicit to also not show expected and diff stuff
2713 // Also, we need to check details.expected existence, as it can exist and be undefined
2714 if ( !details.result && hasOwn.call( details, "expected" ) ) {
2715 expected = escapeText( QUnit.dump.parse( details.expected ) );
2716 actual = escapeText( QUnit.dump.parse( details.actual ) );
2717 message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
2721 if ( actual !== expected ) {
2722 message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
2723 actual + "</pre></td></tr>" +
2724 "<tr class='test-diff'><th>Diff: </th><td><pre>" +
2725 QUnit.diff( expected, actual ) + "</pre></td></tr>";
2728 if ( details.source ) {
2729 message += "<tr class='test-source'><th>Source: </th><td><pre>" +
2730 escapeText( details.source ) + "</pre></td></tr>";
2733 message += "</table>";
2735 // this occours when pushFailure is set and we have an extracted stack trace
2736 } else if ( !details.result && details.source ) {
2737 message += "<table>" +
2738 "<tr class='test-source'><th>Source: </th><td><pre>" +
2739 escapeText( details.source ) + "</pre></td></tr>" +
2743 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
2745 assertLi = document.createElement( "li" );
2746 assertLi.className = details.result ? "pass" : "fail";
2747 assertLi.innerHTML = message;
2748 assertList.appendChild( assertLi );
2751 QUnit.testDone(function( details ) {
2752 var testTitle, time, testItem, assertList,
2753 good, bad, testCounts, skipped,
2754 tests = id( "qunit-tests" );
2760 testItem = id( "qunit-test-output-" + details.testId );
2762 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
2764 good = details.passed;
2765 bad = details.failed;
2767 // store result when possible
2768 if ( config.reorder && defined.sessionStorage ) {
2770 sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
2772 sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
2777 addClass( assertList, "qunit-collapsed" );
2780 // testItem.firstChild is the test name
2781 testTitle = testItem.firstChild;
2784 "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
2787 testTitle.innerHTML += " <b class='counts'>(" + testCounts +
2788 details.assertions.length + ")</b>";
2790 if ( details.skipped ) {
2791 addClass( testItem, "skipped" );
2792 skipped = document.createElement( "em" );
2793 skipped.className = "qunit-skipped-label";
2794 skipped.innerHTML = "skipped";
2795 testItem.insertBefore( skipped, testTitle );
2797 addEvent( testTitle, "click", function() {
2798 toggleClass( assertList, "qunit-collapsed" );
2801 testItem.className = bad ? "fail" : "pass";
2803 time = document.createElement( "span" );
2804 time.className = "runtime";
2805 time.innerHTML = details.runtime + " ms";
2806 testItem.insertBefore( time, assertList );
2810 if ( !defined.document || document.readyState === "complete" ) {
2811 config.pageLoaded = true;
2812 config.autorun = true;
2815 if ( defined.document ) {
2816 addEvent( window, "load", QUnit.load );