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