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