Don't check namespace in SpecialWantedtemplates
[lhc/web/wiklou.git] / resources / lib / qunitjs / qunit.js
1 /*!
2 * QUnit 1.17.1
3 * http://qunitjs.com/
4 *
5 * Copyright jQuery Foundation and other contributors
6 * Released under the MIT license
7 * http://jquery.org/license
8 *
9 * Date: 2015-01-20T19:39Z
10 */
11
12 (function( window ) {
13
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();
25 },
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;
41 }
42 }())
43 },
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
48 *
49 * Based on http://es5.github.com/#x15.11.4.4
50 *
51 * @param {String|Error} error
52 * @return {String} error message
53 */
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";
68 }
69 } else {
70 return errorString;
71 }
72 },
73 /**
74 * Makes a clone of an object using only Array or Object as base,
75 * and copies over the own enumerable properties.
76 *
77 * @param {Object} obj
78 * @return {Object} New object with only the own properties (recursively).
79 */
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;
87 }
88 }
89 return vals;
90 };
91
92 QUnit = {};
93
94 /**
95 * Config object: Maintain internal state
96 * Later exposed as QUnit.config
97 * `config` initialized at top of scope
98 */
99 config = {
100 // The queue of tests to run
101 queue: [],
102
103 // block until document ready
104 blocking: true,
105
106 // by default, run previously failed tests first
107 // very useful in combination with "Hide passed tests" checked
108 reorder: true,
109
110 // by default, modify document.title when suite is done
111 altertitle: true,
112
113 // by default, scroll to top of the page when suite is done
114 scrolltop: true,
115
116 // when enabled, all tests must call expect()
117 requireExpects: false,
118
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: [
122 {
123 id: "hidepassed",
124 label: "Hide passed tests",
125 tooltip: "Only show tests and assertions that fail. Stored as query-strings."
126 },
127 {
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."
132 },
133 {
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."
138 }
139 ],
140
141 // Set of all modules.
142 modules: [],
143
144 // The first unnamed module
145 currentModule: {
146 name: "",
147 tests: []
148 },
149
150 callbacks: {}
151 };
152
153 // Push a loose unnamed module to the modules collection
154 config.modules.push( config.currentModule );
155
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 = {};
163
164 if ( params[ 0 ] ) {
165 for ( i = 0; i < length; i++ ) {
166 current = params[ i ].split( "=" );
167 current[ 0 ] = decodeURIComponent( current[ 0 ] );
168
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 ];
175 }
176 }
177 }
178
179 if ( urlParams.filter === true ) {
180 delete urlParams.filter;
181 }
182
183 QUnit.urlParams = urlParams;
184
185 // String search anywhere in moduleName+testName
186 config.filter = urlParams.filter;
187
188 config.testId = [];
189 if ( urlParams.testId ) {
190
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 ] );
195 }
196 }
197
198 // Figure out if we're running the tests from a server or not
199 QUnit.isLocal = location.protocol === "file:";
200 }());
201
202 // Root QUnit object.
203 // `QUnit` initialized at top of scope
204 extend( QUnit, {
205
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: []
212 };
213
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;
219 }
220 if ( testEnvironment && testEnvironment.teardown ) {
221 testEnvironment.afterEach = testEnvironment.teardown;
222 delete testEnvironment.teardown;
223 }
224
225 config.modules.push( currentModule );
226 config.currentModule = currentModule;
227 },
228
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;
234 }
235
236 QUnit.test( testName, expected, callback, true );
237 },
238
239 test: function( testName, expected, callback, async ) {
240 var test;
241
242 if ( arguments.length === 2 ) {
243 callback = expected;
244 expected = null;
245 }
246
247 test = new Test({
248 testName: testName,
249 expected: expected,
250 async: async,
251 callback: callback
252 });
253
254 test.queue();
255 },
256
257 skip: function( testName ) {
258 var test = new Test({
259 testName: testName,
260 skip: true
261 });
262
263 test.queue();
264 },
265
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;
270
271 if ( !config.current ) {
272 globalStartCalled = true;
273
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 ) {
282
283 // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
284 config.autostart = true;
285 return;
286 }
287 } else {
288
289 // If a test is running, adjust its semaphore
290 config.current.semaphore -= count || 1;
291
292 // Don't start until equal number of stop-calls
293 if ( config.current.semaphore > 0 ) {
294 return;
295 }
296
297 // throw an Error if start is called more often than stop
298 if ( config.current.semaphore < 0 ) {
299 config.current.semaphore = 0;
300
301 QUnit.pushFailure(
302 "Called start() while already started (test's semaphore was 0 already)",
303 sourceFromStacktrace( 2 )
304 );
305 return;
306 }
307 }
308
309 resumeProcessing();
310 },
311
312 // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
313 stop: function( count ) {
314
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" );
318 }
319
320 // If a test is running, adjust its semaphore
321 config.current.semaphore += count || 1;
322
323 pauseProcessing();
324 },
325
326 config: config,
327
328 // Safe object type checking
329 is: function( type, obj ) {
330 return QUnit.objectType( obj ) === type;
331 },
332
333 objectType: function( obj ) {
334 if ( typeof obj === "undefined" ) {
335 return "undefined";
336 }
337
338 // Consider: typeof null === object
339 if ( obj === null ) {
340 return "null";
341 }
342
343 var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
344 type = match && match[ 1 ] || "";
345
346 switch ( type ) {
347 case "Number":
348 if ( isNaN( obj ) ) {
349 return "nan";
350 }
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();
359 }
360 if ( typeof obj === "object" ) {
361 return "object";
362 }
363 return undefined;
364 },
365
366 extend: extend,
367
368 load: function() {
369 config.pageLoaded = true;
370
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 );
380
381 config.blocking = false;
382
383 if ( config.autostart ) {
384 resumeProcessing();
385 }
386 }
387 });
388
389 // Register logging callbacks
390 (function() {
391 var i, l, key,
392 callbacks = [ "begin", "done", "log", "testStart", "testDone",
393 "moduleStart", "moduleDone" ];
394
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."
400 );
401 }
402
403 config.callbacks[ key ].push( callback );
404 };
405
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;
410
411 return loggingCallback;
412 }
413
414 for ( i = 0, l = callbacks.length; i < l; i++ ) {
415 key = callbacks[ i ];
416
417 // Initialize key collection of logging callback
418 if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
419 config.callbacks[ key ] = [];
420 }
421
422 QUnit[ key ] = registerLoggingCallback( key );
423 }
424 })();
425
426 // `onErrorFnPrev` initialized at top of scope
427 // Preserve other handlers
428 onErrorFnPrev = window.onerror;
429
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 );
437 }
438
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;
445 }
446 QUnit.pushFailure( error, filePath + ":" + linerNr );
447 } else {
448 QUnit.test( "global failure", extend(function() {
449 QUnit.pushFailure( error, filePath + ":" + linerNr );
450 }, { validTest: true } ) );
451 }
452 return false;
453 }
454
455 return ret;
456 };
457
458 function done() {
459 var runtime, passed;
460
461 config.autorun = true;
462
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
472 });
473 }
474 delete config.previousModule;
475
476 runtime = now() - config.started;
477 passed = config.stats.all - config.stats.bad;
478
479 runLoggingCallbacks( "done", {
480 failed: config.stats.bad,
481 passed: passed,
482 total: config.stats.all,
483 runtime: runtime
484 });
485 }
486
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;
491
492 var stack, include, i;
493
494 if ( e.stacktrace ) {
495
496 // Opera 12.x
497 return e.stacktrace.split( "\n" )[ offset + 3 ];
498 } else if ( e.stack ) {
499
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();
504 }
505 if ( fileName ) {
506 include = [];
507 for ( i = offset; i < stack.length; i++ ) {
508 if ( stack[ i ].indexOf( fileName ) !== -1 ) {
509 break;
510 }
511 include.push( stack[ i ] );
512 }
513 if ( include.length ) {
514 return include.join( "\n" );
515 }
516 }
517 return stack[ offset ];
518 } else if ( e.sourceURL ) {
519
520 // Safari < 6
521 // exclude useless self-reference for generated Error objects
522 if ( /qunit.js$/.test( e.sourceURL ) ) {
523 return;
524 }
525
526 // for actual exceptions, this is useful
527 return e.sourceURL + ":" + e.line;
528 }
529 }
530
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;
539 }
540 }
541 return extractStacktrace( e, offset );
542 }
543
544 function synchronize( callback, last ) {
545 if ( QUnit.objectType( callback ) === "array" ) {
546 while ( callback.length ) {
547 synchronize( callback.shift() );
548 }
549 return;
550 }
551 config.queue.push( callback );
552
553 if ( config.autorun && !config.blocking ) {
554 process( last );
555 }
556 }
557
558 function process( last ) {
559 function next() {
560 process( last );
561 }
562 var start = now();
563 config.depth = ( config.depth || 0 ) + 1;
564
565 while ( config.queue.length && !config.blocking ) {
566 if ( !defined.setTimeout || config.updateRate <= 0 ||
567 ( ( now() - start ) < config.updateRate ) ) {
568 if ( config.current ) {
569
570 // Reset async tracking for each phase of the Test lifecycle
571 config.current.usedAsync = false;
572 }
573 config.queue.shift()();
574 } else {
575 setTimeout( next, 13 );
576 break;
577 }
578 }
579 config.depth--;
580 if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
581 done();
582 }
583 }
584
585 function begin() {
586 var i, l,
587 modulesLog = [];
588
589 // If the test run hasn't officially begun yet
590 if ( !config.started ) {
591
592 // Record the time of the test run's beginning
593 config.started = now();
594
595 verifyLoggingCallbacks();
596
597 // Delete the loose unnamed module if unused.
598 if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
599 config.modules.shift();
600 }
601
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
607 });
608 }
609
610 // The test run is officially beginning now
611 runLoggingCallbacks( "begin", {
612 totalTests: Test.count,
613 modules: modulesLog
614 });
615 }
616
617 config.blocking = false;
618 process( true );
619 }
620
621 function resumeProcessing() {
622 runStarted = true;
623
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;
629 }
630 if ( config.timeout ) {
631 clearTimeout( config.timeout );
632 }
633
634 begin();
635 }, 13 );
636 } else {
637 begin();
638 }
639 }
640
641 function pauseProcessing() {
642 config.blocking = true;
643
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" );
652 }
653 resumeProcessing();
654 }, config.testTimeout );
655 }
656 }
657
658 function saveGlobal() {
659 config.pollution = [];
660
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;
667 }
668 config.pollution.push( key );
669 }
670 }
671 }
672 }
673
674 function checkPollution() {
675 var newGlobals,
676 deletedGlobals,
677 old = config.pollution;
678
679 saveGlobal();
680
681 newGlobals = diff( config.pollution, old );
682 if ( newGlobals.length > 0 ) {
683 QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
684 }
685
686 deletedGlobals = diff( old, config.pollution );
687 if ( deletedGlobals.length > 0 ) {
688 QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
689 }
690 }
691
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();
696
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;
703 }
704 }
705 }
706 return result;
707 }
708
709 function extend( a, b, undefOnly ) {
710 for ( var prop in b ) {
711 if ( hasOwn.call( b, prop ) ) {
712
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 ];
719 }
720 }
721 }
722 }
723
724 return a;
725 }
726
727 function runLoggingCallbacks( key, args ) {
728 var i, l, callbacks;
729
730 callbacks = config.callbacks[ key ];
731 for ( i = 0, l = callbacks.length; i < l; i++ ) {
732 callbacks[ i ]( args );
733 }
734 }
735
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;
741
742 for ( loggingCallback in loggingCallbacks ) {
743 if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
744
745 userCallback = QUnit[ loggingCallback ];
746
747 // Restore the callback function
748 QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
749
750 // Assign the deprecated given callback
751 QUnit[ loggingCallback ]( userCallback );
752
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/"
758 );
759 }
760 }
761 }
762 }
763
764 // from jquery.js
765 function inArray( elem, array ) {
766 if ( array.indexOf ) {
767 return array.indexOf( elem );
768 }
769
770 for ( var i = 0, length = array.length; i < length; i++ ) {
771 if ( array[ i ] === elem ) {
772 return i;
773 }
774 }
775
776 return -1;
777 }
778
779 function Test( settings ) {
780 var i, l;
781
782 ++Test.count;
783
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 );
790
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 += " ";
795 }
796 }
797
798 this.testId = generateHash( this.module.name, this.testName );
799
800 this.module.tests.push({
801 name: this.testName,
802 testId: this.testId
803 });
804
805 if ( settings.skip ) {
806
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 );
813 }
814 }
815
816 Test.count = 0;
817
818 Test.prototype = {
819 before: function() {
820 if (
821
822 // Emit moduleStart when we're switching from one module to another
823 this.module !== config.previousModule ||
824
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" )
830 ) {
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
839 });
840 }
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
846 });
847 }
848
849 config.current = this;
850
851 this.testEnvironment = extend( {}, this.module.testEnvironment );
852 delete this.testEnvironment.beforeEach;
853 delete this.testEnvironment.afterEach;
854
855 this.started = now();
856 runLoggingCallbacks( "testStart", {
857 name: this.testName,
858 module: this.module.name,
859 testId: this.testId
860 });
861
862 if ( !config.pollution ) {
863 saveGlobal();
864 }
865 },
866
867 run: function() {
868 var promise;
869
870 config.current = this;
871
872 if ( this.async ) {
873 QUnit.stop();
874 }
875
876 this.callbackStarted = now();
877
878 if ( config.notrycatch ) {
879 promise = this.callback.call( this.testEnvironment, this.assert );
880 this.resolvePromise( promise );
881 return;
882 }
883
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 ) );
890
891 // else next test will carry the responsibility
892 saveGlobal();
893
894 // Restart the tests if they're blocking
895 if ( config.blocking ) {
896 QUnit.start();
897 }
898 }
899 },
900
901 after: function() {
902 checkPollution();
903 },
904
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;
914 }
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 ) );
921 }
922 };
923 },
924
925 // Currently only used for module level hooks, can be used to add global level ones
926 hooks: function( handler ) {
927 var hooks = [];
928
929 // Hooks are ignored on skipped tests
930 if ( this.skip ) {
931 return hooks;
932 }
933
934 if ( this.module.testEnvironment &&
935 QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
936 hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
937 }
938
939 return hooks;
940 },
941
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 );
953 }
954
955 var i,
956 bad = 0;
957
958 this.runtime = now() - this.started;
959 config.stats.all += this.assertions.length;
960 config.moduleStats.all += this.assertions.length;
961
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++;
967 }
968 }
969
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,
978
979 // HTML Reporter use
980 assertions: this.assertions,
981 testId: this.testId,
982
983 // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
984 duration: this.runtime
985 });
986
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();
991
992 config.current = undefined;
993 },
994
995 queue: function() {
996 var bad,
997 test = this;
998
999 if ( !this.valid() ) {
1000 return;
1001 }
1002
1003 function run() {
1004
1005 // each of these can by async
1006 synchronize([
1007 function() {
1008 test.before();
1009 },
1010
1011 test.hooks( "beforeEach" ),
1012
1013 function() {
1014 test.run();
1015 },
1016
1017 test.hooks( "afterEach" ).reverse(),
1018
1019 function() {
1020 test.after();
1021 },
1022 function() {
1023 test.finish();
1024 }
1025 ]);
1026 }
1027
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 );
1032
1033 if ( bad ) {
1034 run();
1035 } else {
1036 synchronize( run, true );
1037 }
1038 },
1039
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
1051 };
1052
1053 if ( !result ) {
1054 source = sourceFromStacktrace();
1055
1056 if ( source ) {
1057 details.source = source;
1058 }
1059 }
1060
1061 runLoggingCallbacks( "log", details );
1062
1063 this.assertions.push({
1064 result: !!result,
1065 message: message
1066 });
1067 },
1068
1069 pushFailure: function( message, source, actual ) {
1070 if ( !this instanceof Test ) {
1071 throw new Error( "pushFailure() assertion outside test context, was " +
1072 sourceFromStacktrace( 2 ) );
1073 }
1074
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
1083 };
1084
1085 if ( source ) {
1086 details.source = source;
1087 }
1088
1089 runLoggingCallbacks( "log", details );
1090
1091 this.assertions.push({
1092 result: false,
1093 message: message
1094 });
1095 },
1096
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 ) );
1112
1113 // else next test will carry the responsibility
1114 saveGlobal();
1115
1116 // Unblock
1117 QUnit.start();
1118 }
1119 );
1120 }
1121 }
1122 },
1123
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();
1129
1130 // Internally-generated tests are always valid
1131 if ( this.callback && this.callback.validTest ) {
1132 return true;
1133 }
1134
1135 if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
1136 return false;
1137 }
1138
1139 if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {
1140 return false;
1141 }
1142
1143 if ( !filter ) {
1144 return true;
1145 }
1146
1147 include = filter.charAt( 0 ) !== "!";
1148 if ( !include ) {
1149 filter = filter.toLowerCase().slice( 1 );
1150 }
1151
1152 // If the filter matches, we need to honour include
1153 if ( fullName.indexOf( filter ) !== -1 ) {
1154 return include;
1155 }
1156
1157 // Otherwise, do the opposite
1158 return !include;
1159 }
1160
1161 };
1162
1163 // Resets the test setup. Useful for tests that modify the DOM.
1164 /*
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
1168 */
1169 QUnit.reset = function() {
1170
1171 // Return on non-browser environments
1172 // This is necessary to not break on node tests
1173 if ( typeof window === "undefined" ) {
1174 return;
1175 }
1176
1177 var fixture = defined.document && document.getElementById &&
1178 document.getElementById( "qunit-fixture" );
1179
1180 if ( fixture ) {
1181 fixture.innerHTML = config.fixture;
1182 }
1183 };
1184
1185 QUnit.pushFailure = function() {
1186 if ( !QUnit.config.current ) {
1187 throw new Error( "pushFailure() assertion outside test context, in " +
1188 sourceFromStacktrace( 2 ) );
1189 }
1190
1191 // Gets current test obj
1192 var currentTest = QUnit.config.current;
1193
1194 return currentTest.pushFailure.apply( currentTest, arguments );
1195 };
1196
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;
1205
1206 for ( ; i < len; i++ ) {
1207 hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
1208 hash |= 0;
1209 }
1210
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;
1216 }
1217
1218 return hex.slice( -8 );
1219 }
1220
1221 function Assert( testContext ) {
1222 this.test = testContext;
1223 }
1224
1225 // Assert helpers
1226 QUnit.assert = Assert.prototype = {
1227
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;
1235 }
1236 },
1237
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;
1243
1244 test.semaphore += 1;
1245 test.usedAsync = true;
1246 pauseProcessing();
1247
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 ) );
1256 }
1257 };
1258 },
1259
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;
1264
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 ) );
1272 }
1273
1274 if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
1275 currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
1276 sourceFromStacktrace( 2 ) );
1277
1278 // Allow this assertion to continue running anyway...
1279 }
1280
1281 if ( !( assert instanceof Assert ) ) {
1282 assert = currentTest.assert;
1283 }
1284 return assert.test.push.apply( assert.test, arguments );
1285 },
1286
1287 /**
1288 * Asserts rough true-ish result.
1289 * @name ok
1290 * @function
1291 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
1292 */
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 );
1297 },
1298
1299 /**
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" );
1305 */
1306 equal: function( actual, expected, message ) {
1307 /*jshint eqeqeq:false */
1308 this.push( expected == actual, actual, expected, message );
1309 },
1310
1311 /**
1312 * @name notEqual
1313 * @function
1314 */
1315 notEqual: function( actual, expected, message ) {
1316 /*jshint eqeqeq:false */
1317 this.push( expected != actual, actual, expected, message );
1318 },
1319
1320 /**
1321 * @name propEqual
1322 * @function
1323 */
1324 propEqual: function( actual, expected, message ) {
1325 actual = objectValues( actual );
1326 expected = objectValues( expected );
1327 this.push( QUnit.equiv( actual, expected ), actual, expected, message );
1328 },
1329
1330 /**
1331 * @name notPropEqual
1332 * @function
1333 */
1334 notPropEqual: function( actual, expected, message ) {
1335 actual = objectValues( actual );
1336 expected = objectValues( expected );
1337 this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
1338 },
1339
1340 /**
1341 * @name deepEqual
1342 * @function
1343 */
1344 deepEqual: function( actual, expected, message ) {
1345 this.push( QUnit.equiv( actual, expected ), actual, expected, message );
1346 },
1347
1348 /**
1349 * @name notDeepEqual
1350 * @function
1351 */
1352 notDeepEqual: function( actual, expected, message ) {
1353 this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
1354 },
1355
1356 /**
1357 * @name strictEqual
1358 * @function
1359 */
1360 strictEqual: function( actual, expected, message ) {
1361 this.push( expected === actual, actual, expected, message );
1362 },
1363
1364 /**
1365 * @name notStrictEqual
1366 * @function
1367 */
1368 notStrictEqual: function( actual, expected, message ) {
1369 this.push( expected !== actual, actual, expected, message );
1370 },
1371
1372 "throws": function( block, expected, message ) {
1373 var actual, expectedType,
1374 expectedOutput = expected,
1375 ok = false;
1376
1377 // 'expected' is optional unless doing string comparison
1378 if ( message == null && typeof expected === "string" ) {
1379 message = expected;
1380 expected = null;
1381 }
1382
1383 this.test.ignoreGlobalErrors = true;
1384 try {
1385 block.call( this.test.testEnvironment );
1386 } catch (e) {
1387 actual = e;
1388 }
1389 this.test.ignoreGlobalErrors = false;
1390
1391 if ( actual ) {
1392 expectedType = QUnit.objectType( expected );
1393
1394 // we don't want to validate thrown error
1395 if ( !expected ) {
1396 ok = true;
1397 expectedOutput = null;
1398
1399 // expected is a regexp
1400 } else if ( expectedType === "regexp" ) {
1401 ok = expected.test( errorString( actual ) );
1402
1403 // expected is a string
1404 } else if ( expectedType === "string" ) {
1405 ok = expected === errorString( actual );
1406
1407 // expected is a constructor, maybe an Error constructor
1408 } else if ( expectedType === "function" && actual instanceof expected ) {
1409 ok = true;
1410
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;
1416
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;
1421 }
1422
1423 this.push( ok, actual, expectedOutput, message );
1424 } else {
1425 this.test.pushFailure( message, null, "No exception was thrown." );
1426 }
1427 }
1428 };
1429
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 }());
1436
1437 // Test for equality any JavaScript type.
1438 // Author: Philippe Rathé <prathe@gmail.com>
1439 QUnit.equiv = (function() {
1440
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
1449 }
1450 }
1451 }
1452
1453 // the real equiv function
1454 var innerEquiv,
1455
1456 // stack to decide between skip/abort functions
1457 callers = [],
1458
1459 // stack to avoiding loops from circular referencing
1460 parents = [],
1461 parentsB = [],
1462
1463 getProto = Object.getPrototypeOf || function( obj ) {
1464 /* jshint camelcase: false, proto: true */
1465 return obj.__proto__;
1466 },
1467 callbacks = (function() {
1468
1469 // for string, boolean, number and null
1470 function useStrictEquality( b, a ) {
1471
1472 /*jshint eqeqeq:false */
1473 if ( b instanceof a.constructor || a instanceof b.constructor ) {
1474
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;
1482 }
1483 }
1484
1485 return {
1486 "string": useStrictEquality,
1487 "boolean": useStrictEquality,
1488 "number": useStrictEquality,
1489 "null": useStrictEquality,
1490 "undefined": useStrictEquality,
1491
1492 "nan": function( b ) {
1493 return isNaN( b );
1494 },
1495
1496 "date": function( b, a ) {
1497 return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
1498 },
1499
1500 "regexp": function( b, a ) {
1501 return QUnit.objectType( b ) === "regexp" &&
1502
1503 // the regex itself
1504 a.source === b.source &&
1505
1506 // and its modifiers
1507 a.global === b.global &&
1508
1509 // (gmi) ...
1510 a.ignoreCase === b.ignoreCase &&
1511 a.multiline === b.multiline &&
1512 a.sticky === b.sticky;
1513 },
1514
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";
1521 },
1522
1523 "array": function( b, a ) {
1524 var i, j, len, loop, aCircular, bCircular;
1525
1526 // b could be an object literal here
1527 if ( QUnit.objectType( b ) !== "array" ) {
1528 return false;
1529 }
1530
1531 len = a.length;
1532 if ( len !== b.length ) {
1533 // safe and faster
1534 return false;
1535 }
1536
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;
1552 }
1553 }
1554 }
1555 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1556 parents.pop();
1557 parentsB.pop();
1558 return false;
1559 }
1560 }
1561 parents.pop();
1562 parentsB.pop();
1563 return true;
1564 },
1565
1566 "object": function( b, a ) {
1567
1568 /*jshint forin:false */
1569 var i, j, loop, aCircular, bCircular,
1570 // Default to true
1571 eq = true,
1572 aProperties = [],
1573 bProperties = [];
1574
1575 // comparing constructors is more strict than using
1576 // instanceof
1577 if ( a.constructor !== b.constructor ) {
1578
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;
1584 }
1585 }
1586
1587 // stack constructor before traversing properties
1588 callers.push( a.constructor );
1589
1590 // track reference to avoid circular references
1591 parents.push( a );
1592 parentsB.push( b );
1593
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;
1606 }
1607 }
1608 }
1609 aProperties.push( i );
1610 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1611 eq = false;
1612 break;
1613 }
1614 }
1615
1616 parents.pop();
1617 parentsB.pop();
1618 callers.pop(); // unstack, we are done
1619
1620 for ( i in b ) {
1621 bProperties.push( i ); // collect b's properties
1622 }
1623
1624 // Ensures identical properties name
1625 return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
1626 }
1627 };
1628 }());
1629
1630 innerEquiv = function() { // can take multiple arguments
1631 var args = [].slice.apply( arguments );
1632 if ( args.length < 2 ) {
1633 return true; // end transition
1634 }
1635
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 ) ) {
1642
1643 // don't lose time with error prone cases
1644 return false;
1645 } else {
1646 return bindCallbacks( a, callbacks, [ b, a ] );
1647 }
1648
1649 // apply transition with (1..n) arguments
1650 }( args[ 0 ], args[ 1 ] ) ) &&
1651 innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
1652 };
1653
1654 return innerEquiv;
1655 }());
1656
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, "\\\"" ) + "\"";
1662 }
1663 function literal( o ) {
1664 return o + "";
1665 }
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 );
1672 }
1673 if ( !arr ) {
1674 return pre + post;
1675 }
1676 return [ pre, inner + arr, base + post ].join( s );
1677 }
1678 function array( arr, stack ) {
1679 var i = arr.length,
1680 ret = new Array( i );
1681
1682 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1683 return "[object Array]";
1684 }
1685
1686 this.up();
1687 while ( i-- ) {
1688 ret[ i ] = this.parse( arr[ i ], undefined, stack );
1689 }
1690 this.down();
1691 return join( "[", ret, "]" );
1692 }
1693
1694 var reName = /^function (\w+)/,
1695 dump = {
1696
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 );
1702
1703 if ( inStack !== -1 ) {
1704 return "recursion(" + ( inStack - stack.length ) + ")";
1705 }
1706
1707 objType = objType || this.typeOf( obj );
1708 parser = this.parsers[ objType ];
1709 parserType = typeof parser;
1710
1711 if ( parserType === "function" ) {
1712 stack.push( obj );
1713 res = parser.call( this, obj, stack );
1714 stack.pop();
1715 return res;
1716 }
1717 return ( parserType === "string" ) ? parser : this.parsers.error;
1718 },
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 (
1740
1741 // native arrays
1742 toString.call( obj ) === "[object Array]" ||
1743
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 ) ) )
1748 ) {
1749 type = "array";
1750 } else if ( obj.constructor === Error.prototype.constructor ) {
1751 type = "error";
1752 } else {
1753 type = typeof obj;
1754 }
1755 return type;
1756 },
1757 separator: function() {
1758 return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
1759 },
1760 // extra can be a number, shortcut for increasing-calling-decreasing
1761 indent: function( extra ) {
1762 if ( !this.multiline ) {
1763 return "";
1764 }
1765 var chr = this.indentChar;
1766 if ( this.HTML ) {
1767 chr = chr.replace( /\t/g, " " ).replace( / /g, "&#160;" );
1768 }
1769 return new Array( this.depth + ( extra || 0 ) ).join( chr );
1770 },
1771 up: function( a ) {
1772 this.depth += a || 1;
1773 },
1774 down: function( a ) {
1775 this.depth -= a || 1;
1776 },
1777 setParser: function( name, parser ) {
1778 this.parsers[ name ] = parser;
1779 },
1780 // The next 3 are exposed so you can use them
1781 quote: quote,
1782 literal: literal,
1783 join: join,
1784 //
1785 depth: 1,
1786 maxDepth: 5,
1787
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 + "\")";
1794 },
1795 unknown: "[Unknown]",
1796 "null": "null",
1797 "undefined": "undefined",
1798 "function": function( fn ) {
1799 var ret = "function",
1800
1801 // functions never have name in IE
1802 name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
1803
1804 if ( name ) {
1805 ret += " " + name;
1806 }
1807 ret += "( ";
1808
1809 ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
1810 return join( ret, dump.parse( fn, "functionCode" ), "}" );
1811 },
1812 array: array,
1813 nodelist: array,
1814 "arguments": array,
1815 object: function( map, stack ) {
1816 var keys, key, val, i, nonEnumerableProperties,
1817 ret = [];
1818
1819 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1820 return "[object Object]";
1821 }
1822
1823 dump.up();
1824 keys = [];
1825 for ( key in map ) {
1826 keys.push( key );
1827 }
1828
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 );
1835 }
1836 }
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 ) );
1843 }
1844 dump.down();
1845 return join( "{", ret, "}" );
1846 },
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;
1854
1855 if ( attrs ) {
1856 for ( i = 0, len = attrs.length; i < len; i++ ) {
1857 val = attrs[ i ].nodeValue;
1858
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" );
1865 }
1866 }
1867 }
1868 ret += close;
1869
1870 // Show content of TextNode or CDATASection
1871 if ( node.nodeType === 3 || node.nodeType === 4 ) {
1872 ret += node.nodeValue;
1873 }
1874
1875 return ret + open + "/" + tag + close;
1876 },
1877
1878 // function calls it internally, it's the arguments part of the function
1879 functionArgs: function( fn ) {
1880 var args,
1881 l = fn.length;
1882
1883 if ( !l ) {
1884 return "";
1885 }
1886
1887 args = new Array( l );
1888 while ( l-- ) {
1889
1890 // 97 is 'a'
1891 args[ l ] = String.fromCharCode( 97 + l );
1892 }
1893 return " " + args.join( ", " ) + " ";
1894 },
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
1906 },
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
1913 };
1914
1915 return dump;
1916 }());
1917
1918 // back compat
1919 QUnit.jsDump = QUnit.dump;
1920
1921 // For browser, export only select globals
1922 if ( typeof window !== "undefined" ) {
1923
1924 // Deprecated
1925 // Extend assert methods to QUnit and Global scope through Backwards compatibility
1926 (function() {
1927 var i,
1928 assertions = Assert.prototype;
1929
1930 function applyCurrent( current ) {
1931 return function() {
1932 var assert = new Assert( QUnit.config.current );
1933 current.apply( assert, arguments );
1934 };
1935 }
1936
1937 for ( i in assertions ) {
1938 QUnit[ i ] = applyCurrent( assertions[ i ] );
1939 }
1940 })();
1941
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"
1961 ];
1962
1963 for ( i = 0, l = keys.length; i < l; i++ ) {
1964 window[ keys[ i ] ] = QUnit[ keys[ i ] ];
1965 }
1966 })();
1967
1968 window.QUnit = QUnit;
1969 }
1970
1971 // For nodejs
1972 if ( typeof module !== "undefined" && module && module.exports ) {
1973 module.exports = QUnit;
1974
1975 // For consistency with CommonJS environments' exports
1976 module.exports.QUnit = QUnit;
1977 }
1978
1979 // For CommonJS with exports, but without module.exports, like Rhino
1980 if ( typeof exports !== "undefined" && exports ) {
1981 exports.QUnit = QUnit;
1982 }
1983
1984 // Get a reference to the global object, like window in browsers
1985 }( (function() {
1986 return this;
1987 })() ));
1988
1989 /*istanbul ignore next */
1990 // jscs:disable maximumLineLength
1991 /*
1992 * Javascript Diff Algorithm
1993 * By John Resig (http://ejohn.org/)
1994 * Modified by Chu Alan "sprite"
1995 *
1996 * Released under the MIT license.
1997 *
1998 * More Info:
1999 * http://ejohn.org/projects/javascript-diff-algorithm/
2000 *
2001 * Usage: QUnit.diff(expected, actual)
2002 *
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"
2004 */
2005 QUnit.diff = (function() {
2006 var hasOwn = Object.prototype.hasOwnProperty;
2007
2008 /*jshint eqeqeq:false, eqnull:true */
2009 function diff( o, n ) {
2010 var i,
2011 ns = {},
2012 os = {};
2013
2014 for ( i = 0; i < n.length; i++ ) {
2015 if ( !hasOwn.call( ns, n[ i ] ) ) {
2016 ns[ n[ i ] ] = {
2017 rows: [],
2018 o: null
2019 };
2020 }
2021 ns[ n[ i ] ].rows.push( i );
2022 }
2023
2024 for ( i = 0; i < o.length; i++ ) {
2025 if ( !hasOwn.call( os, o[ i ] ) ) {
2026 os[ o[ i ] ] = {
2027 rows: [],
2028 n: null
2029 };
2030 }
2031 os[ o[ i ] ].rows.push( i );
2032 }
2033
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 ]
2040 };
2041 o[ os[ i ].rows[ 0 ] ] = {
2042 text: o[ os[ i ].rows[ 0 ] ],
2043 row: ns[ i ].rows[ 0 ]
2044 };
2045 }
2046 }
2047 }
2048
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 ] ) {
2052
2053 n[ i + 1 ] = {
2054 text: n[ i + 1 ],
2055 row: n[ i ].row + 1
2056 };
2057 o[ n[ i ].row + 1 ] = {
2058 text: o[ n[ i ].row + 1 ],
2059 row: i + 1
2060 };
2061 }
2062 }
2063
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 ] ) {
2067
2068 n[ i - 1 ] = {
2069 text: n[ i - 1 ],
2070 row: n[ i ].row - 1
2071 };
2072 o[ n[ i ].row - 1 ] = {
2073 text: o[ n[ i ].row - 1 ],
2074 row: i - 1
2075 };
2076 }
2077 }
2078
2079 return {
2080 o: o,
2081 n: n
2082 };
2083 }
2084
2085 return function( o, n ) {
2086 o = o.replace( /\s+$/, "" );
2087 n = n.replace( /\s+$/, "" );
2088
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 );
2094
2095 if ( oSpace == null ) {
2096 oSpace = [ " " ];
2097 } else {
2098 oSpace.push( " " );
2099 }
2100
2101 if ( nSpace == null ) {
2102 nSpace = [ " " ];
2103 } else {
2104 nSpace.push( " " );
2105 }
2106
2107 if ( out.n.length === 0 ) {
2108 for ( i = 0; i < out.o.length; i++ ) {
2109 str += "<del>" + out.o[ i ] + oSpace[ i ] + "</del>";
2110 }
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>";
2115 }
2116 }
2117
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 {
2122
2123 // `pre` initialized at top of scope
2124 pre = "";
2125
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>";
2128 }
2129 str += " " + out.n[ i ].text + nSpace[ i ] + pre;
2130 }
2131 }
2132 }
2133
2134 return str;
2135 };
2136 }());
2137 // jscs:enable
2138
2139 (function() {
2140
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;
2146
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 = [];
2156
2157 // Return on non-browser environments
2158 // This is necessary to not break on node tests
2159 if ( typeof window === "undefined" ) {
2160 return;
2161 }
2162
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>";
2171 }
2172
2173 tests = id( "qunit-tests" );
2174 banner = id( "qunit-banner" );
2175 result = id( "qunit-testresult" );
2176
2177 if ( tests ) {
2178 tests.innerHTML = "";
2179 }
2180
2181 if ( banner ) {
2182 banner.className = "";
2183 }
2184
2185 if ( result ) {
2186 result.parentNode.removeChild( result );
2187 }
2188
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;";
2195 }
2196 };
2197
2198 // Don't load the HTML Reporter on non-Browser environments
2199 if ( typeof window === "undefined" ) {
2200 return;
2201 }
2202
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;
2215 }
2216 }())
2217 },
2218 modulesList = [];
2219
2220 /**
2221 * Escape text for attribute or text content.
2222 */
2223 function escapeText( s ) {
2224 if ( !s ) {
2225 return "";
2226 }
2227 s = s + "";
2228
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;";
2242 }
2243 });
2244 }
2245
2246 /**
2247 * @param {HTMLElement} elem
2248 * @param {string} type
2249 * @param {Function} fn
2250 */
2251 function addEvent( elem, type, fn ) {
2252 if ( elem.addEventListener ) {
2253
2254 // Standards-based browsers
2255 elem.addEventListener( type, fn, false );
2256 } else if ( elem.attachEvent ) {
2257
2258 // support: IE <9
2259 elem.attachEvent( "on" + type, fn );
2260 }
2261 }
2262
2263 /**
2264 * @param {Array|NodeList} elems
2265 * @param {string} type
2266 * @param {Function} fn
2267 */
2268 function addEvents( elems, type, fn ) {
2269 var i = elems.length;
2270 while ( i-- ) {
2271 addEvent( elems[ i ], type, fn );
2272 }
2273 }
2274
2275 function hasClass( elem, name ) {
2276 return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
2277 }
2278
2279 function addClass( elem, name ) {
2280 if ( !hasClass( elem, name ) ) {
2281 elem.className += ( elem.className ? " " : "" ) + name;
2282 }
2283 }
2284
2285 function toggleClass( elem, name ) {
2286 if ( hasClass( elem, name ) ) {
2287 removeClass( elem, name );
2288 } else {
2289 addClass( elem, name );
2290 }
2291 }
2292
2293 function removeClass( elem, name ) {
2294 var set = " " + elem.className + " ";
2295
2296 // Class name may appear multiple times
2297 while ( set.indexOf( " " + name + " " ) >= 0 ) {
2298 set = set.replace( " " + name + " ", " " );
2299 }
2300
2301 // trim for prettiness
2302 elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
2303 }
2304
2305 function id( name ) {
2306 return defined.document && document.getElementById && document.getElementById( name );
2307 }
2308
2309 function getUrlConfigHtml() {
2310 var i, j, val,
2311 escaped, escapedTooltip,
2312 selection = false,
2313 len = config.urlConfig.length,
2314 urlConfigHtml = "";
2315
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
2322 };
2323 }
2324
2325 escaped = escapeText( val.id );
2326 escapedTooltip = escapeText( val.tooltip );
2327
2328 if ( config[ val.id ] === undefined ) {
2329 config[ val.id ] = QUnit.urlParams[ val.id ];
2330 }
2331
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>";
2344
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>";
2352 }
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>";
2360 }
2361 }
2362 }
2363 if ( config[ val.id ] && !selection ) {
2364 escaped = escapeText( config[ val.id ] );
2365 urlConfigHtml += "<option value='" + escaped +
2366 "' selected='selected' disabled='disabled'>" + escaped + "</option>";
2367 }
2368 urlConfigHtml += "</select>";
2369 }
2370 }
2371
2372 return urlConfigHtml;
2373 }
2374
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 = {};
2381
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;
2387 }
2388
2389 params[ field.name ] = value;
2390 updatedUrl = setUrl( params );
2391
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" );
2398 }
2399
2400 // It is not necessary to refresh the whole page
2401 window.history.replaceState( null, "", updatedUrl );
2402 } else {
2403 window.location = updatedUrl;
2404 }
2405 }
2406
2407 function setUrl( params ) {
2408 var key,
2409 querystring = "?";
2410
2411 params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
2412
2413 for ( key in params ) {
2414 if ( hasOwn.call( params, key ) ) {
2415 if ( params[ key ] === undefined ) {
2416 continue;
2417 }
2418 querystring += encodeURIComponent( key );
2419 if ( params[ key ] !== true ) {
2420 querystring += "=" + encodeURIComponent( params[ key ] );
2421 }
2422 querystring += "&";
2423 }
2424 }
2425 return location.protocol + "//" + location.host +
2426 location.pathname + querystring.slice( 0, -1 );
2427 }
2428
2429 function applyUrlParams() {
2430 var selectBox = id( "qunit-modulefilter" ),
2431 selection = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value ),
2432 filter = id( "qunit-filter-input" ).value;
2433
2434 window.location = setUrl({
2435 module: ( selection === "" ) ? undefined : selection,
2436 filter: ( filter === "" ) ? undefined : filter,
2437
2438 // Remove testId filter
2439 testId: undefined
2440 });
2441 }
2442
2443 function toolbarUrlConfigContainer() {
2444 var urlConfigContainer = document.createElement( "span" );
2445
2446 urlConfigContainer.innerHTML = getUrlConfigHtml();
2447 addClass( urlConfigContainer, "qunit-url-config" );
2448
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 );
2454
2455 return urlConfigContainer;
2456 }
2457
2458 function toolbarLooseFilter() {
2459 var filter = document.createElement( "form" ),
2460 label = document.createElement( "label" ),
2461 input = document.createElement( "input" ),
2462 button = document.createElement( "button" );
2463
2464 addClass( filter, "qunit-filter" );
2465
2466 label.innerHTML = "Filter: ";
2467
2468 input.type = "text";
2469 input.value = config.filter || "";
2470 input.name = "filter";
2471 input.id = "qunit-filter-input";
2472
2473 button.innerHTML = "Go";
2474
2475 label.appendChild( input );
2476
2477 filter.appendChild( label );
2478 filter.appendChild( button );
2479 addEvent( filter, "submit", function( ev ) {
2480 applyUrlParams();
2481
2482 if ( ev && ev.preventDefault ) {
2483 ev.preventDefault();
2484 }
2485
2486 return false;
2487 });
2488
2489 return filter;
2490 }
2491
2492 function toolbarModuleFilterHtml() {
2493 var i,
2494 moduleFilterHtml = "";
2495
2496 if ( !modulesList.length ) {
2497 return false;
2498 }
2499
2500 modulesList.sort(function( a, b ) {
2501 return a.localeCompare( b );
2502 });
2503
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>";
2508
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>";
2514 }
2515 moduleFilterHtml += "</select>";
2516
2517 return moduleFilterHtml;
2518 }
2519
2520 function toolbarModuleFilter() {
2521 var toolbar = id( "qunit-testrunner-toolbar" ),
2522 moduleFilter = document.createElement( "span" ),
2523 moduleFilterHtml = toolbarModuleFilterHtml();
2524
2525 if ( !toolbar || !moduleFilterHtml ) {
2526 return false;
2527 }
2528
2529 moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
2530 moduleFilter.innerHTML = moduleFilterHtml;
2531
2532 addEvent( moduleFilter.lastChild, "change", applyUrlParams );
2533
2534 toolbar.appendChild( moduleFilter );
2535 }
2536
2537 function appendToolbar() {
2538 var toolbar = id( "qunit-testrunner-toolbar" );
2539
2540 if ( toolbar ) {
2541 toolbar.appendChild( toolbarUrlConfigContainer() );
2542 toolbar.appendChild( toolbarLooseFilter() );
2543 }
2544 }
2545
2546 function appendHeader() {
2547 var header = id( "qunit-header" );
2548
2549 if ( header ) {
2550 header.innerHTML = "<a href='" +
2551 setUrl({ filter: undefined, module: undefined, testId: undefined }) +
2552 "'>" + header.innerHTML + "</a> ";
2553 }
2554 }
2555
2556 function appendBanner() {
2557 var banner = id( "qunit-banner" );
2558
2559 if ( banner ) {
2560 banner.className = "";
2561 }
2562 }
2563
2564 function appendTestResults() {
2565 var tests = id( "qunit-tests" ),
2566 result = id( "qunit-testresult" );
2567
2568 if ( result ) {
2569 result.parentNode.removeChild( result );
2570 }
2571
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;";
2579 }
2580 }
2581
2582 function storeFixture() {
2583 var fixture = id( "qunit-fixture" );
2584 if ( fixture ) {
2585 config.fixture = fixture.innerHTML;
2586 }
2587 }
2588
2589 function appendUserAgent() {
2590 var userAgent = id( "qunit-userAgent" );
2591 if ( userAgent ) {
2592 userAgent.innerHTML = "";
2593 userAgent.appendChild( document.createTextNode( navigator.userAgent ) );
2594 }
2595 }
2596
2597 function appendTestsList( modules ) {
2598 var i, l, x, z, test, moduleObj;
2599
2600 for ( i = 0, l = modules.length; i < l; i++ ) {
2601 moduleObj = modules[ i ];
2602
2603 if ( moduleObj.name ) {
2604 modulesList.push( moduleObj.name );
2605 }
2606
2607 for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
2608 test = moduleObj.tests[ x ];
2609
2610 appendTest( test.name, test.testId, moduleObj.name );
2611 }
2612 }
2613 }
2614
2615 function appendTest( name, testId, moduleName ) {
2616 var title, rerunTrigger, testBlock, assertList,
2617 tests = id( "qunit-tests" );
2618
2619 if ( !tests ) {
2620 return;
2621 }
2622
2623 title = document.createElement( "strong" );
2624 title.innerHTML = getNameHtml( name, moduleName );
2625
2626 rerunTrigger = document.createElement( "a" );
2627 rerunTrigger.innerHTML = "Rerun";
2628 rerunTrigger.href = setUrl({ testId: testId });
2629
2630 testBlock = document.createElement( "li" );
2631 testBlock.appendChild( title );
2632 testBlock.appendChild( rerunTrigger );
2633 testBlock.id = "qunit-test-output-" + testId;
2634
2635 assertList = document.createElement( "ol" );
2636 assertList.className = "qunit-assert-list";
2637
2638 testBlock.appendChild( assertList );
2639
2640 tests.appendChild( testBlock );
2641 }
2642
2643 // HTML Reporter initialization and load
2644 QUnit.begin(function( details ) {
2645 var qunit = id( "qunit" );
2646
2647 // Fixture is the only one necessary to run without the #qunit element
2648 storeFixture();
2649
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>";
2657 }
2658
2659 appendHeader();
2660 appendBanner();
2661 appendTestResults();
2662 appendUserAgent();
2663 appendToolbar();
2664 appendTestsList( details.modules );
2665 toolbarModuleFilter();
2666
2667 if ( qunit && config.hidepassed ) {
2668 addClass( qunit.lastChild, "hidepass" );
2669 }
2670 });
2671
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( "" );
2688
2689 if ( banner ) {
2690 banner.className = details.failed ? "qunit-fail" : "qunit-pass";
2691 }
2692
2693 if ( tests ) {
2694 id( "qunit-testresult" ).innerHTML = html;
2695 }
2696
2697 if ( config.altertitle && defined.document && document.title ) {
2698
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( " " );
2705 }
2706
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 );
2713 }
2714 }
2715 }
2716
2717 // scroll back to top to show results
2718 if ( config.scrolltop && window.scrollTo ) {
2719 window.scrollTo( 0, 0 );
2720 }
2721 });
2722
2723 function getNameHtml( name, module ) {
2724 var nameHtml = "";
2725
2726 if ( module ) {
2727 nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
2728 }
2729
2730 nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
2731
2732 return nameHtml;
2733 }
2734
2735 QUnit.testStart(function( details ) {
2736 var running, testBlock;
2737
2738 testBlock = id( "qunit-test-output-" + details.testId );
2739 if ( testBlock ) {
2740 testBlock.className = "running";
2741 } else {
2742
2743 // Report later registered tests
2744 appendTest( details.name, details.testId, details.module );
2745 }
2746
2747 running = id( "qunit-testresult" );
2748 if ( running ) {
2749 running.innerHTML = "Running: <br />" + getNameHtml( details.name, details.module );
2750 }
2751
2752 });
2753
2754 QUnit.log(function( details ) {
2755 var assertList, assertLi,
2756 message, expected, actual,
2757 testItem = id( "qunit-test-output-" + details.testId );
2758
2759 if ( !testItem ) {
2760 return;
2761 }
2762
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>";
2766
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>";
2776
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>";
2782 }
2783
2784 if ( details.source ) {
2785 message += "<tr class='test-source'><th>Source: </th><td><pre>" +
2786 escapeText( details.source ) + "</pre></td></tr>";
2787 }
2788
2789 message += "</table>";
2790
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>";
2797 }
2798
2799 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
2800
2801 assertLi = document.createElement( "li" );
2802 assertLi.className = details.result ? "pass" : "fail";
2803 assertLi.innerHTML = message;
2804 assertList.appendChild( assertLi );
2805 });
2806
2807 QUnit.testDone(function( details ) {
2808 var testTitle, time, testItem, assertList,
2809 good, bad, testCounts, skipped,
2810 tests = id( "qunit-tests" );
2811
2812 if ( !tests ) {
2813 return;
2814 }
2815
2816 testItem = id( "qunit-test-output-" + details.testId );
2817
2818 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
2819
2820 good = details.passed;
2821 bad = details.failed;
2822
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 );
2829 }
2830 }
2831
2832 if ( bad === 0 ) {
2833 addClass( assertList, "qunit-collapsed" );
2834 }
2835
2836 // testItem.firstChild is the test name
2837 testTitle = testItem.firstChild;
2838
2839 testCounts = bad ?
2840 "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
2841 "";
2842
2843 testTitle.innerHTML += " <b class='counts'>(" + testCounts +
2844 details.assertions.length + ")</b>";
2845
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" );
2855 });
2856
2857 testItem.className = bad ? "fail" : "pass";
2858
2859 time = document.createElement( "span" );
2860 time.className = "runtime";
2861 time.innerHTML = details.runtime + " ms";
2862 testItem.insertBefore( time, assertList );
2863 }
2864 });
2865
2866 if ( !defined.document || document.readyState === "complete" ) {
2867 config.pageLoaded = true;
2868 config.autorun = true;
2869 }
2870
2871 if ( defined.document ) {
2872 addEvent( window, "load", QUnit.load );
2873 }
2874
2875 })();