Merge "Generate valid HTML code on error pages"
[lhc/web/wiklou.git] / resources / lib / jquery / jquery.qunit.js
1 /*!
2 * QUnit 1.16.0
3 * http://qunitjs.com/
4 *
5 * Copyright 2006, 2014 jQuery Foundation and other contributors
6 * Released under the MIT license
7 * http://jquery.org/license
8 *
9 * Date: 2014-12-03T16:32Z
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 // when enabled, show only failing tests
107 // gets persisted through sessionStorage and can be changed in UI via checkbox
108 hidepassed: false,
109
110 // by default, run previously failed tests first
111 // very useful in combination with "Hide passed tests" checked
112 reorder: true,
113
114 // by default, modify document.title when suite is done
115 altertitle: true,
116
117 // by default, scroll to top of the page when suite is done
118 scrolltop: true,
119
120 // when enabled, all tests must call expect()
121 requireExpects: false,
122
123 // add checkboxes that are persisted in the query-string
124 // when enabled, the id is set to `true` as a `QUnit.config` property
125 urlConfig: [
126 {
127 id: "hidepassed",
128 label: "Hide passed tests",
129 tooltip: "Only show tests and assertions that fail. Stored as query-strings."
130 },
131 {
132 id: "noglobals",
133 label: "Check for Globals",
134 tooltip: "Enabling this will test if any test introduces new properties on the " +
135 "`window` object. Stored as query-strings."
136 },
137 {
138 id: "notrycatch",
139 label: "No try-catch",
140 tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
141 "exceptions in IE reasonable. Stored as query-strings."
142 }
143 ],
144
145 // Set of all modules.
146 modules: [],
147
148 // The first unnamed module
149 currentModule: {
150 name: "",
151 tests: []
152 },
153
154 callbacks: {}
155 };
156
157 // Push a loose unnamed module to the modules collection
158 config.modules.push( config.currentModule );
159
160 // Initialize more QUnit.config and QUnit.urlParams
161 (function() {
162 var i, current,
163 location = window.location || { search: "", protocol: "file:" },
164 params = location.search.slice( 1 ).split( "&" ),
165 length = params.length,
166 urlParams = {};
167
168 if ( params[ 0 ] ) {
169 for ( i = 0; i < length; i++ ) {
170 current = params[ i ].split( "=" );
171 current[ 0 ] = decodeURIComponent( current[ 0 ] );
172
173 // allow just a key to turn on a flag, e.g., test.html?noglobals
174 current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
175 if ( urlParams[ current[ 0 ] ] ) {
176 urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );
177 } else {
178 urlParams[ current[ 0 ] ] = current[ 1 ];
179 }
180 }
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 url: function( params ) {
367 params = extend( extend( {}, QUnit.urlParams ), params );
368 var key,
369 querystring = "?";
370
371 for ( key in params ) {
372 if ( hasOwn.call( params, key ) ) {
373 querystring += encodeURIComponent( key );
374 if ( params[ key ] !== true ) {
375 querystring += "=" + encodeURIComponent( params[ key ] );
376 }
377 querystring += "&";
378 }
379 }
380 return location.protocol + "//" + location.host +
381 location.pathname + querystring.slice( 0, -1 );
382 },
383
384 extend: extend,
385
386 load: function() {
387 config.pageLoaded = true;
388
389 // Initialize the configuration options
390 extend( config, {
391 stats: { all: 0, bad: 0 },
392 moduleStats: { all: 0, bad: 0 },
393 started: 0,
394 updateRate: 1000,
395 autostart: true,
396 filter: ""
397 }, true );
398
399 config.blocking = false;
400
401 if ( config.autostart ) {
402 resumeProcessing();
403 }
404 }
405 });
406
407 // Register logging callbacks
408 (function() {
409 var i, l, key,
410 callbacks = [ "begin", "done", "log", "testStart", "testDone",
411 "moduleStart", "moduleDone" ];
412
413 function registerLoggingCallback( key ) {
414 var loggingCallback = function( callback ) {
415 if ( QUnit.objectType( callback ) !== "function" ) {
416 throw new Error(
417 "QUnit logging methods require a callback function as their first parameters."
418 );
419 }
420
421 config.callbacks[ key ].push( callback );
422 };
423
424 // DEPRECATED: This will be removed on QUnit 2.0.0+
425 // Stores the registered functions allowing restoring
426 // at verifyLoggingCallbacks() if modified
427 loggingCallbacks[ key ] = loggingCallback;
428
429 return loggingCallback;
430 }
431
432 for ( i = 0, l = callbacks.length; i < l; i++ ) {
433 key = callbacks[ i ];
434
435 // Initialize key collection of logging callback
436 if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
437 config.callbacks[ key ] = [];
438 }
439
440 QUnit[ key ] = registerLoggingCallback( key );
441 }
442 })();
443
444 // `onErrorFnPrev` initialized at top of scope
445 // Preserve other handlers
446 onErrorFnPrev = window.onerror;
447
448 // Cover uncaught exceptions
449 // Returning true will suppress the default browser handler,
450 // returning false will let it run.
451 window.onerror = function( error, filePath, linerNr ) {
452 var ret = false;
453 if ( onErrorFnPrev ) {
454 ret = onErrorFnPrev( error, filePath, linerNr );
455 }
456
457 // Treat return value as window.onerror itself does,
458 // Only do our handling if not suppressed.
459 if ( ret !== true ) {
460 if ( QUnit.config.current ) {
461 if ( QUnit.config.current.ignoreGlobalErrors ) {
462 return true;
463 }
464 QUnit.pushFailure( error, filePath + ":" + linerNr );
465 } else {
466 QUnit.test( "global failure", extend(function() {
467 QUnit.pushFailure( error, filePath + ":" + linerNr );
468 }, { validTest: true } ) );
469 }
470 return false;
471 }
472
473 return ret;
474 };
475
476 function done() {
477 var runtime, passed;
478
479 config.autorun = true;
480
481 // Log the last module results
482 if ( config.previousModule ) {
483 runLoggingCallbacks( "moduleDone", {
484 name: config.previousModule.name,
485 tests: config.previousModule.tests,
486 failed: config.moduleStats.bad,
487 passed: config.moduleStats.all - config.moduleStats.bad,
488 total: config.moduleStats.all,
489 runtime: now() - config.moduleStats.started
490 });
491 }
492 delete config.previousModule;
493
494 runtime = now() - config.started;
495 passed = config.stats.all - config.stats.bad;
496
497 runLoggingCallbacks( "done", {
498 failed: config.stats.bad,
499 passed: passed,
500 total: config.stats.all,
501 runtime: runtime
502 });
503 }
504
505 // Doesn't support IE6 to IE9
506 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
507 function extractStacktrace( e, offset ) {
508 offset = offset === undefined ? 4 : offset;
509
510 var stack, include, i;
511
512 if ( e.stacktrace ) {
513
514 // Opera 12.x
515 return e.stacktrace.split( "\n" )[ offset + 3 ];
516 } else if ( e.stack ) {
517
518 // Firefox, Chrome, Safari 6+, IE10+, PhantomJS and Node
519 stack = e.stack.split( "\n" );
520 if ( /^error$/i.test( stack[ 0 ] ) ) {
521 stack.shift();
522 }
523 if ( fileName ) {
524 include = [];
525 for ( i = offset; i < stack.length; i++ ) {
526 if ( stack[ i ].indexOf( fileName ) !== -1 ) {
527 break;
528 }
529 include.push( stack[ i ] );
530 }
531 if ( include.length ) {
532 return include.join( "\n" );
533 }
534 }
535 return stack[ offset ];
536 } else if ( e.sourceURL ) {
537
538 // Safari < 6
539 // exclude useless self-reference for generated Error objects
540 if ( /qunit.js$/.test( e.sourceURL ) ) {
541 return;
542 }
543
544 // for actual exceptions, this is useful
545 return e.sourceURL + ":" + e.line;
546 }
547 }
548
549 function sourceFromStacktrace( offset ) {
550 var e = new Error();
551 if ( !e.stack ) {
552 try {
553 throw e;
554 } catch ( err ) {
555 // This should already be true in most browsers
556 e = err;
557 }
558 }
559 return extractStacktrace( e, offset );
560 }
561
562 function synchronize( callback, last ) {
563 if ( QUnit.objectType( callback ) === "array" ) {
564 while ( callback.length ) {
565 synchronize( callback.shift() );
566 }
567 return;
568 }
569 config.queue.push( callback );
570
571 if ( config.autorun && !config.blocking ) {
572 process( last );
573 }
574 }
575
576 function process( last ) {
577 function next() {
578 process( last );
579 }
580 var start = now();
581 config.depth = config.depth ? config.depth + 1 : 1;
582
583 while ( config.queue.length && !config.blocking ) {
584 if ( !defined.setTimeout || config.updateRate <= 0 ||
585 ( ( now() - start ) < config.updateRate ) ) {
586 if ( config.current ) {
587
588 // Reset async tracking for each phase of the Test lifecycle
589 config.current.usedAsync = false;
590 }
591 config.queue.shift()();
592 } else {
593 setTimeout( next, 13 );
594 break;
595 }
596 }
597 config.depth--;
598 if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
599 done();
600 }
601 }
602
603 function begin() {
604 var i, l,
605 modulesLog = [];
606
607 // If the test run hasn't officially begun yet
608 if ( !config.started ) {
609
610 // Record the time of the test run's beginning
611 config.started = now();
612
613 verifyLoggingCallbacks();
614
615 // Delete the loose unnamed module if unused.
616 if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
617 config.modules.shift();
618 }
619
620 // Avoid unnecessary information by not logging modules' test environments
621 for ( i = 0, l = config.modules.length; i < l; i++ ) {
622 modulesLog.push({
623 name: config.modules[ i ].name,
624 tests: config.modules[ i ].tests
625 });
626 }
627
628 // The test run is officially beginning now
629 runLoggingCallbacks( "begin", {
630 totalTests: Test.count,
631 modules: modulesLog
632 });
633 }
634
635 config.blocking = false;
636 process( true );
637 }
638
639 function resumeProcessing() {
640 runStarted = true;
641
642 // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
643 if ( defined.setTimeout ) {
644 setTimeout(function() {
645 if ( config.current && config.current.semaphore > 0 ) {
646 return;
647 }
648 if ( config.timeout ) {
649 clearTimeout( config.timeout );
650 }
651
652 begin();
653 }, 13 );
654 } else {
655 begin();
656 }
657 }
658
659 function pauseProcessing() {
660 config.blocking = true;
661
662 if ( config.testTimeout && defined.setTimeout ) {
663 clearTimeout( config.timeout );
664 config.timeout = setTimeout(function() {
665 if ( config.current ) {
666 config.current.semaphore = 0;
667 QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
668 } else {
669 throw new Error( "Test timed out" );
670 }
671 resumeProcessing();
672 }, config.testTimeout );
673 }
674 }
675
676 function saveGlobal() {
677 config.pollution = [];
678
679 if ( config.noglobals ) {
680 for ( var key in window ) {
681 if ( hasOwn.call( window, key ) ) {
682 // in Opera sometimes DOM element ids show up here, ignore them
683 if ( /^qunit-test-output/.test( key ) ) {
684 continue;
685 }
686 config.pollution.push( key );
687 }
688 }
689 }
690 }
691
692 function checkPollution() {
693 var newGlobals,
694 deletedGlobals,
695 old = config.pollution;
696
697 saveGlobal();
698
699 newGlobals = diff( config.pollution, old );
700 if ( newGlobals.length > 0 ) {
701 QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
702 }
703
704 deletedGlobals = diff( old, config.pollution );
705 if ( deletedGlobals.length > 0 ) {
706 QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
707 }
708 }
709
710 // returns a new Array with the elements that are in a but not in b
711 function diff( a, b ) {
712 var i, j,
713 result = a.slice();
714
715 for ( i = 0; i < result.length; i++ ) {
716 for ( j = 0; j < b.length; j++ ) {
717 if ( result[ i ] === b[ j ] ) {
718 result.splice( i, 1 );
719 i--;
720 break;
721 }
722 }
723 }
724 return result;
725 }
726
727 function extend( a, b, undefOnly ) {
728 for ( var prop in b ) {
729 if ( hasOwn.call( b, prop ) ) {
730
731 // Avoid "Member not found" error in IE8 caused by messing with window.constructor
732 if ( !( prop === "constructor" && a === window ) ) {
733 if ( b[ prop ] === undefined ) {
734 delete a[ prop ];
735 } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
736 a[ prop ] = b[ prop ];
737 }
738 }
739 }
740 }
741
742 return a;
743 }
744
745 function runLoggingCallbacks( key, args ) {
746 var i, l, callbacks;
747
748 callbacks = config.callbacks[ key ];
749 for ( i = 0, l = callbacks.length; i < l; i++ ) {
750 callbacks[ i ]( args );
751 }
752 }
753
754 // DEPRECATED: This will be removed on 2.0.0+
755 // This function verifies if the loggingCallbacks were modified by the user
756 // If so, it will restore it, assign the given callback and print a console warning
757 function verifyLoggingCallbacks() {
758 var loggingCallback, userCallback;
759
760 for ( loggingCallback in loggingCallbacks ) {
761 if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
762
763 userCallback = QUnit[ loggingCallback ];
764
765 // Restore the callback function
766 QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
767
768 // Assign the deprecated given callback
769 QUnit[ loggingCallback ]( userCallback );
770
771 if ( window.console && window.console.warn ) {
772 window.console.warn(
773 "QUnit." + loggingCallback + " was replaced with a new value.\n" +
774 "Please, check out the documentation on how to apply logging callbacks.\n" +
775 "Reference: http://api.qunitjs.com/category/callbacks/"
776 );
777 }
778 }
779 }
780 }
781
782 // from jquery.js
783 function inArray( elem, array ) {
784 if ( array.indexOf ) {
785 return array.indexOf( elem );
786 }
787
788 for ( var i = 0, length = array.length; i < length; i++ ) {
789 if ( array[ i ] === elem ) {
790 return i;
791 }
792 }
793
794 return -1;
795 }
796
797 function Test( settings ) {
798 var i, l;
799
800 ++Test.count;
801
802 extend( this, settings );
803 this.assertions = [];
804 this.semaphore = 0;
805 this.usedAsync = false;
806 this.module = config.currentModule;
807 this.stack = sourceFromStacktrace( 3 );
808
809 // Register unique strings
810 for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
811 if ( this.module.tests[ i ].name === this.testName ) {
812 this.testName += " ";
813 }
814 }
815
816 this.testId = generateHash( this.module.name, this.testName );
817
818 this.module.tests.push({
819 name: this.testName,
820 testId: this.testId
821 });
822
823 if ( settings.skip ) {
824
825 // Skipped tests will fully ignore any sent callback
826 this.callback = function() {};
827 this.async = false;
828 this.expected = 0;
829 } else {
830 this.assert = new Assert( this );
831 }
832 }
833
834 Test.count = 0;
835
836 Test.prototype = {
837 before: function() {
838 if (
839
840 // Emit moduleStart when we're switching from one module to another
841 this.module !== config.previousModule ||
842
843 // They could be equal (both undefined) but if the previousModule property doesn't
844 // yet exist it means this is the first test in a suite that isn't wrapped in a
845 // module, in which case we'll just emit a moduleStart event for 'undefined'.
846 // Without this, reporters can get testStart before moduleStart which is a problem.
847 !hasOwn.call( config, "previousModule" )
848 ) {
849 if ( hasOwn.call( config, "previousModule" ) ) {
850 runLoggingCallbacks( "moduleDone", {
851 name: config.previousModule.name,
852 tests: config.previousModule.tests,
853 failed: config.moduleStats.bad,
854 passed: config.moduleStats.all - config.moduleStats.bad,
855 total: config.moduleStats.all,
856 runtime: now() - config.moduleStats.started
857 });
858 }
859 config.previousModule = this.module;
860 config.moduleStats = { all: 0, bad: 0, started: now() };
861 runLoggingCallbacks( "moduleStart", {
862 name: this.module.name,
863 tests: this.module.tests
864 });
865 }
866
867 config.current = this;
868
869 this.testEnvironment = extend( {}, this.module.testEnvironment );
870 delete this.testEnvironment.beforeEach;
871 delete this.testEnvironment.afterEach;
872
873 this.started = now();
874 runLoggingCallbacks( "testStart", {
875 name: this.testName,
876 module: this.module.name,
877 testId: this.testId
878 });
879
880 if ( !config.pollution ) {
881 saveGlobal();
882 }
883 },
884
885 run: function() {
886 var promise;
887
888 config.current = this;
889
890 if ( this.async ) {
891 QUnit.stop();
892 }
893
894 this.callbackStarted = now();
895
896 if ( config.notrycatch ) {
897 promise = this.callback.call( this.testEnvironment, this.assert );
898 this.resolvePromise( promise );
899 return;
900 }
901
902 try {
903 promise = this.callback.call( this.testEnvironment, this.assert );
904 this.resolvePromise( promise );
905 } catch ( e ) {
906 this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
907 this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
908
909 // else next test will carry the responsibility
910 saveGlobal();
911
912 // Restart the tests if they're blocking
913 if ( config.blocking ) {
914 QUnit.start();
915 }
916 }
917 },
918
919 after: function() {
920 checkPollution();
921 },
922
923 queueHook: function( hook, hookName ) {
924 var promise,
925 test = this;
926 return function runHook() {
927 config.current = test;
928 if ( config.notrycatch ) {
929 promise = hook.call( test.testEnvironment, test.assert );
930 test.resolvePromise( promise, hookName );
931 return;
932 }
933 try {
934 promise = hook.call( test.testEnvironment, test.assert );
935 test.resolvePromise( promise, hookName );
936 } catch ( error ) {
937 test.pushFailure( hookName + " failed on " + test.testName + ": " +
938 ( error.message || error ), extractStacktrace( error, 0 ) );
939 }
940 };
941 },
942
943 // Currently only used for module level hooks, can be used to add global level ones
944 hooks: function( handler ) {
945 var hooks = [];
946
947 // Hooks are ignored on skipped tests
948 if ( this.skip ) {
949 return hooks;
950 }
951
952 if ( this.module.testEnvironment &&
953 QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
954 hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
955 }
956
957 return hooks;
958 },
959
960 finish: function() {
961 config.current = this;
962 if ( config.requireExpects && this.expected === null ) {
963 this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
964 "not called.", this.stack );
965 } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
966 this.pushFailure( "Expected " + this.expected + " assertions, but " +
967 this.assertions.length + " were run", this.stack );
968 } else if ( this.expected === null && !this.assertions.length ) {
969 this.pushFailure( "Expected at least one assertion, but none were run - call " +
970 "expect(0) to accept zero assertions.", this.stack );
971 }
972
973 var i,
974 bad = 0;
975
976 this.runtime = now() - this.started;
977 config.stats.all += this.assertions.length;
978 config.moduleStats.all += this.assertions.length;
979
980 for ( i = 0; i < this.assertions.length; i++ ) {
981 if ( !this.assertions[ i ].result ) {
982 bad++;
983 config.stats.bad++;
984 config.moduleStats.bad++;
985 }
986 }
987
988 runLoggingCallbacks( "testDone", {
989 name: this.testName,
990 module: this.module.name,
991 skipped: !!this.skip,
992 failed: bad,
993 passed: this.assertions.length - bad,
994 total: this.assertions.length,
995 runtime: this.runtime,
996
997 // HTML Reporter use
998 assertions: this.assertions,
999 testId: this.testId,
1000
1001 // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
1002 duration: this.runtime
1003 });
1004
1005 // QUnit.reset() is deprecated and will be replaced for a new
1006 // fixture reset function on QUnit 2.0/2.1.
1007 // It's still called here for backwards compatibility handling
1008 QUnit.reset();
1009
1010 config.current = undefined;
1011 },
1012
1013 queue: function() {
1014 var bad,
1015 test = this;
1016
1017 if ( !this.valid() ) {
1018 return;
1019 }
1020
1021 function run() {
1022
1023 // each of these can by async
1024 synchronize([
1025 function() {
1026 test.before();
1027 },
1028
1029 test.hooks( "beforeEach" ),
1030
1031 function() {
1032 test.run();
1033 },
1034
1035 test.hooks( "afterEach" ).reverse(),
1036
1037 function() {
1038 test.after();
1039 },
1040 function() {
1041 test.finish();
1042 }
1043 ]);
1044 }
1045
1046 // `bad` initialized at top of scope
1047 // defer when previous test run passed, if storage is available
1048 bad = QUnit.config.reorder && defined.sessionStorage &&
1049 +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
1050
1051 if ( bad ) {
1052 run();
1053 } else {
1054 synchronize( run, true );
1055 }
1056 },
1057
1058 push: function( result, actual, expected, message ) {
1059 var source,
1060 details = {
1061 module: this.module.name,
1062 name: this.testName,
1063 result: result,
1064 message: message,
1065 actual: actual,
1066 expected: expected,
1067 testId: this.testId,
1068 runtime: now() - this.started
1069 };
1070
1071 if ( !result ) {
1072 source = sourceFromStacktrace();
1073
1074 if ( source ) {
1075 details.source = source;
1076 }
1077 }
1078
1079 runLoggingCallbacks( "log", details );
1080
1081 this.assertions.push({
1082 result: !!result,
1083 message: message
1084 });
1085 },
1086
1087 pushFailure: function( message, source, actual ) {
1088 if ( !this instanceof Test ) {
1089 throw new Error( "pushFailure() assertion outside test context, was " +
1090 sourceFromStacktrace( 2 ) );
1091 }
1092
1093 var details = {
1094 module: this.module.name,
1095 name: this.testName,
1096 result: false,
1097 message: message || "error",
1098 actual: actual || null,
1099 testId: this.testId,
1100 runtime: now() - this.started
1101 };
1102
1103 if ( source ) {
1104 details.source = source;
1105 }
1106
1107 runLoggingCallbacks( "log", details );
1108
1109 this.assertions.push({
1110 result: false,
1111 message: message
1112 });
1113 },
1114
1115 resolvePromise: function( promise, phase ) {
1116 var then, message,
1117 test = this;
1118 if ( promise != null ) {
1119 then = promise.then;
1120 if ( QUnit.objectType( then ) === "function" ) {
1121 QUnit.stop();
1122 then.call(
1123 promise,
1124 QUnit.start,
1125 function( error ) {
1126 message = "Promise rejected " +
1127 ( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
1128 " " + test.testName + ": " + ( error.message || error );
1129 test.pushFailure( message, extractStacktrace( error, 0 ) );
1130
1131 // else next test will carry the responsibility
1132 saveGlobal();
1133
1134 // Unblock
1135 QUnit.start();
1136 }
1137 );
1138 }
1139 }
1140 },
1141
1142 valid: function() {
1143 var include,
1144 filter = config.filter && config.filter.toLowerCase(),
1145 module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
1146 fullName = ( this.module.name + ": " + this.testName ).toLowerCase();
1147
1148 // Internally-generated tests are always valid
1149 if ( this.callback && this.callback.validTest ) {
1150 return true;
1151 }
1152
1153 if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
1154 return false;
1155 }
1156
1157 if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {
1158 return false;
1159 }
1160
1161 if ( !filter ) {
1162 return true;
1163 }
1164
1165 include = filter.charAt( 0 ) !== "!";
1166 if ( !include ) {
1167 filter = filter.slice( 1 );
1168 }
1169
1170 // If the filter matches, we need to honour include
1171 if ( fullName.indexOf( filter ) !== -1 ) {
1172 return include;
1173 }
1174
1175 // Otherwise, do the opposite
1176 return !include;
1177 }
1178
1179 };
1180
1181 // Resets the test setup. Useful for tests that modify the DOM.
1182 /*
1183 DEPRECATED: Use multiple tests instead of resetting inside a test.
1184 Use testStart or testDone for custom cleanup.
1185 This method will throw an error in 2.0, and will be removed in 2.1
1186 */
1187 QUnit.reset = function() {
1188
1189 // Return on non-browser environments
1190 // This is necessary to not break on node tests
1191 if ( typeof window === "undefined" ) {
1192 return;
1193 }
1194
1195 var fixture = defined.document && document.getElementById &&
1196 document.getElementById( "qunit-fixture" );
1197
1198 if ( fixture ) {
1199 fixture.innerHTML = config.fixture;
1200 }
1201 };
1202
1203 QUnit.pushFailure = function() {
1204 if ( !QUnit.config.current ) {
1205 throw new Error( "pushFailure() assertion outside test context, in " +
1206 sourceFromStacktrace( 2 ) );
1207 }
1208
1209 // Gets current test obj
1210 var currentTest = QUnit.config.current;
1211
1212 return currentTest.pushFailure.apply( currentTest, arguments );
1213 };
1214
1215 // Based on Java's String.hashCode, a simple but not
1216 // rigorously collision resistant hashing function
1217 function generateHash( module, testName ) {
1218 var hex,
1219 i = 0,
1220 hash = 0,
1221 str = module + "\x1C" + testName,
1222 len = str.length;
1223
1224 for ( ; i < len; i++ ) {
1225 hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
1226 hash |= 0;
1227 }
1228
1229 // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
1230 // strictly necessary but increases user understanding that the id is a SHA-like hash
1231 hex = ( 0x100000000 + hash ).toString( 16 );
1232 if ( hex.length < 8 ) {
1233 hex = "0000000" + hex;
1234 }
1235
1236 return hex.slice( -8 );
1237 }
1238
1239 function Assert( testContext ) {
1240 this.test = testContext;
1241 }
1242
1243 // Assert helpers
1244 QUnit.assert = Assert.prototype = {
1245
1246 // Specify the number of expected assertions to guarantee that failed test
1247 // (no assertions are run at all) don't slip through.
1248 expect: function( asserts ) {
1249 if ( arguments.length === 1 ) {
1250 this.test.expected = asserts;
1251 } else {
1252 return this.test.expected;
1253 }
1254 },
1255
1256 // Increment this Test's semaphore counter, then return a single-use function that
1257 // decrements that counter a maximum of once.
1258 async: function() {
1259 var test = this.test,
1260 popped = false;
1261
1262 test.semaphore += 1;
1263 test.usedAsync = true;
1264 pauseProcessing();
1265
1266 return function done() {
1267 if ( !popped ) {
1268 test.semaphore -= 1;
1269 popped = true;
1270 resumeProcessing();
1271 } else {
1272 test.pushFailure( "Called the callback returned from `assert.async` more than once",
1273 sourceFromStacktrace( 2 ) );
1274 }
1275 };
1276 },
1277
1278 // Exports test.push() to the user API
1279 push: function( /* result, actual, expected, message */ ) {
1280 var assert = this,
1281 currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
1282
1283 // Backwards compatibility fix.
1284 // Allows the direct use of global exported assertions and QUnit.assert.*
1285 // Although, it's use is not recommended as it can leak assertions
1286 // to other tests from async tests, because we only get a reference to the current test,
1287 // not exactly the test where assertion were intended to be called.
1288 if ( !currentTest ) {
1289 throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
1290 }
1291
1292 if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
1293 currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
1294 sourceFromStacktrace( 2 ) );
1295
1296 // Allow this assertion to continue running anyway...
1297 }
1298
1299 if ( !( assert instanceof Assert ) ) {
1300 assert = currentTest.assert;
1301 }
1302 return assert.test.push.apply( assert.test, arguments );
1303 },
1304
1305 /**
1306 * Asserts rough true-ish result.
1307 * @name ok
1308 * @function
1309 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
1310 */
1311 ok: function( result, message ) {
1312 message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
1313 QUnit.dump.parse( result ) );
1314 this.push( !!result, result, true, message );
1315 },
1316
1317 /**
1318 * Assert that the first two arguments are equal, with an optional message.
1319 * Prints out both actual and expected values.
1320 * @name equal
1321 * @function
1322 * @example equal( format( "{0} bytes.", 2), "2 bytes.", "replaces {0} with next argument" );
1323 */
1324 equal: function( actual, expected, message ) {
1325 /*jshint eqeqeq:false */
1326 this.push( expected == actual, actual, expected, message );
1327 },
1328
1329 /**
1330 * @name notEqual
1331 * @function
1332 */
1333 notEqual: function( actual, expected, message ) {
1334 /*jshint eqeqeq:false */
1335 this.push( expected != actual, actual, expected, message );
1336 },
1337
1338 /**
1339 * @name propEqual
1340 * @function
1341 */
1342 propEqual: function( actual, expected, message ) {
1343 actual = objectValues( actual );
1344 expected = objectValues( expected );
1345 this.push( QUnit.equiv( actual, expected ), actual, expected, message );
1346 },
1347
1348 /**
1349 * @name notPropEqual
1350 * @function
1351 */
1352 notPropEqual: function( actual, expected, message ) {
1353 actual = objectValues( actual );
1354 expected = objectValues( expected );
1355 this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
1356 },
1357
1358 /**
1359 * @name deepEqual
1360 * @function
1361 */
1362 deepEqual: function( actual, expected, message ) {
1363 this.push( QUnit.equiv( actual, expected ), actual, expected, message );
1364 },
1365
1366 /**
1367 * @name notDeepEqual
1368 * @function
1369 */
1370 notDeepEqual: function( actual, expected, message ) {
1371 this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
1372 },
1373
1374 /**
1375 * @name strictEqual
1376 * @function
1377 */
1378 strictEqual: function( actual, expected, message ) {
1379 this.push( expected === actual, actual, expected, message );
1380 },
1381
1382 /**
1383 * @name notStrictEqual
1384 * @function
1385 */
1386 notStrictEqual: function( actual, expected, message ) {
1387 this.push( expected !== actual, actual, expected, message );
1388 },
1389
1390 "throws": function( block, expected, message ) {
1391 var actual, expectedType,
1392 expectedOutput = expected,
1393 ok = false;
1394
1395 // 'expected' is optional unless doing string comparison
1396 if ( message == null && typeof expected === "string" ) {
1397 message = expected;
1398 expected = null;
1399 }
1400
1401 this.test.ignoreGlobalErrors = true;
1402 try {
1403 block.call( this.test.testEnvironment );
1404 } catch (e) {
1405 actual = e;
1406 }
1407 this.test.ignoreGlobalErrors = false;
1408
1409 if ( actual ) {
1410 expectedType = QUnit.objectType( expected );
1411
1412 // we don't want to validate thrown error
1413 if ( !expected ) {
1414 ok = true;
1415 expectedOutput = null;
1416
1417 // expected is a regexp
1418 } else if ( expectedType === "regexp" ) {
1419 ok = expected.test( errorString( actual ) );
1420
1421 // expected is a string
1422 } else if ( expectedType === "string" ) {
1423 ok = expected === errorString( actual );
1424
1425 // expected is a constructor, maybe an Error constructor
1426 } else if ( expectedType === "function" && actual instanceof expected ) {
1427 ok = true;
1428
1429 // expected is an Error object
1430 } else if ( expectedType === "object" ) {
1431 ok = actual instanceof expected.constructor &&
1432 actual.name === expected.name &&
1433 actual.message === expected.message;
1434
1435 // expected is a validation function which returns true if validation passed
1436 } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
1437 expectedOutput = null;
1438 ok = true;
1439 }
1440
1441 this.push( ok, actual, expectedOutput, message );
1442 } else {
1443 this.test.pushFailure( message, null, "No exception was thrown." );
1444 }
1445 }
1446 };
1447
1448 // Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word
1449 // Known to us are: Closure Compiler, Narwhal
1450 (function() {
1451 /*jshint sub:true */
1452 Assert.prototype.raises = Assert.prototype[ "throws" ];
1453 }());
1454
1455 // Test for equality any JavaScript type.
1456 // Author: Philippe Rathé <prathe@gmail.com>
1457 QUnit.equiv = (function() {
1458
1459 // Call the o related callback with the given arguments.
1460 function bindCallbacks( o, callbacks, args ) {
1461 var prop = QUnit.objectType( o );
1462 if ( prop ) {
1463 if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
1464 return callbacks[ prop ].apply( callbacks, args );
1465 } else {
1466 return callbacks[ prop ]; // or undefined
1467 }
1468 }
1469 }
1470
1471 // the real equiv function
1472 var innerEquiv,
1473
1474 // stack to decide between skip/abort functions
1475 callers = [],
1476
1477 // stack to avoiding loops from circular referencing
1478 parents = [],
1479 parentsB = [],
1480
1481 getProto = Object.getPrototypeOf || function( obj ) {
1482 /* jshint camelcase: false, proto: true */
1483 return obj.__proto__;
1484 },
1485 callbacks = (function() {
1486
1487 // for string, boolean, number and null
1488 function useStrictEquality( b, a ) {
1489
1490 /*jshint eqeqeq:false */
1491 if ( b instanceof a.constructor || a instanceof b.constructor ) {
1492
1493 // to catch short annotation VS 'new' annotation of a
1494 // declaration
1495 // e.g. var i = 1;
1496 // var j = new Number(1);
1497 return a == b;
1498 } else {
1499 return a === b;
1500 }
1501 }
1502
1503 return {
1504 "string": useStrictEquality,
1505 "boolean": useStrictEquality,
1506 "number": useStrictEquality,
1507 "null": useStrictEquality,
1508 "undefined": useStrictEquality,
1509
1510 "nan": function( b ) {
1511 return isNaN( b );
1512 },
1513
1514 "date": function( b, a ) {
1515 return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
1516 },
1517
1518 "regexp": function( b, a ) {
1519 return QUnit.objectType( b ) === "regexp" &&
1520
1521 // the regex itself
1522 a.source === b.source &&
1523
1524 // and its modifiers
1525 a.global === b.global &&
1526
1527 // (gmi) ...
1528 a.ignoreCase === b.ignoreCase &&
1529 a.multiline === b.multiline &&
1530 a.sticky === b.sticky;
1531 },
1532
1533 // - skip when the property is a method of an instance (OOP)
1534 // - abort otherwise,
1535 // initial === would have catch identical references anyway
1536 "function": function() {
1537 var caller = callers[ callers.length - 1 ];
1538 return caller !== Object && typeof caller !== "undefined";
1539 },
1540
1541 "array": function( b, a ) {
1542 var i, j, len, loop, aCircular, bCircular;
1543
1544 // b could be an object literal here
1545 if ( QUnit.objectType( b ) !== "array" ) {
1546 return false;
1547 }
1548
1549 len = a.length;
1550 if ( len !== b.length ) {
1551 // safe and faster
1552 return false;
1553 }
1554
1555 // track reference to avoid circular references
1556 parents.push( a );
1557 parentsB.push( b );
1558 for ( i = 0; i < len; i++ ) {
1559 loop = false;
1560 for ( j = 0; j < parents.length; j++ ) {
1561 aCircular = parents[ j ] === a[ i ];
1562 bCircular = parentsB[ j ] === b[ i ];
1563 if ( aCircular || bCircular ) {
1564 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1565 loop = true;
1566 } else {
1567 parents.pop();
1568 parentsB.pop();
1569 return false;
1570 }
1571 }
1572 }
1573 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1574 parents.pop();
1575 parentsB.pop();
1576 return false;
1577 }
1578 }
1579 parents.pop();
1580 parentsB.pop();
1581 return true;
1582 },
1583
1584 "object": function( b, a ) {
1585
1586 /*jshint forin:false */
1587 var i, j, loop, aCircular, bCircular,
1588 // Default to true
1589 eq = true,
1590 aProperties = [],
1591 bProperties = [];
1592
1593 // comparing constructors is more strict than using
1594 // instanceof
1595 if ( a.constructor !== b.constructor ) {
1596
1597 // Allow objects with no prototype to be equivalent to
1598 // objects with Object as their constructor.
1599 if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
1600 ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
1601 return false;
1602 }
1603 }
1604
1605 // stack constructor before traversing properties
1606 callers.push( a.constructor );
1607
1608 // track reference to avoid circular references
1609 parents.push( a );
1610 parentsB.push( b );
1611
1612 // be strict: don't ensure hasOwnProperty and go deep
1613 for ( i in a ) {
1614 loop = false;
1615 for ( j = 0; j < parents.length; j++ ) {
1616 aCircular = parents[ j ] === a[ i ];
1617 bCircular = parentsB[ j ] === b[ i ];
1618 if ( aCircular || bCircular ) {
1619 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1620 loop = true;
1621 } else {
1622 eq = false;
1623 break;
1624 }
1625 }
1626 }
1627 aProperties.push( i );
1628 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1629 eq = false;
1630 break;
1631 }
1632 }
1633
1634 parents.pop();
1635 parentsB.pop();
1636 callers.pop(); // unstack, we are done
1637
1638 for ( i in b ) {
1639 bProperties.push( i ); // collect b's properties
1640 }
1641
1642 // Ensures identical properties name
1643 return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
1644 }
1645 };
1646 }());
1647
1648 innerEquiv = function() { // can take multiple arguments
1649 var args = [].slice.apply( arguments );
1650 if ( args.length < 2 ) {
1651 return true; // end transition
1652 }
1653
1654 return ( (function( a, b ) {
1655 if ( a === b ) {
1656 return true; // catch the most you can
1657 } else if ( a === null || b === null || typeof a === "undefined" ||
1658 typeof b === "undefined" ||
1659 QUnit.objectType( a ) !== QUnit.objectType( b ) ) {
1660
1661 // don't lose time with error prone cases
1662 return false;
1663 } else {
1664 return bindCallbacks( a, callbacks, [ b, a ] );
1665 }
1666
1667 // apply transition with (1..n) arguments
1668 }( args[ 0 ], args[ 1 ] ) ) &&
1669 innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
1670 };
1671
1672 return innerEquiv;
1673 }());
1674
1675 // Based on jsDump by Ariel Flesler
1676 // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
1677 QUnit.dump = (function() {
1678 function quote( str ) {
1679 return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
1680 }
1681 function literal( o ) {
1682 return o + "";
1683 }
1684 function join( pre, arr, post ) {
1685 var s = dump.separator(),
1686 base = dump.indent(),
1687 inner = dump.indent( 1 );
1688 if ( arr.join ) {
1689 arr = arr.join( "," + s + inner );
1690 }
1691 if ( !arr ) {
1692 return pre + post;
1693 }
1694 return [ pre, inner + arr, base + post ].join( s );
1695 }
1696 function array( arr, stack ) {
1697 var i = arr.length,
1698 ret = new Array( i );
1699
1700 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1701 return "[object Array]";
1702 }
1703
1704 this.up();
1705 while ( i-- ) {
1706 ret[ i ] = this.parse( arr[ i ], undefined, stack );
1707 }
1708 this.down();
1709 return join( "[", ret, "]" );
1710 }
1711
1712 var reName = /^function (\w+)/,
1713 dump = {
1714
1715 // objType is used mostly internally, you can fix a (custom) type in advance
1716 parse: function( obj, objType, stack ) {
1717 stack = stack || [];
1718 var res, parser, parserType,
1719 inStack = inArray( obj, stack );
1720
1721 if ( inStack !== -1 ) {
1722 return "recursion(" + ( inStack - stack.length ) + ")";
1723 }
1724
1725 objType = objType || this.typeOf( obj );
1726 parser = this.parsers[ objType ];
1727 parserType = typeof parser;
1728
1729 if ( parserType === "function" ) {
1730 stack.push( obj );
1731 res = parser.call( this, obj, stack );
1732 stack.pop();
1733 return res;
1734 }
1735 return ( parserType === "string" ) ? parser : this.parsers.error;
1736 },
1737 typeOf: function( obj ) {
1738 var type;
1739 if ( obj === null ) {
1740 type = "null";
1741 } else if ( typeof obj === "undefined" ) {
1742 type = "undefined";
1743 } else if ( QUnit.is( "regexp", obj ) ) {
1744 type = "regexp";
1745 } else if ( QUnit.is( "date", obj ) ) {
1746 type = "date";
1747 } else if ( QUnit.is( "function", obj ) ) {
1748 type = "function";
1749 } else if ( obj.setInterval !== undefined &&
1750 obj.document !== undefined &&
1751 obj.nodeType === undefined ) {
1752 type = "window";
1753 } else if ( obj.nodeType === 9 ) {
1754 type = "document";
1755 } else if ( obj.nodeType ) {
1756 type = "node";
1757 } else if (
1758
1759 // native arrays
1760 toString.call( obj ) === "[object Array]" ||
1761
1762 // NodeList objects
1763 ( typeof obj.length === "number" && obj.item !== undefined &&
1764 ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
1765 obj[ 0 ] === undefined ) ) )
1766 ) {
1767 type = "array";
1768 } else if ( obj.constructor === Error.prototype.constructor ) {
1769 type = "error";
1770 } else {
1771 type = typeof obj;
1772 }
1773 return type;
1774 },
1775 separator: function() {
1776 return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
1777 },
1778 // extra can be a number, shortcut for increasing-calling-decreasing
1779 indent: function( extra ) {
1780 if ( !this.multiline ) {
1781 return "";
1782 }
1783 var chr = this.indentChar;
1784 if ( this.HTML ) {
1785 chr = chr.replace( /\t/g, " " ).replace( / /g, "&#160;" );
1786 }
1787 return new Array( this.depth + ( extra || 0 ) ).join( chr );
1788 },
1789 up: function( a ) {
1790 this.depth += a || 1;
1791 },
1792 down: function( a ) {
1793 this.depth -= a || 1;
1794 },
1795 setParser: function( name, parser ) {
1796 this.parsers[ name ] = parser;
1797 },
1798 // The next 3 are exposed so you can use them
1799 quote: quote,
1800 literal: literal,
1801 join: join,
1802 //
1803 depth: 1,
1804 maxDepth: 5,
1805
1806 // This is the list of parsers, to modify them, use dump.setParser
1807 parsers: {
1808 window: "[Window]",
1809 document: "[Document]",
1810 error: function( error ) {
1811 return "Error(\"" + error.message + "\")";
1812 },
1813 unknown: "[Unknown]",
1814 "null": "null",
1815 "undefined": "undefined",
1816 "function": function( fn ) {
1817 var ret = "function",
1818
1819 // functions never have name in IE
1820 name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
1821
1822 if ( name ) {
1823 ret += " " + name;
1824 }
1825 ret += "( ";
1826
1827 ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
1828 return join( ret, dump.parse( fn, "functionCode" ), "}" );
1829 },
1830 array: array,
1831 nodelist: array,
1832 "arguments": array,
1833 object: function( map, stack ) {
1834 var keys, key, val, i, nonEnumerableProperties,
1835 ret = [];
1836
1837 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1838 return "[object Object]";
1839 }
1840
1841 dump.up();
1842 keys = [];
1843 for ( key in map ) {
1844 keys.push( key );
1845 }
1846
1847 // Some properties are not always enumerable on Error objects.
1848 nonEnumerableProperties = [ "message", "name" ];
1849 for ( i in nonEnumerableProperties ) {
1850 key = nonEnumerableProperties[ i ];
1851 if ( key in map && !( key in keys ) ) {
1852 keys.push( key );
1853 }
1854 }
1855 keys.sort();
1856 for ( i = 0; i < keys.length; i++ ) {
1857 key = keys[ i ];
1858 val = map[ key ];
1859 ret.push( dump.parse( key, "key" ) + ": " +
1860 dump.parse( val, undefined, stack ) );
1861 }
1862 dump.down();
1863 return join( "{", ret, "}" );
1864 },
1865 node: function( node ) {
1866 var len, i, val,
1867 open = dump.HTML ? "&lt;" : "<",
1868 close = dump.HTML ? "&gt;" : ">",
1869 tag = node.nodeName.toLowerCase(),
1870 ret = open + tag,
1871 attrs = node.attributes;
1872
1873 if ( attrs ) {
1874 for ( i = 0, len = attrs.length; i < len; i++ ) {
1875 val = attrs[ i ].nodeValue;
1876
1877 // IE6 includes all attributes in .attributes, even ones not explicitly
1878 // set. Those have values like undefined, null, 0, false, "" or
1879 // "inherit".
1880 if ( val && val !== "inherit" ) {
1881 ret += " " + attrs[ i ].nodeName + "=" +
1882 dump.parse( val, "attribute" );
1883 }
1884 }
1885 }
1886 ret += close;
1887
1888 // Show content of TextNode or CDATASection
1889 if ( node.nodeType === 3 || node.nodeType === 4 ) {
1890 ret += node.nodeValue;
1891 }
1892
1893 return ret + open + "/" + tag + close;
1894 },
1895
1896 // function calls it internally, it's the arguments part of the function
1897 functionArgs: function( fn ) {
1898 var args,
1899 l = fn.length;
1900
1901 if ( !l ) {
1902 return "";
1903 }
1904
1905 args = new Array( l );
1906 while ( l-- ) {
1907
1908 // 97 is 'a'
1909 args[ l ] = String.fromCharCode( 97 + l );
1910 }
1911 return " " + args.join( ", " ) + " ";
1912 },
1913 // object calls it internally, the key part of an item in a map
1914 key: quote,
1915 // function calls it internally, it's the content of the function
1916 functionCode: "[code]",
1917 // node calls it internally, it's an html attribute value
1918 attribute: quote,
1919 string: quote,
1920 date: quote,
1921 regexp: literal,
1922 number: literal,
1923 "boolean": literal
1924 },
1925 // if true, entities are escaped ( <, >, \t, space and \n )
1926 HTML: false,
1927 // indentation unit
1928 indentChar: " ",
1929 // if true, items in a collection, are separated by a \n, else just a space.
1930 multiline: true
1931 };
1932
1933 return dump;
1934 }());
1935
1936 // back compat
1937 QUnit.jsDump = QUnit.dump;
1938
1939 // For browser, export only select globals
1940 if ( typeof window !== "undefined" ) {
1941
1942 // Deprecated
1943 // Extend assert methods to QUnit and Global scope through Backwards compatibility
1944 (function() {
1945 var i,
1946 assertions = Assert.prototype;
1947
1948 function applyCurrent( current ) {
1949 return function() {
1950 var assert = new Assert( QUnit.config.current );
1951 current.apply( assert, arguments );
1952 };
1953 }
1954
1955 for ( i in assertions ) {
1956 QUnit[ i ] = applyCurrent( assertions[ i ] );
1957 }
1958 })();
1959
1960 (function() {
1961 var i, l,
1962 keys = [
1963 "test",
1964 "module",
1965 "expect",
1966 "asyncTest",
1967 "start",
1968 "stop",
1969 "ok",
1970 "equal",
1971 "notEqual",
1972 "propEqual",
1973 "notPropEqual",
1974 "deepEqual",
1975 "notDeepEqual",
1976 "strictEqual",
1977 "notStrictEqual",
1978 "throws"
1979 ];
1980
1981 for ( i = 0, l = keys.length; i < l; i++ ) {
1982 window[ keys[ i ] ] = QUnit[ keys[ i ] ];
1983 }
1984 })();
1985
1986 window.QUnit = QUnit;
1987 }
1988
1989 // For nodejs
1990 if ( typeof module !== "undefined" && module.exports ) {
1991 module.exports = QUnit;
1992 }
1993
1994 // For CommonJS with exports, but without module.exports, like Rhino
1995 if ( typeof exports !== "undefined" ) {
1996 exports.QUnit = QUnit;
1997 }
1998
1999 // Get a reference to the global object, like window in browsers
2000 }( (function() {
2001 return this;
2002 })() ));
2003
2004 /*istanbul ignore next */
2005 // jscs:disable maximumLineLength
2006 /*
2007 * Javascript Diff Algorithm
2008 * By John Resig (http://ejohn.org/)
2009 * Modified by Chu Alan "sprite"
2010 *
2011 * Released under the MIT license.
2012 *
2013 * More Info:
2014 * http://ejohn.org/projects/javascript-diff-algorithm/
2015 *
2016 * Usage: QUnit.diff(expected, actual)
2017 *
2018 * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
2019 */
2020 QUnit.diff = (function() {
2021 var hasOwn = Object.prototype.hasOwnProperty;
2022
2023 /*jshint eqeqeq:false, eqnull:true */
2024 function diff( o, n ) {
2025 var i,
2026 ns = {},
2027 os = {};
2028
2029 for ( i = 0; i < n.length; i++ ) {
2030 if ( !hasOwn.call( ns, n[ i ] ) ) {
2031 ns[ n[ i ] ] = {
2032 rows: [],
2033 o: null
2034 };
2035 }
2036 ns[ n[ i ] ].rows.push( i );
2037 }
2038
2039 for ( i = 0; i < o.length; i++ ) {
2040 if ( !hasOwn.call( os, o[ i ] ) ) {
2041 os[ o[ i ] ] = {
2042 rows: [],
2043 n: null
2044 };
2045 }
2046 os[ o[ i ] ].rows.push( i );
2047 }
2048
2049 for ( i in ns ) {
2050 if ( hasOwn.call( ns, i ) ) {
2051 if ( ns[ i ].rows.length === 1 && hasOwn.call( os, i ) && os[ i ].rows.length === 1 ) {
2052 n[ ns[ i ].rows[ 0 ] ] = {
2053 text: n[ ns[ i ].rows[ 0 ] ],
2054 row: os[ i ].rows[ 0 ]
2055 };
2056 o[ os[ i ].rows[ 0 ] ] = {
2057 text: o[ os[ i ].rows[ 0 ] ],
2058 row: ns[ i ].rows[ 0 ]
2059 };
2060 }
2061 }
2062 }
2063
2064 for ( i = 0; i < n.length - 1; i++ ) {
2065 if ( n[ i ].text != null && n[ i + 1 ].text == null && n[ i ].row + 1 < o.length && o[ n[ i ].row + 1 ].text == null &&
2066 n[ i + 1 ] == o[ n[ i ].row + 1 ] ) {
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 for ( i = n.length - 1; i > 0; i-- ) {
2080 if ( n[ i ].text != null && n[ i - 1 ].text == null && n[ i ].row > 0 && o[ n[ i ].row - 1 ].text == null &&
2081 n[ i - 1 ] == o[ n[ i ].row - 1 ] ) {
2082
2083 n[ i - 1 ] = {
2084 text: n[ i - 1 ],
2085 row: n[ i ].row - 1
2086 };
2087 o[ n[ i ].row - 1 ] = {
2088 text: o[ n[ i ].row - 1 ],
2089 row: i - 1
2090 };
2091 }
2092 }
2093
2094 return {
2095 o: o,
2096 n: n
2097 };
2098 }
2099
2100 return function( o, n ) {
2101 o = o.replace( /\s+$/, "" );
2102 n = n.replace( /\s+$/, "" );
2103
2104 var i, pre,
2105 str = "",
2106 out = diff( o === "" ? [] : o.split( /\s+/ ), n === "" ? [] : n.split( /\s+/ ) ),
2107 oSpace = o.match( /\s+/g ),
2108 nSpace = n.match( /\s+/g );
2109
2110 if ( oSpace == null ) {
2111 oSpace = [ " " ];
2112 } else {
2113 oSpace.push( " " );
2114 }
2115
2116 if ( nSpace == null ) {
2117 nSpace = [ " " ];
2118 } else {
2119 nSpace.push( " " );
2120 }
2121
2122 if ( out.n.length === 0 ) {
2123 for ( i = 0; i < out.o.length; i++ ) {
2124 str += "<del>" + out.o[ i ] + oSpace[ i ] + "</del>";
2125 }
2126 } else {
2127 if ( out.n[ 0 ].text == null ) {
2128 for ( n = 0; n < out.o.length && out.o[ n ].text == null; n++ ) {
2129 str += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>";
2130 }
2131 }
2132
2133 for ( i = 0; i < out.n.length; i++ ) {
2134 if ( out.n[ i ].text == null ) {
2135 str += "<ins>" + out.n[ i ] + nSpace[ i ] + "</ins>";
2136 } else {
2137
2138 // `pre` initialized at top of scope
2139 pre = "";
2140
2141 for ( n = out.n[ i ].row + 1; n < out.o.length && out.o[ n ].text == null; n++ ) {
2142 pre += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>";
2143 }
2144 str += " " + out.n[ i ].text + nSpace[ i ] + pre;
2145 }
2146 }
2147 }
2148
2149 return str;
2150 };
2151 }());
2152 // jscs:enable
2153
2154 (function() {
2155
2156 // Deprecated QUnit.init - Ref #530
2157 // Re-initialize the configuration options
2158 QUnit.init = function() {
2159 var tests, banner, result, qunit,
2160 config = QUnit.config;
2161
2162 config.stats = { all: 0, bad: 0 };
2163 config.moduleStats = { all: 0, bad: 0 };
2164 config.started = 0;
2165 config.updateRate = 1000;
2166 config.blocking = false;
2167 config.autostart = true;
2168 config.autorun = false;
2169 config.filter = "";
2170 config.queue = [];
2171
2172 // Return on non-browser environments
2173 // This is necessary to not break on node tests
2174 if ( typeof window === "undefined" ) {
2175 return;
2176 }
2177
2178 qunit = id( "qunit" );
2179 if ( qunit ) {
2180 qunit.innerHTML =
2181 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
2182 "<h2 id='qunit-banner'></h2>" +
2183 "<div id='qunit-testrunner-toolbar'></div>" +
2184 "<h2 id='qunit-userAgent'></h2>" +
2185 "<ol id='qunit-tests'></ol>";
2186 }
2187
2188 tests = id( "qunit-tests" );
2189 banner = id( "qunit-banner" );
2190 result = id( "qunit-testresult" );
2191
2192 if ( tests ) {
2193 tests.innerHTML = "";
2194 }
2195
2196 if ( banner ) {
2197 banner.className = "";
2198 }
2199
2200 if ( result ) {
2201 result.parentNode.removeChild( result );
2202 }
2203
2204 if ( tests ) {
2205 result = document.createElement( "p" );
2206 result.id = "qunit-testresult";
2207 result.className = "result";
2208 tests.parentNode.insertBefore( result, tests );
2209 result.innerHTML = "Running...<br />&#160;";
2210 }
2211 };
2212
2213 // Don't load the HTML Reporter on non-Browser environments
2214 if ( typeof window === "undefined" ) {
2215 return;
2216 }
2217
2218 var config = QUnit.config,
2219 hasOwn = Object.prototype.hasOwnProperty,
2220 defined = {
2221 document: window.document !== undefined,
2222 sessionStorage: (function() {
2223 var x = "qunit-test-string";
2224 try {
2225 sessionStorage.setItem( x, x );
2226 sessionStorage.removeItem( x );
2227 return true;
2228 } catch ( e ) {
2229 return false;
2230 }
2231 }())
2232 },
2233 modulesList = [];
2234
2235 /**
2236 * Escape text for attribute or text content.
2237 */
2238 function escapeText( s ) {
2239 if ( !s ) {
2240 return "";
2241 }
2242 s = s + "";
2243
2244 // Both single quotes and double quotes (for attributes)
2245 return s.replace( /['"<>&]/g, function( s ) {
2246 switch ( s ) {
2247 case "'":
2248 return "&#039;";
2249 case "\"":
2250 return "&quot;";
2251 case "<":
2252 return "&lt;";
2253 case ">":
2254 return "&gt;";
2255 case "&":
2256 return "&amp;";
2257 }
2258 });
2259 }
2260
2261 /**
2262 * @param {HTMLElement} elem
2263 * @param {string} type
2264 * @param {Function} fn
2265 */
2266 function addEvent( elem, type, fn ) {
2267 if ( elem.addEventListener ) {
2268
2269 // Standards-based browsers
2270 elem.addEventListener( type, fn, false );
2271 } else if ( elem.attachEvent ) {
2272
2273 // support: IE <9
2274 elem.attachEvent( "on" + type, fn );
2275 }
2276 }
2277
2278 /**
2279 * @param {Array|NodeList} elems
2280 * @param {string} type
2281 * @param {Function} fn
2282 */
2283 function addEvents( elems, type, fn ) {
2284 var i = elems.length;
2285 while ( i-- ) {
2286 addEvent( elems[ i ], type, fn );
2287 }
2288 }
2289
2290 function hasClass( elem, name ) {
2291 return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
2292 }
2293
2294 function addClass( elem, name ) {
2295 if ( !hasClass( elem, name ) ) {
2296 elem.className += ( elem.className ? " " : "" ) + name;
2297 }
2298 }
2299
2300 function toggleClass( elem, name ) {
2301 if ( hasClass( elem, name ) ) {
2302 removeClass( elem, name );
2303 } else {
2304 addClass( elem, name );
2305 }
2306 }
2307
2308 function removeClass( elem, name ) {
2309 var set = " " + elem.className + " ";
2310
2311 // Class name may appear multiple times
2312 while ( set.indexOf( " " + name + " " ) >= 0 ) {
2313 set = set.replace( " " + name + " ", " " );
2314 }
2315
2316 // trim for prettiness
2317 elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
2318 }
2319
2320 function id( name ) {
2321 return defined.document && document.getElementById && document.getElementById( name );
2322 }
2323
2324 function getUrlConfigHtml() {
2325 var i, j, val,
2326 escaped, escapedTooltip,
2327 selection = false,
2328 len = config.urlConfig.length,
2329 urlConfigHtml = "";
2330
2331 for ( i = 0; i < len; i++ ) {
2332 val = config.urlConfig[ i ];
2333 if ( typeof val === "string" ) {
2334 val = {
2335 id: val,
2336 label: val
2337 };
2338 }
2339
2340 escaped = escapeText( val.id );
2341 escapedTooltip = escapeText( val.tooltip );
2342
2343 config[ val.id ] = QUnit.urlParams[ val.id ];
2344 if ( !val.value || typeof val.value === "string" ) {
2345 urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
2346 "' name='" + escaped + "' type='checkbox'" +
2347 ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
2348 ( config[ val.id ] ? " checked='checked'" : "" ) +
2349 " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
2350 "' title='" + escapedTooltip + "'>" + val.label + "</label>";
2351 } else {
2352 urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
2353 "' title='" + escapedTooltip + "'>" + val.label +
2354 ": </label><select id='qunit-urlconfig-" + escaped +
2355 "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
2356
2357 if ( QUnit.is( "array", val.value ) ) {
2358 for ( j = 0; j < val.value.length; j++ ) {
2359 escaped = escapeText( val.value[ j ] );
2360 urlConfigHtml += "<option value='" + escaped + "'" +
2361 ( config[ val.id ] === val.value[ j ] ?
2362 ( selection = true ) && " selected='selected'" : "" ) +
2363 ">" + escaped + "</option>";
2364 }
2365 } else {
2366 for ( j in val.value ) {
2367 if ( hasOwn.call( val.value, j ) ) {
2368 urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
2369 ( config[ val.id ] === j ?
2370 ( selection = true ) && " selected='selected'" : "" ) +
2371 ">" + escapeText( val.value[ j ] ) + "</option>";
2372 }
2373 }
2374 }
2375 if ( config[ val.id ] && !selection ) {
2376 escaped = escapeText( config[ val.id ] );
2377 urlConfigHtml += "<option value='" + escaped +
2378 "' selected='selected' disabled='disabled'>" + escaped + "</option>";
2379 }
2380 urlConfigHtml += "</select>";
2381 }
2382 }
2383
2384 return urlConfigHtml;
2385 }
2386
2387 // Handle "click" events on toolbar checkboxes and "change" for select menus.
2388 // Updates the URL with the new state of `config.urlConfig` values.
2389 function toolbarChanged() {
2390 var updatedUrl, value,
2391 field = this,
2392 params = {};
2393
2394 // Detect if field is a select menu or a checkbox
2395 if ( "selectedIndex" in field ) {
2396 value = field.options[ field.selectedIndex ].value || undefined;
2397 } else {
2398 value = field.checked ? ( field.defaultValue || true ) : undefined;
2399 }
2400
2401 params[ field.name ] = value;
2402 updatedUrl = QUnit.url( params );
2403
2404 if ( "hidepassed" === field.name && "replaceState" in window.history ) {
2405 config[ field.name ] = value || false;
2406 if ( value ) {
2407 addClass( id( "qunit-tests" ), "hidepass" );
2408 } else {
2409 removeClass( id( "qunit-tests" ), "hidepass" );
2410 }
2411
2412 // It is not necessary to refresh the whole page
2413 window.history.replaceState( null, "", updatedUrl );
2414 } else {
2415 window.location = updatedUrl;
2416 }
2417 }
2418
2419 function toolbarUrlConfigContainer() {
2420 var urlConfigContainer = document.createElement( "span" );
2421
2422 urlConfigContainer.innerHTML = getUrlConfigHtml();
2423
2424 // For oldIE support:
2425 // * Add handlers to the individual elements instead of the container
2426 // * Use "click" instead of "change" for checkboxes
2427 addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
2428 addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
2429
2430 return urlConfigContainer;
2431 }
2432
2433 function toolbarModuleFilterHtml() {
2434 var i,
2435 moduleFilterHtml = "";
2436
2437 if ( !modulesList.length ) {
2438 return false;
2439 }
2440
2441 modulesList.sort(function( a, b ) {
2442 return a.localeCompare( b );
2443 });
2444
2445 moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
2446 "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
2447 ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
2448 ">< All Modules ></option>";
2449
2450 for ( i = 0; i < modulesList.length; i++ ) {
2451 moduleFilterHtml += "<option value='" +
2452 escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
2453 ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
2454 ">" + escapeText( modulesList[ i ] ) + "</option>";
2455 }
2456 moduleFilterHtml += "</select>";
2457
2458 return moduleFilterHtml;
2459 }
2460
2461 function toolbarModuleFilter() {
2462 var toolbar = id( "qunit-testrunner-toolbar" ),
2463 moduleFilter = document.createElement( "span" ),
2464 moduleFilterHtml = toolbarModuleFilterHtml();
2465
2466 if ( !moduleFilterHtml ) {
2467 return false;
2468 }
2469
2470 moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
2471 moduleFilter.innerHTML = moduleFilterHtml;
2472
2473 addEvent( moduleFilter.lastChild, "change", function() {
2474 var selectBox = moduleFilter.getElementsByTagName( "select" )[ 0 ],
2475 selection = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value );
2476
2477 window.location = QUnit.url({
2478 module: ( selection === "" ) ? undefined : selection,
2479
2480 // Remove any existing filters
2481 filter: undefined,
2482 testId: undefined
2483 });
2484 });
2485
2486 toolbar.appendChild( moduleFilter );
2487 }
2488
2489 function appendToolbar() {
2490 var toolbar = id( "qunit-testrunner-toolbar" );
2491
2492 if ( toolbar ) {
2493 toolbar.appendChild( toolbarUrlConfigContainer() );
2494 }
2495 }
2496
2497 function appendBanner() {
2498 var banner = id( "qunit-banner" );
2499
2500 if ( banner ) {
2501 banner.className = "";
2502 banner.innerHTML = "<a href='" +
2503 QUnit.url({ filter: undefined, module: undefined, testId: undefined }) +
2504 "'>" + banner.innerHTML + "</a> ";
2505 }
2506 }
2507
2508 function appendTestResults() {
2509 var tests = id( "qunit-tests" ),
2510 result = id( "qunit-testresult" );
2511
2512 if ( result ) {
2513 result.parentNode.removeChild( result );
2514 }
2515
2516 if ( tests ) {
2517 tests.innerHTML = "";
2518 result = document.createElement( "p" );
2519 result.id = "qunit-testresult";
2520 result.className = "result";
2521 tests.parentNode.insertBefore( result, tests );
2522 result.innerHTML = "Running...<br />&#160;";
2523 }
2524 }
2525
2526 function storeFixture() {
2527 var fixture = id( "qunit-fixture" );
2528 if ( fixture ) {
2529 config.fixture = fixture.innerHTML;
2530 }
2531 }
2532
2533 function appendUserAgent() {
2534 var userAgent = id( "qunit-userAgent" );
2535 if ( userAgent ) {
2536 userAgent.innerHTML = navigator.userAgent;
2537 }
2538 }
2539
2540 function appendTestsList( modules ) {
2541 var i, l, x, z, test, moduleObj;
2542
2543 for ( i = 0, l = modules.length; i < l; i++ ) {
2544 moduleObj = modules[ i ];
2545
2546 if ( moduleObj.name ) {
2547 modulesList.push( moduleObj.name );
2548 }
2549
2550 for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
2551 test = moduleObj.tests[ x ];
2552
2553 appendTest( test.name, test.testId, moduleObj.name );
2554 }
2555 }
2556 }
2557
2558 function appendTest( name, testId, moduleName ) {
2559 var title, rerunTrigger, testBlock, assertList,
2560 tests = id( "qunit-tests" );
2561
2562 if ( !tests ) {
2563 return;
2564 }
2565
2566 title = document.createElement( "strong" );
2567 title.innerHTML = getNameHtml( name, moduleName );
2568
2569 rerunTrigger = document.createElement( "a" );
2570 rerunTrigger.innerHTML = "Rerun";
2571 rerunTrigger.href = QUnit.url({ testId: testId });
2572
2573 testBlock = document.createElement( "li" );
2574 testBlock.appendChild( title );
2575 testBlock.appendChild( rerunTrigger );
2576 testBlock.id = "qunit-test-output-" + testId;
2577
2578 assertList = document.createElement( "ol" );
2579 assertList.className = "qunit-assert-list";
2580
2581 testBlock.appendChild( assertList );
2582
2583 tests.appendChild( testBlock );
2584 }
2585
2586 // HTML Reporter initialization and load
2587 QUnit.begin(function( details ) {
2588 var qunit = id( "qunit" );
2589
2590 // Fixture is the only one necessary to run without the #qunit element
2591 storeFixture();
2592
2593 if ( !qunit ) {
2594 return;
2595 }
2596
2597 qunit.innerHTML =
2598 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
2599 "<h2 id='qunit-banner'></h2>" +
2600 "<div id='qunit-testrunner-toolbar'></div>" +
2601 "<h2 id='qunit-userAgent'></h2>" +
2602 "<ol id='qunit-tests'></ol>";
2603
2604 appendBanner();
2605 appendTestResults();
2606 appendUserAgent();
2607 appendToolbar();
2608 appendTestsList( details.modules );
2609 toolbarModuleFilter();
2610
2611 if ( config.hidepassed ) {
2612 addClass( qunit.lastChild, "hidepass" );
2613 }
2614 });
2615
2616 QUnit.done(function( details ) {
2617 var i, key,
2618 banner = id( "qunit-banner" ),
2619 tests = id( "qunit-tests" ),
2620 html = [
2621 "Tests completed in ",
2622 details.runtime,
2623 " milliseconds.<br />",
2624 "<span class='passed'>",
2625 details.passed,
2626 "</span> assertions of <span class='total'>",
2627 details.total,
2628 "</span> passed, <span class='failed'>",
2629 details.failed,
2630 "</span> failed."
2631 ].join( "" );
2632
2633 if ( banner ) {
2634 banner.className = details.failed ? "qunit-fail" : "qunit-pass";
2635 }
2636
2637 if ( tests ) {
2638 id( "qunit-testresult" ).innerHTML = html;
2639 }
2640
2641 if ( config.altertitle && defined.document && document.title ) {
2642
2643 // show ✖ for good, ✔ for bad suite result in title
2644 // use escape sequences in case file gets loaded with non-utf-8-charset
2645 document.title = [
2646 ( details.failed ? "\u2716" : "\u2714" ),
2647 document.title.replace( /^[\u2714\u2716] /i, "" )
2648 ].join( " " );
2649 }
2650
2651 // clear own sessionStorage items if all tests passed
2652 if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
2653 for ( i = 0; i < sessionStorage.length; i++ ) {
2654 key = sessionStorage.key( i++ );
2655 if ( key.indexOf( "qunit-test-" ) === 0 ) {
2656 sessionStorage.removeItem( key );
2657 }
2658 }
2659 }
2660
2661 // scroll back to top to show results
2662 if ( config.scrolltop && window.scrollTo ) {
2663 window.scrollTo( 0, 0 );
2664 }
2665 });
2666
2667 function getNameHtml( name, module ) {
2668 var nameHtml = "";
2669
2670 if ( module ) {
2671 nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
2672 }
2673
2674 nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
2675
2676 return nameHtml;
2677 }
2678
2679 QUnit.testStart(function( details ) {
2680 var running, testBlock;
2681
2682 testBlock = id( "qunit-test-output-" + details.testId );
2683 if ( testBlock ) {
2684 testBlock.className = "running";
2685 } else {
2686
2687 // Report later registered tests
2688 appendTest( details.name, details.testId, details.module );
2689 }
2690
2691 running = id( "qunit-testresult" );
2692 if ( running ) {
2693 running.innerHTML = "Running: <br />" + getNameHtml( details.name, details.module );
2694 }
2695
2696 });
2697
2698 QUnit.log(function( details ) {
2699 var assertList, assertLi,
2700 message, expected, actual,
2701 testItem = id( "qunit-test-output-" + details.testId );
2702
2703 if ( !testItem ) {
2704 return;
2705 }
2706
2707 message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
2708 message = "<span class='test-message'>" + message + "</span>";
2709 message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
2710
2711 // pushFailure doesn't provide details.expected
2712 // when it calls, it's implicit to also not show expected and diff stuff
2713 // Also, we need to check details.expected existence, as it can exist and be undefined
2714 if ( !details.result && hasOwn.call( details, "expected" ) ) {
2715 expected = escapeText( QUnit.dump.parse( details.expected ) );
2716 actual = escapeText( QUnit.dump.parse( details.actual ) );
2717 message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
2718 expected +
2719 "</pre></td></tr>";
2720
2721 if ( actual !== expected ) {
2722 message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
2723 actual + "</pre></td></tr>" +
2724 "<tr class='test-diff'><th>Diff: </th><td><pre>" +
2725 QUnit.diff( expected, actual ) + "</pre></td></tr>";
2726 }
2727
2728 if ( details.source ) {
2729 message += "<tr class='test-source'><th>Source: </th><td><pre>" +
2730 escapeText( details.source ) + "</pre></td></tr>";
2731 }
2732
2733 message += "</table>";
2734
2735 // this occours when pushFailure is set and we have an extracted stack trace
2736 } else if ( !details.result && details.source ) {
2737 message += "<table>" +
2738 "<tr class='test-source'><th>Source: </th><td><pre>" +
2739 escapeText( details.source ) + "</pre></td></tr>" +
2740 "</table>";
2741 }
2742
2743 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
2744
2745 assertLi = document.createElement( "li" );
2746 assertLi.className = details.result ? "pass" : "fail";
2747 assertLi.innerHTML = message;
2748 assertList.appendChild( assertLi );
2749 });
2750
2751 QUnit.testDone(function( details ) {
2752 var testTitle, time, testItem, assertList,
2753 good, bad, testCounts, skipped,
2754 tests = id( "qunit-tests" );
2755
2756 if ( !tests ) {
2757 return;
2758 }
2759
2760 testItem = id( "qunit-test-output-" + details.testId );
2761
2762 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
2763
2764 good = details.passed;
2765 bad = details.failed;
2766
2767 // store result when possible
2768 if ( config.reorder && defined.sessionStorage ) {
2769 if ( bad ) {
2770 sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
2771 } else {
2772 sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
2773 }
2774 }
2775
2776 if ( bad === 0 ) {
2777 addClass( assertList, "qunit-collapsed" );
2778 }
2779
2780 // testItem.firstChild is the test name
2781 testTitle = testItem.firstChild;
2782
2783 testCounts = bad ?
2784 "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
2785 "";
2786
2787 testTitle.innerHTML += " <b class='counts'>(" + testCounts +
2788 details.assertions.length + ")</b>";
2789
2790 if ( details.skipped ) {
2791 addClass( testItem, "skipped" );
2792 skipped = document.createElement( "em" );
2793 skipped.className = "qunit-skipped-label";
2794 skipped.innerHTML = "skipped";
2795 testItem.insertBefore( skipped, testTitle );
2796 } else {
2797 addEvent( testTitle, "click", function() {
2798 toggleClass( assertList, "qunit-collapsed" );
2799 });
2800
2801 testItem.className = bad ? "fail" : "pass";
2802
2803 time = document.createElement( "span" );
2804 time.className = "runtime";
2805 time.innerHTML = details.runtime + " ms";
2806 testItem.insertBefore( time, assertList );
2807 }
2808 });
2809
2810 if ( !defined.document || document.readyState === "complete" ) {
2811 config.pageLoaded = true;
2812 config.autorun = true;
2813 }
2814
2815 if ( defined.document ) {
2816 addEvent( window, "load", QUnit.load );
2817 }
2818
2819 })();