Merge "Remove not used private member variable mParserWarnings from OutputPage"
[mediawiki.git] / resources / lib / qunitjs / qunit.js
blobf3542ca9d4c2495b5fe73cf8685cce3b32405de1
1 /*!
2  * QUnit 1.18.0
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-04-03T10:23Z
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         // depth up-to which object will be dumped
120         maxDepth: 5,
122         // add checkboxes that are persisted in the query-string
123         // when enabled, the id is set to `true` as a `QUnit.config` property
124         urlConfig: [
125                 {
126                         id: "hidepassed",
127                         label: "Hide passed tests",
128                         tooltip: "Only show tests and assertions that fail. Stored as query-strings."
129                 },
130                 {
131                         id: "noglobals",
132                         label: "Check for Globals",
133                         tooltip: "Enabling this will test if any test introduces new properties on the " +
134                                 "`window` object. Stored as query-strings."
135                 },
136                 {
137                         id: "notrycatch",
138                         label: "No try-catch",
139                         tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
140                                 "exceptions in IE reasonable. Stored as query-strings."
141                 }
142         ],
144         // Set of all modules.
145         modules: [],
147         // The first unnamed module
148         currentModule: {
149                 name: "",
150                 tests: []
151         },
153         callbacks: {}
156 // Push a loose unnamed module to the modules collection
157 config.modules.push( config.currentModule );
159 // Initialize more QUnit.config and QUnit.urlParams
160 (function() {
161         var i, current,
162                 location = window.location || { search: "", protocol: "file:" },
163                 params = location.search.slice( 1 ).split( "&" ),
164                 length = params.length,
165                 urlParams = {};
167         if ( params[ 0 ] ) {
168                 for ( i = 0; i < length; i++ ) {
169                         current = params[ i ].split( "=" );
170                         current[ 0 ] = decodeURIComponent( current[ 0 ] );
172                         // allow just a key to turn on a flag, e.g., test.html?noglobals
173                         current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
174                         if ( urlParams[ current[ 0 ] ] ) {
175                                 urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );
176                         } else {
177                                 urlParams[ current[ 0 ] ] = current[ 1 ];
178                         }
179                 }
180         }
182         if ( urlParams.filter === true ) {
183                 delete urlParams.filter;
184         }
186         QUnit.urlParams = urlParams;
188         // String search anywhere in moduleName+testName
189         config.filter = urlParams.filter;
191         if ( urlParams.maxDepth ) {
192                 config.maxDepth = parseInt( urlParams.maxDepth, 10 ) === -1 ?
193                         Number.POSITIVE_INFINITY :
194                         urlParams.maxDepth;
195         }
197         config.testId = [];
198         if ( urlParams.testId ) {
200                 // Ensure that urlParams.testId is an array
201                 urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," );
202                 for ( i = 0; i < urlParams.testId.length; i++ ) {
203                         config.testId.push( urlParams.testId[ i ] );
204                 }
205         }
207         // Figure out if we're running the tests from a server or not
208         QUnit.isLocal = location.protocol === "file:";
210         // Expose the current QUnit version
211         QUnit.version = "1.18.0";
212 }());
214 // Root QUnit object.
215 // `QUnit` initialized at top of scope
216 extend( QUnit, {
218         // call on start of module test to prepend name to all tests
219         module: function( name, testEnvironment ) {
220                 var currentModule = {
221                         name: name,
222                         testEnvironment: testEnvironment,
223                         tests: []
224                 };
226                 // DEPRECATED: handles setup/teardown functions,
227                 // beforeEach and afterEach should be used instead
228                 if ( testEnvironment && testEnvironment.setup ) {
229                         testEnvironment.beforeEach = testEnvironment.setup;
230                         delete testEnvironment.setup;
231                 }
232                 if ( testEnvironment && testEnvironment.teardown ) {
233                         testEnvironment.afterEach = testEnvironment.teardown;
234                         delete testEnvironment.teardown;
235                 }
237                 config.modules.push( currentModule );
238                 config.currentModule = currentModule;
239         },
241         // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
242         asyncTest: function( testName, expected, callback ) {
243                 if ( arguments.length === 2 ) {
244                         callback = expected;
245                         expected = null;
246                 }
248                 QUnit.test( testName, expected, callback, true );
249         },
251         test: function( testName, expected, callback, async ) {
252                 var test;
254                 if ( arguments.length === 2 ) {
255                         callback = expected;
256                         expected = null;
257                 }
259                 test = new Test({
260                         testName: testName,
261                         expected: expected,
262                         async: async,
263                         callback: callback
264                 });
266                 test.queue();
267         },
269         skip: function( testName ) {
270                 var test = new Test({
271                         testName: testName,
272                         skip: true
273                 });
275                 test.queue();
276         },
278         // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
279         // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
280         start: function( count ) {
281                 var globalStartAlreadyCalled = globalStartCalled;
283                 if ( !config.current ) {
284                         globalStartCalled = true;
286                         if ( runStarted ) {
287                                 throw new Error( "Called start() outside of a test context while already started" );
288                         } else if ( globalStartAlreadyCalled || count > 1 ) {
289                                 throw new Error( "Called start() outside of a test context too many times" );
290                         } else if ( config.autostart ) {
291                                 throw new Error( "Called start() outside of a test context when " +
292                                         "QUnit.config.autostart was true" );
293                         } else if ( !config.pageLoaded ) {
295                                 // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
296                                 config.autostart = true;
297                                 return;
298                         }
299                 } else {
301                         // If a test is running, adjust its semaphore
302                         config.current.semaphore -= count || 1;
304                         // Don't start until equal number of stop-calls
305                         if ( config.current.semaphore > 0 ) {
306                                 return;
307                         }
309                         // throw an Error if start is called more often than stop
310                         if ( config.current.semaphore < 0 ) {
311                                 config.current.semaphore = 0;
313                                 QUnit.pushFailure(
314                                         "Called start() while already started (test's semaphore was 0 already)",
315                                         sourceFromStacktrace( 2 )
316                                 );
317                                 return;
318                         }
319                 }
321                 resumeProcessing();
322         },
324         // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
325         stop: function( count ) {
327                 // If there isn't a test running, don't allow QUnit.stop() to be called
328                 if ( !config.current ) {
329                         throw new Error( "Called stop() outside of a test context" );
330                 }
332                 // If a test is running, adjust its semaphore
333                 config.current.semaphore += count || 1;
335                 pauseProcessing();
336         },
338         config: config,
340         // Safe object type checking
341         is: function( type, obj ) {
342                 return QUnit.objectType( obj ) === type;
343         },
345         objectType: function( obj ) {
346                 if ( typeof obj === "undefined" ) {
347                         return "undefined";
348                 }
350                 // Consider: typeof null === object
351                 if ( obj === null ) {
352                         return "null";
353                 }
355                 var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
356                         type = match && match[ 1 ] || "";
358                 switch ( type ) {
359                         case "Number":
360                                 if ( isNaN( obj ) ) {
361                                         return "nan";
362                                 }
363                                 return "number";
364                         case "String":
365                         case "Boolean":
366                         case "Array":
367                         case "Date":
368                         case "RegExp":
369                         case "Function":
370                                 return type.toLowerCase();
371                 }
372                 if ( typeof obj === "object" ) {
373                         return "object";
374                 }
375                 return undefined;
376         },
378         extend: extend,
380         load: function() {
381                 config.pageLoaded = true;
383                 // Initialize the configuration options
384                 extend( config, {
385                         stats: { all: 0, bad: 0 },
386                         moduleStats: { all: 0, bad: 0 },
387                         started: 0,
388                         updateRate: 1000,
389                         autostart: true,
390                         filter: ""
391                 }, true );
393                 config.blocking = false;
395                 if ( config.autostart ) {
396                         resumeProcessing();
397                 }
398         }
401 // Register logging callbacks
402 (function() {
403         var i, l, key,
404                 callbacks = [ "begin", "done", "log", "testStart", "testDone",
405                         "moduleStart", "moduleDone" ];
407         function registerLoggingCallback( key ) {
408                 var loggingCallback = function( callback ) {
409                         if ( QUnit.objectType( callback ) !== "function" ) {
410                                 throw new Error(
411                                         "QUnit logging methods require a callback function as their first parameters."
412                                 );
413                         }
415                         config.callbacks[ key ].push( callback );
416                 };
418                 // DEPRECATED: This will be removed on QUnit 2.0.0+
419                 // Stores the registered functions allowing restoring
420                 // at verifyLoggingCallbacks() if modified
421                 loggingCallbacks[ key ] = loggingCallback;
423                 return loggingCallback;
424         }
426         for ( i = 0, l = callbacks.length; i < l; i++ ) {
427                 key = callbacks[ i ];
429                 // Initialize key collection of logging callback
430                 if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
431                         config.callbacks[ key ] = [];
432                 }
434                 QUnit[ key ] = registerLoggingCallback( key );
435         }
436 })();
438 // `onErrorFnPrev` initialized at top of scope
439 // Preserve other handlers
440 onErrorFnPrev = window.onerror;
442 // Cover uncaught exceptions
443 // Returning true will suppress the default browser handler,
444 // returning false will let it run.
445 window.onerror = function( error, filePath, linerNr ) {
446         var ret = false;
447         if ( onErrorFnPrev ) {
448                 ret = onErrorFnPrev( error, filePath, linerNr );
449         }
451         // Treat return value as window.onerror itself does,
452         // Only do our handling if not suppressed.
453         if ( ret !== true ) {
454                 if ( QUnit.config.current ) {
455                         if ( QUnit.config.current.ignoreGlobalErrors ) {
456                                 return true;
457                         }
458                         QUnit.pushFailure( error, filePath + ":" + linerNr );
459                 } else {
460                         QUnit.test( "global failure", extend(function() {
461                                 QUnit.pushFailure( error, filePath + ":" + linerNr );
462                         }, { validTest: true } ) );
463                 }
464                 return false;
465         }
467         return ret;
470 function done() {
471         var runtime, passed;
473         config.autorun = true;
475         // Log the last module results
476         if ( config.previousModule ) {
477                 runLoggingCallbacks( "moduleDone", {
478                         name: config.previousModule.name,
479                         tests: config.previousModule.tests,
480                         failed: config.moduleStats.bad,
481                         passed: config.moduleStats.all - config.moduleStats.bad,
482                         total: config.moduleStats.all,
483                         runtime: now() - config.moduleStats.started
484                 });
485         }
486         delete config.previousModule;
488         runtime = now() - config.started;
489         passed = config.stats.all - config.stats.bad;
491         runLoggingCallbacks( "done", {
492                 failed: config.stats.bad,
493                 passed: passed,
494                 total: config.stats.all,
495                 runtime: runtime
496         });
499 // Doesn't support IE6 to IE9, it will return undefined on these browsers
500 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
501 function extractStacktrace( e, offset ) {
502         offset = offset === undefined ? 4 : offset;
504         var stack, include, i;
506         if ( e.stack ) {
507                 stack = e.stack.split( "\n" );
508                 if ( /^error$/i.test( stack[ 0 ] ) ) {
509                         stack.shift();
510                 }
511                 if ( fileName ) {
512                         include = [];
513                         for ( i = offset; i < stack.length; i++ ) {
514                                 if ( stack[ i ].indexOf( fileName ) !== -1 ) {
515                                         break;
516                                 }
517                                 include.push( stack[ i ] );
518                         }
519                         if ( include.length ) {
520                                 return include.join( "\n" );
521                         }
522                 }
523                 return stack[ offset ];
525         // Support: Safari <=6 only
526         } else if ( e.sourceURL ) {
528                 // exclude useless self-reference for generated Error objects
529                 if ( /qunit.js$/.test( e.sourceURL ) ) {
530                         return;
531                 }
533                 // for actual exceptions, this is useful
534                 return e.sourceURL + ":" + e.line;
535         }
538 function sourceFromStacktrace( offset ) {
539         var error = new Error();
541         // Support: Safari <=7 only, IE <=10 - 11 only
542         // Not all browsers generate the `stack` property for `new Error()`, see also #636
543         if ( !error.stack ) {
544                 try {
545                         throw error;
546                 } catch ( err ) {
547                         error = err;
548                 }
549         }
551         return extractStacktrace( error, offset );
554 function synchronize( callback, last ) {
555         if ( QUnit.objectType( callback ) === "array" ) {
556                 while ( callback.length ) {
557                         synchronize( callback.shift() );
558                 }
559                 return;
560         }
561         config.queue.push( callback );
563         if ( config.autorun && !config.blocking ) {
564                 process( last );
565         }
568 function process( last ) {
569         function next() {
570                 process( last );
571         }
572         var start = now();
573         config.depth = ( config.depth || 0 ) + 1;
575         while ( config.queue.length && !config.blocking ) {
576                 if ( !defined.setTimeout || config.updateRate <= 0 ||
577                                 ( ( now() - start ) < config.updateRate ) ) {
578                         if ( config.current ) {
580                                 // Reset async tracking for each phase of the Test lifecycle
581                                 config.current.usedAsync = false;
582                         }
583                         config.queue.shift()();
584                 } else {
585                         setTimeout( next, 13 );
586                         break;
587                 }
588         }
589         config.depth--;
590         if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
591                 done();
592         }
595 function begin() {
596         var i, l,
597                 modulesLog = [];
599         // If the test run hasn't officially begun yet
600         if ( !config.started ) {
602                 // Record the time of the test run's beginning
603                 config.started = now();
605                 verifyLoggingCallbacks();
607                 // Delete the loose unnamed module if unused.
608                 if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
609                         config.modules.shift();
610                 }
612                 // Avoid unnecessary information by not logging modules' test environments
613                 for ( i = 0, l = config.modules.length; i < l; i++ ) {
614                         modulesLog.push({
615                                 name: config.modules[ i ].name,
616                                 tests: config.modules[ i ].tests
617                         });
618                 }
620                 // The test run is officially beginning now
621                 runLoggingCallbacks( "begin", {
622                         totalTests: Test.count,
623                         modules: modulesLog
624                 });
625         }
627         config.blocking = false;
628         process( true );
631 function resumeProcessing() {
632         runStarted = true;
634         // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
635         if ( defined.setTimeout ) {
636                 setTimeout(function() {
637                         if ( config.current && config.current.semaphore > 0 ) {
638                                 return;
639                         }
640                         if ( config.timeout ) {
641                                 clearTimeout( config.timeout );
642                         }
644                         begin();
645                 }, 13 );
646         } else {
647                 begin();
648         }
651 function pauseProcessing() {
652         config.blocking = true;
654         if ( config.testTimeout && defined.setTimeout ) {
655                 clearTimeout( config.timeout );
656                 config.timeout = setTimeout(function() {
657                         if ( config.current ) {
658                                 config.current.semaphore = 0;
659                                 QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
660                         } else {
661                                 throw new Error( "Test timed out" );
662                         }
663                         resumeProcessing();
664                 }, config.testTimeout );
665         }
668 function saveGlobal() {
669         config.pollution = [];
671         if ( config.noglobals ) {
672                 for ( var key in window ) {
673                         if ( hasOwn.call( window, key ) ) {
674                                 // in Opera sometimes DOM element ids show up here, ignore them
675                                 if ( /^qunit-test-output/.test( key ) ) {
676                                         continue;
677                                 }
678                                 config.pollution.push( key );
679                         }
680                 }
681         }
684 function checkPollution() {
685         var newGlobals,
686                 deletedGlobals,
687                 old = config.pollution;
689         saveGlobal();
691         newGlobals = diff( config.pollution, old );
692         if ( newGlobals.length > 0 ) {
693                 QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
694         }
696         deletedGlobals = diff( old, config.pollution );
697         if ( deletedGlobals.length > 0 ) {
698                 QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
699         }
702 // returns a new Array with the elements that are in a but not in b
703 function diff( a, b ) {
704         var i, j,
705                 result = a.slice();
707         for ( i = 0; i < result.length; i++ ) {
708                 for ( j = 0; j < b.length; j++ ) {
709                         if ( result[ i ] === b[ j ] ) {
710                                 result.splice( i, 1 );
711                                 i--;
712                                 break;
713                         }
714                 }
715         }
716         return result;
719 function extend( a, b, undefOnly ) {
720         for ( var prop in b ) {
721                 if ( hasOwn.call( b, prop ) ) {
723                         // Avoid "Member not found" error in IE8 caused by messing with window.constructor
724                         if ( !( prop === "constructor" && a === window ) ) {
725                                 if ( b[ prop ] === undefined ) {
726                                         delete a[ prop ];
727                                 } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
728                                         a[ prop ] = b[ prop ];
729                                 }
730                         }
731                 }
732         }
734         return a;
737 function runLoggingCallbacks( key, args ) {
738         var i, l, callbacks;
740         callbacks = config.callbacks[ key ];
741         for ( i = 0, l = callbacks.length; i < l; i++ ) {
742                 callbacks[ i ]( args );
743         }
746 // DEPRECATED: This will be removed on 2.0.0+
747 // This function verifies if the loggingCallbacks were modified by the user
748 // If so, it will restore it, assign the given callback and print a console warning
749 function verifyLoggingCallbacks() {
750         var loggingCallback, userCallback;
752         for ( loggingCallback in loggingCallbacks ) {
753                 if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
755                         userCallback = QUnit[ loggingCallback ];
757                         // Restore the callback function
758                         QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
760                         // Assign the deprecated given callback
761                         QUnit[ loggingCallback ]( userCallback );
763                         if ( window.console && window.console.warn ) {
764                                 window.console.warn(
765                                         "QUnit." + loggingCallback + " was replaced with a new value.\n" +
766                                         "Please, check out the documentation on how to apply logging callbacks.\n" +
767                                         "Reference: http://api.qunitjs.com/category/callbacks/"
768                                 );
769                         }
770                 }
771         }
774 // from jquery.js
775 function inArray( elem, array ) {
776         if ( array.indexOf ) {
777                 return array.indexOf( elem );
778         }
780         for ( var i = 0, length = array.length; i < length; i++ ) {
781                 if ( array[ i ] === elem ) {
782                         return i;
783                 }
784         }
786         return -1;
789 function Test( settings ) {
790         var i, l;
792         ++Test.count;
794         extend( this, settings );
795         this.assertions = [];
796         this.semaphore = 0;
797         this.usedAsync = false;
798         this.module = config.currentModule;
799         this.stack = sourceFromStacktrace( 3 );
801         // Register unique strings
802         for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
803                 if ( this.module.tests[ i ].name === this.testName ) {
804                         this.testName += " ";
805                 }
806         }
808         this.testId = generateHash( this.module.name, this.testName );
810         this.module.tests.push({
811                 name: this.testName,
812                 testId: this.testId
813         });
815         if ( settings.skip ) {
817                 // Skipped tests will fully ignore any sent callback
818                 this.callback = function() {};
819                 this.async = false;
820                 this.expected = 0;
821         } else {
822                 this.assert = new Assert( this );
823         }
826 Test.count = 0;
828 Test.prototype = {
829         before: function() {
830                 if (
832                         // Emit moduleStart when we're switching from one module to another
833                         this.module !== config.previousModule ||
835                                 // They could be equal (both undefined) but if the previousModule property doesn't
836                                 // yet exist it means this is the first test in a suite that isn't wrapped in a
837                                 // module, in which case we'll just emit a moduleStart event for 'undefined'.
838                                 // Without this, reporters can get testStart before moduleStart  which is a problem.
839                                 !hasOwn.call( config, "previousModule" )
840                 ) {
841                         if ( hasOwn.call( config, "previousModule" ) ) {
842                                 runLoggingCallbacks( "moduleDone", {
843                                         name: config.previousModule.name,
844                                         tests: config.previousModule.tests,
845                                         failed: config.moduleStats.bad,
846                                         passed: config.moduleStats.all - config.moduleStats.bad,
847                                         total: config.moduleStats.all,
848                                         runtime: now() - config.moduleStats.started
849                                 });
850                         }
851                         config.previousModule = this.module;
852                         config.moduleStats = { all: 0, bad: 0, started: now() };
853                         runLoggingCallbacks( "moduleStart", {
854                                 name: this.module.name,
855                                 tests: this.module.tests
856                         });
857                 }
859                 config.current = this;
861                 this.testEnvironment = extend( {}, this.module.testEnvironment );
862                 delete this.testEnvironment.beforeEach;
863                 delete this.testEnvironment.afterEach;
865                 this.started = now();
866                 runLoggingCallbacks( "testStart", {
867                         name: this.testName,
868                         module: this.module.name,
869                         testId: this.testId
870                 });
872                 if ( !config.pollution ) {
873                         saveGlobal();
874                 }
875         },
877         run: function() {
878                 var promise;
880                 config.current = this;
882                 if ( this.async ) {
883                         QUnit.stop();
884                 }
886                 this.callbackStarted = now();
888                 if ( config.notrycatch ) {
889                         promise = this.callback.call( this.testEnvironment, this.assert );
890                         this.resolvePromise( promise );
891                         return;
892                 }
894                 try {
895                         promise = this.callback.call( this.testEnvironment, this.assert );
896                         this.resolvePromise( promise );
897                 } catch ( e ) {
898                         this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
899                                 this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
901                         // else next test will carry the responsibility
902                         saveGlobal();
904                         // Restart the tests if they're blocking
905                         if ( config.blocking ) {
906                                 QUnit.start();
907                         }
908                 }
909         },
911         after: function() {
912                 checkPollution();
913         },
915         queueHook: function( hook, hookName ) {
916                 var promise,
917                         test = this;
918                 return function runHook() {
919                         config.current = test;
920                         if ( config.notrycatch ) {
921                                 promise = hook.call( test.testEnvironment, test.assert );
922                                 test.resolvePromise( promise, hookName );
923                                 return;
924                         }
925                         try {
926                                 promise = hook.call( test.testEnvironment, test.assert );
927                                 test.resolvePromise( promise, hookName );
928                         } catch ( error ) {
929                                 test.pushFailure( hookName + " failed on " + test.testName + ": " +
930                                         ( error.message || error ), extractStacktrace( error, 0 ) );
931                         }
932                 };
933         },
935         // Currently only used for module level hooks, can be used to add global level ones
936         hooks: function( handler ) {
937                 var hooks = [];
939                 // Hooks are ignored on skipped tests
940                 if ( this.skip ) {
941                         return hooks;
942                 }
944                 if ( this.module.testEnvironment &&
945                                 QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
946                         hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
947                 }
949                 return hooks;
950         },
952         finish: function() {
953                 config.current = this;
954                 if ( config.requireExpects && this.expected === null ) {
955                         this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
956                                 "not called.", this.stack );
957                 } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
958                         this.pushFailure( "Expected " + this.expected + " assertions, but " +
959                                 this.assertions.length + " were run", this.stack );
960                 } else if ( this.expected === null && !this.assertions.length ) {
961                         this.pushFailure( "Expected at least one assertion, but none were run - call " +
962                                 "expect(0) to accept zero assertions.", this.stack );
963                 }
965                 var i,
966                         bad = 0;
968                 this.runtime = now() - this.started;
969                 config.stats.all += this.assertions.length;
970                 config.moduleStats.all += this.assertions.length;
972                 for ( i = 0; i < this.assertions.length; i++ ) {
973                         if ( !this.assertions[ i ].result ) {
974                                 bad++;
975                                 config.stats.bad++;
976                                 config.moduleStats.bad++;
977                         }
978                 }
980                 runLoggingCallbacks( "testDone", {
981                         name: this.testName,
982                         module: this.module.name,
983                         skipped: !!this.skip,
984                         failed: bad,
985                         passed: this.assertions.length - bad,
986                         total: this.assertions.length,
987                         runtime: this.runtime,
989                         // HTML Reporter use
990                         assertions: this.assertions,
991                         testId: this.testId,
993                         // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
994                         duration: this.runtime
995                 });
997                 // QUnit.reset() is deprecated and will be replaced for a new
998                 // fixture reset function on QUnit 2.0/2.1.
999                 // It's still called here for backwards compatibility handling
1000                 QUnit.reset();
1002                 config.current = undefined;
1003         },
1005         queue: function() {
1006                 var bad,
1007                         test = this;
1009                 if ( !this.valid() ) {
1010                         return;
1011                 }
1013                 function run() {
1015                         // each of these can by async
1016                         synchronize([
1017                                 function() {
1018                                         test.before();
1019                                 },
1021                                 test.hooks( "beforeEach" ),
1023                                 function() {
1024                                         test.run();
1025                                 },
1027                                 test.hooks( "afterEach" ).reverse(),
1029                                 function() {
1030                                         test.after();
1031                                 },
1032                                 function() {
1033                                         test.finish();
1034                                 }
1035                         ]);
1036                 }
1038                 // `bad` initialized at top of scope
1039                 // defer when previous test run passed, if storage is available
1040                 bad = QUnit.config.reorder && defined.sessionStorage &&
1041                                 +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
1043                 if ( bad ) {
1044                         run();
1045                 } else {
1046                         synchronize( run, true );
1047                 }
1048         },
1050         push: function( result, actual, expected, message ) {
1051                 var source,
1052                         details = {
1053                                 module: this.module.name,
1054                                 name: this.testName,
1055                                 result: result,
1056                                 message: message,
1057                                 actual: actual,
1058                                 expected: expected,
1059                                 testId: this.testId,
1060                                 runtime: now() - this.started
1061                         };
1063                 if ( !result ) {
1064                         source = sourceFromStacktrace();
1066                         if ( source ) {
1067                                 details.source = source;
1068                         }
1069                 }
1071                 runLoggingCallbacks( "log", details );
1073                 this.assertions.push({
1074                         result: !!result,
1075                         message: message
1076                 });
1077         },
1079         pushFailure: function( message, source, actual ) {
1080                 if ( !this instanceof Test ) {
1081                         throw new Error( "pushFailure() assertion outside test context, was " +
1082                                 sourceFromStacktrace( 2 ) );
1083                 }
1085                 var details = {
1086                                 module: this.module.name,
1087                                 name: this.testName,
1088                                 result: false,
1089                                 message: message || "error",
1090                                 actual: actual || null,
1091                                 testId: this.testId,
1092                                 runtime: now() - this.started
1093                         };
1095                 if ( source ) {
1096                         details.source = source;
1097                 }
1099                 runLoggingCallbacks( "log", details );
1101                 this.assertions.push({
1102                         result: false,
1103                         message: message
1104                 });
1105         },
1107         resolvePromise: function( promise, phase ) {
1108                 var then, message,
1109                         test = this;
1110                 if ( promise != null ) {
1111                         then = promise.then;
1112                         if ( QUnit.objectType( then ) === "function" ) {
1113                                 QUnit.stop();
1114                                 then.call(
1115                                         promise,
1116                                         QUnit.start,
1117                                         function( error ) {
1118                                                 message = "Promise rejected " +
1119                                                         ( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
1120                                                         " " + test.testName + ": " + ( error.message || error );
1121                                                 test.pushFailure( message, extractStacktrace( error, 0 ) );
1123                                                 // else next test will carry the responsibility
1124                                                 saveGlobal();
1126                                                 // Unblock
1127                                                 QUnit.start();
1128                                         }
1129                                 );
1130                         }
1131                 }
1132         },
1134         valid: function() {
1135                 var include,
1136                         filter = config.filter && config.filter.toLowerCase(),
1137                         module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
1138                         fullName = ( this.module.name + ": " + this.testName ).toLowerCase();
1140                 // Internally-generated tests are always valid
1141                 if ( this.callback && this.callback.validTest ) {
1142                         return true;
1143                 }
1145                 if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
1146                         return false;
1147                 }
1149                 if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {
1150                         return false;
1151                 }
1153                 if ( !filter ) {
1154                         return true;
1155                 }
1157                 include = filter.charAt( 0 ) !== "!";
1158                 if ( !include ) {
1159                         filter = filter.slice( 1 );
1160                 }
1162                 // If the filter matches, we need to honour include
1163                 if ( fullName.indexOf( filter ) !== -1 ) {
1164                         return include;
1165                 }
1167                 // Otherwise, do the opposite
1168                 return !include;
1169         }
1173 // Resets the test setup. Useful for tests that modify the DOM.
1175 DEPRECATED: Use multiple tests instead of resetting inside a test.
1176 Use testStart or testDone for custom cleanup.
1177 This method will throw an error in 2.0, and will be removed in 2.1
1179 QUnit.reset = function() {
1181         // Return on non-browser environments
1182         // This is necessary to not break on node tests
1183         if ( typeof window === "undefined" ) {
1184                 return;
1185         }
1187         var fixture = defined.document && document.getElementById &&
1188                         document.getElementById( "qunit-fixture" );
1190         if ( fixture ) {
1191                 fixture.innerHTML = config.fixture;
1192         }
1195 QUnit.pushFailure = function() {
1196         if ( !QUnit.config.current ) {
1197                 throw new Error( "pushFailure() assertion outside test context, in " +
1198                         sourceFromStacktrace( 2 ) );
1199         }
1201         // Gets current test obj
1202         var currentTest = QUnit.config.current;
1204         return currentTest.pushFailure.apply( currentTest, arguments );
1207 // Based on Java's String.hashCode, a simple but not
1208 // rigorously collision resistant hashing function
1209 function generateHash( module, testName ) {
1210         var hex,
1211                 i = 0,
1212                 hash = 0,
1213                 str = module + "\x1C" + testName,
1214                 len = str.length;
1216         for ( ; i < len; i++ ) {
1217                 hash  = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
1218                 hash |= 0;
1219         }
1221         // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
1222         // strictly necessary but increases user understanding that the id is a SHA-like hash
1223         hex = ( 0x100000000 + hash ).toString( 16 );
1224         if ( hex.length < 8 ) {
1225                 hex = "0000000" + hex;
1226         }
1228         return hex.slice( -8 );
1231 function Assert( testContext ) {
1232         this.test = testContext;
1235 // Assert helpers
1236 QUnit.assert = Assert.prototype = {
1238         // Specify the number of expected assertions to guarantee that failed test
1239         // (no assertions are run at all) don't slip through.
1240         expect: function( asserts ) {
1241                 if ( arguments.length === 1 ) {
1242                         this.test.expected = asserts;
1243                 } else {
1244                         return this.test.expected;
1245                 }
1246         },
1248         // Increment this Test's semaphore counter, then return a single-use function that
1249         // decrements that counter a maximum of once.
1250         async: function() {
1251                 var test = this.test,
1252                         popped = false;
1254                 test.semaphore += 1;
1255                 test.usedAsync = true;
1256                 pauseProcessing();
1258                 return function done() {
1259                         if ( !popped ) {
1260                                 test.semaphore -= 1;
1261                                 popped = true;
1262                                 resumeProcessing();
1263                         } else {
1264                                 test.pushFailure( "Called the callback returned from `assert.async` more than once",
1265                                         sourceFromStacktrace( 2 ) );
1266                         }
1267                 };
1268         },
1270         // Exports test.push() to the user API
1271         push: function( /* result, actual, expected, message */ ) {
1272                 var assert = this,
1273                         currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
1275                 // Backwards compatibility fix.
1276                 // Allows the direct use of global exported assertions and QUnit.assert.*
1277                 // Although, it's use is not recommended as it can leak assertions
1278                 // to other tests from async tests, because we only get a reference to the current test,
1279                 // not exactly the test where assertion were intended to be called.
1280                 if ( !currentTest ) {
1281                         throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
1282                 }
1284                 if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
1285                         currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
1286                                 sourceFromStacktrace( 2 ) );
1288                         // Allow this assertion to continue running anyway...
1289                 }
1291                 if ( !( assert instanceof Assert ) ) {
1292                         assert = currentTest.assert;
1293                 }
1294                 return assert.test.push.apply( assert.test, arguments );
1295         },
1297         ok: function( result, message ) {
1298                 message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
1299                         QUnit.dump.parse( result ) );
1300                 this.push( !!result, result, true, message );
1301         },
1303         notOk: function( result, message ) {
1304                 message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " +
1305                         QUnit.dump.parse( result ) );
1306                 this.push( !result, result, false, message );
1307         },
1309         equal: function( actual, expected, message ) {
1310                 /*jshint eqeqeq:false */
1311                 this.push( expected == actual, actual, expected, message );
1312         },
1314         notEqual: function( actual, expected, message ) {
1315                 /*jshint eqeqeq:false */
1316                 this.push( expected != actual, actual, expected, message );
1317         },
1319         propEqual: function( actual, expected, message ) {
1320                 actual = objectValues( actual );
1321                 expected = objectValues( expected );
1322                 this.push( QUnit.equiv( actual, expected ), actual, expected, message );
1323         },
1325         notPropEqual: function( actual, expected, message ) {
1326                 actual = objectValues( actual );
1327                 expected = objectValues( expected );
1328                 this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
1329         },
1331         deepEqual: function( actual, expected, message ) {
1332                 this.push( QUnit.equiv( actual, expected ), actual, expected, message );
1333         },
1335         notDeepEqual: function( actual, expected, message ) {
1336                 this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
1337         },
1339         strictEqual: function( actual, expected, message ) {
1340                 this.push( expected === actual, actual, expected, message );
1341         },
1343         notStrictEqual: function( actual, expected, message ) {
1344                 this.push( expected !== actual, actual, expected, message );
1345         },
1347         "throws": function( block, expected, message ) {
1348                 var actual, expectedType,
1349                         expectedOutput = expected,
1350                         ok = false,
1351                         currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current;
1353                 // 'expected' is optional unless doing string comparison
1354                 if ( message == null && typeof expected === "string" ) {
1355                         message = expected;
1356                         expected = null;
1357                 }
1359                 currentTest.ignoreGlobalErrors = true;
1360                 try {
1361                         block.call( currentTest.testEnvironment );
1362                 } catch (e) {
1363                         actual = e;
1364                 }
1365                 currentTest.ignoreGlobalErrors = false;
1367                 if ( actual ) {
1368                         expectedType = QUnit.objectType( expected );
1370                         // we don't want to validate thrown error
1371                         if ( !expected ) {
1372                                 ok = true;
1373                                 expectedOutput = null;
1375                         // expected is a regexp
1376                         } else if ( expectedType === "regexp" ) {
1377                                 ok = expected.test( errorString( actual ) );
1379                         // expected is a string
1380                         } else if ( expectedType === "string" ) {
1381                                 ok = expected === errorString( actual );
1383                         // expected is a constructor, maybe an Error constructor
1384                         } else if ( expectedType === "function" && actual instanceof expected ) {
1385                                 ok = true;
1387                         // expected is an Error object
1388                         } else if ( expectedType === "object" ) {
1389                                 ok = actual instanceof expected.constructor &&
1390                                         actual.name === expected.name &&
1391                                         actual.message === expected.message;
1393                         // expected is a validation function which returns true if validation passed
1394                         } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
1395                                 expectedOutput = null;
1396                                 ok = true;
1397                         }
1398                 }
1400                 currentTest.assert.push( ok, actual, expectedOutput, message );
1401         }
1404 // Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word
1405 // Known to us are: Closure Compiler, Narwhal
1406 (function() {
1407         /*jshint sub:true */
1408         Assert.prototype.raises = Assert.prototype[ "throws" ];
1409 }());
1411 // Test for equality any JavaScript type.
1412 // Author: Philippe Rathé <prathe@gmail.com>
1413 QUnit.equiv = (function() {
1415         // Call the o related callback with the given arguments.
1416         function bindCallbacks( o, callbacks, args ) {
1417                 var prop = QUnit.objectType( o );
1418                 if ( prop ) {
1419                         if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
1420                                 return callbacks[ prop ].apply( callbacks, args );
1421                         } else {
1422                                 return callbacks[ prop ]; // or undefined
1423                         }
1424                 }
1425         }
1427         // the real equiv function
1428         var innerEquiv,
1430                 // stack to decide between skip/abort functions
1431                 callers = [],
1433                 // stack to avoiding loops from circular referencing
1434                 parents = [],
1435                 parentsB = [],
1437                 getProto = Object.getPrototypeOf || function( obj ) {
1438                         /* jshint camelcase: false, proto: true */
1439                         return obj.__proto__;
1440                 },
1441                 callbacks = (function() {
1443                         // for string, boolean, number and null
1444                         function useStrictEquality( b, a ) {
1446                                 /*jshint eqeqeq:false */
1447                                 if ( b instanceof a.constructor || a instanceof b.constructor ) {
1449                                         // to catch short annotation VS 'new' annotation of a
1450                                         // declaration
1451                                         // e.g. var i = 1;
1452                                         // var j = new Number(1);
1453                                         return a == b;
1454                                 } else {
1455                                         return a === b;
1456                                 }
1457                         }
1459                         return {
1460                                 "string": useStrictEquality,
1461                                 "boolean": useStrictEquality,
1462                                 "number": useStrictEquality,
1463                                 "null": useStrictEquality,
1464                                 "undefined": useStrictEquality,
1466                                 "nan": function( b ) {
1467                                         return isNaN( b );
1468                                 },
1470                                 "date": function( b, a ) {
1471                                         return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
1472                                 },
1474                                 "regexp": function( b, a ) {
1475                                         return QUnit.objectType( b ) === "regexp" &&
1477                                                 // the regex itself
1478                                                 a.source === b.source &&
1480                                                 // and its modifiers
1481                                                 a.global === b.global &&
1483                                                 // (gmi) ...
1484                                                 a.ignoreCase === b.ignoreCase &&
1485                                                 a.multiline === b.multiline &&
1486                                                 a.sticky === b.sticky;
1487                                 },
1489                                 // - skip when the property is a method of an instance (OOP)
1490                                 // - abort otherwise,
1491                                 // initial === would have catch identical references anyway
1492                                 "function": function() {
1493                                         var caller = callers[ callers.length - 1 ];
1494                                         return caller !== Object && typeof caller !== "undefined";
1495                                 },
1497                                 "array": function( b, a ) {
1498                                         var i, j, len, loop, aCircular, bCircular;
1500                                         // b could be an object literal here
1501                                         if ( QUnit.objectType( b ) !== "array" ) {
1502                                                 return false;
1503                                         }
1505                                         len = a.length;
1506                                         if ( len !== b.length ) {
1507                                                 // safe and faster
1508                                                 return false;
1509                                         }
1511                                         // track reference to avoid circular references
1512                                         parents.push( a );
1513                                         parentsB.push( b );
1514                                         for ( i = 0; i < len; i++ ) {
1515                                                 loop = false;
1516                                                 for ( j = 0; j < parents.length; j++ ) {
1517                                                         aCircular = parents[ j ] === a[ i ];
1518                                                         bCircular = parentsB[ j ] === b[ i ];
1519                                                         if ( aCircular || bCircular ) {
1520                                                                 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1521                                                                         loop = true;
1522                                                                 } else {
1523                                                                         parents.pop();
1524                                                                         parentsB.pop();
1525                                                                         return false;
1526                                                                 }
1527                                                         }
1528                                                 }
1529                                                 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1530                                                         parents.pop();
1531                                                         parentsB.pop();
1532                                                         return false;
1533                                                 }
1534                                         }
1535                                         parents.pop();
1536                                         parentsB.pop();
1537                                         return true;
1538                                 },
1540                                 "object": function( b, a ) {
1542                                         /*jshint forin:false */
1543                                         var i, j, loop, aCircular, bCircular,
1544                                                 // Default to true
1545                                                 eq = true,
1546                                                 aProperties = [],
1547                                                 bProperties = [];
1549                                         // comparing constructors is more strict than using
1550                                         // instanceof
1551                                         if ( a.constructor !== b.constructor ) {
1553                                                 // Allow objects with no prototype to be equivalent to
1554                                                 // objects with Object as their constructor.
1555                                                 if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
1556                                                         ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
1557                                                         return false;
1558                                                 }
1559                                         }
1561                                         // stack constructor before traversing properties
1562                                         callers.push( a.constructor );
1564                                         // track reference to avoid circular references
1565                                         parents.push( a );
1566                                         parentsB.push( b );
1568                                         // be strict: don't ensure hasOwnProperty and go deep
1569                                         for ( i in a ) {
1570                                                 loop = false;
1571                                                 for ( j = 0; j < parents.length; j++ ) {
1572                                                         aCircular = parents[ j ] === a[ i ];
1573                                                         bCircular = parentsB[ j ] === b[ i ];
1574                                                         if ( aCircular || bCircular ) {
1575                                                                 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1576                                                                         loop = true;
1577                                                                 } else {
1578                                                                         eq = false;
1579                                                                         break;
1580                                                                 }
1581                                                         }
1582                                                 }
1583                                                 aProperties.push( i );
1584                                                 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1585                                                         eq = false;
1586                                                         break;
1587                                                 }
1588                                         }
1590                                         parents.pop();
1591                                         parentsB.pop();
1592                                         callers.pop(); // unstack, we are done
1594                                         for ( i in b ) {
1595                                                 bProperties.push( i ); // collect b's properties
1596                                         }
1598                                         // Ensures identical properties name
1599                                         return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
1600                                 }
1601                         };
1602                 }());
1604         innerEquiv = function() { // can take multiple arguments
1605                 var args = [].slice.apply( arguments );
1606                 if ( args.length < 2 ) {
1607                         return true; // end transition
1608                 }
1610                 return ( (function( a, b ) {
1611                         if ( a === b ) {
1612                                 return true; // catch the most you can
1613                         } else if ( a === null || b === null || typeof a === "undefined" ||
1614                                         typeof b === "undefined" ||
1615                                         QUnit.objectType( a ) !== QUnit.objectType( b ) ) {
1617                                 // don't lose time with error prone cases
1618                                 return false;
1619                         } else {
1620                                 return bindCallbacks( a, callbacks, [ b, a ] );
1621                         }
1623                         // apply transition with (1..n) arguments
1624                 }( args[ 0 ], args[ 1 ] ) ) &&
1625                         innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
1626         };
1628         return innerEquiv;
1629 }());
1631 // Based on jsDump by Ariel Flesler
1632 // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
1633 QUnit.dump = (function() {
1634         function quote( str ) {
1635                 return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
1636         }
1637         function literal( o ) {
1638                 return o + "";
1639         }
1640         function join( pre, arr, post ) {
1641                 var s = dump.separator(),
1642                         base = dump.indent(),
1643                         inner = dump.indent( 1 );
1644                 if ( arr.join ) {
1645                         arr = arr.join( "," + s + inner );
1646                 }
1647                 if ( !arr ) {
1648                         return pre + post;
1649                 }
1650                 return [ pre, inner + arr, base + post ].join( s );
1651         }
1652         function array( arr, stack ) {
1653                 var i = arr.length,
1654                         ret = new Array( i );
1656                 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1657                         return "[object Array]";
1658                 }
1660                 this.up();
1661                 while ( i-- ) {
1662                         ret[ i ] = this.parse( arr[ i ], undefined, stack );
1663                 }
1664                 this.down();
1665                 return join( "[", ret, "]" );
1666         }
1668         var reName = /^function (\w+)/,
1669                 dump = {
1671                         // objType is used mostly internally, you can fix a (custom) type in advance
1672                         parse: function( obj, objType, stack ) {
1673                                 stack = stack || [];
1674                                 var res, parser, parserType,
1675                                         inStack = inArray( obj, stack );
1677                                 if ( inStack !== -1 ) {
1678                                         return "recursion(" + ( inStack - stack.length ) + ")";
1679                                 }
1681                                 objType = objType || this.typeOf( obj  );
1682                                 parser = this.parsers[ objType ];
1683                                 parserType = typeof parser;
1685                                 if ( parserType === "function" ) {
1686                                         stack.push( obj );
1687                                         res = parser.call( this, obj, stack );
1688                                         stack.pop();
1689                                         return res;
1690                                 }
1691                                 return ( parserType === "string" ) ? parser : this.parsers.error;
1692                         },
1693                         typeOf: function( obj ) {
1694                                 var type;
1695                                 if ( obj === null ) {
1696                                         type = "null";
1697                                 } else if ( typeof obj === "undefined" ) {
1698                                         type = "undefined";
1699                                 } else if ( QUnit.is( "regexp", obj ) ) {
1700                                         type = "regexp";
1701                                 } else if ( QUnit.is( "date", obj ) ) {
1702                                         type = "date";
1703                                 } else if ( QUnit.is( "function", obj ) ) {
1704                                         type = "function";
1705                                 } else if ( obj.setInterval !== undefined &&
1706                                                 obj.document !== undefined &&
1707                                                 obj.nodeType === undefined ) {
1708                                         type = "window";
1709                                 } else if ( obj.nodeType === 9 ) {
1710                                         type = "document";
1711                                 } else if ( obj.nodeType ) {
1712                                         type = "node";
1713                                 } else if (
1715                                         // native arrays
1716                                         toString.call( obj ) === "[object Array]" ||
1718                                         // NodeList objects
1719                                         ( typeof obj.length === "number" && obj.item !== undefined &&
1720                                         ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
1721                                         obj[ 0 ] === undefined ) ) )
1722                                 ) {
1723                                         type = "array";
1724                                 } else if ( obj.constructor === Error.prototype.constructor ) {
1725                                         type = "error";
1726                                 } else {
1727                                         type = typeof obj;
1728                                 }
1729                                 return type;
1730                         },
1731                         separator: function() {
1732                                 return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
1733                         },
1734                         // extra can be a number, shortcut for increasing-calling-decreasing
1735                         indent: function( extra ) {
1736                                 if ( !this.multiline ) {
1737                                         return "";
1738                                 }
1739                                 var chr = this.indentChar;
1740                                 if ( this.HTML ) {
1741                                         chr = chr.replace( /\t/g, "   " ).replace( / /g, "&#160;" );
1742                                 }
1743                                 return new Array( this.depth + ( extra || 0 ) ).join( chr );
1744                         },
1745                         up: function( a ) {
1746                                 this.depth += a || 1;
1747                         },
1748                         down: function( a ) {
1749                                 this.depth -= a || 1;
1750                         },
1751                         setParser: function( name, parser ) {
1752                                 this.parsers[ name ] = parser;
1753                         },
1754                         // The next 3 are exposed so you can use them
1755                         quote: quote,
1756                         literal: literal,
1757                         join: join,
1758                         //
1759                         depth: 1,
1760                         maxDepth: QUnit.config.maxDepth,
1762                         // This is the list of parsers, to modify them, use dump.setParser
1763                         parsers: {
1764                                 window: "[Window]",
1765                                 document: "[Document]",
1766                                 error: function( error ) {
1767                                         return "Error(\"" + error.message + "\")";
1768                                 },
1769                                 unknown: "[Unknown]",
1770                                 "null": "null",
1771                                 "undefined": "undefined",
1772                                 "function": function( fn ) {
1773                                         var ret = "function",
1775                                                 // functions never have name in IE
1776                                                 name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
1778                                         if ( name ) {
1779                                                 ret += " " + name;
1780                                         }
1781                                         ret += "( ";
1783                                         ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
1784                                         return join( ret, dump.parse( fn, "functionCode" ), "}" );
1785                                 },
1786                                 array: array,
1787                                 nodelist: array,
1788                                 "arguments": array,
1789                                 object: function( map, stack ) {
1790                                         var keys, key, val, i, nonEnumerableProperties,
1791                                                 ret = [];
1793                                         if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1794                                                 return "[object Object]";
1795                                         }
1797                                         dump.up();
1798                                         keys = [];
1799                                         for ( key in map ) {
1800                                                 keys.push( key );
1801                                         }
1803                                         // Some properties are not always enumerable on Error objects.
1804                                         nonEnumerableProperties = [ "message", "name" ];
1805                                         for ( i in nonEnumerableProperties ) {
1806                                                 key = nonEnumerableProperties[ i ];
1807                                                 if ( key in map && inArray( key, keys ) < 0 ) {
1808                                                         keys.push( key );
1809                                                 }
1810                                         }
1811                                         keys.sort();
1812                                         for ( i = 0; i < keys.length; i++ ) {
1813                                                 key = keys[ i ];
1814                                                 val = map[ key ];
1815                                                 ret.push( dump.parse( key, "key" ) + ": " +
1816                                                         dump.parse( val, undefined, stack ) );
1817                                         }
1818                                         dump.down();
1819                                         return join( "{", ret, "}" );
1820                                 },
1821                                 node: function( node ) {
1822                                         var len, i, val,
1823                                                 open = dump.HTML ? "&lt;" : "<",
1824                                                 close = dump.HTML ? "&gt;" : ">",
1825                                                 tag = node.nodeName.toLowerCase(),
1826                                                 ret = open + tag,
1827                                                 attrs = node.attributes;
1829                                         if ( attrs ) {
1830                                                 for ( i = 0, len = attrs.length; i < len; i++ ) {
1831                                                         val = attrs[ i ].nodeValue;
1833                                                         // IE6 includes all attributes in .attributes, even ones not explicitly
1834                                                         // set. Those have values like undefined, null, 0, false, "" or
1835                                                         // "inherit".
1836                                                         if ( val && val !== "inherit" ) {
1837                                                                 ret += " " + attrs[ i ].nodeName + "=" +
1838                                                                         dump.parse( val, "attribute" );
1839                                                         }
1840                                                 }
1841                                         }
1842                                         ret += close;
1844                                         // Show content of TextNode or CDATASection
1845                                         if ( node.nodeType === 3 || node.nodeType === 4 ) {
1846                                                 ret += node.nodeValue;
1847                                         }
1849                                         return ret + open + "/" + tag + close;
1850                                 },
1852                                 // function calls it internally, it's the arguments part of the function
1853                                 functionArgs: function( fn ) {
1854                                         var args,
1855                                                 l = fn.length;
1857                                         if ( !l ) {
1858                                                 return "";
1859                                         }
1861                                         args = new Array( l );
1862                                         while ( l-- ) {
1864                                                 // 97 is 'a'
1865                                                 args[ l ] = String.fromCharCode( 97 + l );
1866                                         }
1867                                         return " " + args.join( ", " ) + " ";
1868                                 },
1869                                 // object calls it internally, the key part of an item in a map
1870                                 key: quote,
1871                                 // function calls it internally, it's the content of the function
1872                                 functionCode: "[code]",
1873                                 // node calls it internally, it's an html attribute value
1874                                 attribute: quote,
1875                                 string: quote,
1876                                 date: quote,
1877                                 regexp: literal,
1878                                 number: literal,
1879                                 "boolean": literal
1880                         },
1881                         // if true, entities are escaped ( <, >, \t, space and \n )
1882                         HTML: false,
1883                         // indentation unit
1884                         indentChar: "  ",
1885                         // if true, items in a collection, are separated by a \n, else just a space.
1886                         multiline: true
1887                 };
1889         return dump;
1890 }());
1892 // back compat
1893 QUnit.jsDump = QUnit.dump;
1895 // For browser, export only select globals
1896 if ( typeof window !== "undefined" ) {
1898         // Deprecated
1899         // Extend assert methods to QUnit and Global scope through Backwards compatibility
1900         (function() {
1901                 var i,
1902                         assertions = Assert.prototype;
1904                 function applyCurrent( current ) {
1905                         return function() {
1906                                 var assert = new Assert( QUnit.config.current );
1907                                 current.apply( assert, arguments );
1908                         };
1909                 }
1911                 for ( i in assertions ) {
1912                         QUnit[ i ] = applyCurrent( assertions[ i ] );
1913                 }
1914         })();
1916         (function() {
1917                 var i, l,
1918                         keys = [
1919                                 "test",
1920                                 "module",
1921                                 "expect",
1922                                 "asyncTest",
1923                                 "start",
1924                                 "stop",
1925                                 "ok",
1926                                 "notOk",
1927                                 "equal",
1928                                 "notEqual",
1929                                 "propEqual",
1930                                 "notPropEqual",
1931                                 "deepEqual",
1932                                 "notDeepEqual",
1933                                 "strictEqual",
1934                                 "notStrictEqual",
1935                                 "throws"
1936                         ];
1938                 for ( i = 0, l = keys.length; i < l; i++ ) {
1939                         window[ keys[ i ] ] = QUnit[ keys[ i ] ];
1940                 }
1941         })();
1943         window.QUnit = QUnit;
1946 // For nodejs
1947 if ( typeof module !== "undefined" && module && module.exports ) {
1948         module.exports = QUnit;
1950         // For consistency with CommonJS environments' exports
1951         module.exports.QUnit = QUnit;
1954 // For CommonJS with exports, but without module.exports, like Rhino
1955 if ( typeof exports !== "undefined" && exports ) {
1956         exports.QUnit = QUnit;
1959 if ( typeof define === "function" && define.amd ) {
1960         define( function() {
1961                 return QUnit;
1962         } );
1963         QUnit.config.autostart = false;
1966 // Get a reference to the global object, like window in browsers
1967 }( (function() {
1968         return this;
1969 })() ));
1971 /*istanbul ignore next */
1972 // jscs:disable maximumLineLength
1974  * This file is a modified version of google-diff-match-patch's JavaScript implementation
1975  * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
1976  * modifications are licensed as more fully set forth in LICENSE.txt.
1978  * The original source of google-diff-match-patch is attributable and licensed as follows:
1980  * Copyright 2006 Google Inc.
1981  * http://code.google.com/p/google-diff-match-patch/
1983  * Licensed under the Apache License, Version 2.0 (the "License");
1984  * you may not use this file except in compliance with the License.
1985  * You may obtain a copy of the License at
1987  * http://www.apache.org/licenses/LICENSE-2.0
1989  * Unless required by applicable law or agreed to in writing, software
1990  * distributed under the License is distributed on an "AS IS" BASIS,
1991  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1992  * See the License for the specific language governing permissions and
1993  * limitations under the License.
1995  * More Info:
1996  *  https://code.google.com/p/google-diff-match-patch/
1998  * Usage: QUnit.diff(expected, actual)
2000  * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) === "the  quick <del>brown </del> fox jump<ins>s</ins><del>ed</del over"
2001  */
2002 QUnit.diff = (function() {
2004     function DiffMatchPatch() {
2006         // Defaults.
2007         // Redefine these in your program to override the defaults.
2009         // Number of seconds to map a diff before giving up (0 for infinity).
2010         this.DiffTimeout = 1.0;
2011         // Cost of an empty edit operation in terms of edit characters.
2012         this.DiffEditCost = 4;
2013     }
2015     //  DIFF FUNCTIONS
2017     /**
2018      * The data structure representing a diff is an array of tuples:
2019      * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
2020      * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
2021      */
2022     var DIFF_DELETE = -1,
2023                 DIFF_INSERT = 1,
2024                 DIFF_EQUAL = 0;
2026     /**
2027      * Find the differences between two texts.  Simplifies the problem by stripping
2028      * any common prefix or suffix off the texts before diffing.
2029      * @param {string} text1 Old string to be diffed.
2030      * @param {string} text2 New string to be diffed.
2031      * @param {boolean=} optChecklines Optional speedup flag. If present and false,
2032      *     then don't run a line-level diff first to identify the changed areas.
2033      *     Defaults to true, which does a faster, slightly less optimal diff.
2034      * @param {number} optDeadline Optional time when the diff should be complete
2035      *     by.  Used internally for recursive calls.  Users should set DiffTimeout
2036      *     instead.
2037      * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2038      */
2039     DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines, optDeadline ) {
2040         var deadline, checklines, commonlength,
2041                         commonprefix, commonsuffix, diffs;
2042         // Set a deadline by which time the diff must be complete.
2043         if ( typeof optDeadline === "undefined" ) {
2044             if ( this.DiffTimeout <= 0 ) {
2045                 optDeadline = Number.MAX_VALUE;
2046             } else {
2047                 optDeadline = ( new Date() ).getTime() + this.DiffTimeout * 1000;
2048             }
2049         }
2050         deadline = optDeadline;
2052         // Check for null inputs.
2053         if ( text1 === null || text2 === null ) {
2054             throw new Error( "Null input. (DiffMain)" );
2055         }
2057         // Check for equality (speedup).
2058         if ( text1 === text2 ) {
2059             if ( text1 ) {
2060                 return [
2061                     [ DIFF_EQUAL, text1 ]
2062                 ];
2063             }
2064             return [];
2065         }
2067         if ( typeof optChecklines === "undefined" ) {
2068             optChecklines = true;
2069         }
2071         checklines = optChecklines;
2073         // Trim off common prefix (speedup).
2074         commonlength = this.diffCommonPrefix( text1, text2 );
2075         commonprefix = text1.substring( 0, commonlength );
2076         text1 = text1.substring( commonlength );
2077         text2 = text2.substring( commonlength );
2079         // Trim off common suffix (speedup).
2080         /////////
2081         commonlength = this.diffCommonSuffix( text1, text2 );
2082         commonsuffix = text1.substring( text1.length - commonlength );
2083         text1 = text1.substring( 0, text1.length - commonlength );
2084         text2 = text2.substring( 0, text2.length - commonlength );
2086         // Compute the diff on the middle block.
2087         diffs = this.diffCompute( text1, text2, checklines, deadline );
2089         // Restore the prefix and suffix.
2090         if ( commonprefix ) {
2091             diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
2092         }
2093         if ( commonsuffix ) {
2094             diffs.push( [ DIFF_EQUAL, commonsuffix ] );
2095         }
2096         this.diffCleanupMerge( diffs );
2097         return diffs;
2098     };
2100     /**
2101      * Reduce the number of edits by eliminating operationally trivial equalities.
2102      * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2103      */
2104     DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
2105         var changes, equalities, equalitiesLength, lastequality,
2106                         pointer, preIns, preDel, postIns, postDel;
2107         changes = false;
2108         equalities = []; // Stack of indices where equalities are found.
2109         equalitiesLength = 0; // Keeping our own length var is faster in JS.
2110         /** @type {?string} */
2111         lastequality = null;
2112         // Always equal to diffs[equalities[equalitiesLength - 1]][1]
2113         pointer = 0; // Index of current position.
2114         // Is there an insertion operation before the last equality.
2115         preIns = false;
2116         // Is there a deletion operation before the last equality.
2117         preDel = false;
2118         // Is there an insertion operation after the last equality.
2119         postIns = false;
2120         // Is there a deletion operation after the last equality.
2121         postDel = false;
2122         while ( pointer < diffs.length ) {
2123             if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
2124                 if ( diffs[ pointer ][ 1 ].length < this.DiffEditCost && ( postIns || postDel ) ) {
2125                     // Candidate found.
2126                     equalities[ equalitiesLength++ ] = pointer;
2127                     preIns = postIns;
2128                     preDel = postDel;
2129                     lastequality = diffs[ pointer ][ 1 ];
2130                 } else {
2131                     // Not a candidate, and can never become one.
2132                     equalitiesLength = 0;
2133                     lastequality = null;
2134                 }
2135                 postIns = postDel = false;
2136             } else { // An insertion or deletion.
2137                 if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
2138                     postDel = true;
2139                 } else {
2140                     postIns = true;
2141                 }
2142                 /*
2143                  * Five types to be split:
2144                  * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
2145                  * <ins>A</ins>X<ins>C</ins><del>D</del>
2146                  * <ins>A</ins><del>B</del>X<ins>C</ins>
2147                  * <ins>A</del>X<ins>C</ins><del>D</del>
2148                  * <ins>A</ins><del>B</del>X<del>C</del>
2149                  */
2150                 if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
2151                         ( ( lastequality.length < this.DiffEditCost / 2 ) &&
2152                             ( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
2153                     // Duplicate record.
2154                     diffs.splice( equalities[equalitiesLength - 1], 0, [ DIFF_DELETE, lastequality ] );
2155                     // Change second copy to insert.
2156                     diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
2157                     equalitiesLength--; // Throw away the equality we just deleted;
2158                     lastequality = null;
2159                     if (preIns && preDel) {
2160                         // No changes made which could affect previous entry, keep going.
2161                         postIns = postDel = true;
2162                         equalitiesLength = 0;
2163                     } else {
2164                         equalitiesLength--; // Throw away the previous equality.
2165                         pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
2166                         postIns = postDel = false;
2167                     }
2168                     changes = true;
2169                 }
2170             }
2171             pointer++;
2172         }
2174         if ( changes ) {
2175             this.diffCleanupMerge( diffs );
2176         }
2177     };
2179     /**
2180      * Convert a diff array into a pretty HTML report.
2181      * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2182      * @param {integer} string to be beautified.
2183      * @return {string} HTML representation.
2184      */
2185     DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
2186         var op, data, x, html = [];
2187         for ( x = 0; x < diffs.length; x++ ) {
2188             op = diffs[x][0]; // Operation (insert, delete, equal)
2189             data = diffs[x][1]; // Text of change.
2190             switch ( op ) {
2191                 case DIFF_INSERT:
2192                     html[x] = "<ins>" + data + "</ins>";
2193                     break;
2194                 case DIFF_DELETE:
2195                     html[x] = "<del>" + data + "</del>";
2196                     break;
2197                 case DIFF_EQUAL:
2198                     html[x] = "<span>" + data + "</span>";
2199                     break;
2200             }
2201         }
2202         return html.join("");
2203     };
2205     /**
2206      * Determine the common prefix of two strings.
2207      * @param {string} text1 First string.
2208      * @param {string} text2 Second string.
2209      * @return {number} The number of characters common to the start of each
2210      *     string.
2211      */
2212     DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
2213         var pointermid, pointermax, pointermin, pointerstart;
2214         // Quick check for common null cases.
2215         if ( !text1 || !text2 || text1.charAt(0) !== text2.charAt(0) ) {
2216             return 0;
2217         }
2218         // Binary search.
2219         // Performance analysis: http://neil.fraser.name/news/2007/10/09/
2220         pointermin = 0;
2221         pointermax = Math.min( text1.length, text2.length );
2222         pointermid = pointermax;
2223         pointerstart = 0;
2224         while ( pointermin < pointermid ) {
2225             if ( text1.substring( pointerstart, pointermid ) === text2.substring( pointerstart, pointermid ) ) {
2226                 pointermin = pointermid;
2227                 pointerstart = pointermin;
2228             } else {
2229                 pointermax = pointermid;
2230             }
2231             pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
2232         }
2233         return pointermid;
2234     };
2236     /**
2237      * Determine the common suffix of two strings.
2238      * @param {string} text1 First string.
2239      * @param {string} text2 Second string.
2240      * @return {number} The number of characters common to the end of each string.
2241      */
2242     DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
2243         var pointermid, pointermax, pointermin, pointerend;
2244         // Quick check for common null cases.
2245         if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) {
2246             return 0;
2247         }
2248         // Binary search.
2249         // Performance analysis: http://neil.fraser.name/news/2007/10/09/
2250         pointermin = 0;
2251         pointermax = Math.min(text1.length, text2.length);
2252         pointermid = pointermax;
2253         pointerend = 0;
2254         while ( pointermin < pointermid ) {
2255             if (text1.substring( text1.length - pointermid, text1.length - pointerend ) ===
2256                 text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {
2257                 pointermin = pointermid;
2258                 pointerend = pointermin;
2259             } else {
2260                 pointermax = pointermid;
2261             }
2262             pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
2263         }
2264         return pointermid;
2265     };
2267     /**
2268      * Find the differences between two texts.  Assumes that the texts do not
2269      * have any common prefix or suffix.
2270      * @param {string} text1 Old string to be diffed.
2271      * @param {string} text2 New string to be diffed.
2272      * @param {boolean} checklines Speedup flag.  If false, then don't run a
2273      *     line-level diff first to identify the changed areas.
2274      *     If true, then run a faster, slightly less optimal diff.
2275      * @param {number} deadline Time when the diff should be complete by.
2276      * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2277      * @private
2278      */
2279     DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
2280         var diffs, longtext, shorttext, i, hm,
2281                         text1A, text2A, text1B, text2B,
2282                         midCommon, diffsA, diffsB;
2284         if ( !text1 ) {
2285             // Just add some text (speedup).
2286             return [
2287                 [ DIFF_INSERT, text2 ]
2288             ];
2289         }
2291         if (!text2) {
2292             // Just delete some text (speedup).
2293             return [
2294                 [ DIFF_DELETE, text1 ]
2295             ];
2296         }
2298         longtext = text1.length > text2.length ? text1 : text2;
2299         shorttext = text1.length > text2.length ? text2 : text1;
2300         i = longtext.indexOf( shorttext );
2301         if ( i !== -1 ) {
2302             // Shorter text is inside the longer text (speedup).
2303             diffs = [
2304                 [ DIFF_INSERT, longtext.substring( 0, i ) ],
2305                 [ DIFF_EQUAL, shorttext ],
2306                 [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
2307             ];
2308             // Swap insertions for deletions if diff is reversed.
2309             if ( text1.length > text2.length ) {
2310                 diffs[0][0] = diffs[2][0] = DIFF_DELETE;
2311             }
2312             return diffs;
2313         }
2315         if ( shorttext.length === 1 ) {
2316             // Single character string.
2317             // After the previous speedup, the character can't be an equality.
2318             return [
2319                 [ DIFF_DELETE, text1 ],
2320                 [ DIFF_INSERT, text2 ]
2321             ];
2322         }
2324         // Check to see if the problem can be split in two.
2325         hm = this.diffHalfMatch(text1, text2);
2326         if (hm) {
2327             // A half-match was found, sort out the return data.
2328             text1A = hm[0];
2329             text1B = hm[1];
2330             text2A = hm[2];
2331             text2B = hm[3];
2332             midCommon = hm[4];
2333             // Send both pairs off for separate processing.
2334             diffsA = this.DiffMain(text1A, text2A, checklines, deadline);
2335             diffsB = this.DiffMain(text1B, text2B, checklines, deadline);
2336             // Merge the results.
2337             return diffsA.concat([
2338                 [ DIFF_EQUAL, midCommon ]
2339             ], diffsB);
2340         }
2342         if (checklines && text1.length > 100 && text2.length > 100) {
2343             return this.diffLineMode(text1, text2, deadline);
2344         }
2346         return this.diffBisect(text1, text2, deadline);
2347     };
2349     /**
2350      * Do the two texts share a substring which is at least half the length of the
2351      * longer text?
2352      * This speedup can produce non-minimal diffs.
2353      * @param {string} text1 First string.
2354      * @param {string} text2 Second string.
2355      * @return {Array.<string>} Five element Array, containing the prefix of
2356      *     text1, the suffix of text1, the prefix of text2, the suffix of
2357      *     text2 and the common middle.  Or null if there was no match.
2358      * @private
2359      */
2360     DiffMatchPatch.prototype.diffHalfMatch = function(text1, text2) {
2361         var longtext, shorttext, dmp,
2362                         text1A, text2B, text2A, text1B, midCommon,
2363                         hm1, hm2, hm;
2364         if (this.DiffTimeout <= 0) {
2365             // Don't risk returning a non-optimal diff if we have unlimited time.
2366             return null;
2367         }
2368         longtext = text1.length > text2.length ? text1 : text2;
2369         shorttext = text1.length > text2.length ? text2 : text1;
2370         if (longtext.length < 4 || shorttext.length * 2 < longtext.length) {
2371             return null; // Pointless.
2372         }
2373         dmp = this; // 'this' becomes 'window' in a closure.
2375         /**
2376          * Does a substring of shorttext exist within longtext such that the substring
2377          * is at least half the length of longtext?
2378          * Closure, but does not reference any external variables.
2379          * @param {string} longtext Longer string.
2380          * @param {string} shorttext Shorter string.
2381          * @param {number} i Start index of quarter length substring within longtext.
2382          * @return {Array.<string>} Five element Array, containing the prefix of
2383          *     longtext, the suffix of longtext, the prefix of shorttext, the suffix
2384          *     of shorttext and the common middle.  Or null if there was no match.
2385          * @private
2386          */
2387         function diffHalfMatchI(longtext, shorttext, i) {
2388             var seed, j, bestCommon, prefixLength, suffixLength,
2389                                 bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
2390             // Start with a 1/4 length substring at position i as a seed.
2391             seed = longtext.substring(i, i + Math.floor(longtext.length / 4));
2392             j = -1;
2393             bestCommon = "";
2394             while ((j = shorttext.indexOf(seed, j + 1)) !== -1) {
2395                 prefixLength = dmp.diffCommonPrefix(longtext.substring(i),
2396                     shorttext.substring(j));
2397                 suffixLength = dmp.diffCommonSuffix(longtext.substring(0, i),
2398                     shorttext.substring(0, j));
2399                 if (bestCommon.length < suffixLength + prefixLength) {
2400                     bestCommon = shorttext.substring(j - suffixLength, j) +
2401                         shorttext.substring(j, j + prefixLength);
2402                     bestLongtextA = longtext.substring(0, i - suffixLength);
2403                     bestLongtextB = longtext.substring(i + prefixLength);
2404                     bestShorttextA = shorttext.substring(0, j - suffixLength);
2405                     bestShorttextB = shorttext.substring(j + prefixLength);
2406                 }
2407             }
2408             if (bestCommon.length * 2 >= longtext.length) {
2409                 return [ bestLongtextA, bestLongtextB,
2410                     bestShorttextA, bestShorttextB, bestCommon
2411                 ];
2412             } else {
2413                 return null;
2414             }
2415         }
2417         // First check if the second quarter is the seed for a half-match.
2418         hm1 = diffHalfMatchI(longtext, shorttext,
2419             Math.ceil(longtext.length / 4));
2420         // Check again based on the third quarter.
2421         hm2 = diffHalfMatchI(longtext, shorttext,
2422             Math.ceil(longtext.length / 2));
2423         if (!hm1 && !hm2) {
2424             return null;
2425         } else if (!hm2) {
2426             hm = hm1;
2427         } else if (!hm1) {
2428             hm = hm2;
2429         } else {
2430             // Both matched.  Select the longest.
2431             hm = hm1[4].length > hm2[4].length ? hm1 : hm2;
2432         }
2434         // A half-match was found, sort out the return data.
2435         text1A, text1B, text2A, text2B;
2436         if (text1.length > text2.length) {
2437             text1A = hm[0];
2438             text1B = hm[1];
2439             text2A = hm[2];
2440             text2B = hm[3];
2441         } else {
2442             text2A = hm[0];
2443             text2B = hm[1];
2444             text1A = hm[2];
2445             text1B = hm[3];
2446         }
2447         midCommon = hm[4];
2448         return [ text1A, text1B, text2A, text2B, midCommon ];
2449     };
2451     /**
2452      * Do a quick line-level diff on both strings, then rediff the parts for
2453      * greater accuracy.
2454      * This speedup can produce non-minimal diffs.
2455      * @param {string} text1 Old string to be diffed.
2456      * @param {string} text2 New string to be diffed.
2457      * @param {number} deadline Time when the diff should be complete by.
2458      * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2459      * @private
2460      */
2461     DiffMatchPatch.prototype.diffLineMode = function(text1, text2, deadline) {
2462         var a, diffs, linearray, pointer, countInsert,
2463                         countDelete, textInsert, textDelete, j;
2464         // Scan the text on a line-by-line basis first.
2465         a = this.diffLinesToChars(text1, text2);
2466         text1 = a.chars1;
2467         text2 = a.chars2;
2468         linearray = a.lineArray;
2470         diffs = this.DiffMain(text1, text2, false, deadline);
2472         // Convert the diff back to original text.
2473         this.diffCharsToLines(diffs, linearray);
2474         // Eliminate freak matches (e.g. blank lines)
2475         this.diffCleanupSemantic(diffs);
2477         // Rediff any replacement blocks, this time character-by-character.
2478         // Add a dummy entry at the end.
2479         diffs.push( [ DIFF_EQUAL, "" ] );
2480         pointer = 0;
2481         countDelete = 0;
2482         countInsert = 0;
2483         textDelete = "";
2484         textInsert = "";
2485         while (pointer < diffs.length) {
2486             switch ( diffs[pointer][0] ) {
2487                 case DIFF_INSERT:
2488                     countInsert++;
2489                     textInsert += diffs[pointer][1];
2490                     break;
2491                 case DIFF_DELETE:
2492                     countDelete++;
2493                     textDelete += diffs[pointer][1];
2494                     break;
2495                 case DIFF_EQUAL:
2496                     // Upon reaching an equality, check for prior redundancies.
2497                     if (countDelete >= 1 && countInsert >= 1) {
2498                         // Delete the offending records and add the merged ones.
2499                         diffs.splice(pointer - countDelete - countInsert,
2500                             countDelete + countInsert);
2501                         pointer = pointer - countDelete - countInsert;
2502                         a = this.DiffMain(textDelete, textInsert, false, deadline);
2503                         for (j = a.length - 1; j >= 0; j--) {
2504                             diffs.splice( pointer, 0, a[j] );
2505                         }
2506                         pointer = pointer + a.length;
2507                     }
2508                     countInsert = 0;
2509                     countDelete = 0;
2510                     textDelete = "";
2511                     textInsert = "";
2512                     break;
2513             }
2514             pointer++;
2515         }
2516         diffs.pop(); // Remove the dummy entry at the end.
2518         return diffs;
2519     };
2521     /**
2522      * Find the 'middle snake' of a diff, split the problem in two
2523      * and return the recursively constructed diff.
2524      * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
2525      * @param {string} text1 Old string to be diffed.
2526      * @param {string} text2 New string to be diffed.
2527      * @param {number} deadline Time at which to bail if not yet complete.
2528      * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2529      * @private
2530      */
2531     DiffMatchPatch.prototype.diffBisect = function(text1, text2, deadline) {
2532         var text1Length, text2Length, maxD, vOffset, vLength,
2533                         v1, v2, x, delta, front, k1start, k1end, k2start,
2534                         k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
2535         // Cache the text lengths to prevent multiple calls.
2536         text1Length = text1.length;
2537         text2Length = text2.length;
2538         maxD = Math.ceil((text1Length + text2Length) / 2);
2539         vOffset = maxD;
2540         vLength = 2 * maxD;
2541         v1 = new Array(vLength);
2542         v2 = new Array(vLength);
2543         // Setting all elements to -1 is faster in Chrome & Firefox than mixing
2544         // integers and undefined.
2545         for (x = 0; x < vLength; x++) {
2546             v1[x] = -1;
2547             v2[x] = -1;
2548         }
2549         v1[vOffset + 1] = 0;
2550         v2[vOffset + 1] = 0;
2551         delta = text1Length - text2Length;
2552         // If the total number of characters is odd, then the front path will collide
2553         // with the reverse path.
2554         front = (delta % 2 !== 0);
2555         // Offsets for start and end of k loop.
2556         // Prevents mapping of space beyond the grid.
2557         k1start = 0;
2558         k1end = 0;
2559         k2start = 0;
2560         k2end = 0;
2561         for (d = 0; d < maxD; d++) {
2562             // Bail out if deadline is reached.
2563             if ((new Date()).getTime() > deadline) {
2564                 break;
2565             }
2567             // Walk the front path one step.
2568             for (k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
2569                 k1Offset = vOffset + k1;
2570                 if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {
2571                     x1 = v1[k1Offset + 1];
2572                 } else {
2573                     x1 = v1[k1Offset - 1] + 1;
2574                 }
2575                 y1 = x1 - k1;
2576                 while (x1 < text1Length && y1 < text2Length &&
2577                     text1.charAt(x1) === text2.charAt(y1)) {
2578                     x1++;
2579                     y1++;
2580                 }
2581                 v1[k1Offset] = x1;
2582                 if (x1 > text1Length) {
2583                     // Ran off the right of the graph.
2584                     k1end += 2;
2585                 } else if (y1 > text2Length) {
2586                     // Ran off the bottom of the graph.
2587                     k1start += 2;
2588                 } else if (front) {
2589                     k2Offset = vOffset + delta - k1;
2590                     if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) {
2591                         // Mirror x2 onto top-left coordinate system.
2592                         x2 = text1Length - v2[k2Offset];
2593                         if (x1 >= x2) {
2594                             // Overlap detected.
2595                             return this.diffBisectSplit(text1, text2, x1, y1, deadline);
2596                         }
2597                     }
2598                 }
2599             }
2601             // Walk the reverse path one step.
2602             for (k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
2603                 k2Offset = vOffset + k2;
2604                 if ( k2 === -d || (k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {
2605                     x2 = v2[k2Offset + 1];
2606                 } else {
2607                     x2 = v2[k2Offset - 1] + 1;
2608                 }
2609                 y2 = x2 - k2;
2610                 while (x2 < text1Length && y2 < text2Length &&
2611                     text1.charAt(text1Length - x2 - 1) ===
2612                     text2.charAt(text2Length - y2 - 1)) {
2613                     x2++;
2614                     y2++;
2615                 }
2616                 v2[k2Offset] = x2;
2617                 if (x2 > text1Length) {
2618                     // Ran off the left of the graph.
2619                     k2end += 2;
2620                 } else if (y2 > text2Length) {
2621                     // Ran off the top of the graph.
2622                     k2start += 2;
2623                 } else if (!front) {
2624                     k1Offset = vOffset + delta - k2;
2625                     if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) {
2626                         x1 = v1[k1Offset];
2627                         y1 = vOffset + x1 - k1Offset;
2628                         // Mirror x2 onto top-left coordinate system.
2629                         x2 = text1Length - x2;
2630                         if (x1 >= x2) {
2631                             // Overlap detected.
2632                             return this.diffBisectSplit(text1, text2, x1, y1, deadline);
2633                         }
2634                     }
2635                 }
2636             }
2637         }
2638         // Diff took too long and hit the deadline or
2639         // number of diffs equals number of characters, no commonality at all.
2640         return [
2641             [ DIFF_DELETE, text1 ],
2642             [ DIFF_INSERT, text2 ]
2643         ];
2644     };
2646     /**
2647      * Given the location of the 'middle snake', split the diff in two parts
2648      * and recurse.
2649      * @param {string} text1 Old string to be diffed.
2650      * @param {string} text2 New string to be diffed.
2651      * @param {number} x Index of split point in text1.
2652      * @param {number} y Index of split point in text2.
2653      * @param {number} deadline Time at which to bail if not yet complete.
2654      * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2655      * @private
2656      */
2657     DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
2658         var text1a, text1b, text2a, text2b, diffs, diffsb;
2659         text1a = text1.substring(0, x);
2660         text2a = text2.substring(0, y);
2661         text1b = text1.substring(x);
2662         text2b = text2.substring(y);
2664         // Compute both diffs serially.
2665         diffs = this.DiffMain(text1a, text2a, false, deadline);
2666         diffsb = this.DiffMain(text1b, text2b, false, deadline);
2668         return diffs.concat(diffsb);
2669     };
2671     /**
2672      * Reduce the number of edits by eliminating semantically trivial equalities.
2673      * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2674      */
2675     DiffMatchPatch.prototype.diffCleanupSemantic = function(diffs) {
2676         var changes, equalities, equalitiesLength, lastequality,
2677                         pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
2678                         lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
2679         changes = false;
2680         equalities = []; // Stack of indices where equalities are found.
2681         equalitiesLength = 0; // Keeping our own length var is faster in JS.
2682         /** @type {?string} */
2683         lastequality = null;
2684         // Always equal to diffs[equalities[equalitiesLength - 1]][1]
2685         pointer = 0; // Index of current position.
2686         // Number of characters that changed prior to the equality.
2687         lengthInsertions1 = 0;
2688         lengthDeletions1 = 0;
2689         // Number of characters that changed after the equality.
2690         lengthInsertions2 = 0;
2691         lengthDeletions2 = 0;
2692         while (pointer < diffs.length) {
2693             if (diffs[pointer][0] === DIFF_EQUAL) { // Equality found.
2694                 equalities[equalitiesLength++] = pointer;
2695                 lengthInsertions1 = lengthInsertions2;
2696                 lengthDeletions1 = lengthDeletions2;
2697                 lengthInsertions2 = 0;
2698                 lengthDeletions2 = 0;
2699                 lastequality = diffs[pointer][1];
2700             } else { // An insertion or deletion.
2701                 if (diffs[pointer][0] === DIFF_INSERT) {
2702                     lengthInsertions2 += diffs[pointer][1].length;
2703                 } else {
2704                     lengthDeletions2 += diffs[pointer][1].length;
2705                 }
2706                 // Eliminate an equality that is smaller or equal to the edits on both
2707                 // sides of it.
2708                 if (lastequality && (lastequality.length <=
2709                         Math.max(lengthInsertions1, lengthDeletions1)) &&
2710                     (lastequality.length <= Math.max(lengthInsertions2,
2711                         lengthDeletions2))) {
2712                     // Duplicate record.
2713                     diffs.splice( equalities[ equalitiesLength - 1 ], 0, [ DIFF_DELETE, lastequality ] );
2714                     // Change second copy to insert.
2715                     diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
2716                     // Throw away the equality we just deleted.
2717                     equalitiesLength--;
2718                     // Throw away the previous equality (it needs to be reevaluated).
2719                     equalitiesLength--;
2720                     pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
2721                     lengthInsertions1 = 0; // Reset the counters.
2722                     lengthDeletions1 = 0;
2723                     lengthInsertions2 = 0;
2724                     lengthDeletions2 = 0;
2725                     lastequality = null;
2726                     changes = true;
2727                 }
2728             }
2729             pointer++;
2730         }
2732         // Normalize the diff.
2733         if (changes) {
2734             this.diffCleanupMerge(diffs);
2735         }
2737         // Find any overlaps between deletions and insertions.
2738         // e.g: <del>abcxxx</del><ins>xxxdef</ins>
2739         //   -> <del>abc</del>xxx<ins>def</ins>
2740         // e.g: <del>xxxabc</del><ins>defxxx</ins>
2741         //   -> <ins>def</ins>xxx<del>abc</del>
2742         // Only extract an overlap if it is as big as the edit ahead or behind it.
2743         pointer = 1;
2744         while (pointer < diffs.length) {
2745             if (diffs[pointer - 1][0] === DIFF_DELETE &&
2746                 diffs[pointer][0] === DIFF_INSERT) {
2747                 deletion = diffs[pointer - 1][1];
2748                 insertion = diffs[pointer][1];
2749                 overlapLength1 = this.diffCommonOverlap(deletion, insertion);
2750                 overlapLength2 = this.diffCommonOverlap(insertion, deletion);
2751                 if (overlapLength1 >= overlapLength2) {
2752                     if (overlapLength1 >= deletion.length / 2 ||
2753                         overlapLength1 >= insertion.length / 2) {
2754                         // Overlap found.  Insert an equality and trim the surrounding edits.
2755                         diffs.splice( pointer, 0, [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ] );
2756                         diffs[pointer - 1][1] =
2757                             deletion.substring(0, deletion.length - overlapLength1);
2758                         diffs[pointer + 1][1] = insertion.substring(overlapLength1);
2759                         pointer++;
2760                     }
2761                 } else {
2762                     if (overlapLength2 >= deletion.length / 2 ||
2763                         overlapLength2 >= insertion.length / 2) {
2764                         // Reverse overlap found.
2765                         // Insert an equality and swap and trim the surrounding edits.
2766                         diffs.splice( pointer, 0, [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ] );
2767                         diffs[pointer - 1][0] = DIFF_INSERT;
2768                         diffs[pointer - 1][1] =
2769                             insertion.substring(0, insertion.length - overlapLength2);
2770                         diffs[pointer + 1][0] = DIFF_DELETE;
2771                         diffs[pointer + 1][1] =
2772                             deletion.substring(overlapLength2);
2773                         pointer++;
2774                     }
2775                 }
2776                 pointer++;
2777             }
2778             pointer++;
2779         }
2780     };
2782     /**
2783      * Determine if the suffix of one string is the prefix of another.
2784      * @param {string} text1 First string.
2785      * @param {string} text2 Second string.
2786      * @return {number} The number of characters common to the end of the first
2787      *     string and the start of the second string.
2788      * @private
2789      */
2790     DiffMatchPatch.prototype.diffCommonOverlap = function(text1, text2) {
2791         var text1Length, text2Length, textLength,
2792                         best, length, pattern, found;
2793         // Cache the text lengths to prevent multiple calls.
2794         text1Length = text1.length;
2795         text2Length = text2.length;
2796         // Eliminate the null case.
2797         if (text1Length === 0 || text2Length === 0) {
2798             return 0;
2799         }
2800         // Truncate the longer string.
2801         if (text1Length > text2Length) {
2802             text1 = text1.substring(text1Length - text2Length);
2803         } else if (text1Length < text2Length) {
2804             text2 = text2.substring(0, text1Length);
2805         }
2806         textLength = Math.min(text1Length, text2Length);
2807         // Quick check for the worst case.
2808         if (text1 === text2) {
2809             return textLength;
2810         }
2812         // Start by looking for a single character match
2813         // and increase length until no match is found.
2814         // Performance analysis: http://neil.fraser.name/news/2010/11/04/
2815         best = 0;
2816         length = 1;
2817         while (true) {
2818             pattern = text1.substring(textLength - length);
2819             found = text2.indexOf(pattern);
2820             if (found === -1) {
2821                 return best;
2822             }
2823             length += found;
2824             if (found === 0 || text1.substring(textLength - length) ===
2825                 text2.substring(0, length)) {
2826                 best = length;
2827                 length++;
2828             }
2829         }
2830     };
2832     /**
2833      * Split two texts into an array of strings.  Reduce the texts to a string of
2834      * hashes where each Unicode character represents one line.
2835      * @param {string} text1 First string.
2836      * @param {string} text2 Second string.
2837      * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
2838      *     An object containing the encoded text1, the encoded text2 and
2839      *     the array of unique strings.
2840      *     The zeroth element of the array of unique strings is intentionally blank.
2841      * @private
2842      */
2843     DiffMatchPatch.prototype.diffLinesToChars = function(text1, text2) {
2844         var lineArray, lineHash, chars1, chars2;
2845         lineArray = []; // e.g. lineArray[4] === 'Hello\n'
2846         lineHash = {}; // e.g. lineHash['Hello\n'] === 4
2848         // '\x00' is a valid character, but various debuggers don't like it.
2849         // So we'll insert a junk entry to avoid generating a null character.
2850         lineArray[0] = "";
2852         /**
2853          * Split a text into an array of strings.  Reduce the texts to a string of
2854          * hashes where each Unicode character represents one line.
2855          * Modifies linearray and linehash through being a closure.
2856          * @param {string} text String to encode.
2857          * @return {string} Encoded string.
2858          * @private
2859          */
2860         function diffLinesToCharsMunge(text) {
2861             var chars, lineStart, lineEnd, lineArrayLength, line;
2862             chars = "";
2863             // Walk the text, pulling out a substring for each line.
2864             // text.split('\n') would would temporarily double our memory footprint.
2865             // Modifying text would create many large strings to garbage collect.
2866             lineStart = 0;
2867             lineEnd = -1;
2868             // Keeping our own length variable is faster than looking it up.
2869             lineArrayLength = lineArray.length;
2870             while (lineEnd < text.length - 1) {
2871                 lineEnd = text.indexOf("\n", lineStart);
2872                 if (lineEnd === -1) {
2873                     lineEnd = text.length - 1;
2874                 }
2875                 line = text.substring(lineStart, lineEnd + 1);
2876                 lineStart = lineEnd + 1;
2878                 if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) :
2879                     (lineHash[line] !== undefined)) {
2880                     chars += String.fromCharCode( lineHash[ line ] );
2881                 } else {
2882                     chars += String.fromCharCode(lineArrayLength);
2883                     lineHash[line] = lineArrayLength;
2884                     lineArray[lineArrayLength++] = line;
2885                 }
2886             }
2887             return chars;
2888         }
2890         chars1 = diffLinesToCharsMunge(text1);
2891         chars2 = diffLinesToCharsMunge(text2);
2892         return {
2893             chars1: chars1,
2894             chars2: chars2,
2895             lineArray: lineArray
2896         };
2897     };
2899     /**
2900      * Rehydrate the text in a diff from a string of line hashes to real lines of
2901      * text.
2902      * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2903      * @param {!Array.<string>} lineArray Array of unique strings.
2904      * @private
2905      */
2906     DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {
2907         var x, chars, text, y;
2908         for ( x = 0; x < diffs.length; x++ ) {
2909             chars = diffs[x][1];
2910             text = [];
2911             for ( y = 0; y < chars.length; y++ ) {
2912                 text[y] = lineArray[chars.charCodeAt(y)];
2913             }
2914             diffs[x][1] = text.join("");
2915         }
2916     };
2918     /**
2919      * Reorder and merge like edit sections.  Merge equalities.
2920      * Any edit section can move as long as it doesn't cross an equality.
2921      * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2922      */
2923     DiffMatchPatch.prototype.diffCleanupMerge = function(diffs) {
2924         var pointer, countDelete, countInsert, textInsert, textDelete,
2925                         commonlength, changes;
2926         diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
2927         pointer = 0;
2928         countDelete = 0;
2929         countInsert = 0;
2930         textDelete = "";
2931         textInsert = "";
2932         commonlength;
2933         while (pointer < diffs.length) {
2934             switch ( diffs[ pointer ][ 0 ] ) {
2935                 case DIFF_INSERT:
2936                     countInsert++;
2937                     textInsert += diffs[pointer][1];
2938                     pointer++;
2939                     break;
2940                 case DIFF_DELETE:
2941                     countDelete++;
2942                     textDelete += diffs[pointer][1];
2943                     pointer++;
2944                     break;
2945                 case DIFF_EQUAL:
2946                     // Upon reaching an equality, check for prior redundancies.
2947                     if (countDelete + countInsert > 1) {
2948                         if (countDelete !== 0 && countInsert !== 0) {
2949                             // Factor out any common prefixies.
2950                             commonlength = this.diffCommonPrefix(textInsert, textDelete);
2951                             if (commonlength !== 0) {
2952                                 if ((pointer - countDelete - countInsert) > 0 &&
2953                                     diffs[pointer - countDelete - countInsert - 1][0] ===
2954                                     DIFF_EQUAL) {
2955                                     diffs[pointer - countDelete - countInsert - 1][1] +=
2956                                         textInsert.substring(0, commonlength);
2957                                 } else {
2958                                     diffs.splice( 0, 0, [ DIFF_EQUAL,
2959                                         textInsert.substring( 0, commonlength )
2960                                      ] );
2961                                     pointer++;
2962                                 }
2963                                 textInsert = textInsert.substring(commonlength);
2964                                 textDelete = textDelete.substring(commonlength);
2965                             }
2966                             // Factor out any common suffixies.
2967                             commonlength = this.diffCommonSuffix(textInsert, textDelete);
2968                             if (commonlength !== 0) {
2969                                 diffs[pointer][1] = textInsert.substring(textInsert.length -
2970                                     commonlength) + diffs[pointer][1];
2971                                 textInsert = textInsert.substring(0, textInsert.length -
2972                                     commonlength);
2973                                 textDelete = textDelete.substring(0, textDelete.length -
2974                                     commonlength);
2975                             }
2976                         }
2977                         // Delete the offending records and add the merged ones.
2978                         if (countDelete === 0) {
2979                             diffs.splice( pointer - countInsert,
2980                                 countDelete + countInsert, [ DIFF_INSERT, textInsert ] );
2981                         } else if (countInsert === 0) {
2982                             diffs.splice( pointer - countDelete,
2983                                 countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
2984                         } else {
2985                             diffs.splice( pointer - countDelete - countInsert,
2986                                 countDelete + countInsert, [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ] );
2987                         }
2988                         pointer = pointer - countDelete - countInsert +
2989                             (countDelete ? 1 : 0) + (countInsert ? 1 : 0) + 1;
2990                     } else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) {
2991                         // Merge this equality with the previous one.
2992                         diffs[pointer - 1][1] += diffs[pointer][1];
2993                         diffs.splice(pointer, 1);
2994                     } else {
2995                         pointer++;
2996                     }
2997                     countInsert = 0;
2998                     countDelete = 0;
2999                     textDelete = "";
3000                     textInsert = "";
3001                     break;
3002             }
3003         }
3004         if (diffs[diffs.length - 1][1] === "") {
3005             diffs.pop(); // Remove the dummy entry at the end.
3006         }
3008         // Second pass: look for single edits surrounded on both sides by equalities
3009         // which can be shifted sideways to eliminate an equality.
3010         // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
3011         changes = false;
3012         pointer = 1;
3013         // Intentionally ignore the first and last element (don't need checking).
3014         while (pointer < diffs.length - 1) {
3015             if (diffs[pointer - 1][0] === DIFF_EQUAL &&
3016                 diffs[pointer + 1][0] === DIFF_EQUAL) {
3017                 // This is a single edit surrounded by equalities.
3018                 if ( diffs[ pointer ][ 1 ].substring( diffs[ pointer ][ 1 ].length -
3019                         diffs[ pointer - 1 ][ 1 ].length ) === diffs[ pointer - 1 ][ 1 ] ) {
3020                     // Shift the edit over the previous equality.
3021                     diffs[pointer][1] = diffs[pointer - 1][1] +
3022                         diffs[pointer][1].substring(0, diffs[pointer][1].length -
3023                             diffs[pointer - 1][1].length);
3024                     diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
3025                     diffs.splice(pointer - 1, 1);
3026                     changes = true;
3027                 } else if ( diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
3028                     diffs[ pointer + 1 ][ 1 ] ) {
3029                     // Shift the edit over the next equality.
3030                     diffs[pointer - 1][1] += diffs[pointer + 1][1];
3031                     diffs[pointer][1] =
3032                         diffs[pointer][1].substring(diffs[pointer + 1][1].length) +
3033                         diffs[pointer + 1][1];
3034                     diffs.splice(pointer + 1, 1);
3035                     changes = true;
3036                 }
3037             }
3038             pointer++;
3039         }
3040         // If shifts were made, the diff needs reordering and another shift sweep.
3041         if (changes) {
3042             this.diffCleanupMerge(diffs);
3043         }
3044     };
3046     return function(o, n) {
3047                 var diff, output, text;
3048         diff = new DiffMatchPatch();
3049         output = diff.DiffMain(o, n);
3050         //console.log(output);
3051         diff.diffCleanupEfficiency(output);
3052         text = diff.diffPrettyHtml(output);
3054         return text;
3055     };
3056 }());
3057 // jscs:enable
3059 (function() {
3061 // Deprecated QUnit.init - Ref #530
3062 // Re-initialize the configuration options
3063 QUnit.init = function() {
3064         var tests, banner, result, qunit,
3065                 config = QUnit.config;
3067         config.stats = { all: 0, bad: 0 };
3068         config.moduleStats = { all: 0, bad: 0 };
3069         config.started = 0;
3070         config.updateRate = 1000;
3071         config.blocking = false;
3072         config.autostart = true;
3073         config.autorun = false;
3074         config.filter = "";
3075         config.queue = [];
3077         // Return on non-browser environments
3078         // This is necessary to not break on node tests
3079         if ( typeof window === "undefined" ) {
3080                 return;
3081         }
3083         qunit = id( "qunit" );
3084         if ( qunit ) {
3085                 qunit.innerHTML =
3086                         "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
3087                         "<h2 id='qunit-banner'></h2>" +
3088                         "<div id='qunit-testrunner-toolbar'></div>" +
3089                         "<h2 id='qunit-userAgent'></h2>" +
3090                         "<ol id='qunit-tests'></ol>";
3091         }
3093         tests = id( "qunit-tests" );
3094         banner = id( "qunit-banner" );
3095         result = id( "qunit-testresult" );
3097         if ( tests ) {
3098                 tests.innerHTML = "";
3099         }
3101         if ( banner ) {
3102                 banner.className = "";
3103         }
3105         if ( result ) {
3106                 result.parentNode.removeChild( result );
3107         }
3109         if ( tests ) {
3110                 result = document.createElement( "p" );
3111                 result.id = "qunit-testresult";
3112                 result.className = "result";
3113                 tests.parentNode.insertBefore( result, tests );
3114                 result.innerHTML = "Running...<br />&#160;";
3115         }
3118 // Don't load the HTML Reporter on non-Browser environments
3119 if ( typeof window === "undefined" ) {
3120         return;
3123 var config = QUnit.config,
3124         hasOwn = Object.prototype.hasOwnProperty,
3125         defined = {
3126                 document: window.document !== undefined,
3127                 sessionStorage: (function() {
3128                         var x = "qunit-test-string";
3129                         try {
3130                                 sessionStorage.setItem( x, x );
3131                                 sessionStorage.removeItem( x );
3132                                 return true;
3133                         } catch ( e ) {
3134                                 return false;
3135                         }
3136                 }())
3137         },
3138         modulesList = [];
3141 * Escape text for attribute or text content.
3143 function escapeText( s ) {
3144         if ( !s ) {
3145                 return "";
3146         }
3147         s = s + "";
3149         // Both single quotes and double quotes (for attributes)
3150         return s.replace( /['"<>&]/g, function( s ) {
3151                 switch ( s ) {
3152                 case "'":
3153                         return "&#039;";
3154                 case "\"":
3155                         return "&quot;";
3156                 case "<":
3157                         return "&lt;";
3158                 case ">":
3159                         return "&gt;";
3160                 case "&":
3161                         return "&amp;";
3162                 }
3163         });
3167  * @param {HTMLElement} elem
3168  * @param {string} type
3169  * @param {Function} fn
3170  */
3171 function addEvent( elem, type, fn ) {
3172         if ( elem.addEventListener ) {
3174                 // Standards-based browsers
3175                 elem.addEventListener( type, fn, false );
3176         } else if ( elem.attachEvent ) {
3178                 // support: IE <9
3179                 elem.attachEvent( "on" + type, function() {
3180                         var event = window.event;
3181                         if ( !event.target ) {
3182                                 event.target = event.srcElement || document;
3183                         }
3185                         fn.call( elem, event );
3186                 });
3187         }
3191  * @param {Array|NodeList} elems
3192  * @param {string} type
3193  * @param {Function} fn
3194  */
3195 function addEvents( elems, type, fn ) {
3196         var i = elems.length;
3197         while ( i-- ) {
3198                 addEvent( elems[ i ], type, fn );
3199         }
3202 function hasClass( elem, name ) {
3203         return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
3206 function addClass( elem, name ) {
3207         if ( !hasClass( elem, name ) ) {
3208                 elem.className += ( elem.className ? " " : "" ) + name;
3209         }
3212 function toggleClass( elem, name ) {
3213         if ( hasClass( elem, name ) ) {
3214                 removeClass( elem, name );
3215         } else {
3216                 addClass( elem, name );
3217         }
3220 function removeClass( elem, name ) {
3221         var set = " " + elem.className + " ";
3223         // Class name may appear multiple times
3224         while ( set.indexOf( " " + name + " " ) >= 0 ) {
3225                 set = set.replace( " " + name + " ", " " );
3226         }
3228         // trim for prettiness
3229         elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
3232 function id( name ) {
3233         return defined.document && document.getElementById && document.getElementById( name );
3236 function getUrlConfigHtml() {
3237         var i, j, val,
3238                 escaped, escapedTooltip,
3239                 selection = false,
3240                 len = config.urlConfig.length,
3241                 urlConfigHtml = "";
3243         for ( i = 0; i < len; i++ ) {
3244                 val = config.urlConfig[ i ];
3245                 if ( typeof val === "string" ) {
3246                         val = {
3247                                 id: val,
3248                                 label: val
3249                         };
3250                 }
3252                 escaped = escapeText( val.id );
3253                 escapedTooltip = escapeText( val.tooltip );
3255                 if ( config[ val.id ] === undefined ) {
3256                         config[ val.id ] = QUnit.urlParams[ val.id ];
3257                 }
3259                 if ( !val.value || typeof val.value === "string" ) {
3260                         urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
3261                                 "' name='" + escaped + "' type='checkbox'" +
3262                                 ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
3263                                 ( config[ val.id ] ? " checked='checked'" : "" ) +
3264                                 " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
3265                                 "' title='" + escapedTooltip + "'>" + val.label + "</label>";
3266                 } else {
3267                         urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
3268                                 "' title='" + escapedTooltip + "'>" + val.label +
3269                                 ": </label><select id='qunit-urlconfig-" + escaped +
3270                                 "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
3272                         if ( QUnit.is( "array", val.value ) ) {
3273                                 for ( j = 0; j < val.value.length; j++ ) {
3274                                         escaped = escapeText( val.value[ j ] );
3275                                         urlConfigHtml += "<option value='" + escaped + "'" +
3276                                                 ( config[ val.id ] === val.value[ j ] ?
3277                                                         ( selection = true ) && " selected='selected'" : "" ) +
3278                                                 ">" + escaped + "</option>";
3279                                 }
3280                         } else {
3281                                 for ( j in val.value ) {
3282                                         if ( hasOwn.call( val.value, j ) ) {
3283                                                 urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
3284                                                         ( config[ val.id ] === j ?
3285                                                                 ( selection = true ) && " selected='selected'" : "" ) +
3286                                                         ">" + escapeText( val.value[ j ] ) + "</option>";
3287                                         }
3288                                 }
3289                         }
3290                         if ( config[ val.id ] && !selection ) {
3291                                 escaped = escapeText( config[ val.id ] );
3292                                 urlConfigHtml += "<option value='" + escaped +
3293                                         "' selected='selected' disabled='disabled'>" + escaped + "</option>";
3294                         }
3295                         urlConfigHtml += "</select>";
3296                 }
3297         }
3299         return urlConfigHtml;
3302 // Handle "click" events on toolbar checkboxes and "change" for select menus.
3303 // Updates the URL with the new state of `config.urlConfig` values.
3304 function toolbarChanged() {
3305         var updatedUrl, value,
3306                 field = this,
3307                 params = {};
3309         // Detect if field is a select menu or a checkbox
3310         if ( "selectedIndex" in field ) {
3311                 value = field.options[ field.selectedIndex ].value || undefined;
3312         } else {
3313                 value = field.checked ? ( field.defaultValue || true ) : undefined;
3314         }
3316         params[ field.name ] = value;
3317         updatedUrl = setUrl( params );
3319         if ( "hidepassed" === field.name && "replaceState" in window.history ) {
3320                 config[ field.name ] = value || false;
3321                 if ( value ) {
3322                         addClass( id( "qunit-tests" ), "hidepass" );
3323                 } else {
3324                         removeClass( id( "qunit-tests" ), "hidepass" );
3325                 }
3327                 // It is not necessary to refresh the whole page
3328                 window.history.replaceState( null, "", updatedUrl );
3329         } else {
3330                 window.location = updatedUrl;
3331         }
3334 function setUrl( params ) {
3335         var key,
3336                 querystring = "?";
3338         params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
3340         for ( key in params ) {
3341                 if ( hasOwn.call( params, key ) ) {
3342                         if ( params[ key ] === undefined ) {
3343                                 continue;
3344                         }
3345                         querystring += encodeURIComponent( key );
3346                         if ( params[ key ] !== true ) {
3347                                 querystring += "=" + encodeURIComponent( params[ key ] );
3348                         }
3349                         querystring += "&";
3350                 }
3351         }
3352         return location.protocol + "//" + location.host +
3353                 location.pathname + querystring.slice( 0, -1 );
3356 function applyUrlParams() {
3357         var selectedModule,
3358                 modulesList = id( "qunit-modulefilter" ),
3359                 filter = id( "qunit-filter-input" ).value;
3361         selectedModule = modulesList ?
3362                 decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) :
3363                 undefined;
3365         window.location = setUrl({
3366                 module: ( selectedModule === "" ) ? undefined : selectedModule,
3367                 filter: ( filter === "" ) ? undefined : filter,
3369                 // Remove testId filter
3370                 testId: undefined
3371         });
3374 function toolbarUrlConfigContainer() {
3375         var urlConfigContainer = document.createElement( "span" );
3377         urlConfigContainer.innerHTML = getUrlConfigHtml();
3378         addClass( urlConfigContainer, "qunit-url-config" );
3380         // For oldIE support:
3381         // * Add handlers to the individual elements instead of the container
3382         // * Use "click" instead of "change" for checkboxes
3383         addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
3384         addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
3386         return urlConfigContainer;
3389 function toolbarLooseFilter() {
3390         var filter = document.createElement( "form" ),
3391                 label = document.createElement( "label" ),
3392                 input = document.createElement( "input" ),
3393                 button = document.createElement( "button" );
3395         addClass( filter, "qunit-filter" );
3397         label.innerHTML = "Filter: ";
3399         input.type = "text";
3400         input.value = config.filter || "";
3401         input.name = "filter";
3402         input.id = "qunit-filter-input";
3404         button.innerHTML = "Go";
3406         label.appendChild( input );
3408         filter.appendChild( label );
3409         filter.appendChild( button );
3410         addEvent( filter, "submit", function( ev ) {
3411                 applyUrlParams();
3413                 if ( ev && ev.preventDefault ) {
3414                         ev.preventDefault();
3415                 }
3417                 return false;
3418         });
3420         return filter;
3423 function toolbarModuleFilterHtml() {
3424         var i,
3425                 moduleFilterHtml = "";
3427         if ( !modulesList.length ) {
3428                 return false;
3429         }
3431         modulesList.sort(function( a, b ) {
3432                 return a.localeCompare( b );
3433         });
3435         moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
3436                 "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
3437                 ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
3438                 ">< All Modules ></option>";
3440         for ( i = 0; i < modulesList.length; i++ ) {
3441                 moduleFilterHtml += "<option value='" +
3442                         escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
3443                         ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
3444                         ">" + escapeText( modulesList[ i ] ) + "</option>";
3445         }
3446         moduleFilterHtml += "</select>";
3448         return moduleFilterHtml;
3451 function toolbarModuleFilter() {
3452         var toolbar = id( "qunit-testrunner-toolbar" ),
3453                 moduleFilter = document.createElement( "span" ),
3454                 moduleFilterHtml = toolbarModuleFilterHtml();
3456         if ( !toolbar || !moduleFilterHtml ) {
3457                 return false;
3458         }
3460         moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
3461         moduleFilter.innerHTML = moduleFilterHtml;
3463         addEvent( moduleFilter.lastChild, "change", applyUrlParams );
3465         toolbar.appendChild( moduleFilter );
3468 function appendToolbar() {
3469         var toolbar = id( "qunit-testrunner-toolbar" );
3471         if ( toolbar ) {
3472                 toolbar.appendChild( toolbarUrlConfigContainer() );
3473                 toolbar.appendChild( toolbarLooseFilter() );
3474         }
3477 function appendHeader() {
3478         var header = id( "qunit-header" );
3480         if ( header ) {
3481                 header.innerHTML = "<a href='" +
3482                         setUrl({ filter: undefined, module: undefined, testId: undefined }) +
3483                         "'>" + header.innerHTML + "</a> ";
3484         }
3487 function appendBanner() {
3488         var banner = id( "qunit-banner" );
3490         if ( banner ) {
3491                 banner.className = "";
3492         }
3495 function appendTestResults() {
3496         var tests = id( "qunit-tests" ),
3497                 result = id( "qunit-testresult" );
3499         if ( result ) {
3500                 result.parentNode.removeChild( result );
3501         }
3503         if ( tests ) {
3504                 tests.innerHTML = "";
3505                 result = document.createElement( "p" );
3506                 result.id = "qunit-testresult";
3507                 result.className = "result";
3508                 tests.parentNode.insertBefore( result, tests );
3509                 result.innerHTML = "Running...<br />&#160;";
3510         }
3513 function storeFixture() {
3514         var fixture = id( "qunit-fixture" );
3515         if ( fixture ) {
3516                 config.fixture = fixture.innerHTML;
3517         }
3520 function appendUserAgent() {
3521         var userAgent = id( "qunit-userAgent" );
3523         if ( userAgent ) {
3524                 userAgent.innerHTML = "";
3525                 userAgent.appendChild(
3526                         document.createTextNode(
3527                                 "QUnit " + QUnit.version  + "; " + navigator.userAgent
3528                         )
3529                 );
3530         }
3533 function appendTestsList( modules ) {
3534         var i, l, x, z, test, moduleObj;
3536         for ( i = 0, l = modules.length; i < l; i++ ) {
3537                 moduleObj = modules[ i ];
3539                 if ( moduleObj.name ) {
3540                         modulesList.push( moduleObj.name );
3541                 }
3543                 for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
3544                         test = moduleObj.tests[ x ];
3546                         appendTest( test.name, test.testId, moduleObj.name );
3547                 }
3548         }
3551 function appendTest( name, testId, moduleName ) {
3552         var title, rerunTrigger, testBlock, assertList,
3553                 tests = id( "qunit-tests" );
3555         if ( !tests ) {
3556                 return;
3557         }
3559         title = document.createElement( "strong" );
3560         title.innerHTML = getNameHtml( name, moduleName );
3562         rerunTrigger = document.createElement( "a" );
3563         rerunTrigger.innerHTML = "Rerun";
3564         rerunTrigger.href = setUrl({ testId: testId });
3566         testBlock = document.createElement( "li" );
3567         testBlock.appendChild( title );
3568         testBlock.appendChild( rerunTrigger );
3569         testBlock.id = "qunit-test-output-" + testId;
3571         assertList = document.createElement( "ol" );
3572         assertList.className = "qunit-assert-list";
3574         testBlock.appendChild( assertList );
3576         tests.appendChild( testBlock );
3579 // HTML Reporter initialization and load
3580 QUnit.begin(function( details ) {
3581         var qunit = id( "qunit" );
3583         // Fixture is the only one necessary to run without the #qunit element
3584         storeFixture();
3586         if ( qunit ) {
3587                 qunit.innerHTML =
3588                         "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
3589                         "<h2 id='qunit-banner'></h2>" +
3590                         "<div id='qunit-testrunner-toolbar'></div>" +
3591                         "<h2 id='qunit-userAgent'></h2>" +
3592                         "<ol id='qunit-tests'></ol>";
3593         }
3595         appendHeader();
3596         appendBanner();
3597         appendTestResults();
3598         appendUserAgent();
3599         appendToolbar();
3600         appendTestsList( details.modules );
3601         toolbarModuleFilter();
3603         if ( qunit && config.hidepassed ) {
3604                 addClass( qunit.lastChild, "hidepass" );
3605         }
3608 QUnit.done(function( details ) {
3609         var i, key,
3610                 banner = id( "qunit-banner" ),
3611                 tests = id( "qunit-tests" ),
3612                 html = [
3613                         "Tests completed in ",
3614                         details.runtime,
3615                         " milliseconds.<br />",
3616                         "<span class='passed'>",
3617                         details.passed,
3618                         "</span> assertions of <span class='total'>",
3619                         details.total,
3620                         "</span> passed, <span class='failed'>",
3621                         details.failed,
3622                         "</span> failed."
3623                 ].join( "" );
3625         if ( banner ) {
3626                 banner.className = details.failed ? "qunit-fail" : "qunit-pass";
3627         }
3629         if ( tests ) {
3630                 id( "qunit-testresult" ).innerHTML = html;
3631         }
3633         if ( config.altertitle && defined.document && document.title ) {
3635                 // show ✖ for good, ✔ for bad suite result in title
3636                 // use escape sequences in case file gets loaded with non-utf-8-charset
3637                 document.title = [
3638                         ( details.failed ? "\u2716" : "\u2714" ),
3639                         document.title.replace( /^[\u2714\u2716] /i, "" )
3640                 ].join( " " );
3641         }
3643         // clear own sessionStorage items if all tests passed
3644         if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
3645                 for ( i = 0; i < sessionStorage.length; i++ ) {
3646                         key = sessionStorage.key( i++ );
3647                         if ( key.indexOf( "qunit-test-" ) === 0 ) {
3648                                 sessionStorage.removeItem( key );
3649                         }
3650                 }
3651         }
3653         // scroll back to top to show results
3654         if ( config.scrolltop && window.scrollTo ) {
3655                 window.scrollTo( 0, 0 );
3656         }
3659 function getNameHtml( name, module ) {
3660         var nameHtml = "";
3662         if ( module ) {
3663                 nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
3664         }
3666         nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
3668         return nameHtml;
3671 QUnit.testStart(function( details ) {
3672         var running, testBlock, bad;
3674         testBlock = id( "qunit-test-output-" + details.testId );
3675         if ( testBlock ) {
3676                 testBlock.className = "running";
3677         } else {
3679                 // Report later registered tests
3680                 appendTest( details.name, details.testId, details.module );
3681         }
3683         running = id( "qunit-testresult" );
3684         if ( running ) {
3685                 bad = QUnit.config.reorder && defined.sessionStorage &&
3686                         +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name );
3688                 running.innerHTML = ( bad ?
3689                         "Rerunning previously failed test: <br />" :
3690                         "Running: <br />" ) +
3691                         getNameHtml( details.name, details.module );
3692         }
3696 QUnit.log(function( details ) {
3697         var assertList, assertLi,
3698                 message, expected, actual,
3699                 testItem = id( "qunit-test-output-" + details.testId );
3701         if ( !testItem ) {
3702                 return;
3703         }
3705         message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
3706         message = "<span class='test-message'>" + message + "</span>";
3707         message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
3709         // pushFailure doesn't provide details.expected
3710         // when it calls, it's implicit to also not show expected and diff stuff
3711         // Also, we need to check details.expected existence, as it can exist and be undefined
3712         if ( !details.result && hasOwn.call( details, "expected" ) ) {
3713                 expected = escapeText( QUnit.dump.parse( details.expected ) );
3714                 actual = escapeText( QUnit.dump.parse( details.actual ) );
3715                 message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
3716                         expected +
3717                         "</pre></td></tr>";
3719                 if ( actual !== expected ) {
3720                         message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
3721                                 actual + "</pre></td></tr>" +
3722                                 "<tr class='test-diff'><th>Diff: </th><td><pre>" +
3723                                 QUnit.diff( expected, actual ) + "</pre></td></tr>";
3724                 } else {
3725                         if ( expected.indexOf( "[object Array]" ) !== -1 ||
3726                                         expected.indexOf( "[object Object]" ) !== -1 ) {
3727                                 message += "<tr class='test-message'><th>Message: </th><td>" +
3728                                         "Diff suppressed as the depth of object is more than current max depth (" +
3729                                         QUnit.config.maxDepth + ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " +
3730                                         " run with a higher max depth or <a href='" + setUrl({ maxDepth: -1 }) + "'>" +
3731                                         "Rerun</a> without max depth.</p></td></tr>";
3732                         }
3733                 }
3735                 if ( details.source ) {
3736                         message += "<tr class='test-source'><th>Source: </th><td><pre>" +
3737                                 escapeText( details.source ) + "</pre></td></tr>";
3738                 }
3740                 message += "</table>";
3742         // this occours when pushFailure is set and we have an extracted stack trace
3743         } else if ( !details.result && details.source ) {
3744                 message += "<table>" +
3745                         "<tr class='test-source'><th>Source: </th><td><pre>" +
3746                         escapeText( details.source ) + "</pre></td></tr>" +
3747                         "</table>";
3748         }
3750         assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
3752         assertLi = document.createElement( "li" );
3753         assertLi.className = details.result ? "pass" : "fail";
3754         assertLi.innerHTML = message;
3755         assertList.appendChild( assertLi );
3758 QUnit.testDone(function( details ) {
3759         var testTitle, time, testItem, assertList,
3760                 good, bad, testCounts, skipped,
3761                 tests = id( "qunit-tests" );
3763         if ( !tests ) {
3764                 return;
3765         }
3767         testItem = id( "qunit-test-output-" + details.testId );
3769         assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
3771         good = details.passed;
3772         bad = details.failed;
3774         // store result when possible
3775         if ( config.reorder && defined.sessionStorage ) {
3776                 if ( bad ) {
3777                         sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
3778                 } else {
3779                         sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
3780                 }
3781         }
3783         if ( bad === 0 ) {
3784                 addClass( assertList, "qunit-collapsed" );
3785         }
3787         // testItem.firstChild is the test name
3788         testTitle = testItem.firstChild;
3790         testCounts = bad ?
3791                 "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
3792                 "";
3794         testTitle.innerHTML += " <b class='counts'>(" + testCounts +
3795                 details.assertions.length + ")</b>";
3797         if ( details.skipped ) {
3798                 testItem.className = "skipped";
3799                 skipped = document.createElement( "em" );
3800                 skipped.className = "qunit-skipped-label";
3801                 skipped.innerHTML = "skipped";
3802                 testItem.insertBefore( skipped, testTitle );
3803         } else {
3804                 addEvent( testTitle, "click", function() {
3805                         toggleClass( assertList, "qunit-collapsed" );
3806                 });
3808                 testItem.className = bad ? "fail" : "pass";
3810                 time = document.createElement( "span" );
3811                 time.className = "runtime";
3812                 time.innerHTML = details.runtime + " ms";
3813                 testItem.insertBefore( time, assertList );
3814         }
3817 if ( defined.document ) {
3818         if ( document.readyState === "complete" ) {
3819                 QUnit.load();
3820         } else {
3821                 addEvent( window, "load", QUnit.load );
3822         }
3823 } else {
3824         config.pageLoaded = true;
3825         config.autorun = true;
3828 })();