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