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