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