UNPKG

65.7 kBJavaScriptView Raw
1/*!
2 * QUnit 1.15.0
3 * http://qunitjs.com/
4 *
5 * Copyright 2014 jQuery Foundation and other contributors
6 * Released under the MIT license
7 * http://jquery.org/license
8 *
9 * Date: 2014-08-08T16:00Z
10 */
11
12(function( window ) {
13
14var QUnit,
15 config,
16 onErrorFnPrev,
17 fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ),
18 toString = Object.prototype.toString,
19 hasOwn = Object.prototype.hasOwnProperty,
20 // Keep a local reference to Date (GH-283)
21 Date = window.Date,
22 now = Date.now || function() {
23 return new Date().getTime();
24 },
25 setTimeout = window.setTimeout,
26 clearTimeout = window.clearTimeout,
27 defined = {
28 document: typeof window.document !== "undefined",
29 setTimeout: typeof window.setTimeout !== "undefined",
30 sessionStorage: (function() {
31 var x = "qunit-test-string";
32 try {
33 sessionStorage.setItem( x, x );
34 sessionStorage.removeItem( x );
35 return true;
36 } catch ( e ) {
37 return false;
38 }
39 }())
40 },
41 /**
42 * Provides a normalized error string, correcting an issue
43 * with IE 7 (and prior) where Error.prototype.toString is
44 * not properly implemented
45 *
46 * Based on http://es5.github.com/#x15.11.4.4
47 *
48 * @param {String|Error} error
49 * @return {String} error message
50 */
51 errorString = function( error ) {
52 var name, message,
53 errorString = error.toString();
54 if ( errorString.substring( 0, 7 ) === "[object" ) {
55 name = error.name ? error.name.toString() : "Error";
56 message = error.message ? error.message.toString() : "";
57 if ( name && message ) {
58 return name + ": " + message;
59 } else if ( name ) {
60 return name;
61 } else if ( message ) {
62 return message;
63 } else {
64 return "Error";
65 }
66 } else {
67 return errorString;
68 }
69 },
70 /**
71 * Makes a clone of an object using only Array or Object as base,
72 * and copies over the own enumerable properties.
73 *
74 * @param {Object} obj
75 * @return {Object} New object with only the own properties (recursively).
76 */
77 objectValues = function( obj ) {
78 var key, val,
79 vals = QUnit.is( "array", obj ) ? [] : {};
80 for ( key in obj ) {
81 if ( hasOwn.call( obj, key ) ) {
82 val = obj[ key ];
83 vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
84 }
85 }
86 return vals;
87 };
88
89// Root QUnit object.
90// `QUnit` initialized at top of scope
91QUnit = {
92
93 // call on start of module test to prepend name to all tests
94 module: function( name, testEnvironment ) {
95 config.currentModule = name;
96 config.currentModuleTestEnvironment = testEnvironment;
97 config.modules[ name ] = true;
98 },
99
100 asyncTest: function( testName, expected, callback ) {
101 if ( arguments.length === 2 ) {
102 callback = expected;
103 expected = null;
104 }
105
106 QUnit.test( testName, expected, callback, true );
107 },
108
109 test: function( testName, expected, callback, async ) {
110 var test;
111
112 if ( arguments.length === 2 ) {
113 callback = expected;
114 expected = null;
115 }
116
117 test = new Test({
118 testName: testName,
119 expected: expected,
120 async: async,
121 callback: callback,
122 module: config.currentModule,
123 moduleTestEnvironment: config.currentModuleTestEnvironment,
124 stack: sourceFromStacktrace( 2 )
125 });
126
127 if ( !validTest( test ) ) {
128 return;
129 }
130
131 test.queue();
132 },
133
134 start: function( count ) {
135 var message;
136
137 // QUnit hasn't been initialized yet.
138 // Note: RequireJS (et al) may delay onLoad
139 if ( config.semaphore === undefined ) {
140 QUnit.begin(function() {
141 // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first
142 setTimeout(function() {
143 QUnit.start( count );
144 });
145 });
146 return;
147 }
148
149 config.semaphore -= count || 1;
150 // don't start until equal number of stop-calls
151 if ( config.semaphore > 0 ) {
152 return;
153 }
154
155 // Set the starting time when the first test is run
156 QUnit.config.started = QUnit.config.started || now();
157 // ignore if start is called more often then stop
158 if ( config.semaphore < 0 ) {
159 config.semaphore = 0;
160
161 message = "Called start() while already started (QUnit.config.semaphore was 0 already)";
162
163 if ( config.current ) {
164 QUnit.pushFailure( message, sourceFromStacktrace( 2 ) );
165 } else {
166 throw new Error( message );
167 }
168
169 return;
170 }
171 // A slight delay, to avoid any current callbacks
172 if ( defined.setTimeout ) {
173 setTimeout(function() {
174 if ( config.semaphore > 0 ) {
175 return;
176 }
177 if ( config.timeout ) {
178 clearTimeout( config.timeout );
179 }
180
181 config.blocking = false;
182 process( true );
183 }, 13 );
184 } else {
185 config.blocking = false;
186 process( true );
187 }
188 },
189
190 stop: function( count ) {
191 config.semaphore += count || 1;
192 config.blocking = true;
193
194 if ( config.testTimeout && defined.setTimeout ) {
195 clearTimeout( config.timeout );
196 config.timeout = setTimeout(function() {
197 QUnit.ok( false, "Test timed out" );
198 config.semaphore = 1;
199 QUnit.start();
200 }, config.testTimeout );
201 }
202 }
203};
204
205// We use the prototype to distinguish between properties that should
206// be exposed as globals (and in exports) and those that shouldn't
207(function() {
208 function F() {}
209 F.prototype = QUnit;
210 QUnit = new F();
211
212 // Make F QUnit's constructor so that we can add to the prototype later
213 QUnit.constructor = F;
214}());
215
216/**
217 * Config object: Maintain internal state
218 * Later exposed as QUnit.config
219 * `config` initialized at top of scope
220 */
221config = {
222 // The queue of tests to run
223 queue: [],
224
225 // block until document ready
226 blocking: true,
227
228 // when enabled, show only failing tests
229 // gets persisted through sessionStorage and can be changed in UI via checkbox
230 hidepassed: false,
231
232 // by default, run previously failed tests first
233 // very useful in combination with "Hide passed tests" checked
234 reorder: true,
235
236 // by default, modify document.title when suite is done
237 altertitle: true,
238
239 // by default, scroll to top of the page when suite is done
240 scrolltop: true,
241
242 // when enabled, all tests must call expect()
243 requireExpects: false,
244
245 // add checkboxes that are persisted in the query-string
246 // when enabled, the id is set to `true` as a `QUnit.config` property
247 urlConfig: [
248 {
249 id: "noglobals",
250 label: "Check for Globals",
251 tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings."
252 },
253 {
254 id: "notrycatch",
255 label: "No try-catch",
256 tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings."
257 }
258 ],
259
260 // Set of all modules.
261 modules: {},
262
263 callbacks: {}
264};
265
266// Initialize more QUnit.config and QUnit.urlParams
267(function() {
268 var i, current,
269 location = window.location || { search: "", protocol: "file:" },
270 params = location.search.slice( 1 ).split( "&" ),
271 length = params.length,
272 urlParams = {};
273
274 if ( params[ 0 ] ) {
275 for ( i = 0; i < length; i++ ) {
276 current = params[ i ].split( "=" );
277 current[ 0 ] = decodeURIComponent( current[ 0 ] );
278
279 // allow just a key to turn on a flag, e.g., test.html?noglobals
280 current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
281 if ( urlParams[ current[ 0 ] ] ) {
282 urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );
283 } else {
284 urlParams[ current[ 0 ] ] = current[ 1 ];
285 }
286 }
287 }
288
289 QUnit.urlParams = urlParams;
290
291 // String search anywhere in moduleName+testName
292 config.filter = urlParams.filter;
293
294 // Exact match of the module name
295 config.module = urlParams.module;
296
297 config.testNumber = [];
298 if ( urlParams.testNumber ) {
299
300 // Ensure that urlParams.testNumber is an array
301 urlParams.testNumber = [].concat( urlParams.testNumber );
302 for ( i = 0; i < urlParams.testNumber.length; i++ ) {
303 current = urlParams.testNumber[ i ];
304 config.testNumber.push( parseInt( current, 10 ) );
305 }
306 }
307
308 // Figure out if we're running the tests from a server or not
309 QUnit.isLocal = location.protocol === "file:";
310}());
311
312extend( QUnit, {
313
314 config: config,
315
316 // Safe object type checking
317 is: function( type, obj ) {
318 return QUnit.objectType( obj ) === type;
319 },
320
321 objectType: function( obj ) {
322 if ( typeof obj === "undefined" ) {
323 return "undefined";
324 }
325
326 // Consider: typeof null === object
327 if ( obj === null ) {
328 return "null";
329 }
330
331 var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
332 type = match && match[ 1 ] || "";
333
334 switch ( type ) {
335 case "Number":
336 if ( isNaN( obj ) ) {
337 return "nan";
338 }
339 return "number";
340 case "String":
341 case "Boolean":
342 case "Array":
343 case "Date":
344 case "RegExp":
345 case "Function":
346 return type.toLowerCase();
347 }
348 if ( typeof obj === "object" ) {
349 return "object";
350 }
351 return undefined;
352 },
353
354 url: function( params ) {
355 params = extend( extend( {}, QUnit.urlParams ), params );
356 var key,
357 querystring = "?";
358
359 for ( key in params ) {
360 if ( hasOwn.call( params, key ) ) {
361 querystring += encodeURIComponent( key ) + "=" +
362 encodeURIComponent( params[ key ] ) + "&";
363 }
364 }
365 return window.location.protocol + "//" + window.location.host +
366 window.location.pathname + querystring.slice( 0, -1 );
367 },
368
369 extend: extend
370});
371
372/**
373 * @deprecated: Created for backwards compatibility with test runner that set the hook function
374 * into QUnit.{hook}, instead of invoking it and passing the hook function.
375 * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here.
376 * Doing this allows us to tell if the following methods have been overwritten on the actual
377 * QUnit object.
378 */
379extend( QUnit.constructor.prototype, {
380
381 // Logging callbacks; all receive a single argument with the listed properties
382 // run test/logs.html for any related changes
383 begin: registerLoggingCallback( "begin" ),
384
385 // done: { failed, passed, total, runtime }
386 done: registerLoggingCallback( "done" ),
387
388 // log: { result, actual, expected, message }
389 log: registerLoggingCallback( "log" ),
390
391 // testStart: { name }
392 testStart: registerLoggingCallback( "testStart" ),
393
394 // testDone: { name, failed, passed, total, runtime }
395 testDone: registerLoggingCallback( "testDone" ),
396
397 // moduleStart: { name }
398 moduleStart: registerLoggingCallback( "moduleStart" ),
399
400 // moduleDone: { name, failed, passed, total }
401 moduleDone: registerLoggingCallback( "moduleDone" )
402});
403
404QUnit.load = function() {
405 runLoggingCallbacks( "begin", {
406 totalTests: Test.count
407 });
408
409 // Initialize the configuration options
410 extend( config, {
411 stats: { all: 0, bad: 0 },
412 moduleStats: { all: 0, bad: 0 },
413 started: 0,
414 updateRate: 1000,
415 autostart: true,
416 filter: "",
417 semaphore: 1
418 }, true );
419
420 config.blocking = false;
421
422 if ( config.autostart ) {
423 QUnit.start();
424 }
425};
426
427// `onErrorFnPrev` initialized at top of scope
428// Preserve other handlers
429onErrorFnPrev = window.onerror;
430
431// Cover uncaught exceptions
432// Returning true will suppress the default browser handler,
433// returning false will let it run.
434window.onerror = function( error, filePath, linerNr ) {
435 var ret = false;
436 if ( onErrorFnPrev ) {
437 ret = onErrorFnPrev( error, filePath, linerNr );
438 }
439
440 // Treat return value as window.onerror itself does,
441 // Only do our handling if not suppressed.
442 if ( ret !== true ) {
443 if ( QUnit.config.current ) {
444 if ( QUnit.config.current.ignoreGlobalErrors ) {
445 return true;
446 }
447 QUnit.pushFailure( error, filePath + ":" + linerNr );
448 } else {
449 QUnit.test( "global failure", extend(function() {
450 QUnit.pushFailure( error, filePath + ":" + linerNr );
451 }, { validTest: validTest } ) );
452 }
453 return false;
454 }
455
456 return ret;
457};
458
459function done() {
460 config.autorun = true;
461
462 // Log the last module results
463 if ( config.previousModule ) {
464 runLoggingCallbacks( "moduleDone", {
465 name: config.previousModule,
466 failed: config.moduleStats.bad,
467 passed: config.moduleStats.all - config.moduleStats.bad,
468 total: config.moduleStats.all
469 });
470 }
471 delete config.previousModule;
472
473 var runtime = now() - config.started,
474 passed = config.stats.all - config.stats.bad;
475
476 runLoggingCallbacks( "done", {
477 failed: config.stats.bad,
478 passed: passed,
479 total: config.stats.all,
480 runtime: runtime
481 });
482}
483
484/** @return Boolean: true if this test should be ran */
485function validTest( test ) {
486 var include,
487 filter = config.filter && config.filter.toLowerCase(),
488 module = config.module && config.module.toLowerCase(),
489 fullName = ( test.module + ": " + test.testName ).toLowerCase();
490
491 // Internally-generated tests are always valid
492 if ( test.callback && test.callback.validTest === validTest ) {
493 delete test.callback.validTest;
494 return true;
495 }
496
497 if ( config.testNumber.length > 0 ) {
498 if ( inArray( test.testNumber, config.testNumber ) < 0 ) {
499 return false;
500 }
501 }
502
503 if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
504 return false;
505 }
506
507 if ( !filter ) {
508 return true;
509 }
510
511 include = filter.charAt( 0 ) !== "!";
512 if ( !include ) {
513 filter = filter.slice( 1 );
514 }
515
516 // If the filter matches, we need to honour include
517 if ( fullName.indexOf( filter ) !== -1 ) {
518 return include;
519 }
520
521 // Otherwise, do the opposite
522 return !include;
523}
524
525// Doesn't support IE6 to IE9
526// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
527function extractStacktrace( e, offset ) {
528 offset = offset === undefined ? 4 : offset;
529
530 var stack, include, i;
531
532 if ( e.stacktrace ) {
533
534 // Opera 12.x
535 return e.stacktrace.split( "\n" )[ offset + 3 ];
536 } else if ( e.stack ) {
537
538 // Firefox, Chrome, Safari 6+, IE10+, PhantomJS and Node
539 stack = e.stack.split( "\n" );
540 if ( /^error$/i.test( stack[ 0 ] ) ) {
541 stack.shift();
542 }
543 if ( fileName ) {
544 include = [];
545 for ( i = offset; i < stack.length; i++ ) {
546 if ( stack[ i ].indexOf( fileName ) !== -1 ) {
547 break;
548 }
549 include.push( stack[ i ] );
550 }
551 if ( include.length ) {
552 return include.join( "\n" );
553 }
554 }
555 return stack[ offset ];
556 } else if ( e.sourceURL ) {
557
558 // Safari < 6
559 // exclude useless self-reference for generated Error objects
560 if ( /qunit.js$/.test( e.sourceURL ) ) {
561 return;
562 }
563
564 // for actual exceptions, this is useful
565 return e.sourceURL + ":" + e.line;
566 }
567}
568function sourceFromStacktrace( offset ) {
569 try {
570 throw new Error();
571 } catch ( e ) {
572 return extractStacktrace( e, offset );
573 }
574}
575
576function synchronize( callback, last ) {
577 config.queue.push( callback );
578
579 if ( config.autorun && !config.blocking ) {
580 process( last );
581 }
582}
583
584function process( last ) {
585 function next() {
586 process( last );
587 }
588 var start = now();
589 config.depth = config.depth ? config.depth + 1 : 1;
590
591 while ( config.queue.length && !config.blocking ) {
592 if ( !defined.setTimeout || config.updateRate <= 0 || ( ( now() - start ) < config.updateRate ) ) {
593 config.queue.shift()();
594 } else {
595 setTimeout( next, 13 );
596 break;
597 }
598 }
599 config.depth--;
600 if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
601 done();
602 }
603}
604
605function saveGlobal() {
606 config.pollution = [];
607
608 if ( config.noglobals ) {
609 for ( var key in window ) {
610 if ( hasOwn.call( window, key ) ) {
611 // in Opera sometimes DOM element ids show up here, ignore them
612 if ( /^qunit-test-output/.test( key ) ) {
613 continue;
614 }
615 config.pollution.push( key );
616 }
617 }
618 }
619}
620
621function checkPollution() {
622 var newGlobals,
623 deletedGlobals,
624 old = config.pollution;
625
626 saveGlobal();
627
628 newGlobals = diff( config.pollution, old );
629 if ( newGlobals.length > 0 ) {
630 QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
631 }
632
633 deletedGlobals = diff( old, config.pollution );
634 if ( deletedGlobals.length > 0 ) {
635 QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
636 }
637}
638
639// returns a new Array with the elements that are in a but not in b
640function diff( a, b ) {
641 var i, j,
642 result = a.slice();
643
644 for ( i = 0; i < result.length; i++ ) {
645 for ( j = 0; j < b.length; j++ ) {
646 if ( result[ i ] === b[ j ] ) {
647 result.splice( i, 1 );
648 i--;
649 break;
650 }
651 }
652 }
653 return result;
654}
655
656function extend( a, b, undefOnly ) {
657 for ( var prop in b ) {
658 if ( hasOwn.call( b, prop ) ) {
659
660 // Avoid "Member not found" error in IE8 caused by messing with window.constructor
661 if ( !( prop === "constructor" && a === window ) ) {
662 if ( b[ prop ] === undefined ) {
663 delete a[ prop ];
664 } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
665 a[ prop ] = b[ prop ];
666 }
667 }
668 }
669 }
670
671 return a;
672}
673
674function registerLoggingCallback( key ) {
675
676 // Initialize key collection of logging callback
677 if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
678 config.callbacks[ key ] = [];
679 }
680
681 return function( callback ) {
682 config.callbacks[ key ].push( callback );
683 };
684}
685
686function runLoggingCallbacks( key, args ) {
687 var i, l, callbacks;
688
689 callbacks = config.callbacks[ key ];
690 for ( i = 0, l = callbacks.length; i < l; i++ ) {
691 callbacks[ i ]( args );
692 }
693}
694
695// from jquery.js
696function inArray( elem, array ) {
697 if ( array.indexOf ) {
698 return array.indexOf( elem );
699 }
700
701 for ( var i = 0, length = array.length; i < length; i++ ) {
702 if ( array[ i ] === elem ) {
703 return i;
704 }
705 }
706
707 return -1;
708}
709
710function Test( settings ) {
711 extend( this, settings );
712 this.assert = new Assert( this );
713 this.assertions = [];
714 this.testNumber = ++Test.count;
715}
716
717Test.count = 0;
718
719Test.prototype = {
720 setup: function() {
721 if (
722
723 // Emit moduleStart when we're switching from one module to another
724 this.module !== config.previousModule ||
725
726 // They could be equal (both undefined) but if the previousModule property doesn't
727 // yet exist it means this is the first test in a suite that isn't wrapped in a
728 // module, in which case we'll just emit a moduleStart event for 'undefined'.
729 // Without this, reporters can get testStart before moduleStart which is a problem.
730 !hasOwn.call( config, "previousModule" )
731 ) {
732 if ( hasOwn.call( config, "previousModule" ) ) {
733 runLoggingCallbacks( "moduleDone", {
734 name: config.previousModule,
735 failed: config.moduleStats.bad,
736 passed: config.moduleStats.all - config.moduleStats.bad,
737 total: config.moduleStats.all
738 });
739 }
740 config.previousModule = this.module;
741 config.moduleStats = { all: 0, bad: 0 };
742 runLoggingCallbacks( "moduleStart", {
743 name: this.module
744 });
745 }
746
747 config.current = this;
748
749 this.testEnvironment = extend({
750 setup: function() {},
751 teardown: function() {}
752 }, this.moduleTestEnvironment );
753
754 this.started = now();
755 runLoggingCallbacks( "testStart", {
756 name: this.testName,
757 module: this.module,
758 testNumber: this.testNumber
759 });
760
761 if ( !config.pollution ) {
762 saveGlobal();
763 }
764 if ( config.notrycatch ) {
765 this.testEnvironment.setup.call( this.testEnvironment, this.assert );
766 return;
767 }
768 try {
769 this.testEnvironment.setup.call( this.testEnvironment, this.assert );
770 } catch ( e ) {
771 this.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
772 }
773 },
774 run: function() {
775 config.current = this;
776
777 if ( this.async ) {
778 QUnit.stop();
779 }
780
781 this.callbackStarted = now();
782
783 if ( config.notrycatch ) {
784 this.callback.call( this.testEnvironment, this.assert );
785 this.callbackRuntime = now() - this.callbackStarted;
786 return;
787 }
788
789 try {
790 this.callback.call( this.testEnvironment, this.assert );
791 this.callbackRuntime = now() - this.callbackStarted;
792 } catch ( e ) {
793 this.callbackRuntime = now() - this.callbackStarted;
794
795 this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
796
797 // else next test will carry the responsibility
798 saveGlobal();
799
800 // Restart the tests if they're blocking
801 if ( config.blocking ) {
802 QUnit.start();
803 }
804 }
805 },
806 teardown: function() {
807 config.current = this;
808 if ( config.notrycatch ) {
809 if ( typeof this.callbackRuntime === "undefined" ) {
810 this.callbackRuntime = now() - this.callbackStarted;
811 }
812 this.testEnvironment.teardown.call( this.testEnvironment, this.assert );
813 return;
814 } else {
815 try {
816 this.testEnvironment.teardown.call( this.testEnvironment, this.assert );
817 } catch ( e ) {
818 this.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
819 }
820 }
821 checkPollution();
822 },
823 finish: function() {
824 config.current = this;
825 if ( config.requireExpects && this.expected === null ) {
826 this.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
827 } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
828 this.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
829 } else if ( this.expected === null && !this.assertions.length ) {
830 this.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
831 }
832
833 var i,
834 bad = 0;
835
836 this.runtime = now() - this.started;
837 config.stats.all += this.assertions.length;
838 config.moduleStats.all += this.assertions.length;
839
840 for ( i = 0; i < this.assertions.length; i++ ) {
841 if ( !this.assertions[ i ].result ) {
842 bad++;
843 config.stats.bad++;
844 config.moduleStats.bad++;
845 }
846 }
847
848 runLoggingCallbacks( "testDone", {
849 name: this.testName,
850 module: this.module,
851 failed: bad,
852 passed: this.assertions.length - bad,
853 total: this.assertions.length,
854 runtime: this.runtime,
855
856 // HTML Reporter use
857 assertions: this.assertions,
858 testNumber: this.testNumber,
859
860 // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
861 duration: this.runtime
862 });
863
864 config.current = undefined;
865 },
866
867 queue: function() {
868 var bad,
869 test = this;
870
871 function run() {
872 // each of these can by async
873 synchronize(function() {
874 test.setup();
875 });
876 synchronize(function() {
877 test.run();
878 });
879 synchronize(function() {
880 test.teardown();
881 });
882 synchronize(function() {
883 test.finish();
884 });
885 }
886
887 // `bad` initialized at top of scope
888 // defer when previous test run passed, if storage is available
889 bad = QUnit.config.reorder && defined.sessionStorage &&
890 +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName );
891
892 if ( bad ) {
893 run();
894 } else {
895 synchronize( run, true );
896 }
897 },
898
899 push: function( result, actual, expected, message ) {
900 var source,
901 details = {
902 module: this.module,
903 name: this.testName,
904 result: result,
905 message: message,
906 actual: actual,
907 expected: expected,
908 testNumber: this.testNumber
909 };
910
911 if ( !result ) {
912 source = sourceFromStacktrace();
913
914 if ( source ) {
915 details.source = source;
916 }
917 }
918
919 runLoggingCallbacks( "log", details );
920
921 this.assertions.push({
922 result: !!result,
923 message: message
924 });
925 },
926
927 pushFailure: function( message, source, actual ) {
928 if ( !this instanceof Test ) {
929 throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace( 2 ) );
930 }
931
932 var details = {
933 module: this.module,
934 name: this.testName,
935 result: false,
936 message: message || "error",
937 actual: actual || null,
938 testNumber: this.testNumber
939 };
940
941 if ( source ) {
942 details.source = source;
943 }
944
945 runLoggingCallbacks( "log", details );
946
947 this.assertions.push({
948 result: false,
949 message: message
950 });
951 }
952};
953
954QUnit.pushFailure = function() {
955 if ( !QUnit.config.current ) {
956 throw new Error( "pushFailure() assertion outside test context, in " + sourceFromStacktrace( 2 ) );
957 }
958
959 // Gets current test obj
960 var currentTest = QUnit.config.current.assert.test;
961
962 return currentTest.pushFailure.apply( currentTest, arguments );
963};
964
965function Assert( testContext ) {
966 this.test = testContext;
967}
968
969// Assert helpers
970QUnit.assert = Assert.prototype = {
971
972 // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through.
973 expect: function( asserts ) {
974 if ( arguments.length === 1 ) {
975 this.test.expected = asserts;
976 } else {
977 return this.test.expected;
978 }
979 },
980
981 // Exports test.push() to the user API
982 push: function() {
983 var assert = this;
984
985 // Backwards compatibility fix.
986 // Allows the direct use of global exported assertions and QUnit.assert.*
987 // Although, it's use is not recommended as it can leak assertions
988 // to other tests from async tests, because we only get a reference to the current test,
989 // not exactly the test where assertion were intended to be called.
990 if ( !QUnit.config.current ) {
991 throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
992 }
993 if ( !( assert instanceof Assert ) ) {
994 assert = QUnit.config.current.assert;
995 }
996 return assert.test.push.apply( assert.test, arguments );
997 },
998
999 /**
1000 * Asserts rough true-ish result.
1001 * @name ok
1002 * @function
1003 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
1004 */
1005 ok: function( result, message ) {
1006 message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
1007 QUnit.dump.parse( result ) );
1008 if ( !!result ) {
1009 this.push( true, result, true, message );
1010 } else {
1011 this.test.pushFailure( message, null, result );
1012 }
1013 },
1014
1015 /**
1016 * Assert that the first two arguments are equal, with an optional message.
1017 * Prints out both actual and expected values.
1018 * @name equal
1019 * @function
1020 * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
1021 */
1022 equal: function( actual, expected, message ) {
1023 /*jshint eqeqeq:false */
1024 this.push( expected == actual, actual, expected, message );
1025 },
1026
1027 /**
1028 * @name notEqual
1029 * @function
1030 */
1031 notEqual: function( actual, expected, message ) {
1032 /*jshint eqeqeq:false */
1033 this.push( expected != actual, actual, expected, message );
1034 },
1035
1036 /**
1037 * @name propEqual
1038 * @function
1039 */
1040 propEqual: function( actual, expected, message ) {
1041 actual = objectValues( actual );
1042 expected = objectValues( expected );
1043 this.push( QUnit.equiv( actual, expected ), actual, expected, message );
1044 },
1045
1046 /**
1047 * @name notPropEqual
1048 * @function
1049 */
1050 notPropEqual: function( actual, expected, message ) {
1051 actual = objectValues( actual );
1052 expected = objectValues( expected );
1053 this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
1054 },
1055
1056 /**
1057 * @name deepEqual
1058 * @function
1059 */
1060 deepEqual: function( actual, expected, message ) {
1061 this.push( QUnit.equiv( actual, expected ), actual, expected, message );
1062 },
1063
1064 /**
1065 * @name notDeepEqual
1066 * @function
1067 */
1068 notDeepEqual: function( actual, expected, message ) {
1069 this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
1070 },
1071
1072 /**
1073 * @name strictEqual
1074 * @function
1075 */
1076 strictEqual: function( actual, expected, message ) {
1077 this.push( expected === actual, actual, expected, message );
1078 },
1079
1080 /**
1081 * @name notStrictEqual
1082 * @function
1083 */
1084 notStrictEqual: function( actual, expected, message ) {
1085 this.push( expected !== actual, actual, expected, message );
1086 },
1087
1088 "throws": function( block, expected, message ) {
1089 var actual, expectedType,
1090 expectedOutput = expected,
1091 ok = false;
1092
1093 // 'expected' is optional unless doing string comparison
1094 if ( message == null && typeof expected === "string" ) {
1095 message = expected;
1096 expected = null;
1097 }
1098
1099 this.test.ignoreGlobalErrors = true;
1100 try {
1101 block.call( this.test.testEnvironment );
1102 } catch (e) {
1103 actual = e;
1104 }
1105 this.test.ignoreGlobalErrors = false;
1106
1107 if ( actual ) {
1108 expectedType = QUnit.objectType( expected );
1109
1110 // we don't want to validate thrown error
1111 if ( !expected ) {
1112 ok = true;
1113 expectedOutput = null;
1114
1115 // expected is a regexp
1116 } else if ( expectedType === "regexp" ) {
1117 ok = expected.test( errorString( actual ) );
1118
1119 // expected is a string
1120 } else if ( expectedType === "string" ) {
1121 ok = expected === errorString( actual );
1122
1123 // expected is a constructor, maybe an Error constructor
1124 } else if ( expectedType === "function" && actual instanceof expected ) {
1125 ok = true;
1126
1127 // expected is an Error object
1128 } else if ( expectedType === "object" ) {
1129 ok = actual instanceof expected.constructor &&
1130 actual.name === expected.name &&
1131 actual.message === expected.message;
1132
1133 // expected is a validation function which returns true if validation passed
1134 } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
1135 expectedOutput = null;
1136 ok = true;
1137 }
1138
1139 this.push( ok, actual, expectedOutput, message );
1140 } else {
1141 this.test.pushFailure( message, null, "No exception was thrown." );
1142 }
1143 }
1144};
1145
1146// Test for equality any JavaScript type.
1147// Author: Philippe Rathé <prathe@gmail.com>
1148QUnit.equiv = (function() {
1149
1150 // Call the o related callback with the given arguments.
1151 function bindCallbacks( o, callbacks, args ) {
1152 var prop = QUnit.objectType( o );
1153 if ( prop ) {
1154 if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
1155 return callbacks[ prop ].apply( callbacks, args );
1156 } else {
1157 return callbacks[ prop ]; // or undefined
1158 }
1159 }
1160 }
1161
1162 // the real equiv function
1163 var innerEquiv,
1164
1165 // stack to decide between skip/abort functions
1166 callers = [],
1167
1168 // stack to avoiding loops from circular referencing
1169 parents = [],
1170 parentsB = [],
1171
1172 getProto = Object.getPrototypeOf || function( obj ) {
1173 /* jshint camelcase: false, proto: true */
1174 return obj.__proto__;
1175 },
1176 callbacks = (function() {
1177
1178 // for string, boolean, number and null
1179 function useStrictEquality( b, a ) {
1180
1181 /*jshint eqeqeq:false */
1182 if ( b instanceof a.constructor || a instanceof b.constructor ) {
1183
1184 // to catch short annotation VS 'new' annotation of a
1185 // declaration
1186 // e.g. var i = 1;
1187 // var j = new Number(1);
1188 return a == b;
1189 } else {
1190 return a === b;
1191 }
1192 }
1193
1194 return {
1195 "string": useStrictEquality,
1196 "boolean": useStrictEquality,
1197 "number": useStrictEquality,
1198 "null": useStrictEquality,
1199 "undefined": useStrictEquality,
1200
1201 "nan": function( b ) {
1202 return isNaN( b );
1203 },
1204
1205 "date": function( b, a ) {
1206 return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
1207 },
1208
1209 "regexp": function( b, a ) {
1210 return QUnit.objectType( b ) === "regexp" &&
1211
1212 // the regex itself
1213 a.source === b.source &&
1214
1215 // and its modifiers
1216 a.global === b.global &&
1217
1218 // (gmi) ...
1219 a.ignoreCase === b.ignoreCase &&
1220 a.multiline === b.multiline &&
1221 a.sticky === b.sticky;
1222 },
1223
1224 // - skip when the property is a method of an instance (OOP)
1225 // - abort otherwise,
1226 // initial === would have catch identical references anyway
1227 "function": function() {
1228 var caller = callers[ callers.length - 1 ];
1229 return caller !== Object && typeof caller !== "undefined";
1230 },
1231
1232 "array": function( b, a ) {
1233 var i, j, len, loop, aCircular, bCircular;
1234
1235 // b could be an object literal here
1236 if ( QUnit.objectType( b ) !== "array" ) {
1237 return false;
1238 }
1239
1240 len = a.length;
1241 if ( len !== b.length ) {
1242 // safe and faster
1243 return false;
1244 }
1245
1246 // track reference to avoid circular references
1247 parents.push( a );
1248 parentsB.push( b );
1249 for ( i = 0; i < len; i++ ) {
1250 loop = false;
1251 for ( j = 0; j < parents.length; j++ ) {
1252 aCircular = parents[ j ] === a[ i ];
1253 bCircular = parentsB[ j ] === b[ i ];
1254 if ( aCircular || bCircular ) {
1255 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1256 loop = true;
1257 } else {
1258 parents.pop();
1259 parentsB.pop();
1260 return false;
1261 }
1262 }
1263 }
1264 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1265 parents.pop();
1266 parentsB.pop();
1267 return false;
1268 }
1269 }
1270 parents.pop();
1271 parentsB.pop();
1272 return true;
1273 },
1274
1275 "object": function( b, a ) {
1276
1277 /*jshint forin:false */
1278 var i, j, loop, aCircular, bCircular,
1279 // Default to true
1280 eq = true,
1281 aProperties = [],
1282 bProperties = [];
1283
1284 // comparing constructors is more strict than using
1285 // instanceof
1286 if ( a.constructor !== b.constructor ) {
1287
1288 // Allow objects with no prototype to be equivalent to
1289 // objects with Object as their constructor.
1290 if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
1291 ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
1292 return false;
1293 }
1294 }
1295
1296 // stack constructor before traversing properties
1297 callers.push( a.constructor );
1298
1299 // track reference to avoid circular references
1300 parents.push( a );
1301 parentsB.push( b );
1302
1303 // be strict: don't ensure hasOwnProperty and go deep
1304 for ( i in a ) {
1305 loop = false;
1306 for ( j = 0; j < parents.length; j++ ) {
1307 aCircular = parents[ j ] === a[ i ];
1308 bCircular = parentsB[ j ] === b[ i ];
1309 if ( aCircular || bCircular ) {
1310 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1311 loop = true;
1312 } else {
1313 eq = false;
1314 break;
1315 }
1316 }
1317 }
1318 aProperties.push( i );
1319 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1320 eq = false;
1321 break;
1322 }
1323 }
1324
1325 parents.pop();
1326 parentsB.pop();
1327 callers.pop(); // unstack, we are done
1328
1329 for ( i in b ) {
1330 bProperties.push( i ); // collect b's properties
1331 }
1332
1333 // Ensures identical properties name
1334 return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
1335 }
1336 };
1337 }());
1338
1339 innerEquiv = function() { // can take multiple arguments
1340 var args = [].slice.apply( arguments );
1341 if ( args.length < 2 ) {
1342 return true; // end transition
1343 }
1344
1345 return ( (function( a, b ) {
1346 if ( a === b ) {
1347 return true; // catch the most you can
1348 } else if ( a === null || b === null || typeof a === "undefined" ||
1349 typeof b === "undefined" ||
1350 QUnit.objectType( a ) !== QUnit.objectType( b ) ) {
1351
1352 // don't lose time with error prone cases
1353 return false;
1354 } else {
1355 return bindCallbacks( a, callbacks, [ b, a ] );
1356 }
1357
1358 // apply transition with (1..n) arguments
1359 }( args[ 0 ], args[ 1 ] ) ) && innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
1360 };
1361
1362 return innerEquiv;
1363}());
1364
1365// Based on jsDump by Ariel Flesler
1366// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
1367QUnit.dump = (function() {
1368 function quote( str ) {
1369 return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
1370 }
1371 function literal( o ) {
1372 return o + "";
1373 }
1374 function join( pre, arr, post ) {
1375 var s = dump.separator(),
1376 base = dump.indent(),
1377 inner = dump.indent( 1 );
1378 if ( arr.join ) {
1379 arr = arr.join( "," + s + inner );
1380 }
1381 if ( !arr ) {
1382 return pre + post;
1383 }
1384 return [ pre, inner + arr, base + post ].join( s );
1385 }
1386 function array( arr, stack ) {
1387 var i = arr.length,
1388 ret = new Array( i );
1389 this.up();
1390 while ( i-- ) {
1391 ret[ i ] = this.parse( arr[ i ], undefined, stack );
1392 }
1393 this.down();
1394 return join( "[", ret, "]" );
1395 }
1396
1397 var reName = /^function (\w+)/,
1398 dump = {
1399 // type is used mostly internally, you can fix a (custom)type in advance
1400 parse: function( obj, type, stack ) {
1401 stack = stack || [];
1402 var inStack, res,
1403 parser = this.parsers[ type || this.typeOf( obj ) ];
1404
1405 type = typeof parser;
1406 inStack = inArray( obj, stack );
1407
1408 if ( inStack !== -1 ) {
1409 return "recursion(" + ( inStack - stack.length ) + ")";
1410 }
1411 if ( type === "function" ) {
1412 stack.push( obj );
1413 res = parser.call( this, obj, stack );
1414 stack.pop();
1415 return res;
1416 }
1417 return ( type === "string" ) ? parser : this.parsers.error;
1418 },
1419 typeOf: function( obj ) {
1420 var type;
1421 if ( obj === null ) {
1422 type = "null";
1423 } else if ( typeof obj === "undefined" ) {
1424 type = "undefined";
1425 } else if ( QUnit.is( "regexp", obj ) ) {
1426 type = "regexp";
1427 } else if ( QUnit.is( "date", obj ) ) {
1428 type = "date";
1429 } else if ( QUnit.is( "function", obj ) ) {
1430 type = "function";
1431 } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) {
1432 type = "window";
1433 } else if ( obj.nodeType === 9 ) {
1434 type = "document";
1435 } else if ( obj.nodeType ) {
1436 type = "node";
1437 } else if (
1438
1439 // native arrays
1440 toString.call( obj ) === "[object Array]" ||
1441
1442 // NodeList objects
1443 ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null && typeof obj[ 0 ] === "undefined" ) ) )
1444 ) {
1445 type = "array";
1446 } else if ( obj.constructor === Error.prototype.constructor ) {
1447 type = "error";
1448 } else {
1449 type = typeof obj;
1450 }
1451 return type;
1452 },
1453 separator: function() {
1454 return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&nbsp;" : " ";
1455 },
1456 // extra can be a number, shortcut for increasing-calling-decreasing
1457 indent: function( extra ) {
1458 if ( !this.multiline ) {
1459 return "";
1460 }
1461 var chr = this.indentChar;
1462 if ( this.HTML ) {
1463 chr = chr.replace( /\t/g, " " ).replace( / /g, "&nbsp;" );
1464 }
1465 return new Array( this.depth + ( extra || 0 ) ).join( chr );
1466 },
1467 up: function( a ) {
1468 this.depth += a || 1;
1469 },
1470 down: function( a ) {
1471 this.depth -= a || 1;
1472 },
1473 setParser: function( name, parser ) {
1474 this.parsers[ name ] = parser;
1475 },
1476 // The next 3 are exposed so you can use them
1477 quote: quote,
1478 literal: literal,
1479 join: join,
1480 //
1481 depth: 1,
1482 // This is the list of parsers, to modify them, use dump.setParser
1483 parsers: {
1484 window: "[Window]",
1485 document: "[Document]",
1486 error: function( error ) {
1487 return "Error(\"" + error.message + "\")";
1488 },
1489 unknown: "[Unknown]",
1490 "null": "null",
1491 "undefined": "undefined",
1492 "function": function( fn ) {
1493 var ret = "function",
1494 // functions never have name in IE
1495 name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
1496
1497 if ( name ) {
1498 ret += " " + name;
1499 }
1500 ret += "( ";
1501
1502 ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
1503 return join( ret, dump.parse( fn, "functionCode" ), "}" );
1504 },
1505 array: array,
1506 nodelist: array,
1507 "arguments": array,
1508 object: function( map, stack ) {
1509 /*jshint forin:false */
1510 var ret = [], keys, key, val, i, nonEnumerableProperties;
1511 dump.up();
1512 keys = [];
1513 for ( key in map ) {
1514 keys.push( key );
1515 }
1516
1517 // Some properties are not always enumerable on Error objects.
1518 nonEnumerableProperties = [ "message", "name" ];
1519 for ( i in nonEnumerableProperties ) {
1520 key = nonEnumerableProperties[ i ];
1521 if ( key in map && !( key in keys ) ) {
1522 keys.push( key );
1523 }
1524 }
1525 keys.sort();
1526 for ( i = 0; i < keys.length; i++ ) {
1527 key = keys[ i ];
1528 val = map[ key ];
1529 ret.push( dump.parse( key, "key" ) + ": " + dump.parse( val, undefined, stack ) );
1530 }
1531 dump.down();
1532 return join( "{", ret, "}" );
1533 },
1534 node: function( node ) {
1535 var len, i, val,
1536 open = dump.HTML ? "&lt;" : "<",
1537 close = dump.HTML ? "&gt;" : ">",
1538 tag = node.nodeName.toLowerCase(),
1539 ret = open + tag,
1540 attrs = node.attributes;
1541
1542 if ( attrs ) {
1543 for ( i = 0, len = attrs.length; i < len; i++ ) {
1544 val = attrs[ i ].nodeValue;
1545
1546 // IE6 includes all attributes in .attributes, even ones not explicitly set.
1547 // Those have values like undefined, null, 0, false, "" or "inherit".
1548 if ( val && val !== "inherit" ) {
1549 ret += " " + attrs[ i ].nodeName + "=" + dump.parse( val, "attribute" );
1550 }
1551 }
1552 }
1553 ret += close;
1554
1555 // Show content of TextNode or CDATASection
1556 if ( node.nodeType === 3 || node.nodeType === 4 ) {
1557 ret += node.nodeValue;
1558 }
1559
1560 return ret + open + "/" + tag + close;
1561 },
1562
1563 // function calls it internally, it's the arguments part of the function
1564 functionArgs: function( fn ) {
1565 var args,
1566 l = fn.length;
1567
1568 if ( !l ) {
1569 return "";
1570 }
1571
1572 args = new Array( l );
1573 while ( l-- ) {
1574
1575 // 97 is 'a'
1576 args[ l ] = String.fromCharCode( 97 + l );
1577 }
1578 return " " + args.join( ", " ) + " ";
1579 },
1580 // object calls it internally, the key part of an item in a map
1581 key: quote,
1582 // function calls it internally, it's the content of the function
1583 functionCode: "[code]",
1584 // node calls it internally, it's an html attribute value
1585 attribute: quote,
1586 string: quote,
1587 date: quote,
1588 regexp: literal,
1589 number: literal,
1590 "boolean": literal
1591 },
1592 // if true, entities are escaped ( <, >, \t, space and \n )
1593 HTML: false,
1594 // indentation unit
1595 indentChar: " ",
1596 // if true, items in a collection, are separated by a \n, else just a space.
1597 multiline: true
1598 };
1599
1600 return dump;
1601}());
1602
1603// back compat
1604QUnit.jsDump = QUnit.dump;
1605
1606// For browser, export only select globals
1607if ( typeof window !== "undefined" ) {
1608
1609 // Deprecated
1610 // Extend assert methods to QUnit and Global scope through Backwards compatibility
1611 (function() {
1612 var i,
1613 assertions = Assert.prototype;
1614
1615 function applyCurrent( current ) {
1616 return function() {
1617 var assert = new Assert( QUnit.config.current );
1618 current.apply( assert, arguments );
1619 };
1620 }
1621
1622 for ( i in assertions ) {
1623 QUnit[ i ] = applyCurrent( assertions[ i ] );
1624 }
1625 })();
1626
1627 (function() {
1628 var i, l,
1629 keys = [
1630 "test",
1631 "module",
1632 "expect",
1633 "asyncTest",
1634 "start",
1635 "stop",
1636 "ok",
1637 "equal",
1638 "notEqual",
1639 "propEqual",
1640 "notPropEqual",
1641 "deepEqual",
1642 "notDeepEqual",
1643 "strictEqual",
1644 "notStrictEqual",
1645 "throws"
1646 ];
1647
1648 for ( i = 0, l = keys.length; i < l; i++ ) {
1649 window[ keys[ i ] ] = QUnit[ keys[ i ] ];
1650 }
1651 })();
1652
1653 window.QUnit = QUnit;
1654}
1655
1656// For CommonJS environments, export everything
1657if ( typeof module !== "undefined" && module.exports ) {
1658 module.exports = QUnit;
1659}
1660
1661// Get a reference to the global object, like window in browsers
1662}( (function() {
1663 return this;
1664})() ));
1665
1666/*istanbul ignore next */
1667/*
1668 * Javascript Diff Algorithm
1669 * By John Resig (http://ejohn.org/)
1670 * Modified by Chu Alan "sprite"
1671 *
1672 * Released under the MIT license.
1673 *
1674 * More Info:
1675 * http://ejohn.org/projects/javascript-diff-algorithm/
1676 *
1677 * Usage: QUnit.diff(expected, actual)
1678 *
1679 * 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"
1680 */
1681QUnit.diff = (function() {
1682 var hasOwn = Object.prototype.hasOwnProperty;
1683
1684 /*jshint eqeqeq:false, eqnull:true */
1685 function diff( o, n ) {
1686 var i,
1687 ns = {},
1688 os = {};
1689
1690 for ( i = 0; i < n.length; i++ ) {
1691 if ( !hasOwn.call( ns, n[ i ] ) ) {
1692 ns[ n[ i ] ] = {
1693 rows: [],
1694 o: null
1695 };
1696 }
1697 ns[ n[ i ] ].rows.push( i );
1698 }
1699
1700 for ( i = 0; i < o.length; i++ ) {
1701 if ( !hasOwn.call( os, o[ i ] ) ) {
1702 os[ o[ i ] ] = {
1703 rows: [],
1704 n: null
1705 };
1706 }
1707 os[ o[ i ] ].rows.push( i );
1708 }
1709
1710 for ( i in ns ) {
1711 if ( hasOwn.call( ns, i ) ) {
1712 if ( ns[ i ].rows.length === 1 && hasOwn.call( os, i ) && os[ i ].rows.length === 1 ) {
1713 n[ ns[ i ].rows[ 0 ] ] = {
1714 text: n[ ns[ i ].rows[ 0 ] ],
1715 row: os[ i ].rows[ 0 ]
1716 };
1717 o[ os[ i ].rows[ 0 ] ] = {
1718 text: o[ os[ i ].rows[ 0 ] ],
1719 row: ns[ i ].rows[ 0 ]
1720 };
1721 }
1722 }
1723 }
1724
1725 for ( i = 0; i < n.length - 1; i++ ) {
1726 if ( n[ i ].text != null && n[ i + 1 ].text == null && n[ i ].row + 1 < o.length && o[ n[ i ].row + 1 ].text == null &&
1727 n[ i + 1 ] == o[ n[ i ].row + 1 ] ) {
1728
1729 n[ i + 1 ] = {
1730 text: n[ i + 1 ],
1731 row: n[ i ].row + 1
1732 };
1733 o[ n[ i ].row + 1 ] = {
1734 text: o[ n[ i ].row + 1 ],
1735 row: i + 1
1736 };
1737 }
1738 }
1739
1740 for ( i = n.length - 1; i > 0; i-- ) {
1741 if ( n[ i ].text != null && n[ i - 1 ].text == null && n[ i ].row > 0 && o[ n[ i ].row - 1 ].text == null &&
1742 n[ i - 1 ] == o[ n[ i ].row - 1 ] ) {
1743
1744 n[ i - 1 ] = {
1745 text: n[ i - 1 ],
1746 row: n[ i ].row - 1
1747 };
1748 o[ n[ i ].row - 1 ] = {
1749 text: o[ n[ i ].row - 1 ],
1750 row: i - 1
1751 };
1752 }
1753 }
1754
1755 return {
1756 o: o,
1757 n: n
1758 };
1759 }
1760
1761 return function( o, n ) {
1762 o = o.replace( /\s+$/, "" );
1763 n = n.replace( /\s+$/, "" );
1764
1765 var i, pre,
1766 str = "",
1767 out = diff( o === "" ? [] : o.split( /\s+/ ), n === "" ? [] : n.split( /\s+/ ) ),
1768 oSpace = o.match( /\s+/g ),
1769 nSpace = n.match( /\s+/g );
1770
1771 if ( oSpace == null ) {
1772 oSpace = [ " " ];
1773 } else {
1774 oSpace.push( " " );
1775 }
1776
1777 if ( nSpace == null ) {
1778 nSpace = [ " " ];
1779 } else {
1780 nSpace.push( " " );
1781 }
1782
1783 if ( out.n.length === 0 ) {
1784 for ( i = 0; i < out.o.length; i++ ) {
1785 str += "<del>" + out.o[ i ] + oSpace[ i ] + "</del>";
1786 }
1787 } else {
1788 if ( out.n[ 0 ].text == null ) {
1789 for ( n = 0; n < out.o.length && out.o[ n ].text == null; n++ ) {
1790 str += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>";
1791 }
1792 }
1793
1794 for ( i = 0; i < out.n.length; i++ ) {
1795 if ( out.n[ i ].text == null ) {
1796 str += "<ins>" + out.n[ i ] + nSpace[ i ] + "</ins>";
1797 } else {
1798
1799 // `pre` initialized at top of scope
1800 pre = "";
1801
1802 for ( n = out.n[ i ].row + 1; n < out.o.length && out.o[ n ].text == null; n++ ) {
1803 pre += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>";
1804 }
1805 str += " " + out.n[ i ].text + nSpace[ i ] + pre;
1806 }
1807 }
1808 }
1809
1810 return str;
1811 };
1812}());
1813
1814(function() {
1815
1816// Deprecated QUnit.init - Ref #530
1817// Re-initialize the configuration options
1818QUnit.init = function() {
1819 var tests, banner, result, qunit,
1820 config = QUnit.config;
1821
1822 config.stats = { all: 0, bad: 0 };
1823 config.moduleStats = { all: 0, bad: 0 };
1824 config.started = 0;
1825 config.updateRate = 1000;
1826 config.blocking = false;
1827 config.autostart = true;
1828 config.autorun = false;
1829 config.filter = "";
1830 config.queue = [];
1831 config.semaphore = 1;
1832
1833 // Return on non-browser environments
1834 // This is necessary to not break on node tests
1835 if ( typeof window === "undefined" ) {
1836 return;
1837 }
1838
1839 qunit = id( "qunit" );
1840 if ( qunit ) {
1841 qunit.innerHTML =
1842 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
1843 "<h2 id='qunit-banner'></h2>" +
1844 "<div id='qunit-testrunner-toolbar'></div>" +
1845 "<h2 id='qunit-userAgent'></h2>" +
1846 "<ol id='qunit-tests'></ol>";
1847 }
1848
1849 tests = id( "qunit-tests" );
1850 banner = id( "qunit-banner" );
1851 result = id( "qunit-testresult" );
1852
1853 if ( tests ) {
1854 tests.innerHTML = "";
1855 }
1856
1857 if ( banner ) {
1858 banner.className = "";
1859 }
1860
1861 if ( result ) {
1862 result.parentNode.removeChild( result );
1863 }
1864
1865 if ( tests ) {
1866 result = document.createElement( "p" );
1867 result.id = "qunit-testresult";
1868 result.className = "result";
1869 tests.parentNode.insertBefore( result, tests );
1870 result.innerHTML = "Running...<br/>&nbsp;";
1871 }
1872};
1873
1874// Resets the test setup. Useful for tests that modify the DOM.
1875/*
1876DEPRECATED: Use multiple tests instead of resetting inside a test.
1877Use testStart or testDone for custom cleanup.
1878This method will throw an error in 2.0, and will be removed in 2.1
1879*/
1880QUnit.reset = function() {
1881
1882 // Return on non-browser environments
1883 // This is necessary to not break on node tests
1884 if ( typeof window === "undefined" ) {
1885 return;
1886 }
1887
1888 var fixture = id( "qunit-fixture" );
1889 if ( fixture ) {
1890 fixture.innerHTML = config.fixture;
1891 }
1892};
1893
1894// Don't load the HTML Reporter on non-Browser environments
1895if ( typeof window === "undefined" ) {
1896 return;
1897}
1898
1899var config = QUnit.config,
1900 hasOwn = Object.prototype.hasOwnProperty,
1901 defined = {
1902 document: typeof window.document !== "undefined",
1903 sessionStorage: (function() {
1904 var x = "qunit-test-string";
1905 try {
1906 sessionStorage.setItem( x, x );
1907 sessionStorage.removeItem( x );
1908 return true;
1909 } catch ( e ) {
1910 return false;
1911 }
1912 }())
1913 };
1914
1915/**
1916* Escape text for attribute or text content.
1917*/
1918function escapeText( s ) {
1919 if ( !s ) {
1920 return "";
1921 }
1922 s = s + "";
1923
1924 // Both single quotes and double quotes (for attributes)
1925 return s.replace( /['"<>&]/g, function( s ) {
1926 switch ( s ) {
1927 case "'":
1928 return "&#039;";
1929 case "\"":
1930 return "&quot;";
1931 case "<":
1932 return "&lt;";
1933 case ">":
1934 return "&gt;";
1935 case "&":
1936 return "&amp;";
1937 }
1938 });
1939}
1940
1941/**
1942 * @param {HTMLElement} elem
1943 * @param {string} type
1944 * @param {Function} fn
1945 */
1946function addEvent( elem, type, fn ) {
1947 if ( elem.addEventListener ) {
1948
1949 // Standards-based browsers
1950 elem.addEventListener( type, fn, false );
1951 } else if ( elem.attachEvent ) {
1952
1953 // support: IE <9
1954 elem.attachEvent( "on" + type, fn );
1955 }
1956}
1957
1958/**
1959 * @param {Array|NodeList} elems
1960 * @param {string} type
1961 * @param {Function} fn
1962 */
1963function addEvents( elems, type, fn ) {
1964 var i = elems.length;
1965 while ( i-- ) {
1966 addEvent( elems[ i ], type, fn );
1967 }
1968}
1969
1970function hasClass( elem, name ) {
1971 return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
1972}
1973
1974function addClass( elem, name ) {
1975 if ( !hasClass( elem, name ) ) {
1976 elem.className += ( elem.className ? " " : "" ) + name;
1977 }
1978}
1979
1980function toggleClass( elem, name ) {
1981 if ( hasClass( elem, name ) ) {
1982 removeClass( elem, name );
1983 } else {
1984 addClass( elem, name );
1985 }
1986}
1987
1988function removeClass( elem, name ) {
1989 var set = " " + elem.className + " ";
1990
1991 // Class name may appear multiple times
1992 while ( set.indexOf( " " + name + " " ) >= 0 ) {
1993 set = set.replace( " " + name + " ", " " );
1994 }
1995
1996 // trim for prettiness
1997 elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
1998}
1999
2000function id( name ) {
2001 return defined.document && document.getElementById && document.getElementById( name );
2002}
2003
2004function getUrlConfigHtml() {
2005 var i, j, val,
2006 escaped, escapedTooltip,
2007 selection = false,
2008 len = config.urlConfig.length,
2009 urlConfigHtml = "";
2010
2011 for ( i = 0; i < len; i++ ) {
2012 val = config.urlConfig[ i ];
2013 if ( typeof val === "string" ) {
2014 val = {
2015 id: val,
2016 label: val
2017 };
2018 }
2019
2020 escaped = escapeText( val.id );
2021 escapedTooltip = escapeText( val.tooltip );
2022
2023 config[ val.id ] = QUnit.urlParams[ val.id ];
2024 if ( !val.value || typeof val.value === "string" ) {
2025 urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
2026 "' name='" + escaped + "' type='checkbox'" +
2027 ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
2028 ( config[ val.id ] ? " checked='checked'" : "" ) +
2029 " title='" + escapedTooltip + "'><label for='qunit-urlconfig-" + escaped +
2030 "' title='" + escapedTooltip + "'>" + val.label + "</label>";
2031 } else {
2032 urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
2033 "' title='" + escapedTooltip + "'>" + val.label +
2034 ": </label><select id='qunit-urlconfig-" + escaped +
2035 "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
2036
2037 if ( QUnit.is( "array", val.value ) ) {
2038 for ( j = 0; j < val.value.length; j++ ) {
2039 escaped = escapeText( val.value[ j ] );
2040 urlConfigHtml += "<option value='" + escaped + "'" +
2041 ( config[ val.id ] === val.value[ j ] ?
2042 ( selection = true ) && " selected='selected'" : "" ) +
2043 ">" + escaped + "</option>";
2044 }
2045 } else {
2046 for ( j in val.value ) {
2047 if ( hasOwn.call( val.value, j ) ) {
2048 urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
2049 ( config[ val.id ] === j ?
2050 ( selection = true ) && " selected='selected'" : "" ) +
2051 ">" + escapeText( val.value[ j ] ) + "</option>";
2052 }
2053 }
2054 }
2055 if ( config[ val.id ] && !selection ) {
2056 escaped = escapeText( config[ val.id ] );
2057 urlConfigHtml += "<option value='" + escaped +
2058 "' selected='selected' disabled='disabled'>" + escaped + "</option>";
2059 }
2060 urlConfigHtml += "</select>";
2061 }
2062 }
2063
2064 return urlConfigHtml;
2065}
2066
2067function toolbarUrlConfigContainer() {
2068 var urlConfigContainer = document.createElement( "span" );
2069
2070 urlConfigContainer.innerHTML = getUrlConfigHtml();
2071
2072 // For oldIE support:
2073 // * Add handlers to the individual elements instead of the container
2074 // * Use "click" instead of "change" for checkboxes
2075 // * Fallback from event.target to event.srcElement
2076 addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", function( event ) {
2077 var params = {},
2078 target = event.target || event.srcElement;
2079 params[ target.name ] = target.checked ?
2080 target.defaultValue || true :
2081 undefined;
2082 window.location = QUnit.url( params );
2083 });
2084 addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", function( event ) {
2085 var params = {},
2086 target = event.target || event.srcElement;
2087 params[ target.name ] = target.options[ target.selectedIndex ].value || undefined;
2088 window.location = QUnit.url( params );
2089 });
2090
2091 return urlConfigContainer;
2092}
2093
2094function getModuleNames() {
2095 var i,
2096 moduleNames = [];
2097
2098 for ( i in config.modules ) {
2099 if ( config.modules.hasOwnProperty( i ) ) {
2100 moduleNames.push( i );
2101 }
2102 }
2103
2104 moduleNames.sort(function( a, b ) {
2105 return a.localeCompare( b );
2106 });
2107
2108 return moduleNames;
2109}
2110
2111function toolbarModuleFilterHtml() {
2112 var i,
2113 moduleFilterHtml = "",
2114 moduleNames = getModuleNames();
2115
2116 if ( moduleNames.length <= 1 ) {
2117 return false;
2118 }
2119
2120 moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
2121 "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
2122 ( config.module === undefined ? "selected='selected'" : "" ) +
2123 ">< All Modules ></option>";
2124
2125 for ( i = 0; i < moduleNames.length; i++ ) {
2126 moduleFilterHtml += "<option value='" +
2127 escapeText( encodeURIComponent( moduleNames[ i ] ) ) + "' " +
2128 ( config.module === moduleNames[ i ] ? "selected='selected'" : "" ) +
2129 ">" + escapeText( moduleNames[ i ] ) + "</option>";
2130 }
2131 moduleFilterHtml += "</select>";
2132
2133 return moduleFilterHtml;
2134}
2135
2136function toolbarModuleFilter() {
2137 var moduleFilter = document.createElement( "span" ),
2138 moduleFilterHtml = toolbarModuleFilterHtml();
2139
2140 if ( !moduleFilterHtml ) {
2141 return false;
2142 }
2143
2144 moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
2145 moduleFilter.innerHTML = moduleFilterHtml;
2146
2147 addEvent( moduleFilter.lastChild, "change", function() {
2148 var selectBox = moduleFilter.getElementsByTagName( "select" )[ 0 ],
2149 selectedModule = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value );
2150
2151 window.location = QUnit.url({
2152 module: ( selectedModule === "" ) ? undefined : selectedModule,
2153
2154 // Remove any existing filters
2155 filter: undefined,
2156 testNumber: undefined
2157 });
2158 });
2159
2160 return moduleFilter;
2161}
2162
2163function toolbarFilter() {
2164 var testList = id( "qunit-tests" ),
2165 filter = document.createElement( "input" );
2166
2167 filter.type = "checkbox";
2168 filter.id = "qunit-filter-pass";
2169
2170 addEvent( filter, "click", function() {
2171 if ( filter.checked ) {
2172 addClass( testList, "hidepass" );
2173 if ( defined.sessionStorage ) {
2174 sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
2175 }
2176 } else {
2177 removeClass( testList, "hidepass" );
2178 if ( defined.sessionStorage ) {
2179 sessionStorage.removeItem( "qunit-filter-passed-tests" );
2180 }
2181 }
2182 });
2183
2184 if ( config.hidepassed || defined.sessionStorage &&
2185 sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
2186 filter.checked = true;
2187
2188 addClass( testList, "hidepass" );
2189 }
2190
2191 return filter;
2192}
2193
2194function toolbarLabel() {
2195 var label = document.createElement( "label" );
2196 label.setAttribute( "for", "qunit-filter-pass" );
2197 label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." );
2198 label.innerHTML = "Hide passed tests";
2199
2200 return label;
2201}
2202
2203function appendToolbar() {
2204 var moduleFilter,
2205 toolbar = id( "qunit-testrunner-toolbar" );
2206
2207 if ( toolbar ) {
2208 toolbar.appendChild( toolbarFilter() );
2209 toolbar.appendChild( toolbarLabel() );
2210 toolbar.appendChild( toolbarUrlConfigContainer() );
2211
2212 moduleFilter = toolbarModuleFilter();
2213 if ( moduleFilter ) {
2214 toolbar.appendChild( moduleFilter );
2215 }
2216 }
2217}
2218
2219function appendBanner() {
2220 var banner = id( "qunit-banner" );
2221
2222 if ( banner ) {
2223 banner.className = "";
2224 banner.innerHTML = "<a href='" +
2225 QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) +
2226 "'>" + banner.innerHTML + "</a> ";
2227 }
2228}
2229
2230function appendTestResults() {
2231 var tests = id( "qunit-tests" ),
2232 result = id( "qunit-testresult" );
2233
2234 if ( result ) {
2235 result.parentNode.removeChild( result );
2236 }
2237
2238 if ( tests ) {
2239 tests.innerHTML = "";
2240 result = document.createElement( "p" );
2241 result.id = "qunit-testresult";
2242 result.className = "result";
2243 tests.parentNode.insertBefore( result, tests );
2244 result.innerHTML = "Running...<br>&nbsp;";
2245 }
2246}
2247
2248function storeFixture() {
2249 var fixture = id( "qunit-fixture" );
2250 if ( fixture ) {
2251 config.fixture = fixture.innerHTML;
2252 }
2253}
2254
2255function appendUserAgent() {
2256 var userAgent = id( "qunit-userAgent" );
2257 if ( userAgent ) {
2258 userAgent.innerHTML = navigator.userAgent;
2259 }
2260}
2261
2262// HTML Reporter initialization and load
2263QUnit.begin(function() {
2264 var qunit = id( "qunit" );
2265
2266 if ( qunit ) {
2267 qunit.innerHTML =
2268 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
2269 "<h2 id='qunit-banner'></h2>" +
2270 "<div id='qunit-testrunner-toolbar'></div>" +
2271 "<h2 id='qunit-userAgent'></h2>" +
2272 "<ol id='qunit-tests'></ol>";
2273 }
2274
2275 appendBanner();
2276 appendTestResults();
2277 appendUserAgent();
2278 appendToolbar();
2279 storeFixture();
2280});
2281
2282QUnit.done(function( details ) {
2283 var i, key,
2284 banner = id( "qunit-banner" ),
2285 tests = id( "qunit-tests" ),
2286 html = [
2287 "Tests completed in ",
2288 details.runtime,
2289 " milliseconds.<br>",
2290 "<span class='passed'>",
2291 details.passed,
2292 "</span> assertions of <span class='total'>",
2293 details.total,
2294 "</span> passed, <span class='failed'>",
2295 details.failed,
2296 "</span> failed."
2297 ].join( "" );
2298
2299 if ( banner ) {
2300 banner.className = details.failed ? "qunit-fail" : "qunit-pass";
2301 }
2302
2303 if ( tests ) {
2304 id( "qunit-testresult" ).innerHTML = html;
2305 }
2306
2307 if ( config.altertitle && defined.document && document.title ) {
2308
2309 // show ✖ for good, ✔ for bad suite result in title
2310 // use escape sequences in case file gets loaded with non-utf-8-charset
2311 document.title = [
2312 ( details.failed ? "\u2716" : "\u2714" ),
2313 document.title.replace( /^[\u2714\u2716] /i, "" )
2314 ].join( " " );
2315 }
2316
2317 // clear own sessionStorage items if all tests passed
2318 if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
2319 for ( i = 0; i < sessionStorage.length; i++ ) {
2320 key = sessionStorage.key( i++ );
2321 if ( key.indexOf( "qunit-test-" ) === 0 ) {
2322 sessionStorage.removeItem( key );
2323 }
2324 }
2325 }
2326
2327 // scroll back to top to show results
2328 if ( config.scrolltop && window.scrollTo ) {
2329 window.scrollTo( 0, 0 );
2330 }
2331});
2332
2333function getNameHtml( name, module ) {
2334 var nameHtml = "";
2335
2336 if ( module ) {
2337 nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
2338 }
2339
2340 nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
2341
2342 return nameHtml;
2343}
2344
2345QUnit.testStart(function( details ) {
2346 var a, b, li, running, assertList,
2347 name = getNameHtml( details.name, details.module ),
2348 tests = id( "qunit-tests" );
2349
2350 if ( tests ) {
2351 b = document.createElement( "strong" );
2352 b.innerHTML = name;
2353
2354 a = document.createElement( "a" );
2355 a.innerHTML = "Rerun";
2356 a.href = QUnit.url({ testNumber: details.testNumber });
2357
2358 li = document.createElement( "li" );
2359 li.appendChild( b );
2360 li.appendChild( a );
2361 li.className = "running";
2362 li.id = "qunit-test-output" + details.testNumber;
2363
2364 assertList = document.createElement( "ol" );
2365 assertList.className = "qunit-assert-list";
2366
2367 li.appendChild( assertList );
2368
2369 tests.appendChild( li );
2370 }
2371
2372 running = id( "qunit-testresult" );
2373 if ( running ) {
2374 running.innerHTML = "Running: <br>" + name;
2375 }
2376
2377});
2378
2379QUnit.log(function( details ) {
2380 var assertList, assertLi,
2381 message, expected, actual,
2382 testItem = id( "qunit-test-output" + details.testNumber );
2383
2384 if ( !testItem ) {
2385 return;
2386 }
2387
2388 message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
2389 message = "<span class='test-message'>" + message + "</span>";
2390
2391 // pushFailure doesn't provide details.expected
2392 // when it calls, it's implicit to also not show expected and diff stuff
2393 // Also, we need to check details.expected existence, as it can exist and be undefined
2394 if ( !details.result && hasOwn.call( details, "expected" ) ) {
2395 expected = escapeText( QUnit.dump.parse( details.expected ) );
2396 actual = escapeText( QUnit.dump.parse( details.actual ) );
2397 message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
2398 expected +
2399 "</pre></td></tr>";
2400
2401 if ( actual !== expected ) {
2402 message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
2403 actual + "</pre></td></tr>" +
2404 "<tr class='test-diff'><th>Diff: </th><td><pre>" +
2405 QUnit.diff( expected, actual ) + "</pre></td></tr>";
2406 }
2407
2408 if ( details.source ) {
2409 message += "<tr class='test-source'><th>Source: </th><td><pre>" +
2410 escapeText( details.source ) + "</pre></td></tr>";
2411 }
2412
2413 message += "</table>";
2414
2415 // this occours when pushFailure is set and we have an extracted stack trace
2416 } else if ( !details.result && details.source ) {
2417 message += "<table>" +
2418 "<tr class='test-source'><th>Source: </th><td><pre>" +
2419 escapeText( details.source ) + "</pre></td></tr>" +
2420 "</table>";
2421 }
2422
2423 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
2424
2425 assertLi = document.createElement( "li" );
2426 assertLi.className = details.result ? "pass" : "fail";
2427 assertLi.innerHTML = message;
2428 assertList.appendChild( assertLi );
2429});
2430
2431QUnit.testDone(function( details ) {
2432 var testTitle, time, testItem, assertList,
2433 good, bad, testCounts,
2434 tests = id( "qunit-tests" );
2435
2436 // QUnit.reset() is deprecated and will be replaced for a new
2437 // fixture reset function on QUnit 2.0/2.1.
2438 // It's still called here for backwards compatibility handling
2439 QUnit.reset();
2440
2441 if ( !tests ) {
2442 return;
2443 }
2444
2445 testItem = id( "qunit-test-output" + details.testNumber );
2446 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
2447
2448 good = details.passed;
2449 bad = details.failed;
2450
2451 // store result when possible
2452 if ( config.reorder && defined.sessionStorage ) {
2453 if ( bad ) {
2454 sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
2455 } else {
2456 sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
2457 }
2458 }
2459
2460 if ( bad === 0 ) {
2461 addClass( assertList, "qunit-collapsed" );
2462 }
2463
2464 // testItem.firstChild is the test name
2465 testTitle = testItem.firstChild;
2466
2467 testCounts = bad ?
2468 "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
2469 "";
2470
2471 testTitle.innerHTML += " <b class='counts'>(" + testCounts +
2472 details.assertions.length + ")</b>";
2473
2474 addEvent( testTitle, "click", function() {
2475 toggleClass( assertList, "qunit-collapsed" );
2476 });
2477
2478 time = document.createElement( "span" );
2479 time.className = "runtime";
2480 time.innerHTML = details.runtime + " ms";
2481
2482 testItem.className = bad ? "fail" : "pass";
2483
2484 testItem.insertBefore( time, assertList );
2485});
2486
2487if ( !defined.document || document.readyState === "complete" ) {
2488 config.autorun = true;
2489}
2490
2491if ( defined.document ) {
2492 addEvent( window, "load", QUnit.load );
2493}
2494
2495})();