cygprofile: increase timeouts to allow showing web contents
[chromium-blink-merge.git] / third_party / qunit / src / qunit.js
blob006ca47474b21f11068f34fc9ab4cd1f290bcb97
1 /*!
2 * QUnit 1.17.1
3 * http://qunitjs.com/
5 * Copyright jQuery Foundation and other contributors
6 * Released under the MIT license
7 * http://jquery.org/license
9 * Date: 2015-01-20T19:39Z
12 (function( window ) {
14 var QUnit,
15 config,
16 onErrorFnPrev,
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)
22 Date = window.Date,
23 now = Date.now || function() {
24 return new Date().getTime();
26 globalStartCalled = false,
27 runStarted = false,
28 setTimeout = window.setTimeout,
29 clearTimeout = window.clearTimeout,
30 defined = {
31 document: window.document !== undefined,
32 setTimeout: window.setTimeout !== undefined,
33 sessionStorage: (function() {
34 var x = "qunit-test-string";
35 try {
36 sessionStorage.setItem( x, x );
37 sessionStorage.removeItem( x );
38 return true;
39 } catch ( e ) {
40 return false;
42 }())
44 /**
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 ) {
55 var name, message,
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;
62 } else if ( name ) {
63 return name;
64 } else if ( message ) {
65 return message;
66 } else {
67 return "Error";
69 } else {
70 return errorString;
73 /**
74 * Makes a clone of an object using only Array or Object as base,
75 * and copies over the own enumerable properties.
77 * @param {Object} obj
78 * @return {Object} New object with only the own properties (recursively).
80 objectValues = function( obj ) {
81 var key, val,
82 vals = QUnit.is( "array", obj ) ? [] : {};
83 for ( key in obj ) {
84 if ( hasOwn.call( obj, key ) ) {
85 val = obj[ key ];
86 vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
89 return vals;
92 QUnit = {};
94 /**
95 * Config object: Maintain internal state
96 * Later exposed as QUnit.config
97 * `config` initialized at top of scope
99 config = {
100 // The queue of tests to run
101 queue: [],
103 // block until document ready
104 blocking: true,
106 // by default, run previously failed tests first
107 // very useful in combination with "Hide passed tests" checked
108 reorder: true,
110 // by default, modify document.title when suite is done
111 altertitle: true,
113 // by default, scroll to top of the page when suite is done
114 scrolltop: true,
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
121 urlConfig: [
123 id: "hidepassed",
124 label: "Hide passed tests",
125 tooltip: "Only show tests and assertions that fail. Stored as query-strings."
128 id: "noglobals",
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."
134 id: "notrycatch",
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.
142 modules: [],
144 // The first unnamed module
145 currentModule: {
146 name: "",
147 tests: []
150 callbacks: {}
153 // Push a loose unnamed module to the modules collection
154 config.modules.push( config.currentModule );
156 // Initialize more QUnit.config and QUnit.urlParams
157 (function() {
158 var i, current,
159 location = window.location || { search: "", protocol: "file:" },
160 params = location.search.slice( 1 ).split( "&" ),
161 length = params.length,
162 urlParams = {};
164 if ( params[ 0 ] ) {
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 ] );
173 } else {
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;
188 config.testId = [];
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:";
200 }());
202 // Root QUnit object.
203 // `QUnit` initialized at top of scope
204 extend( QUnit, {
206 // call on start of module test to prepend name to all tests
207 module: function( name, testEnvironment ) {
208 var currentModule = {
209 name: name,
210 testEnvironment: testEnvironment,
211 tests: []
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 ) {
232 callback = expected;
233 expected = null;
236 QUnit.test( testName, expected, callback, true );
239 test: function( testName, expected, callback, async ) {
240 var test;
242 if ( arguments.length === 2 ) {
243 callback = expected;
244 expected = null;
247 test = new Test({
248 testName: testName,
249 expected: expected,
250 async: async,
251 callback: callback
254 test.queue();
257 skip: function( testName ) {
258 var test = new Test({
259 testName: testName,
260 skip: true
263 test.queue();
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;
274 if ( runStarted ) {
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;
285 return;
287 } else {
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 ) {
294 return;
297 // throw an Error if start is called more often than stop
298 if ( config.current.semaphore < 0 ) {
299 config.current.semaphore = 0;
301 QUnit.pushFailure(
302 "Called start() while already started (test's semaphore was 0 already)",
303 sourceFromStacktrace( 2 )
305 return;
309 resumeProcessing();
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;
323 pauseProcessing();
326 config: config,
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" ) {
335 return "undefined";
338 // Consider: typeof null === object
339 if ( obj === null ) {
340 return "null";
343 var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
344 type = match && match[ 1 ] || "";
346 switch ( type ) {
347 case "Number":
348 if ( isNaN( obj ) ) {
349 return "nan";
351 return "number";
352 case "String":
353 case "Boolean":
354 case "Array":
355 case "Date":
356 case "RegExp":
357 case "Function":
358 return type.toLowerCase();
360 if ( typeof obj === "object" ) {
361 return "object";
363 return undefined;
366 extend: extend,
368 load: function() {
369 config.pageLoaded = true;
371 // Initialize the configuration options
372 extend( config, {
373 stats: { all: 0, bad: 0 },
374 moduleStats: { all: 0, bad: 0 },
375 started: 0,
376 updateRate: 1000,
377 autostart: true,
378 filter: ""
379 }, true );
381 config.blocking = false;
383 if ( config.autostart ) {
384 resumeProcessing();
389 // Register logging callbacks
390 (function() {
391 var i, l, key,
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" ) {
398 throw new Error(
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 );
424 })();
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 ) {
434 var ret = false;
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 ) {
444 return true;
446 QUnit.pushFailure( error, filePath + ":" + linerNr );
447 } else {
448 QUnit.test( "global failure", extend(function() {
449 QUnit.pushFailure( error, filePath + ":" + linerNr );
450 }, { validTest: true } ) );
452 return false;
455 return ret;
458 function done() {
459 var runtime, passed;
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,
481 passed: passed,
482 total: config.stats.all,
483 runtime: runtime
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 ) {
496 // Opera 12.x
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 ] ) ) {
503 stack.shift();
505 if ( fileName ) {
506 include = [];
507 for ( i = offset; i < stack.length; i++ ) {
508 if ( stack[ i ].indexOf( fileName ) !== -1 ) {
509 break;
511 include.push( stack[ i ] );
513 if ( include.length ) {
514 return include.join( "\n" );
517 return stack[ offset ];
518 } else if ( e.sourceURL ) {
520 // Safari < 6
521 // exclude useless self-reference for generated Error objects
522 if ( /qunit.js$/.test( e.sourceURL ) ) {
523 return;
526 // for actual exceptions, this is useful
527 return e.sourceURL + ":" + e.line;
531 function sourceFromStacktrace( offset ) {
532 var e = new Error();
533 if ( !e.stack ) {
534 try {
535 throw e;
536 } catch ( err ) {
537 // This should already be true in most browsers
538 e = err;
541 return extractStacktrace( e, offset );
544 function synchronize( callback, last ) {
545 if ( QUnit.objectType( callback ) === "array" ) {
546 while ( callback.length ) {
547 synchronize( callback.shift() );
549 return;
551 config.queue.push( callback );
553 if ( config.autorun && !config.blocking ) {
554 process( last );
558 function process( last ) {
559 function next() {
560 process( last );
562 var start = now();
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()();
574 } else {
575 setTimeout( next, 13 );
576 break;
579 config.depth--;
580 if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
581 done();
585 function begin() {
586 var i, l,
587 modulesLog = [];
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++ ) {
604 modulesLog.push({
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,
613 modules: modulesLog
617 config.blocking = false;
618 process( true );
621 function resumeProcessing() {
622 runStarted = true;
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 ) {
628 return;
630 if ( config.timeout ) {
631 clearTimeout( config.timeout );
634 begin();
635 }, 13 );
636 } else {
637 begin();
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 ) );
650 } else {
651 throw new Error( "Test timed out" );
653 resumeProcessing();
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 ) ) {
666 continue;
668 config.pollution.push( key );
674 function checkPollution() {
675 var newGlobals,
676 deletedGlobals,
677 old = config.pollution;
679 saveGlobal();
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 ) {
694 var i, j,
695 result = a.slice();
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 );
701 i--;
702 break;
706 return result;
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 ) {
716 delete a[ prop ];
717 } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
718 a[ prop ] = b[ prop ];
724 return a;
727 function runLoggingCallbacks( key, args ) {
728 var i, l, callbacks;
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 ) {
754 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/"
764 // from jquery.js
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 ) {
772 return i;
776 return -1;
779 function Test( settings ) {
780 var i, l;
782 ++Test.count;
784 extend( this, settings );
785 this.assertions = [];
786 this.semaphore = 0;
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({
801 name: this.testName,
802 testId: this.testId
805 if ( settings.skip ) {
807 // Skipped tests will fully ignore any sent callback
808 this.callback = function() {};
809 this.async = false;
810 this.expected = 0;
811 } else {
812 this.assert = new Assert( this );
816 Test.count = 0;
818 Test.prototype = {
819 before: function() {
820 if (
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", {
857 name: this.testName,
858 module: this.module.name,
859 testId: this.testId
862 if ( !config.pollution ) {
863 saveGlobal();
867 run: function() {
868 var promise;
870 config.current = this;
872 if ( this.async ) {
873 QUnit.stop();
876 this.callbackStarted = now();
878 if ( config.notrycatch ) {
879 promise = this.callback.call( this.testEnvironment, this.assert );
880 this.resolvePromise( promise );
881 return;
884 try {
885 promise = this.callback.call( this.testEnvironment, this.assert );
886 this.resolvePromise( promise );
887 } catch ( e ) {
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
892 saveGlobal();
894 // Restart the tests if they're blocking
895 if ( config.blocking ) {
896 QUnit.start();
901 after: function() {
902 checkPollution();
905 queueHook: function( hook, hookName ) {
906 var promise,
907 test = this;
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 );
913 return;
915 try {
916 promise = hook.call( test.testEnvironment, test.assert );
917 test.resolvePromise( promise, hookName );
918 } catch ( error ) {
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 ) {
927 var hooks = [];
929 // Hooks are ignored on skipped tests
930 if ( this.skip ) {
931 return hooks;
934 if ( this.module.testEnvironment &&
935 QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
936 hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
939 return hooks;
942 finish: function() {
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 );
955 var i,
956 bad = 0;
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 ) {
964 bad++;
965 config.stats.bad++;
966 config.moduleStats.bad++;
970 runLoggingCallbacks( "testDone", {
971 name: this.testName,
972 module: this.module.name,
973 skipped: !!this.skip,
974 failed: bad,
975 passed: this.assertions.length - bad,
976 total: this.assertions.length,
977 runtime: this.runtime,
979 // HTML Reporter use
980 assertions: this.assertions,
981 testId: this.testId,
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
990 QUnit.reset();
992 config.current = undefined;
995 queue: function() {
996 var bad,
997 test = this;
999 if ( !this.valid() ) {
1000 return;
1003 function run() {
1005 // each of these can by async
1006 synchronize([
1007 function() {
1008 test.before();
1011 test.hooks( "beforeEach" ),
1013 function() {
1014 test.run();
1017 test.hooks( "afterEach" ).reverse(),
1019 function() {
1020 test.after();
1022 function() {
1023 test.finish();
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 );
1033 if ( bad ) {
1034 run();
1035 } else {
1036 synchronize( run, true );
1040 push: function( result, actual, expected, message ) {
1041 var source,
1042 details = {
1043 module: this.module.name,
1044 name: this.testName,
1045 result: result,
1046 message: message,
1047 actual: actual,
1048 expected: expected,
1049 testId: this.testId,
1050 runtime: now() - this.started
1053 if ( !result ) {
1054 source = sourceFromStacktrace();
1056 if ( source ) {
1057 details.source = source;
1061 runLoggingCallbacks( "log", details );
1063 this.assertions.push({
1064 result: !!result,
1065 message: message
1069 pushFailure: function( message, source, actual ) {
1070 if ( !this instanceof Test ) {
1071 throw new Error( "pushFailure() assertion outside test context, was " +
1072 sourceFromStacktrace( 2 ) );
1075 var details = {
1076 module: this.module.name,
1077 name: this.testName,
1078 result: false,
1079 message: message || "error",
1080 actual: actual || null,
1081 testId: this.testId,
1082 runtime: now() - this.started
1085 if ( source ) {
1086 details.source = source;
1089 runLoggingCallbacks( "log", details );
1091 this.assertions.push({
1092 result: false,
1093 message: message
1097 resolvePromise: function( promise, phase ) {
1098 var then, message,
1099 test = this;
1100 if ( promise != null ) {
1101 then = promise.then;
1102 if ( QUnit.objectType( then ) === "function" ) {
1103 QUnit.stop();
1104 then.call(
1105 promise,
1106 QUnit.start,
1107 function( error ) {
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
1114 saveGlobal();
1116 // Unblock
1117 QUnit.start();
1124 valid: function() {
1125 var include,
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 ) {
1132 return true;
1135 if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
1136 return false;
1139 if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {
1140 return false;
1143 if ( !filter ) {
1144 return true;
1147 include = filter.charAt( 0 ) !== "!";
1148 if ( !include ) {
1149 filter = filter.toLowerCase().slice( 1 );
1152 // If the filter matches, we need to honour include
1153 if ( fullName.indexOf( filter ) !== -1 ) {
1154 return include;
1157 // Otherwise, do the opposite
1158 return !include;
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" ) {
1174 return;
1177 var fixture = defined.document && document.getElementById &&
1178 document.getElementById( "qunit-fixture" );
1180 if ( 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 ) {
1200 var hex,
1201 i = 0,
1202 hash = 0,
1203 str = module + "\x1C" + testName,
1204 len = str.length;
1206 for ( ; i < len; i++ ) {
1207 hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
1208 hash |= 0;
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;
1225 // Assert helpers
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;
1233 } else {
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.
1240 async: function() {
1241 var test = this.test,
1242 popped = false;
1244 test.semaphore += 1;
1245 test.usedAsync = true;
1246 pauseProcessing();
1248 return function done() {
1249 if ( !popped ) {
1250 test.semaphore -= 1;
1251 popped = true;
1252 resumeProcessing();
1253 } else {
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 */ ) {
1262 var assert = this,
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.
1289 * @name ok
1290 * @function
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.
1302 * @name equal
1303 * @function
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 );
1312 * @name notEqual
1313 * @function
1315 notEqual: function( actual, expected, message ) {
1316 /*jshint eqeqeq:false */
1317 this.push( expected != actual, actual, expected, message );
1321 * @name propEqual
1322 * @function
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
1332 * @function
1334 notPropEqual: function( actual, expected, message ) {
1335 actual = objectValues( actual );
1336 expected = objectValues( expected );
1337 this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
1341 * @name deepEqual
1342 * @function
1344 deepEqual: function( actual, expected, message ) {
1345 this.push( QUnit.equiv( actual, expected ), actual, expected, message );
1349 * @name notDeepEqual
1350 * @function
1352 notDeepEqual: function( actual, expected, message ) {
1353 this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
1357 * @name strictEqual
1358 * @function
1360 strictEqual: function( actual, expected, message ) {
1361 this.push( expected === actual, actual, expected, message );
1365 * @name notStrictEqual
1366 * @function
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,
1375 ok = false;
1377 // 'expected' is optional unless doing string comparison
1378 if ( message == null && typeof expected === "string" ) {
1379 message = expected;
1380 expected = null;
1383 this.test.ignoreGlobalErrors = true;
1384 try {
1385 block.call( this.test.testEnvironment );
1386 } catch (e) {
1387 actual = e;
1389 this.test.ignoreGlobalErrors = false;
1391 if ( actual ) {
1392 expectedType = QUnit.objectType( expected );
1394 // we don't want to validate thrown error
1395 if ( !expected ) {
1396 ok = true;
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 ) {
1409 ok = true;
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;
1420 ok = true;
1423 this.push( ok, actual, expectedOutput, message );
1424 } else {
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
1432 (function() {
1433 /*jshint sub:true */
1434 Assert.prototype.raises = Assert.prototype[ "throws" ];
1435 }());
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 );
1444 if ( prop ) {
1445 if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
1446 return callbacks[ prop ].apply( callbacks, args );
1447 } else {
1448 return callbacks[ prop ]; // or undefined
1453 // the real equiv function
1454 var innerEquiv,
1456 // stack to decide between skip/abort functions
1457 callers = [],
1459 // stack to avoiding loops from circular referencing
1460 parents = [],
1461 parentsB = [],
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
1476 // declaration
1477 // e.g. var i = 1;
1478 // var j = new Number(1);
1479 return a == b;
1480 } else {
1481 return a === b;
1485 return {
1486 "string": useStrictEquality,
1487 "boolean": useStrictEquality,
1488 "number": useStrictEquality,
1489 "null": useStrictEquality,
1490 "undefined": useStrictEquality,
1492 "nan": function( b ) {
1493 return isNaN( 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" &&
1503 // the regex itself
1504 a.source === b.source &&
1506 // and its modifiers
1507 a.global === b.global &&
1509 // (gmi) ...
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" ) {
1528 return false;
1531 len = a.length;
1532 if ( len !== b.length ) {
1533 // safe and faster
1534 return false;
1537 // track reference to avoid circular references
1538 parents.push( a );
1539 parentsB.push( b );
1540 for ( i = 0; i < len; i++ ) {
1541 loop = false;
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 ) {
1547 loop = true;
1548 } else {
1549 parents.pop();
1550 parentsB.pop();
1551 return false;
1555 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1556 parents.pop();
1557 parentsB.pop();
1558 return false;
1561 parents.pop();
1562 parentsB.pop();
1563 return true;
1566 "object": function( b, a ) {
1568 /*jshint forin:false */
1569 var i, j, loop, aCircular, bCircular,
1570 // Default to true
1571 eq = true,
1572 aProperties = [],
1573 bProperties = [];
1575 // comparing constructors is more strict than using
1576 // instanceof
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 ) ) ) {
1583 return false;
1587 // stack constructor before traversing properties
1588 callers.push( a.constructor );
1590 // track reference to avoid circular references
1591 parents.push( a );
1592 parentsB.push( b );
1594 // be strict: don't ensure hasOwnProperty and go deep
1595 for ( i in a ) {
1596 loop = false;
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 ) {
1602 loop = true;
1603 } else {
1604 eq = false;
1605 break;
1609 aProperties.push( i );
1610 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1611 eq = false;
1612 break;
1616 parents.pop();
1617 parentsB.pop();
1618 callers.pop(); // unstack, we are done
1620 for ( i in b ) {
1621 bProperties.push( i ); // collect b's properties
1624 // Ensures identical properties name
1625 return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
1628 }());
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 ) {
1637 if ( 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
1644 return false;
1645 } else {
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 ) ) );
1654 return innerEquiv;
1655 }());
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 ) {
1664 return o + "";
1666 function join( pre, arr, post ) {
1667 var s = dump.separator(),
1668 base = dump.indent(),
1669 inner = dump.indent( 1 );
1670 if ( arr.join ) {
1671 arr = arr.join( "," + s + inner );
1673 if ( !arr ) {
1674 return pre + post;
1676 return [ pre, inner + arr, base + post ].join( s );
1678 function array( arr, stack ) {
1679 var i = arr.length,
1680 ret = new Array( i );
1682 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1683 return "[object Array]";
1686 this.up();
1687 while ( i-- ) {
1688 ret[ i ] = this.parse( arr[ i ], undefined, stack );
1690 this.down();
1691 return join( "[", ret, "]" );
1694 var reName = /^function (\w+)/,
1695 dump = {
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" ) {
1712 stack.push( obj );
1713 res = parser.call( this, obj, stack );
1714 stack.pop();
1715 return res;
1717 return ( parserType === "string" ) ? parser : this.parsers.error;
1719 typeOf: function( obj ) {
1720 var type;
1721 if ( obj === null ) {
1722 type = "null";
1723 } else if ( typeof obj === "undefined" ) {
1724 type = "undefined";
1725 } else if ( QUnit.is( "regexp", obj ) ) {
1726 type = "regexp";
1727 } else if ( QUnit.is( "date", obj ) ) {
1728 type = "date";
1729 } else if ( QUnit.is( "function", obj ) ) {
1730 type = "function";
1731 } else if ( obj.setInterval !== undefined &&
1732 obj.document !== undefined &&
1733 obj.nodeType === undefined ) {
1734 type = "window";
1735 } else if ( obj.nodeType === 9 ) {
1736 type = "document";
1737 } else if ( obj.nodeType ) {
1738 type = "node";
1739 } else if (
1741 // native arrays
1742 toString.call( obj ) === "[object Array]" ||
1744 // NodeList objects
1745 ( typeof obj.length === "number" && obj.item !== undefined &&
1746 ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
1747 obj[ 0 ] === undefined ) ) )
1749 type = "array";
1750 } else if ( obj.constructor === Error.prototype.constructor ) {
1751 type = "error";
1752 } else {
1753 type = typeof obj;
1755 return type;
1757 separator: function() {
1758 return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
1760 // extra can be a number, shortcut for increasing-calling-decreasing
1761 indent: function( extra ) {
1762 if ( !this.multiline ) {
1763 return "";
1765 var chr = this.indentChar;
1766 if ( this.HTML ) {
1767 chr = chr.replace( /\t/g, " " ).replace( / /g, "&#160;" );
1769 return new Array( this.depth + ( extra || 0 ) ).join( chr );
1771 up: function( a ) {
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
1781 quote: quote,
1782 literal: literal,
1783 join: join,
1785 depth: 1,
1786 maxDepth: 5,
1788 // This is the list of parsers, to modify them, use dump.setParser
1789 parsers: {
1790 window: "[Window]",
1791 document: "[Document]",
1792 error: function( error ) {
1793 return "Error(\"" + error.message + "\")";
1795 unknown: "[Unknown]",
1796 "null": "null",
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 ];
1804 if ( name ) {
1805 ret += " " + name;
1807 ret += "( ";
1809 ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
1810 return join( ret, dump.parse( fn, "functionCode" ), "}" );
1812 array: array,
1813 nodelist: array,
1814 "arguments": array,
1815 object: function( map, stack ) {
1816 var keys, key, val, i, nonEnumerableProperties,
1817 ret = [];
1819 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1820 return "[object Object]";
1823 dump.up();
1824 keys = [];
1825 for ( key in map ) {
1826 keys.push( key );
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 ) ) {
1834 keys.push( key );
1837 keys.sort();
1838 for ( i = 0; i < keys.length; i++ ) {
1839 key = keys[ i ];
1840 val = map[ key ];
1841 ret.push( dump.parse( key, "key" ) + ": " +
1842 dump.parse( val, undefined, stack ) );
1844 dump.down();
1845 return join( "{", ret, "}" );
1847 node: function( node ) {
1848 var len, i, val,
1849 open = dump.HTML ? "&lt;" : "<",
1850 close = dump.HTML ? "&gt;" : ">",
1851 tag = node.nodeName.toLowerCase(),
1852 ret = open + tag,
1853 attrs = node.attributes;
1855 if ( attrs ) {
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
1861 // "inherit".
1862 if ( val && val !== "inherit" ) {
1863 ret += " " + attrs[ i ].nodeName + "=" +
1864 dump.parse( val, "attribute" );
1868 ret += close;
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 ) {
1880 var args,
1881 l = fn.length;
1883 if ( !l ) {
1884 return "";
1887 args = new Array( l );
1888 while ( l-- ) {
1890 // 97 is 'a'
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
1896 key: quote,
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
1900 attribute: quote,
1901 string: quote,
1902 date: quote,
1903 regexp: literal,
1904 number: literal,
1905 "boolean": literal
1907 // if true, entities are escaped ( <, >, \t, space and \n )
1908 HTML: false,
1909 // indentation unit
1910 indentChar: " ",
1911 // if true, items in a collection, are separated by a \n, else just a space.
1912 multiline: true
1915 return dump;
1916 }());
1918 // back compat
1919 QUnit.jsDump = QUnit.dump;
1921 // For browser, export only select globals
1922 if ( typeof window !== "undefined" ) {
1924 // Deprecated
1925 // Extend assert methods to QUnit and Global scope through Backwards compatibility
1926 (function() {
1927 var i,
1928 assertions = Assert.prototype;
1930 function applyCurrent( current ) {
1931 return function() {
1932 var assert = new Assert( QUnit.config.current );
1933 current.apply( assert, arguments );
1937 for ( i in assertions ) {
1938 QUnit[ i ] = applyCurrent( assertions[ i ] );
1940 })();
1942 (function() {
1943 var i, l,
1944 keys = [
1945 "test",
1946 "module",
1947 "expect",
1948 "asyncTest",
1949 "start",
1950 "stop",
1951 "ok",
1952 "equal",
1953 "notEqual",
1954 "propEqual",
1955 "notPropEqual",
1956 "deepEqual",
1957 "notDeepEqual",
1958 "strictEqual",
1959 "notStrictEqual",
1960 "throws"
1963 for ( i = 0, l = keys.length; i < l; i++ ) {
1964 window[ keys[ i ] ] = QUnit[ keys[ i ] ];
1966 })();
1968 window.QUnit = QUnit;
1971 // For nodejs
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
1985 }( (function() {
1986 return this;
1987 })() ));
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.
1998 * More Info:
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 ) {
2010 var i,
2011 ns = {},
2012 os = {};
2014 for ( i = 0; i < n.length; i++ ) {
2015 if ( !hasOwn.call( ns, n[ i ] ) ) {
2016 ns[ n[ i ] ] = {
2017 rows: [],
2018 o: null
2021 ns[ n[ i ] ].rows.push( i );
2024 for ( i = 0; i < o.length; i++ ) {
2025 if ( !hasOwn.call( os, o[ i ] ) ) {
2026 os[ o[ i ] ] = {
2027 rows: [],
2028 n: null
2031 os[ o[ i ] ].rows.push( i );
2034 for ( i in ns ) {
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 ] ) {
2053 n[ i + 1 ] = {
2054 text: n[ i + 1 ],
2055 row: n[ i ].row + 1
2057 o[ n[ i ].row + 1 ] = {
2058 text: o[ n[ i ].row + 1 ],
2059 row: i + 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 ] ) {
2068 n[ i - 1 ] = {
2069 text: n[ i - 1 ],
2070 row: n[ i ].row - 1
2072 o[ n[ i ].row - 1 ] = {
2073 text: o[ n[ i ].row - 1 ],
2074 row: i - 1
2079 return {
2080 o: o,
2081 n: n
2085 return function( o, n ) {
2086 o = o.replace( /\s+$/, "" );
2087 n = n.replace( /\s+$/, "" );
2089 var i, pre,
2090 str = "",
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 ) {
2096 oSpace = [ " " ];
2097 } else {
2098 oSpace.push( " " );
2101 if ( nSpace == null ) {
2102 nSpace = [ " " ];
2103 } else {
2104 nSpace.push( " " );
2107 if ( out.n.length === 0 ) {
2108 for ( i = 0; i < out.o.length; i++ ) {
2109 str += "<del>" + out.o[ i ] + oSpace[ i ] + "</del>";
2111 } else {
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>";
2121 } else {
2123 // `pre` initialized at top of scope
2124 pre = "";
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;
2134 return str;
2136 }());
2137 // jscs:enable
2139 (function() {
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 };
2149 config.started = 0;
2150 config.updateRate = 1000;
2151 config.blocking = false;
2152 config.autostart = true;
2153 config.autorun = false;
2154 config.filter = "";
2155 config.queue = [];
2157 // Return on non-browser environments
2158 // This is necessary to not break on node tests
2159 if ( typeof window === "undefined" ) {
2160 return;
2163 qunit = id( "qunit" );
2164 if ( qunit ) {
2165 qunit.innerHTML =
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" );
2177 if ( tests ) {
2178 tests.innerHTML = "";
2181 if ( banner ) {
2182 banner.className = "";
2185 if ( result ) {
2186 result.parentNode.removeChild( result );
2189 if ( tests ) {
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 />&#160;";
2198 // Don't load the HTML Reporter on non-Browser environments
2199 if ( typeof window === "undefined" ) {
2200 return;
2203 var config = QUnit.config,
2204 hasOwn = Object.prototype.hasOwnProperty,
2205 defined = {
2206 document: window.document !== undefined,
2207 sessionStorage: (function() {
2208 var x = "qunit-test-string";
2209 try {
2210 sessionStorage.setItem( x, x );
2211 sessionStorage.removeItem( x );
2212 return true;
2213 } catch ( e ) {
2214 return false;
2216 }())
2218 modulesList = [];
2221 * Escape text for attribute or text content.
2223 function escapeText( s ) {
2224 if ( !s ) {
2225 return "";
2227 s = s + "";
2229 // Both single quotes and double quotes (for attributes)
2230 return s.replace( /['"<>&]/g, function( s ) {
2231 switch ( s ) {
2232 case "'":
2233 return "&#039;";
2234 case "\"":
2235 return "&quot;";
2236 case "<":
2237 return "&lt;";
2238 case ">":
2239 return "&gt;";
2240 case "&":
2241 return "&amp;";
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 ) {
2258 // support: IE <9
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;
2270 while ( i-- ) {
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 );
2288 } else {
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() {
2310 var i, j, val,
2311 escaped, escapedTooltip,
2312 selection = false,
2313 len = config.urlConfig.length,
2314 urlConfigHtml = "";
2316 for ( i = 0; i < len; i++ ) {
2317 val = config.urlConfig[ i ];
2318 if ( typeof val === "string" ) {
2319 val = {
2320 id: val,
2321 label: val
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>";
2339 } else {
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>";
2353 } else {
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,
2379 field = this,
2380 params = {};
2382 // Detect if field is a select menu or a checkbox
2383 if ( "selectedIndex" in field ) {
2384 value = field.options[ field.selectedIndex ].value || undefined;
2385 } else {
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;
2394 if ( value ) {
2395 addClass( id( "qunit-tests" ), "hidepass" );
2396 } else {
2397 removeClass( id( "qunit-tests" ), "hidepass" );
2400 // It is not necessary to refresh the whole page
2401 window.history.replaceState( null, "", updatedUrl );
2402 } else {
2403 window.location = updatedUrl;
2407 function setUrl( params ) {
2408 var key,
2409 querystring = "?";
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 ) {
2416 continue;
2418 querystring += encodeURIComponent( key );
2419 if ( params[ key ] !== true ) {
2420 querystring += "=" + encodeURIComponent( params[ key ] );
2422 querystring += "&";
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
2439 testId: undefined
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 ) {
2480 applyUrlParams();
2482 if ( ev && ev.preventDefault ) {
2483 ev.preventDefault();
2486 return false;
2489 return filter;
2492 function toolbarModuleFilterHtml() {
2493 var i,
2494 moduleFilterHtml = "";
2496 if ( !modulesList.length ) {
2497 return false;
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 ) {
2526 return false;
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" );
2540 if ( toolbar ) {
2541 toolbar.appendChild( toolbarUrlConfigContainer() );
2542 toolbar.appendChild( toolbarLooseFilter() );
2546 function appendHeader() {
2547 var header = id( "qunit-header" );
2549 if ( 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" );
2559 if ( banner ) {
2560 banner.className = "";
2564 function appendTestResults() {
2565 var tests = id( "qunit-tests" ),
2566 result = id( "qunit-testresult" );
2568 if ( result ) {
2569 result.parentNode.removeChild( result );
2572 if ( tests ) {
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 />&#160;";
2582 function storeFixture() {
2583 var fixture = id( "qunit-fixture" );
2584 if ( fixture ) {
2585 config.fixture = fixture.innerHTML;
2589 function appendUserAgent() {
2590 var userAgent = id( "qunit-userAgent" );
2591 if ( 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" );
2619 if ( !tests ) {
2620 return;
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
2648 storeFixture();
2650 if ( qunit ) {
2651 qunit.innerHTML =
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>";
2659 appendHeader();
2660 appendBanner();
2661 appendTestResults();
2662 appendUserAgent();
2663 appendToolbar();
2664 appendTestsList( details.modules );
2665 toolbarModuleFilter();
2667 if ( qunit && config.hidepassed ) {
2668 addClass( qunit.lastChild, "hidepass" );
2672 QUnit.done(function( details ) {
2673 var i, key,
2674 banner = id( "qunit-banner" ),
2675 tests = id( "qunit-tests" ),
2676 html = [
2677 "Tests completed in ",
2678 details.runtime,
2679 " milliseconds.<br />",
2680 "<span class='passed'>",
2681 details.passed,
2682 "</span> assertions of <span class='total'>",
2683 details.total,
2684 "</span> passed, <span class='failed'>",
2685 details.failed,
2686 "</span> failed."
2687 ].join( "" );
2689 if ( banner ) {
2690 banner.className = details.failed ? "qunit-fail" : "qunit-pass";
2693 if ( tests ) {
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
2701 document.title = [
2702 ( details.failed ? "\u2716" : "\u2714" ),
2703 document.title.replace( /^[\u2714\u2716] /i, "" )
2704 ].join( " " );
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 ) {
2724 var nameHtml = "";
2726 if ( module ) {
2727 nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
2730 nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
2732 return nameHtml;
2735 QUnit.testStart(function( details ) {
2736 var running, testBlock;
2738 testBlock = id( "qunit-test-output-" + details.testId );
2739 if ( testBlock ) {
2740 testBlock.className = "running";
2741 } else {
2743 // Report later registered tests
2744 appendTest( details.name, details.testId, details.module );
2747 running = id( "qunit-testresult" );
2748 if ( running ) {
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 );
2759 if ( !testItem ) {
2760 return;
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>" +
2774 expected +
2775 "</pre></td></tr>";
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>" +
2796 "</table>";
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" );
2812 if ( !tests ) {
2813 return;
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 ) {
2825 if ( bad ) {
2826 sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
2827 } else {
2828 sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
2832 if ( bad === 0 ) {
2833 addClass( assertList, "qunit-collapsed" );
2836 // testItem.firstChild is the test name
2837 testTitle = testItem.firstChild;
2839 testCounts = bad ?
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 );
2852 } else {
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 );
2875 })();