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
);