Added release notes for 'ContentHandler::runLegacyHooks' removal
[mediawiki.git] / resources / lib / qunitjs / qunit.js
blob5df0822ea428345513a05ff23e78c395c402d06f
1 /*!
2  * QUnit 1.23.1
3  * https://qunitjs.com/
4  *
5  * Copyright jQuery Foundation and other contributors
6  * Released under the MIT license
7  * https://jquery.org/license
8  *
9  * Date: 2016-04-12T17:29Z
10  */
12 ( function( global ) {
14 var QUnit = {};
16 var Date = global.Date;
17 var now = Date.now || function() {
18         return new Date().getTime();
21 var setTimeout = global.setTimeout;
22 var clearTimeout = global.clearTimeout;
24 // Store a local window from the global to allow direct references.
25 var window = global.window;
27 var defined = {
28         document: window && window.document !== undefined,
29         setTimeout: setTimeout !== undefined,
30         sessionStorage: ( function() {
31                 var x = "qunit-test-string";
32                 try {
33                         sessionStorage.setItem( x, x );
34                         sessionStorage.removeItem( x );
35                         return true;
36                 } catch ( e ) {
37                         return false;
38                 }
39         }() )
42 var fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" );
43 var globalStartCalled = false;
44 var runStarted = false;
46 var toString = Object.prototype.toString,
47         hasOwn = Object.prototype.hasOwnProperty;
49 // Returns a new Array with the elements that are in a but not in b
50 function diff( a, b ) {
51         var i, j,
52                 result = a.slice();
54         for ( i = 0; i < result.length; i++ ) {
55                 for ( j = 0; j < b.length; j++ ) {
56                         if ( result[ i ] === b[ j ] ) {
57                                 result.splice( i, 1 );
58                                 i--;
59                                 break;
60                         }
61                 }
62         }
63         return result;
66 // From jquery.js
67 function inArray( elem, array ) {
68         if ( array.indexOf ) {
69                 return array.indexOf( elem );
70         }
72         for ( var i = 0, length = array.length; i < length; i++ ) {
73                 if ( array[ i ] === elem ) {
74                         return i;
75                 }
76         }
78         return -1;
81 /**
82  * Makes a clone of an object using only Array or Object as base,
83  * and copies over the own enumerable properties.
84  *
85  * @param {Object} obj
86  * @return {Object} New object with only the own properties (recursively).
87  */
88 function objectValues ( obj ) {
89         var key, val,
90                 vals = QUnit.is( "array", obj ) ? [] : {};
91         for ( key in obj ) {
92                 if ( hasOwn.call( obj, key ) ) {
93                         val = obj[ key ];
94                         vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
95                 }
96         }
97         return vals;
100 function extend( a, b, undefOnly ) {
101         for ( var prop in b ) {
102                 if ( hasOwn.call( b, prop ) ) {
104                         // Avoid "Member not found" error in IE8 caused by messing with window.constructor
105                         // This block runs on every environment, so `global` is being used instead of `window`
106                         // to avoid errors on node.
107                         if ( prop !== "constructor" || a !== global ) {
108                                 if ( b[ prop ] === undefined ) {
109                                         delete a[ prop ];
110                                 } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
111                                         a[ prop ] = b[ prop ];
112                                 }
113                         }
114                 }
115         }
117         return a;
120 function objectType( obj ) {
121         if ( typeof obj === "undefined" ) {
122                 return "undefined";
123         }
125         // Consider: typeof null === object
126         if ( obj === null ) {
127                 return "null";
128         }
130         var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
131                 type = match && match[ 1 ];
133         switch ( type ) {
134                 case "Number":
135                         if ( isNaN( obj ) ) {
136                                 return "nan";
137                         }
138                         return "number";
139                 case "String":
140                 case "Boolean":
141                 case "Array":
142                 case "Set":
143                 case "Map":
144                 case "Date":
145                 case "RegExp":
146                 case "Function":
147                 case "Symbol":
148                         return type.toLowerCase();
149         }
150         if ( typeof obj === "object" ) {
151                 return "object";
152         }
155 // Safe object type checking
156 function is( type, obj ) {
157         return QUnit.objectType( obj ) === type;
160 // Doesn't support IE6 to IE9, it will return undefined on these browsers
161 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
162 function extractStacktrace( e, offset ) {
163         offset = offset === undefined ? 4 : offset;
165         var stack, include, i;
167         if ( e.stack ) {
168                 stack = e.stack.split( "\n" );
169                 if ( /^error$/i.test( stack[ 0 ] ) ) {
170                         stack.shift();
171                 }
172                 if ( fileName ) {
173                         include = [];
174                         for ( i = offset; i < stack.length; i++ ) {
175                                 if ( stack[ i ].indexOf( fileName ) !== -1 ) {
176                                         break;
177                                 }
178                                 include.push( stack[ i ] );
179                         }
180                         if ( include.length ) {
181                                 return include.join( "\n" );
182                         }
183                 }
184                 return stack[ offset ];
186         // Support: Safari <=6 only
187         } else if ( e.sourceURL ) {
189                 // Exclude useless self-reference for generated Error objects
190                 if ( /qunit.js$/.test( e.sourceURL ) ) {
191                         return;
192                 }
194                 // For actual exceptions, this is useful
195                 return e.sourceURL + ":" + e.line;
196         }
199 function sourceFromStacktrace( offset ) {
200         var error = new Error();
202         // Support: Safari <=7 only, IE <=10 - 11 only
203         // Not all browsers generate the `stack` property for `new Error()`, see also #636
204         if ( !error.stack ) {
205                 try {
206                         throw error;
207                 } catch ( err ) {
208                         error = err;
209                 }
210         }
212         return extractStacktrace( error, offset );
216  * Config object: Maintain internal state
217  * Later exposed as QUnit.config
218  * `config` initialized at top of scope
219  */
220 var config = {
222         // The queue of tests to run
223         queue: [],
225         // Block until document ready
226         blocking: true,
228         // By default, run previously failed tests first
229         // very useful in combination with "Hide passed tests" checked
230         reorder: true,
232         // By default, modify document.title when suite is done
233         altertitle: true,
235         // HTML Reporter: collapse every test except the first failing test
236         // If false, all failing tests will be expanded
237         collapse: true,
239         // By default, scroll to top of the page when suite is done
240         scrolltop: true,
242         // Depth up-to which object will be dumped
243         maxDepth: 5,
245         // When enabled, all tests must call expect()
246         requireExpects: false,
248         // Placeholder for user-configurable form-exposed URL parameters
249         urlConfig: [],
251         // Set of all modules.
252         modules: [],
254         // Stack of nested modules
255         moduleStack: [],
257         // The first unnamed module
258         currentModule: {
259                 name: "",
260                 tests: []
261         },
263         callbacks: {}
266 // Push a loose unnamed module to the modules collection
267 config.modules.push( config.currentModule );
269 var loggingCallbacks = {};
271 // Register logging callbacks
272 function registerLoggingCallbacks( obj ) {
273         var i, l, key,
274                 callbackNames = [ "begin", "done", "log", "testStart", "testDone",
275                         "moduleStart", "moduleDone" ];
277         function registerLoggingCallback( key ) {
278                 var loggingCallback = function( callback ) {
279                         if ( objectType( callback ) !== "function" ) {
280                                 throw new Error(
281                                         "QUnit logging methods require a callback function as their first parameters."
282                                 );
283                         }
285                         config.callbacks[ key ].push( callback );
286                 };
288                 // DEPRECATED: This will be removed on QUnit 2.0.0+
289                 // Stores the registered functions allowing restoring
290                 // at verifyLoggingCallbacks() if modified
291                 loggingCallbacks[ key ] = loggingCallback;
293                 return loggingCallback;
294         }
296         for ( i = 0, l = callbackNames.length; i < l; i++ ) {
297                 key = callbackNames[ i ];
299                 // Initialize key collection of logging callback
300                 if ( objectType( config.callbacks[ key ] ) === "undefined" ) {
301                         config.callbacks[ key ] = [];
302                 }
304                 obj[ key ] = registerLoggingCallback( key );
305         }
308 function runLoggingCallbacks( key, args ) {
309         var i, l, callbacks;
311         callbacks = config.callbacks[ key ];
312         for ( i = 0, l = callbacks.length; i < l; i++ ) {
313                 callbacks[ i ]( args );
314         }
317 // DEPRECATED: This will be removed on 2.0.0+
318 // This function verifies if the loggingCallbacks were modified by the user
319 // If so, it will restore it, assign the given callback and print a console warning
320 function verifyLoggingCallbacks() {
321         var loggingCallback, userCallback;
323         for ( loggingCallback in loggingCallbacks ) {
324                 if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
326                         userCallback = QUnit[ loggingCallback ];
328                         // Restore the callback function
329                         QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
331                         // Assign the deprecated given callback
332                         QUnit[ loggingCallback ]( userCallback );
334                         if ( global.console && global.console.warn ) {
335                                 global.console.warn(
336                                         "QUnit." + loggingCallback + " was replaced with a new value.\n" +
337                                         "Please, check out the documentation on how to apply logging callbacks.\n" +
338                                         "Reference: https://api.qunitjs.com/category/callbacks/"
339                                 );
340                         }
341                 }
342         }
345 ( function() {
346         if ( !defined.document ) {
347                 return;
348         }
350         // `onErrorFnPrev` initialized at top of scope
351         // Preserve other handlers
352         var onErrorFnPrev = window.onerror;
354         // Cover uncaught exceptions
355         // Returning true will suppress the default browser handler,
356         // returning false will let it run.
357         window.onerror = function( error, filePath, linerNr ) {
358                 var ret = false;
359                 if ( onErrorFnPrev ) {
360                         ret = onErrorFnPrev( error, filePath, linerNr );
361                 }
363                 // Treat return value as window.onerror itself does,
364                 // Only do our handling if not suppressed.
365                 if ( ret !== true ) {
366                         if ( QUnit.config.current ) {
367                                 if ( QUnit.config.current.ignoreGlobalErrors ) {
368                                         return true;
369                                 }
370                                 QUnit.pushFailure( error, filePath + ":" + linerNr );
371                         } else {
372                                 QUnit.test( "global failure", extend( function() {
373                                         QUnit.pushFailure( error, filePath + ":" + linerNr );
374                                 }, { validTest: true } ) );
375                         }
376                         return false;
377                 }
379                 return ret;
380         };
381 }() );
383 // Figure out if we're running the tests from a server or not
384 QUnit.isLocal = !( defined.document && window.location.protocol !== "file:" );
386 // Expose the current QUnit version
387 QUnit.version = "1.23.1";
389 extend( QUnit, {
391         // Call on start of module test to prepend name to all tests
392         module: function( name, testEnvironment, executeNow ) {
393                 var module, moduleFns;
394                 var currentModule = config.currentModule;
396                 if ( arguments.length === 2 ) {
397                         if ( objectType( testEnvironment ) === "function" ) {
398                                 executeNow = testEnvironment;
399                                 testEnvironment = undefined;
400                         }
401                 }
403                 // DEPRECATED: handles setup/teardown functions,
404                 // beforeEach and afterEach should be used instead
405                 if ( testEnvironment && testEnvironment.setup ) {
406                         testEnvironment.beforeEach = testEnvironment.setup;
407                         delete testEnvironment.setup;
408                 }
409                 if ( testEnvironment && testEnvironment.teardown ) {
410                         testEnvironment.afterEach = testEnvironment.teardown;
411                         delete testEnvironment.teardown;
412                 }
414                 module = createModule();
416                 moduleFns = {
417                         beforeEach: setHook( module, "beforeEach" ),
418                         afterEach: setHook( module, "afterEach" )
419                 };
421                 if ( objectType( executeNow ) === "function" ) {
422                         config.moduleStack.push( module );
423                         setCurrentModule( module );
424                         executeNow.call( module.testEnvironment, moduleFns );
425                         config.moduleStack.pop();
426                         module = module.parentModule || currentModule;
427                 }
429                 setCurrentModule( module );
431                 function createModule() {
432                         var parentModule = config.moduleStack.length ?
433                                 config.moduleStack.slice( -1 )[ 0 ] : null;
434                         var moduleName = parentModule !== null ?
435                                 [ parentModule.name, name ].join( " > " ) : name;
436                         var module = {
437                                 name: moduleName,
438                                 parentModule: parentModule,
439                                 tests: [],
440                                 moduleId: generateHash( moduleName )
441                         };
443                         var env = {};
444                         if ( parentModule ) {
445                                 extend( env, parentModule.testEnvironment );
446                                 delete env.beforeEach;
447                                 delete env.afterEach;
448                         }
449                         extend( env, testEnvironment );
450                         module.testEnvironment = env;
452                         config.modules.push( module );
453                         return module;
454                 }
456                 function setCurrentModule( module ) {
457                         config.currentModule = module;
458                 }
460         },
462         // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
463         asyncTest: asyncTest,
465         test: test,
467         skip: skip,
469         only: only,
471         // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
472         // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
473         start: function( count ) {
474                 var globalStartAlreadyCalled = globalStartCalled;
476                 if ( !config.current ) {
477                         globalStartCalled = true;
479                         if ( runStarted ) {
480                                 throw new Error( "Called start() outside of a test context while already started" );
481                         } else if ( globalStartAlreadyCalled || count > 1 ) {
482                                 throw new Error( "Called start() outside of a test context too many times" );
483                         } else if ( config.autostart ) {
484                                 throw new Error( "Called start() outside of a test context when " +
485                                         "QUnit.config.autostart was true" );
486                         } else if ( !config.pageLoaded ) {
488                                 // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
489                                 config.autostart = true;
490                                 return;
491                         }
492                 } else {
494                         // If a test is running, adjust its semaphore
495                         config.current.semaphore -= count || 1;
497                         // If semaphore is non-numeric, throw error
498                         if ( isNaN( config.current.semaphore ) ) {
499                                 config.current.semaphore = 0;
501                                 QUnit.pushFailure(
502                                         "Called start() with a non-numeric decrement.",
503                                         sourceFromStacktrace( 2 )
504                                 );
505                                 return;
506                         }
508                         // Don't start until equal number of stop-calls
509                         if ( config.current.semaphore > 0 ) {
510                                 return;
511                         }
513                         // Throw an Error if start is called more often than stop
514                         if ( config.current.semaphore < 0 ) {
515                                 config.current.semaphore = 0;
517                                 QUnit.pushFailure(
518                                         "Called start() while already started (test's semaphore was 0 already)",
519                                         sourceFromStacktrace( 2 )
520                                 );
521                                 return;
522                         }
523                 }
525                 resumeProcessing();
526         },
528         // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
529         stop: function( count ) {
531                 // If there isn't a test running, don't allow QUnit.stop() to be called
532                 if ( !config.current ) {
533                         throw new Error( "Called stop() outside of a test context" );
534                 }
536                 // If a test is running, adjust its semaphore
537                 config.current.semaphore += count || 1;
539                 pauseProcessing();
540         },
542         config: config,
544         is: is,
546         objectType: objectType,
548         extend: extend,
550         load: function() {
551                 config.pageLoaded = true;
553                 // Initialize the configuration options
554                 extend( config, {
555                         stats: { all: 0, bad: 0 },
556                         moduleStats: { all: 0, bad: 0 },
557                         started: 0,
558                         updateRate: 1000,
559                         autostart: true,
560                         filter: ""
561                 }, true );
563                 config.blocking = false;
565                 if ( config.autostart ) {
566                         resumeProcessing();
567                 }
568         },
570         stack: function( offset ) {
571                 offset = ( offset || 0 ) + 2;
572                 return sourceFromStacktrace( offset );
573         }
574 } );
576 registerLoggingCallbacks( QUnit );
578 function begin() {
579         var i, l,
580                 modulesLog = [];
582         // If the test run hasn't officially begun yet
583         if ( !config.started ) {
585                 // Record the time of the test run's beginning
586                 config.started = now();
588                 verifyLoggingCallbacks();
590                 // Delete the loose unnamed module if unused.
591                 if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
592                         config.modules.shift();
593                 }
595                 // Avoid unnecessary information by not logging modules' test environments
596                 for ( i = 0, l = config.modules.length; i < l; i++ ) {
597                         modulesLog.push( {
598                                 name: config.modules[ i ].name,
599                                 tests: config.modules[ i ].tests
600                         } );
601                 }
603                 // The test run is officially beginning now
604                 runLoggingCallbacks( "begin", {
605                         totalTests: Test.count,
606                         modules: modulesLog
607                 } );
608         }
610         config.blocking = false;
611         process( true );
614 function process( last ) {
615         function next() {
616                 process( last );
617         }
618         var start = now();
619         config.depth = ( config.depth || 0 ) + 1;
621         while ( config.queue.length && !config.blocking ) {
622                 if ( !defined.setTimeout || config.updateRate <= 0 ||
623                                 ( ( now() - start ) < config.updateRate ) ) {
624                         if ( config.current ) {
626                                 // Reset async tracking for each phase of the Test lifecycle
627                                 config.current.usedAsync = false;
628                         }
629                         config.queue.shift()();
630                 } else {
631                         setTimeout( next, 13 );
632                         break;
633                 }
634         }
635         config.depth--;
636         if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
637                 done();
638         }
641 function pauseProcessing() {
642         config.blocking = true;
644         if ( config.testTimeout && defined.setTimeout ) {
645                 clearTimeout( config.timeout );
646                 config.timeout = setTimeout( function() {
647                         if ( config.current ) {
648                                 config.current.semaphore = 0;
649                                 QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
650                         } else {
651                                 throw new Error( "Test timed out" );
652                         }
653                         resumeProcessing();
654                 }, config.testTimeout );
655         }
658 function resumeProcessing() {
659         runStarted = true;
661         // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
662         if ( defined.setTimeout ) {
663                 setTimeout( function() {
664                         if ( config.current && config.current.semaphore > 0 ) {
665                                 return;
666                         }
667                         if ( config.timeout ) {
668                                 clearTimeout( config.timeout );
669                         }
671                         begin();
672                 }, 13 );
673         } else {
674                 begin();
675         }
678 function done() {
679         var runtime, passed;
681         config.autorun = true;
683         // Log the last module results
684         if ( config.previousModule ) {
685                 runLoggingCallbacks( "moduleDone", {
686                         name: config.previousModule.name,
687                         tests: config.previousModule.tests,
688                         failed: config.moduleStats.bad,
689                         passed: config.moduleStats.all - config.moduleStats.bad,
690                         total: config.moduleStats.all,
691                         runtime: now() - config.moduleStats.started
692                 } );
693         }
694         delete config.previousModule;
696         runtime = now() - config.started;
697         passed = config.stats.all - config.stats.bad;
699         runLoggingCallbacks( "done", {
700                 failed: config.stats.bad,
701                 passed: passed,
702                 total: config.stats.all,
703                 runtime: runtime
704         } );
707 function setHook( module, hookName ) {
708         if ( module.testEnvironment === undefined ) {
709                 module.testEnvironment = {};
710         }
712         return function( callback ) {
713                 module.testEnvironment[ hookName ] = callback;
714         };
717 var focused = false;
718 var priorityCount = 0;
719 var unitSampler;
721 function Test( settings ) {
722         var i, l;
724         ++Test.count;
726         extend( this, settings );
727         this.assertions = [];
728         this.semaphore = 0;
729         this.usedAsync = false;
730         this.module = config.currentModule;
731         this.stack = sourceFromStacktrace( 3 );
733         // Register unique strings
734         for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
735                 if ( this.module.tests[ i ].name === this.testName ) {
736                         this.testName += " ";
737                 }
738         }
740         this.testId = generateHash( this.module.name, this.testName );
742         this.module.tests.push( {
743                 name: this.testName,
744                 testId: this.testId
745         } );
747         if ( settings.skip ) {
749                 // Skipped tests will fully ignore any sent callback
750                 this.callback = function() {};
751                 this.async = false;
752                 this.expected = 0;
753         } else {
754                 this.assert = new Assert( this );
755         }
758 Test.count = 0;
760 Test.prototype = {
761         before: function() {
762                 if (
764                         // Emit moduleStart when we're switching from one module to another
765                         this.module !== config.previousModule ||
767                                 // They could be equal (both undefined) but if the previousModule property doesn't
768                                 // yet exist it means this is the first test in a suite that isn't wrapped in a
769                                 // module, in which case we'll just emit a moduleStart event for 'undefined'.
770                                 // Without this, reporters can get testStart before moduleStart  which is a problem.
771                                 !hasOwn.call( config, "previousModule" )
772                 ) {
773                         if ( hasOwn.call( config, "previousModule" ) ) {
774                                 runLoggingCallbacks( "moduleDone", {
775                                         name: config.previousModule.name,
776                                         tests: config.previousModule.tests,
777                                         failed: config.moduleStats.bad,
778                                         passed: config.moduleStats.all - config.moduleStats.bad,
779                                         total: config.moduleStats.all,
780                                         runtime: now() - config.moduleStats.started
781                                 } );
782                         }
783                         config.previousModule = this.module;
784                         config.moduleStats = { all: 0, bad: 0, started: now() };
785                         runLoggingCallbacks( "moduleStart", {
786                                 name: this.module.name,
787                                 tests: this.module.tests
788                         } );
789                 }
791                 config.current = this;
793                 if ( this.module.testEnvironment ) {
794                         delete this.module.testEnvironment.beforeEach;
795                         delete this.module.testEnvironment.afterEach;
796                 }
797                 this.testEnvironment = extend( {}, this.module.testEnvironment );
799                 this.started = now();
800                 runLoggingCallbacks( "testStart", {
801                         name: this.testName,
802                         module: this.module.name,
803                         testId: this.testId
804                 } );
806                 if ( !config.pollution ) {
807                         saveGlobal();
808                 }
809         },
811         run: function() {
812                 var promise;
814                 config.current = this;
816                 if ( this.async ) {
817                         QUnit.stop();
818                 }
820                 this.callbackStarted = now();
822                 if ( config.notrycatch ) {
823                         runTest( this );
824                         return;
825                 }
827                 try {
828                         runTest( this );
829                 } catch ( e ) {
830                         this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
831                                 this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
833                         // Else next test will carry the responsibility
834                         saveGlobal();
836                         // Restart the tests if they're blocking
837                         if ( config.blocking ) {
838                                 QUnit.start();
839                         }
840                 }
842                 function runTest( test ) {
843                         promise = test.callback.call( test.testEnvironment, test.assert );
844                         test.resolvePromise( promise );
845                 }
846         },
848         after: function() {
849                 checkPollution();
850         },
852         queueHook: function( hook, hookName ) {
853                 var promise,
854                         test = this;
855                 return function runHook() {
856                         config.current = test;
857                         if ( config.notrycatch ) {
858                                 callHook();
859                                 return;
860                         }
861                         try {
862                                 callHook();
863                         } catch ( error ) {
864                                 test.pushFailure( hookName + " failed on " + test.testName + ": " +
865                                 ( error.message || error ), extractStacktrace( error, 0 ) );
866                         }
868                         function callHook() {
869                                 promise = hook.call( test.testEnvironment, test.assert );
870                                 test.resolvePromise( promise, hookName );
871                         }
872                 };
873         },
875         // Currently only used for module level hooks, can be used to add global level ones
876         hooks: function( handler ) {
877                 var hooks = [];
879                 function processHooks( test, module ) {
880                         if ( module.parentModule ) {
881                                 processHooks( test, module.parentModule );
882                         }
883                         if ( module.testEnvironment &&
884                                 QUnit.objectType( module.testEnvironment[ handler ] ) === "function" ) {
885                                 hooks.push( test.queueHook( module.testEnvironment[ handler ], handler ) );
886                         }
887                 }
889                 // Hooks are ignored on skipped tests
890                 if ( !this.skip ) {
891                         processHooks( this, this.module );
892                 }
893                 return hooks;
894         },
896         finish: function() {
897                 config.current = this;
898                 if ( config.requireExpects && this.expected === null ) {
899                         this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
900                                 "not called.", this.stack );
901                 } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
902                         this.pushFailure( "Expected " + this.expected + " assertions, but " +
903                                 this.assertions.length + " were run", this.stack );
904                 } else if ( this.expected === null && !this.assertions.length ) {
905                         this.pushFailure( "Expected at least one assertion, but none were run - call " +
906                                 "expect(0) to accept zero assertions.", this.stack );
907                 }
909                 var i,
910                         bad = 0;
912                 this.runtime = now() - this.started;
913                 config.stats.all += this.assertions.length;
914                 config.moduleStats.all += this.assertions.length;
916                 for ( i = 0; i < this.assertions.length; i++ ) {
917                         if ( !this.assertions[ i ].result ) {
918                                 bad++;
919                                 config.stats.bad++;
920                                 config.moduleStats.bad++;
921                         }
922                 }
924                 runLoggingCallbacks( "testDone", {
925                         name: this.testName,
926                         module: this.module.name,
927                         skipped: !!this.skip,
928                         failed: bad,
929                         passed: this.assertions.length - bad,
930                         total: this.assertions.length,
931                         runtime: this.runtime,
933                         // HTML Reporter use
934                         assertions: this.assertions,
935                         testId: this.testId,
937                         // Source of Test
938                         source: this.stack,
940                         // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
941                         duration: this.runtime
942                 } );
944                 // QUnit.reset() is deprecated and will be replaced for a new
945                 // fixture reset function on QUnit 2.0/2.1.
946                 // It's still called here for backwards compatibility handling
947                 QUnit.reset();
949                 config.current = undefined;
950         },
952         queue: function() {
953                 var priority,
954                         test = this;
956                 if ( !this.valid() ) {
957                         return;
958                 }
960                 function run() {
962                         // Each of these can by async
963                         synchronize( [
964                                 function() {
965                                         test.before();
966                                 },
968                                 test.hooks( "beforeEach" ),
969                                 function() {
970                                         test.run();
971                                 },
973                                 test.hooks( "afterEach" ).reverse(),
975                                 function() {
976                                         test.after();
977                                 },
978                                 function() {
979                                         test.finish();
980                                 }
981                         ] );
982                 }
984                 // Prioritize previously failed tests, detected from sessionStorage
985                 priority = QUnit.config.reorder && defined.sessionStorage &&
986                                 +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
988                 return synchronize( run, priority, config.seed );
989         },
991         pushResult: function( resultInfo ) {
993                 // Destructure of resultInfo = { result, actual, expected, message, negative }
994                 var source,
995                         details = {
996                                 module: this.module.name,
997                                 name: this.testName,
998                                 result: resultInfo.result,
999                                 message: resultInfo.message,
1000                                 actual: resultInfo.actual,
1001                                 expected: resultInfo.expected,
1002                                 testId: this.testId,
1003                                 negative: resultInfo.negative || false,
1004                                 runtime: now() - this.started
1005                         };
1007                 if ( !resultInfo.result ) {
1008                         source = sourceFromStacktrace();
1010                         if ( source ) {
1011                                 details.source = source;
1012                         }
1013                 }
1015                 runLoggingCallbacks( "log", details );
1017                 this.assertions.push( {
1018                         result: !!resultInfo.result,
1019                         message: resultInfo.message
1020                 } );
1021         },
1023         pushFailure: function( message, source, actual ) {
1024                 if ( !( this instanceof Test ) ) {
1025                         throw new Error( "pushFailure() assertion outside test context, was " +
1026                                 sourceFromStacktrace( 2 ) );
1027                 }
1029                 var details = {
1030                                 module: this.module.name,
1031                                 name: this.testName,
1032                                 result: false,
1033                                 message: message || "error",
1034                                 actual: actual || null,
1035                                 testId: this.testId,
1036                                 runtime: now() - this.started
1037                         };
1039                 if ( source ) {
1040                         details.source = source;
1041                 }
1043                 runLoggingCallbacks( "log", details );
1045                 this.assertions.push( {
1046                         result: false,
1047                         message: message
1048                 } );
1049         },
1051         resolvePromise: function( promise, phase ) {
1052                 var then, message,
1053                         test = this;
1054                 if ( promise != null ) {
1055                         then = promise.then;
1056                         if ( QUnit.objectType( then ) === "function" ) {
1057                                 QUnit.stop();
1058                                 then.call(
1059                                         promise,
1060                                         function() { QUnit.start(); },
1061                                         function( error ) {
1062                                                 message = "Promise rejected " +
1063                                                         ( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
1064                                                         " " + test.testName + ": " + ( error.message || error );
1065                                                 test.pushFailure( message, extractStacktrace( error, 0 ) );
1067                                                 // Else next test will carry the responsibility
1068                                                 saveGlobal();
1070                                                 // Unblock
1071                                                 QUnit.start();
1072                                         }
1073                                 );
1074                         }
1075                 }
1076         },
1078         valid: function() {
1079                 var filter = config.filter,
1080                         regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec( filter ),
1081                         module = config.module && config.module.toLowerCase(),
1082                         fullName = ( this.module.name + ": " + this.testName );
1084                 function moduleChainNameMatch( testModule ) {
1085                         var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
1086                         if ( testModuleName === module ) {
1087                                 return true;
1088                         } else if ( testModule.parentModule ) {
1089                                 return moduleChainNameMatch( testModule.parentModule );
1090                         } else {
1091                                 return false;
1092                         }
1093                 }
1095                 function moduleChainIdMatch( testModule ) {
1096                         return inArray( testModule.moduleId, config.moduleId ) > -1 ||
1097                                 testModule.parentModule && moduleChainIdMatch( testModule.parentModule );
1098                 }
1100                 // Internally-generated tests are always valid
1101                 if ( this.callback && this.callback.validTest ) {
1102                         return true;
1103                 }
1105                 if ( config.moduleId && config.moduleId.length > 0 &&
1106                         !moduleChainIdMatch( this.module ) ) {
1108                         return false;
1109                 }
1111                 if ( config.testId && config.testId.length > 0 &&
1112                         inArray( this.testId, config.testId ) < 0 ) {
1114                         return false;
1115                 }
1117                 if ( module && !moduleChainNameMatch( this.module ) ) {
1118                         return false;
1119                 }
1121                 if ( !filter ) {
1122                         return true;
1123                 }
1125                 return regexFilter ?
1126                         this.regexFilter( !!regexFilter[ 1 ], regexFilter[ 2 ], regexFilter[ 3 ], fullName ) :
1127                         this.stringFilter( filter, fullName );
1128         },
1130         regexFilter: function( exclude, pattern, flags, fullName ) {
1131                 var regex = new RegExp( pattern, flags );
1132                 var match = regex.test( fullName );
1134                 return match !== exclude;
1135         },
1137         stringFilter: function( filter, fullName ) {
1138                 filter = filter.toLowerCase();
1139                 fullName = fullName.toLowerCase();
1141                 var include = filter.charAt( 0 ) !== "!";
1142                 if ( !include ) {
1143                         filter = filter.slice( 1 );
1144                 }
1146                 // If the filter matches, we need to honour include
1147                 if ( fullName.indexOf( filter ) !== -1 ) {
1148                         return include;
1149                 }
1151                 // Otherwise, do the opposite
1152                 return !include;
1153         }
1156 // Resets the test setup. Useful for tests that modify the DOM.
1158 DEPRECATED: Use multiple tests instead of resetting inside a test.
1159 Use testStart or testDone for custom cleanup.
1160 This method will throw an error in 2.0, and will be removed in 2.1
1162 QUnit.reset = function() {
1164         // Return on non-browser environments
1165         // This is necessary to not break on node tests
1166         if ( !defined.document ) {
1167                 return;
1168         }
1170         var fixture = defined.document && document.getElementById &&
1171                         document.getElementById( "qunit-fixture" );
1173         if ( fixture ) {
1174                 fixture.innerHTML = config.fixture;
1175         }
1178 QUnit.pushFailure = function() {
1179         if ( !QUnit.config.current ) {
1180                 throw new Error( "pushFailure() assertion outside test context, in " +
1181                         sourceFromStacktrace( 2 ) );
1182         }
1184         // Gets current test obj
1185         var currentTest = QUnit.config.current;
1187         return currentTest.pushFailure.apply( currentTest, arguments );
1190 // Based on Java's String.hashCode, a simple but not
1191 // rigorously collision resistant hashing function
1192 function generateHash( module, testName ) {
1193         var hex,
1194                 i = 0,
1195                 hash = 0,
1196                 str = module + "\x1C" + testName,
1197                 len = str.length;
1199         for ( ; i < len; i++ ) {
1200                 hash  = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
1201                 hash |= 0;
1202         }
1204         // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
1205         // strictly necessary but increases user understanding that the id is a SHA-like hash
1206         hex = ( 0x100000000 + hash ).toString( 16 );
1207         if ( hex.length < 8 ) {
1208                 hex = "0000000" + hex;
1209         }
1211         return hex.slice( -8 );
1214 function synchronize( callback, priority, seed ) {
1215         var last = !priority,
1216                 index;
1218         if ( QUnit.objectType( callback ) === "array" ) {
1219                 while ( callback.length ) {
1220                         synchronize( callback.shift() );
1221                 }
1222                 return;
1223         }
1225         if ( priority ) {
1226                 config.queue.splice( priorityCount++, 0, callback );
1227         } else if ( seed ) {
1228                 if ( !unitSampler ) {
1229                         unitSampler = unitSamplerGenerator( seed );
1230                 }
1232                 // Insert into a random position after all priority items
1233                 index = Math.floor( unitSampler() * ( config.queue.length - priorityCount + 1 ) );
1234                 config.queue.splice( priorityCount + index, 0, callback );
1235         } else {
1236                 config.queue.push( callback );
1237         }
1239         if ( config.autorun && !config.blocking ) {
1240                 process( last );
1241         }
1244 function unitSamplerGenerator( seed ) {
1246         // 32-bit xorshift, requires only a nonzero seed
1247         // http://excamera.com/sphinx/article-xorshift.html
1248         var sample = parseInt( generateHash( seed ), 16 ) || -1;
1249         return function() {
1250                 sample ^= sample << 13;
1251                 sample ^= sample >>> 17;
1252                 sample ^= sample << 5;
1254                 // ECMAScript has no unsigned number type
1255                 if ( sample < 0 ) {
1256                         sample += 0x100000000;
1257                 }
1259                 return sample / 0x100000000;
1260         };
1263 function saveGlobal() {
1264         config.pollution = [];
1266         if ( config.noglobals ) {
1267                 for ( var key in global ) {
1268                         if ( hasOwn.call( global, key ) ) {
1270                                 // In Opera sometimes DOM element ids show up here, ignore them
1271                                 if ( /^qunit-test-output/.test( key ) ) {
1272                                         continue;
1273                                 }
1274                                 config.pollution.push( key );
1275                         }
1276                 }
1277         }
1280 function checkPollution() {
1281         var newGlobals,
1282                 deletedGlobals,
1283                 old = config.pollution;
1285         saveGlobal();
1287         newGlobals = diff( config.pollution, old );
1288         if ( newGlobals.length > 0 ) {
1289                 QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
1290         }
1292         deletedGlobals = diff( old, config.pollution );
1293         if ( deletedGlobals.length > 0 ) {
1294                 QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
1295         }
1298 // Will be exposed as QUnit.asyncTest
1299 function asyncTest( testName, expected, callback ) {
1300         if ( arguments.length === 2 ) {
1301                 callback = expected;
1302                 expected = null;
1303         }
1305         QUnit.test( testName, expected, callback, true );
1308 // Will be exposed as QUnit.test
1309 function test( testName, expected, callback, async ) {
1310         if ( focused )  { return; }
1312         var newTest;
1314         if ( arguments.length === 2 ) {
1315                 callback = expected;
1316                 expected = null;
1317         }
1319         newTest = new Test( {
1320                 testName: testName,
1321                 expected: expected,
1322                 async: async,
1323                 callback: callback
1324         } );
1326         newTest.queue();
1329 // Will be exposed as QUnit.skip
1330 function skip( testName ) {
1331         if ( focused )  { return; }
1333         var test = new Test( {
1334                 testName: testName,
1335                 skip: true
1336         } );
1338         test.queue();
1341 // Will be exposed as QUnit.only
1342 function only( testName, expected, callback, async ) {
1343         var newTest;
1345         if ( focused )  { return; }
1347         QUnit.config.queue.length = 0;
1348         focused = true;
1350         if ( arguments.length === 2 ) {
1351                 callback = expected;
1352                 expected = null;
1353         }
1355         newTest = new Test( {
1356                 testName: testName,
1357                 expected: expected,
1358                 async: async,
1359                 callback: callback
1360         } );
1362         newTest.queue();
1365 function Assert( testContext ) {
1366         this.test = testContext;
1369 // Assert helpers
1370 QUnit.assert = Assert.prototype = {
1372         // Specify the number of expected assertions to guarantee that failed test
1373         // (no assertions are run at all) don't slip through.
1374         expect: function( asserts ) {
1375                 if ( arguments.length === 1 ) {
1376                         this.test.expected = asserts;
1377                 } else {
1378                         return this.test.expected;
1379                 }
1380         },
1382         // Increment this Test's semaphore counter, then return a function that
1383         // decrements that counter a maximum of once.
1384         async: function( count ) {
1385                 var test = this.test,
1386                         popped = false,
1387                         acceptCallCount = count;
1389                 if ( typeof acceptCallCount === "undefined" ) {
1390                         acceptCallCount = 1;
1391                 }
1393                 test.semaphore += 1;
1394                 test.usedAsync = true;
1395                 pauseProcessing();
1397                 return function done() {
1399                         if ( popped ) {
1400                                 test.pushFailure( "Too many calls to the `assert.async` callback",
1401                                         sourceFromStacktrace( 2 ) );
1402                                 return;
1403                         }
1404                         acceptCallCount -= 1;
1405                         if ( acceptCallCount > 0 ) {
1406                                 return;
1407                         }
1409                         test.semaphore -= 1;
1410                         popped = true;
1411                         resumeProcessing();
1412                 };
1413         },
1415         // Exports test.push() to the user API
1416         // Alias of pushResult.
1417         push: function( result, actual, expected, message, negative ) {
1418                 var currentAssert = this instanceof Assert ? this : QUnit.config.current.assert;
1419                 return currentAssert.pushResult( {
1420                         result: result,
1421                         actual: actual,
1422                         expected: expected,
1423                         message: message,
1424                         negative: negative
1425                 } );
1426         },
1428         pushResult: function( resultInfo ) {
1430                 // Destructure of resultInfo = { result, actual, expected, message, negative }
1431                 var assert = this,
1432                         currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
1434                 // Backwards compatibility fix.
1435                 // Allows the direct use of global exported assertions and QUnit.assert.*
1436                 // Although, it's use is not recommended as it can leak assertions
1437                 // to other tests from async tests, because we only get a reference to the current test,
1438                 // not exactly the test where assertion were intended to be called.
1439                 if ( !currentTest ) {
1440                         throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
1441                 }
1443                 if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
1444                         currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
1445                                 sourceFromStacktrace( 2 ) );
1447                         // Allow this assertion to continue running anyway...
1448                 }
1450                 if ( !( assert instanceof Assert ) ) {
1451                         assert = currentTest.assert;
1452                 }
1454                 return assert.test.pushResult( resultInfo );
1455         },
1457         ok: function( result, message ) {
1458                 message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
1459                         QUnit.dump.parse( result ) );
1460                 this.pushResult( {
1461                         result: !!result,
1462                         actual: result,
1463                         expected: true,
1464                         message: message
1465                 } );
1466         },
1468         notOk: function( result, message ) {
1469                 message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " +
1470                         QUnit.dump.parse( result ) );
1471                 this.pushResult( {
1472                         result: !result,
1473                         actual: result,
1474                         expected: false,
1475                         message: message
1476                 } );
1477         },
1479         equal: function( actual, expected, message ) {
1480                 /*jshint eqeqeq:false */
1481                 this.pushResult( {
1482                         result: expected == actual,
1483                         actual: actual,
1484                         expected: expected,
1485                         message: message
1486                 } );
1487         },
1489         notEqual: function( actual, expected, message ) {
1490                 /*jshint eqeqeq:false */
1491                 this.pushResult( {
1492                         result: expected != actual,
1493                         actual: actual,
1494                         expected: expected,
1495                         message: message,
1496                         negative: true
1497                 } );
1498         },
1500         propEqual: function( actual, expected, message ) {
1501                 actual = objectValues( actual );
1502                 expected = objectValues( expected );
1503                 this.pushResult( {
1504                         result: QUnit.equiv( actual, expected ),
1505                         actual: actual,
1506                         expected: expected,
1507                         message: message
1508                 } );
1509         },
1511         notPropEqual: function( actual, expected, message ) {
1512                 actual = objectValues( actual );
1513                 expected = objectValues( expected );
1514                 this.pushResult( {
1515                         result: !QUnit.equiv( actual, expected ),
1516                         actual: actual,
1517                         expected: expected,
1518                         message: message,
1519                         negative: true
1520                 } );
1521         },
1523         deepEqual: function( actual, expected, message ) {
1524                 this.pushResult( {
1525                         result: QUnit.equiv( actual, expected ),
1526                         actual: actual,
1527                         expected: expected,
1528                         message: message
1529                 } );
1530         },
1532         notDeepEqual: function( actual, expected, message ) {
1533                 this.pushResult( {
1534                         result: !QUnit.equiv( actual, expected ),
1535                         actual: actual,
1536                         expected: expected,
1537                         message: message,
1538                         negative: true
1539                 } );
1540         },
1542         strictEqual: function( actual, expected, message ) {
1543                 this.pushResult( {
1544                         result: expected === actual,
1545                         actual: actual,
1546                         expected: expected,
1547                         message: message
1548                 } );
1549         },
1551         notStrictEqual: function( actual, expected, message ) {
1552                 this.pushResult( {
1553                         result: expected !== actual,
1554                         actual: actual,
1555                         expected: expected,
1556                         message: message,
1557                         negative: true
1558                 } );
1559         },
1561         "throws": function( block, expected, message ) {
1562                 var actual, expectedType,
1563                         expectedOutput = expected,
1564                         ok = false,
1565                         currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current;
1567                 // 'expected' is optional unless doing string comparison
1568                 if ( message == null && typeof expected === "string" ) {
1569                         message = expected;
1570                         expected = null;
1571                 }
1573                 currentTest.ignoreGlobalErrors = true;
1574                 try {
1575                         block.call( currentTest.testEnvironment );
1576                 } catch ( e ) {
1577                         actual = e;
1578                 }
1579                 currentTest.ignoreGlobalErrors = false;
1581                 if ( actual ) {
1582                         expectedType = QUnit.objectType( expected );
1584                         // We don't want to validate thrown error
1585                         if ( !expected ) {
1586                                 ok = true;
1587                                 expectedOutput = null;
1589                         // Expected is a regexp
1590                         } else if ( expectedType === "regexp" ) {
1591                                 ok = expected.test( errorString( actual ) );
1593                         // Expected is a string
1594                         } else if ( expectedType === "string" ) {
1595                                 ok = expected === errorString( actual );
1597                         // Expected is a constructor, maybe an Error constructor
1598                         } else if ( expectedType === "function" && actual instanceof expected ) {
1599                                 ok = true;
1601                         // Expected is an Error object
1602                         } else if ( expectedType === "object" ) {
1603                                 ok = actual instanceof expected.constructor &&
1604                                         actual.name === expected.name &&
1605                                         actual.message === expected.message;
1607                         // Expected is a validation function which returns true if validation passed
1608                         } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
1609                                 expectedOutput = null;
1610                                 ok = true;
1611                         }
1612                 }
1614                 currentTest.assert.pushResult( {
1615                         result: ok,
1616                         actual: actual,
1617                         expected: expectedOutput,
1618                         message: message
1619                 } );
1620         }
1623 // Provide an alternative to assert.throws(), for environments that consider throws a reserved word
1624 // Known to us are: Closure Compiler, Narwhal
1625 ( function() {
1626         /*jshint sub:true */
1627         Assert.prototype.raises = Assert.prototype [ "throws" ]; //jscs:ignore requireDotNotation
1628 }() );
1630 function errorString( error ) {
1631         var name, message,
1632                 resultErrorString = error.toString();
1633         if ( resultErrorString.substring( 0, 7 ) === "[object" ) {
1634                 name = error.name ? error.name.toString() : "Error";
1635                 message = error.message ? error.message.toString() : "";
1636                 if ( name && message ) {
1637                         return name + ": " + message;
1638                 } else if ( name ) {
1639                         return name;
1640                 } else if ( message ) {
1641                         return message;
1642                 } else {
1643                         return "Error";
1644                 }
1645         } else {
1646                 return resultErrorString;
1647         }
1650 // Test for equality any JavaScript type.
1651 // Author: Philippe Rathé <prathe@gmail.com>
1652 QUnit.equiv = ( function() {
1654         // Stack to decide between skip/abort functions
1655         var callers = [];
1657         // Stack to avoiding loops from circular referencing
1658         var parents = [];
1659         var parentsB = [];
1661         var getProto = Object.getPrototypeOf || function( obj ) {
1663                 /*jshint proto: true */
1664                 return obj.__proto__;
1665         };
1667         function useStrictEquality( b, a ) {
1669                 // To catch short annotation VS 'new' annotation of a declaration. e.g.:
1670                 // `var i = 1;`
1671                 // `var j = new Number(1);`
1672                 if ( typeof a === "object" ) {
1673                         a = a.valueOf();
1674                 }
1675                 if ( typeof b === "object" ) {
1676                         b = b.valueOf();
1677                 }
1679                 return a === b;
1680         }
1682         function compareConstructors( a, b ) {
1683                 var protoA = getProto( a );
1684                 var protoB = getProto( b );
1686                 // Comparing constructors is more strict than using `instanceof`
1687                 if ( a.constructor === b.constructor ) {
1688                         return true;
1689                 }
1691                 // Ref #851
1692                 // If the obj prototype descends from a null constructor, treat it
1693                 // as a null prototype.
1694                 if ( protoA && protoA.constructor === null ) {
1695                         protoA = null;
1696                 }
1697                 if ( protoB && protoB.constructor === null ) {
1698                         protoB = null;
1699                 }
1701                 // Allow objects with no prototype to be equivalent to
1702                 // objects with Object as their constructor.
1703                 if ( ( protoA === null && protoB === Object.prototype ) ||
1704                                 ( protoB === null && protoA === Object.prototype ) ) {
1705                         return true;
1706                 }
1708                 return false;
1709         }
1711         function getRegExpFlags( regexp ) {
1712                 return "flags" in regexp ? regexp.flags : regexp.toString().match( /[gimuy]*$/ )[ 0 ];
1713         }
1715         var callbacks = {
1716                 "string": useStrictEquality,
1717                 "boolean": useStrictEquality,
1718                 "number": useStrictEquality,
1719                 "null": useStrictEquality,
1720                 "undefined": useStrictEquality,
1721                 "symbol": useStrictEquality,
1722                 "date": useStrictEquality,
1724                 "nan": function() {
1725                         return true;
1726                 },
1728                 "regexp": function( b, a ) {
1729                         return a.source === b.source &&
1731                                 // Include flags in the comparison
1732                                 getRegExpFlags( a ) === getRegExpFlags( b );
1733                 },
1735                 // - skip when the property is a method of an instance (OOP)
1736                 // - abort otherwise,
1737                 // initial === would have catch identical references anyway
1738                 "function": function() {
1739                         var caller = callers[ callers.length - 1 ];
1740                         return caller !== Object && typeof caller !== "undefined";
1741                 },
1743                 "array": function( b, a ) {
1744                         var i, j, len, loop, aCircular, bCircular;
1746                         len = a.length;
1747                         if ( len !== b.length ) {
1749                                 // Safe and faster
1750                                 return false;
1751                         }
1753                         // Track reference to avoid circular references
1754                         parents.push( a );
1755                         parentsB.push( b );
1756                         for ( i = 0; i < len; i++ ) {
1757                                 loop = false;
1758                                 for ( j = 0; j < parents.length; j++ ) {
1759                                         aCircular = parents[ j ] === a[ i ];
1760                                         bCircular = parentsB[ j ] === b[ i ];
1761                                         if ( aCircular || bCircular ) {
1762                                                 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1763                                                         loop = true;
1764                                                 } else {
1765                                                         parents.pop();
1766                                                         parentsB.pop();
1767                                                         return false;
1768                                                 }
1769                                         }
1770                                 }
1771                                 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1772                                         parents.pop();
1773                                         parentsB.pop();
1774                                         return false;
1775                                 }
1776                         }
1777                         parents.pop();
1778                         parentsB.pop();
1779                         return true;
1780                 },
1782                 "set": function( b, a ) {
1783                         var innerEq,
1784                                 outerEq = true;
1786                         if ( a.size !== b.size ) {
1787                                 return false;
1788                         }
1790                         a.forEach( function( aVal ) {
1791                                 innerEq = false;
1793                                 b.forEach( function( bVal ) {
1794                                         if ( innerEquiv( bVal, aVal ) ) {
1795                                                 innerEq = true;
1796                                         }
1797                                 } );
1799                                 if ( !innerEq ) {
1800                                         outerEq = false;
1801                                 }
1802                         } );
1804                         return outerEq;
1805                 },
1807                 "map": function( b, a ) {
1808                         var innerEq,
1809                                 outerEq = true;
1811                         if ( a.size !== b.size ) {
1812                                 return false;
1813                         }
1815                         a.forEach( function( aVal, aKey ) {
1816                                 innerEq = false;
1818                                 b.forEach( function( bVal, bKey ) {
1819                                         if ( innerEquiv( [ bVal, bKey ], [ aVal, aKey ] ) ) {
1820                                                 innerEq = true;
1821                                         }
1822                                 } );
1824                                 if ( !innerEq ) {
1825                                         outerEq = false;
1826                                 }
1827                         } );
1829                         return outerEq;
1830                 },
1832                 "object": function( b, a ) {
1833                         var i, j, loop, aCircular, bCircular;
1835                         // Default to true
1836                         var eq = true;
1837                         var aProperties = [];
1838                         var bProperties = [];
1840                         if ( compareConstructors( a, b ) === false ) {
1841                                 return false;
1842                         }
1844                         // Stack constructor before traversing properties
1845                         callers.push( a.constructor );
1847                         // Track reference to avoid circular references
1848                         parents.push( a );
1849                         parentsB.push( b );
1851                         // Be strict: don't ensure hasOwnProperty and go deep
1852                         for ( i in a ) {
1853                                 loop = false;
1854                                 for ( j = 0; j < parents.length; j++ ) {
1855                                         aCircular = parents[ j ] === a[ i ];
1856                                         bCircular = parentsB[ j ] === b[ i ];
1857                                         if ( aCircular || bCircular ) {
1858                                                 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1859                                                         loop = true;
1860                                                 } else {
1861                                                         eq = false;
1862                                                         break;
1863                                                 }
1864                                         }
1865                                 }
1866                                 aProperties.push( i );
1867                                 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1868                                         eq = false;
1869                                         break;
1870                                 }
1871                         }
1873                         parents.pop();
1874                         parentsB.pop();
1876                         // Unstack, we are done
1877                         callers.pop();
1879                         for ( i in b ) {
1881                                 // Collect b's properties
1882                                 bProperties.push( i );
1883                         }
1885                         // Ensures identical properties name
1886                         return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
1887                 }
1888         };
1890         function typeEquiv( a, b ) {
1891                 var type = QUnit.objectType( a );
1892                 return QUnit.objectType( b ) === type && callbacks[ type ]( b, a );
1893         }
1895         // The real equiv function
1896         function innerEquiv( a, b ) {
1898                 // We're done when there's nothing more to compare
1899                 if ( arguments.length < 2 ) {
1900                         return true;
1901                 }
1903                 // Require type-specific equality
1904                 return ( a === b || typeEquiv( a, b ) ) &&
1906                         // ...across all consecutive argument pairs
1907                         ( arguments.length === 2 || innerEquiv.apply( this, [].slice.call( arguments, 1 ) ) );
1908         }
1910         return innerEquiv;
1911 }() );
1913 // Based on jsDump by Ariel Flesler
1914 // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
1915 QUnit.dump = ( function() {
1916         function quote( str ) {
1917                 return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\"";
1918         }
1919         function literal( o ) {
1920                 return o + "";
1921         }
1922         function join( pre, arr, post ) {
1923                 var s = dump.separator(),
1924                         base = dump.indent(),
1925                         inner = dump.indent( 1 );
1926                 if ( arr.join ) {
1927                         arr = arr.join( "," + s + inner );
1928                 }
1929                 if ( !arr ) {
1930                         return pre + post;
1931                 }
1932                 return [ pre, inner + arr, base + post ].join( s );
1933         }
1934         function array( arr, stack ) {
1935                 var i = arr.length,
1936                         ret = new Array( i );
1938                 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1939                         return "[object Array]";
1940                 }
1942                 this.up();
1943                 while ( i-- ) {
1944                         ret[ i ] = this.parse( arr[ i ], undefined, stack );
1945                 }
1946                 this.down();
1947                 return join( "[", ret, "]" );
1948         }
1950         var reName = /^function (\w+)/,
1951                 dump = {
1953                         // The objType is used mostly internally, you can fix a (custom) type in advance
1954                         parse: function( obj, objType, stack ) {
1955                                 stack = stack || [];
1956                                 var res, parser, parserType,
1957                                         inStack = inArray( obj, stack );
1959                                 if ( inStack !== -1 ) {
1960                                         return "recursion(" + ( inStack - stack.length ) + ")";
1961                                 }
1963                                 objType = objType || this.typeOf( obj  );
1964                                 parser = this.parsers[ objType ];
1965                                 parserType = typeof parser;
1967                                 if ( parserType === "function" ) {
1968                                         stack.push( obj );
1969                                         res = parser.call( this, obj, stack );
1970                                         stack.pop();
1971                                         return res;
1972                                 }
1973                                 return ( parserType === "string" ) ? parser : this.parsers.error;
1974                         },
1975                         typeOf: function( obj ) {
1976                                 var type;
1977                                 if ( obj === null ) {
1978                                         type = "null";
1979                                 } else if ( typeof obj === "undefined" ) {
1980                                         type = "undefined";
1981                                 } else if ( QUnit.is( "regexp", obj ) ) {
1982                                         type = "regexp";
1983                                 } else if ( QUnit.is( "date", obj ) ) {
1984                                         type = "date";
1985                                 } else if ( QUnit.is( "function", obj ) ) {
1986                                         type = "function";
1987                                 } else if ( obj.setInterval !== undefined &&
1988                                                 obj.document !== undefined &&
1989                                                 obj.nodeType === undefined ) {
1990                                         type = "window";
1991                                 } else if ( obj.nodeType === 9 ) {
1992                                         type = "document";
1993                                 } else if ( obj.nodeType ) {
1994                                         type = "node";
1995                                 } else if (
1997                                         // Native arrays
1998                                         toString.call( obj ) === "[object Array]" ||
2000                                         // NodeList objects
2001                                         ( typeof obj.length === "number" && obj.item !== undefined &&
2002                                         ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
2003                                         obj[ 0 ] === undefined ) ) )
2004                                 ) {
2005                                         type = "array";
2006                                 } else if ( obj.constructor === Error.prototype.constructor ) {
2007                                         type = "error";
2008                                 } else {
2009                                         type = typeof obj;
2010                                 }
2011                                 return type;
2012                         },
2014                         separator: function() {
2015                                 return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
2016                         },
2018                         // Extra can be a number, shortcut for increasing-calling-decreasing
2019                         indent: function( extra ) {
2020                                 if ( !this.multiline ) {
2021                                         return "";
2022                                 }
2023                                 var chr = this.indentChar;
2024                                 if ( this.HTML ) {
2025                                         chr = chr.replace( /\t/g, "   " ).replace( / /g, "&#160;" );
2026                                 }
2027                                 return new Array( this.depth + ( extra || 0 ) ).join( chr );
2028                         },
2029                         up: function( a ) {
2030                                 this.depth += a || 1;
2031                         },
2032                         down: function( a ) {
2033                                 this.depth -= a || 1;
2034                         },
2035                         setParser: function( name, parser ) {
2036                                 this.parsers[ name ] = parser;
2037                         },
2039                         // The next 3 are exposed so you can use them
2040                         quote: quote,
2041                         literal: literal,
2042                         join: join,
2043                         depth: 1,
2044                         maxDepth: QUnit.config.maxDepth,
2046                         // This is the list of parsers, to modify them, use dump.setParser
2047                         parsers: {
2048                                 window: "[Window]",
2049                                 document: "[Document]",
2050                                 error: function( error ) {
2051                                         return "Error(\"" + error.message + "\")";
2052                                 },
2053                                 unknown: "[Unknown]",
2054                                 "null": "null",
2055                                 "undefined": "undefined",
2056                                 "function": function( fn ) {
2057                                         var ret = "function",
2059                                                 // Functions never have name in IE
2060                                                 name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
2062                                         if ( name ) {
2063                                                 ret += " " + name;
2064                                         }
2065                                         ret += "(";
2067                                         ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
2068                                         return join( ret, dump.parse( fn, "functionCode" ), "}" );
2069                                 },
2070                                 array: array,
2071                                 nodelist: array,
2072                                 "arguments": array,
2073                                 object: function( map, stack ) {
2074                                         var keys, key, val, i, nonEnumerableProperties,
2075                                                 ret = [];
2077                                         if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
2078                                                 return "[object Object]";
2079                                         }
2081                                         dump.up();
2082                                         keys = [];
2083                                         for ( key in map ) {
2084                                                 keys.push( key );
2085                                         }
2087                                         // Some properties are not always enumerable on Error objects.
2088                                         nonEnumerableProperties = [ "message", "name" ];
2089                                         for ( i in nonEnumerableProperties ) {
2090                                                 key = nonEnumerableProperties[ i ];
2091                                                 if ( key in map && inArray( key, keys ) < 0 ) {
2092                                                         keys.push( key );
2093                                                 }
2094                                         }
2095                                         keys.sort();
2096                                         for ( i = 0; i < keys.length; i++ ) {
2097                                                 key = keys[ i ];
2098                                                 val = map[ key ];
2099                                                 ret.push( dump.parse( key, "key" ) + ": " +
2100                                                         dump.parse( val, undefined, stack ) );
2101                                         }
2102                                         dump.down();
2103                                         return join( "{", ret, "}" );
2104                                 },
2105                                 node: function( node ) {
2106                                         var len, i, val,
2107                                                 open = dump.HTML ? "&lt;" : "<",
2108                                                 close = dump.HTML ? "&gt;" : ">",
2109                                                 tag = node.nodeName.toLowerCase(),
2110                                                 ret = open + tag,
2111                                                 attrs = node.attributes;
2113                                         if ( attrs ) {
2114                                                 for ( i = 0, len = attrs.length; i < len; i++ ) {
2115                                                         val = attrs[ i ].nodeValue;
2117                                                         // IE6 includes all attributes in .attributes, even ones not explicitly
2118                                                         // set. Those have values like undefined, null, 0, false, "" or
2119                                                         // "inherit".
2120                                                         if ( val && val !== "inherit" ) {
2121                                                                 ret += " " + attrs[ i ].nodeName + "=" +
2122                                                                         dump.parse( val, "attribute" );
2123                                                         }
2124                                                 }
2125                                         }
2126                                         ret += close;
2128                                         // Show content of TextNode or CDATASection
2129                                         if ( node.nodeType === 3 || node.nodeType === 4 ) {
2130                                                 ret += node.nodeValue;
2131                                         }
2133                                         return ret + open + "/" + tag + close;
2134                                 },
2136                                 // Function calls it internally, it's the arguments part of the function
2137                                 functionArgs: function( fn ) {
2138                                         var args,
2139                                                 l = fn.length;
2141                                         if ( !l ) {
2142                                                 return "";
2143                                         }
2145                                         args = new Array( l );
2146                                         while ( l-- ) {
2148                                                 // 97 is 'a'
2149                                                 args[ l ] = String.fromCharCode( 97 + l );
2150                                         }
2151                                         return " " + args.join( ", " ) + " ";
2152                                 },
2154                                 // Object calls it internally, the key part of an item in a map
2155                                 key: quote,
2157                                 // Function calls it internally, it's the content of the function
2158                                 functionCode: "[code]",
2160                                 // Node calls it internally, it's a html attribute value
2161                                 attribute: quote,
2162                                 string: quote,
2163                                 date: quote,
2164                                 regexp: literal,
2165                                 number: literal,
2166                                 "boolean": literal
2167                         },
2169                         // If true, entities are escaped ( <, >, \t, space and \n )
2170                         HTML: false,
2172                         // Indentation unit
2173                         indentChar: "  ",
2175                         // If true, items in a collection, are separated by a \n, else just a space.
2176                         multiline: true
2177                 };
2179         return dump;
2180 }() );
2182 // Back compat
2183 QUnit.jsDump = QUnit.dump;
2185 // Deprecated
2186 // Extend assert methods to QUnit for Backwards compatibility
2187 ( function() {
2188         var i,
2189                 assertions = Assert.prototype;
2191         function applyCurrent( current ) {
2192                 return function() {
2193                         var assert = new Assert( QUnit.config.current );
2194                         current.apply( assert, arguments );
2195                 };
2196         }
2198         for ( i in assertions ) {
2199                 QUnit[ i ] = applyCurrent( assertions[ i ] );
2200         }
2201 }() );
2203 // For browser, export only select globals
2204 if ( defined.document ) {
2206         ( function() {
2207                 var i, l,
2208                         keys = [
2209                                 "test",
2210                                 "module",
2211                                 "expect",
2212                                 "asyncTest",
2213                                 "start",
2214                                 "stop",
2215                                 "ok",
2216                                 "notOk",
2217                                 "equal",
2218                                 "notEqual",
2219                                 "propEqual",
2220                                 "notPropEqual",
2221                                 "deepEqual",
2222                                 "notDeepEqual",
2223                                 "strictEqual",
2224                                 "notStrictEqual",
2225                                 "throws",
2226                                 "raises"
2227                         ];
2229                 for ( i = 0, l = keys.length; i < l; i++ ) {
2230                         window[ keys[ i ] ] = QUnit[ keys[ i ] ];
2231                 }
2232         }() );
2234         window.QUnit = QUnit;
2237 // For nodejs
2238 if ( typeof module !== "undefined" && module && module.exports ) {
2239         module.exports = QUnit;
2241         // For consistency with CommonJS environments' exports
2242         module.exports.QUnit = QUnit;
2245 // For CommonJS with exports, but without module.exports, like Rhino
2246 if ( typeof exports !== "undefined" && exports ) {
2247         exports.QUnit = QUnit;
2250 if ( typeof define === "function" && define.amd ) {
2251         define( function() {
2252                 return QUnit;
2253         } );
2254         QUnit.config.autostart = false;
2257 // Get a reference to the global object, like window in browsers
2258 }( ( function() {
2259         return this;
2260 }() ) ) );
2262 ( function() {
2264 // Only interact with URLs via window.location
2265 var location = typeof window !== "undefined" && window.location;
2266 if ( !location ) {
2267         return;
2270 var urlParams = getUrlParams();
2272 QUnit.urlParams = urlParams;
2274 // Match module/test by inclusion in an array
2275 QUnit.config.moduleId = [].concat( urlParams.moduleId || [] );
2276 QUnit.config.testId = [].concat( urlParams.testId || [] );
2278 // Exact case-insensitive match of the module name
2279 QUnit.config.module = urlParams.module;
2281 // Regular expression or case-insenstive substring match against "moduleName: testName"
2282 QUnit.config.filter = urlParams.filter;
2284 // Test order randomization
2285 if ( urlParams.seed === true ) {
2287         // Generate a random seed if the option is specified without a value
2288         QUnit.config.seed = Math.random().toString( 36 ).slice( 2 );
2289 } else if ( urlParams.seed ) {
2290         QUnit.config.seed = urlParams.seed;
2293 // Add URL-parameter-mapped config values with UI form rendering data
2294 QUnit.config.urlConfig.push(
2295         {
2296                 id: "hidepassed",
2297                 label: "Hide passed tests",
2298                 tooltip: "Only show tests and assertions that fail. Stored as query-strings."
2299         },
2300         {
2301                 id: "noglobals",
2302                 label: "Check for Globals",
2303                 tooltip: "Enabling this will test if any test introduces new properties on the " +
2304                         "global object (`window` in Browsers). Stored as query-strings."
2305         },
2306         {
2307                 id: "notrycatch",
2308                 label: "No try-catch",
2309                 tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
2310                         "exceptions in IE reasonable. Stored as query-strings."
2311         }
2314 QUnit.begin( function() {
2315         var i, option,
2316                 urlConfig = QUnit.config.urlConfig;
2318         for ( i = 0; i < urlConfig.length; i++ ) {
2320                 // Options can be either strings or objects with nonempty "id" properties
2321                 option = QUnit.config.urlConfig[ i ];
2322                 if ( typeof option !== "string" ) {
2323                         option = option.id;
2324                 }
2326                 if ( QUnit.config[ option ] === undefined ) {
2327                         QUnit.config[ option ] = urlParams[ option ];
2328                 }
2329         }
2330 } );
2332 function getUrlParams() {
2333         var i, param, name, value;
2334         var urlParams = {};
2335         var params = location.search.slice( 1 ).split( "&" );
2336         var length = params.length;
2338         for ( i = 0; i < length; i++ ) {
2339                 if ( params[ i ] ) {
2340                         param = params[ i ].split( "=" );
2341                         name = decodeURIComponent( param[ 0 ] );
2343                         // Allow just a key to turn on a flag, e.g., test.html?noglobals
2344                         value = param.length === 1 ||
2345                                 decodeURIComponent( param.slice( 1 ).join( "=" ) ) ;
2346                         if ( urlParams[ name ] ) {
2347                                 urlParams[ name ] = [].concat( urlParams[ name ], value );
2348                         } else {
2349                                 urlParams[ name ] = value;
2350                         }
2351                 }
2352         }
2354         return urlParams;
2357 // Don't load the HTML Reporter on non-browser environments
2358 if ( typeof window === "undefined" || !window.document ) {
2359         return;
2362 // Deprecated QUnit.init - Ref #530
2363 // Re-initialize the configuration options
2364 QUnit.init = function() {
2365         var config = QUnit.config;
2367         config.stats = { all: 0, bad: 0 };
2368         config.moduleStats = { all: 0, bad: 0 };
2369         config.started = 0;
2370         config.updateRate = 1000;
2371         config.blocking = false;
2372         config.autostart = true;
2373         config.autorun = false;
2374         config.filter = "";
2375         config.queue = [];
2377         appendInterface();
2380 var config = QUnit.config,
2381         document = window.document,
2382         collapseNext = false,
2383         hasOwn = Object.prototype.hasOwnProperty,
2384         unfilteredUrl = setUrl( { filter: undefined, module: undefined,
2385                 moduleId: undefined, testId: undefined } ),
2386         defined = {
2387                 sessionStorage: ( function() {
2388                         var x = "qunit-test-string";
2389                         try {
2390                                 sessionStorage.setItem( x, x );
2391                                 sessionStorage.removeItem( x );
2392                                 return true;
2393                         } catch ( e ) {
2394                                 return false;
2395                         }
2396                 }() )
2397         },
2398         modulesList = [];
2401 * Escape text for attribute or text content.
2403 function escapeText( s ) {
2404         if ( !s ) {
2405                 return "";
2406         }
2407         s = s + "";
2409         // Both single quotes and double quotes (for attributes)
2410         return s.replace( /['"<>&]/g, function( s ) {
2411                 switch ( s ) {
2412                 case "'":
2413                         return "&#039;";
2414                 case "\"":
2415                         return "&quot;";
2416                 case "<":
2417                         return "&lt;";
2418                 case ">":
2419                         return "&gt;";
2420                 case "&":
2421                         return "&amp;";
2422                 }
2423         } );
2427  * @param {HTMLElement} elem
2428  * @param {string} type
2429  * @param {Function} fn
2430  */
2431 function addEvent( elem, type, fn ) {
2432         if ( elem.addEventListener ) {
2434                 // Standards-based browsers
2435                 elem.addEventListener( type, fn, false );
2436         } else if ( elem.attachEvent ) {
2438                 // Support: IE <9
2439                 elem.attachEvent( "on" + type, function() {
2440                         var event = window.event;
2441                         if ( !event.target ) {
2442                                 event.target = event.srcElement || document;
2443                         }
2445                         fn.call( elem, event );
2446                 } );
2447         }
2451  * @param {Array|NodeList} elems
2452  * @param {string} type
2453  * @param {Function} fn
2454  */
2455 function addEvents( elems, type, fn ) {
2456         var i = elems.length;
2457         while ( i-- ) {
2458                 addEvent( elems[ i ], type, fn );
2459         }
2462 function hasClass( elem, name ) {
2463         return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
2466 function addClass( elem, name ) {
2467         if ( !hasClass( elem, name ) ) {
2468                 elem.className += ( elem.className ? " " : "" ) + name;
2469         }
2472 function toggleClass( elem, name, force ) {
2473         if ( force || typeof force === "undefined" && !hasClass( elem, name ) ) {
2474                 addClass( elem, name );
2475         } else {
2476                 removeClass( elem, name );
2477         }
2480 function removeClass( elem, name ) {
2481         var set = " " + elem.className + " ";
2483         // Class name may appear multiple times
2484         while ( set.indexOf( " " + name + " " ) >= 0 ) {
2485                 set = set.replace( " " + name + " ", " " );
2486         }
2488         // Trim for prettiness
2489         elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
2492 function id( name ) {
2493         return document.getElementById && document.getElementById( name );
2496 function getUrlConfigHtml() {
2497         var i, j, val,
2498                 escaped, escapedTooltip,
2499                 selection = false,
2500                 urlConfig = config.urlConfig,
2501                 urlConfigHtml = "";
2503         for ( i = 0; i < urlConfig.length; i++ ) {
2505                 // Options can be either strings or objects with nonempty "id" properties
2506                 val = config.urlConfig[ i ];
2507                 if ( typeof val === "string" ) {
2508                         val = {
2509                                 id: val,
2510                                 label: val
2511                         };
2512                 }
2514                 escaped = escapeText( val.id );
2515                 escapedTooltip = escapeText( val.tooltip );
2517                 if ( !val.value || typeof val.value === "string" ) {
2518                         urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
2519                                 "' name='" + escaped + "' type='checkbox'" +
2520                                 ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
2521                                 ( config[ val.id ] ? " checked='checked'" : "" ) +
2522                                 " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
2523                                 "' title='" + escapedTooltip + "'>" + val.label + "</label>";
2524                 } else {
2525                         urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
2526                                 "' title='" + escapedTooltip + "'>" + val.label +
2527                                 ": </label><select id='qunit-urlconfig-" + escaped +
2528                                 "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
2530                         if ( QUnit.is( "array", val.value ) ) {
2531                                 for ( j = 0; j < val.value.length; j++ ) {
2532                                         escaped = escapeText( val.value[ j ] );
2533                                         urlConfigHtml += "<option value='" + escaped + "'" +
2534                                                 ( config[ val.id ] === val.value[ j ] ?
2535                                                         ( selection = true ) && " selected='selected'" : "" ) +
2536                                                 ">" + escaped + "</option>";
2537                                 }
2538                         } else {
2539                                 for ( j in val.value ) {
2540                                         if ( hasOwn.call( val.value, j ) ) {
2541                                                 urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
2542                                                         ( config[ val.id ] === j ?
2543                                                                 ( selection = true ) && " selected='selected'" : "" ) +
2544                                                         ">" + escapeText( val.value[ j ] ) + "</option>";
2545                                         }
2546                                 }
2547                         }
2548                         if ( config[ val.id ] && !selection ) {
2549                                 escaped = escapeText( config[ val.id ] );
2550                                 urlConfigHtml += "<option value='" + escaped +
2551                                         "' selected='selected' disabled='disabled'>" + escaped + "</option>";
2552                         }
2553                         urlConfigHtml += "</select>";
2554                 }
2555         }
2557         return urlConfigHtml;
2560 // Handle "click" events on toolbar checkboxes and "change" for select menus.
2561 // Updates the URL with the new state of `config.urlConfig` values.
2562 function toolbarChanged() {
2563         var updatedUrl, value, tests,
2564                 field = this,
2565                 params = {};
2567         // Detect if field is a select menu or a checkbox
2568         if ( "selectedIndex" in field ) {
2569                 value = field.options[ field.selectedIndex ].value || undefined;
2570         } else {
2571                 value = field.checked ? ( field.defaultValue || true ) : undefined;
2572         }
2574         params[ field.name ] = value;
2575         updatedUrl = setUrl( params );
2577         // Check if we can apply the change without a page refresh
2578         if ( "hidepassed" === field.name && "replaceState" in window.history ) {
2579                 QUnit.urlParams[ field.name ] = value;
2580                 config[ field.name ] = value || false;
2581                 tests = id( "qunit-tests" );
2582                 if ( tests ) {
2583                         toggleClass( tests, "hidepass", value || false );
2584                 }
2585                 window.history.replaceState( null, "", updatedUrl );
2586         } else {
2587                 window.location = updatedUrl;
2588         }
2591 function setUrl( params ) {
2592         var key, arrValue, i,
2593                 querystring = "?",
2594                 location = window.location;
2596         params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
2598         for ( key in params ) {
2600                 // Skip inherited or undefined properties
2601                 if ( hasOwn.call( params, key ) && params[ key ] !== undefined ) {
2603                         // Output a parameter for each value of this key (but usually just one)
2604                         arrValue = [].concat( params[ key ] );
2605                         for ( i = 0; i < arrValue.length; i++ ) {
2606                                 querystring += encodeURIComponent( key );
2607                                 if ( arrValue[ i ] !== true ) {
2608                                         querystring += "=" + encodeURIComponent( arrValue[ i ] );
2609                                 }
2610                                 querystring += "&";
2611                         }
2612                 }
2613         }
2614         return location.protocol + "//" + location.host +
2615                 location.pathname + querystring.slice( 0, -1 );
2618 function applyUrlParams() {
2619         var selectedModule,
2620                 modulesList = id( "qunit-modulefilter" ),
2621                 filter = id( "qunit-filter-input" ).value;
2623         selectedModule = modulesList ?
2624                 decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) :
2625                 undefined;
2627         window.location = setUrl( {
2628                 module: ( selectedModule === "" ) ? undefined : selectedModule,
2629                 filter: ( filter === "" ) ? undefined : filter,
2631                 // Remove moduleId and testId filters
2632                 moduleId: undefined,
2633                 testId: undefined
2634         } );
2637 function toolbarUrlConfigContainer() {
2638         var urlConfigContainer = document.createElement( "span" );
2640         urlConfigContainer.innerHTML = getUrlConfigHtml();
2641         addClass( urlConfigContainer, "qunit-url-config" );
2643         // For oldIE support:
2644         // * Add handlers to the individual elements instead of the container
2645         // * Use "click" instead of "change" for checkboxes
2646         addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
2647         addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
2649         return urlConfigContainer;
2652 function toolbarLooseFilter() {
2653         var filter = document.createElement( "form" ),
2654                 label = document.createElement( "label" ),
2655                 input = document.createElement( "input" ),
2656                 button = document.createElement( "button" );
2658         addClass( filter, "qunit-filter" );
2660         label.innerHTML = "Filter: ";
2662         input.type = "text";
2663         input.value = config.filter || "";
2664         input.name = "filter";
2665         input.id = "qunit-filter-input";
2667         button.innerHTML = "Go";
2669         label.appendChild( input );
2671         filter.appendChild( label );
2672         filter.appendChild( button );
2673         addEvent( filter, "submit", function( ev ) {
2674                 applyUrlParams();
2676                 if ( ev && ev.preventDefault ) {
2677                         ev.preventDefault();
2678                 }
2680                 return false;
2681         } );
2683         return filter;
2686 function toolbarModuleFilterHtml() {
2687         var i,
2688                 moduleFilterHtml = "";
2690         if ( !modulesList.length ) {
2691                 return false;
2692         }
2694         moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
2695                 "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
2696                 ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
2697                 ">< All Modules ></option>";
2699         for ( i = 0; i < modulesList.length; i++ ) {
2700                 moduleFilterHtml += "<option value='" +
2701                         escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
2702                         ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
2703                         ">" + escapeText( modulesList[ i ] ) + "</option>";
2704         }
2705         moduleFilterHtml += "</select>";
2707         return moduleFilterHtml;
2710 function toolbarModuleFilter() {
2711         var toolbar = id( "qunit-testrunner-toolbar" ),
2712                 moduleFilter = document.createElement( "span" ),
2713                 moduleFilterHtml = toolbarModuleFilterHtml();
2715         if ( !toolbar || !moduleFilterHtml ) {
2716                 return false;
2717         }
2719         moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
2720         moduleFilter.innerHTML = moduleFilterHtml;
2722         addEvent( moduleFilter.lastChild, "change", applyUrlParams );
2724         toolbar.appendChild( moduleFilter );
2727 function appendToolbar() {
2728         var toolbar = id( "qunit-testrunner-toolbar" );
2730         if ( toolbar ) {
2731                 toolbar.appendChild( toolbarUrlConfigContainer() );
2732                 toolbar.appendChild( toolbarLooseFilter() );
2733                 toolbarModuleFilter();
2734         }
2737 function appendHeader() {
2738         var header = id( "qunit-header" );
2740         if ( header ) {
2741                 header.innerHTML = "<a href='" + escapeText( unfilteredUrl ) + "'>" + header.innerHTML +
2742                         "</a> ";
2743         }
2746 function appendBanner() {
2747         var banner = id( "qunit-banner" );
2749         if ( banner ) {
2750                 banner.className = "";
2751         }
2754 function appendTestResults() {
2755         var tests = id( "qunit-tests" ),
2756                 result = id( "qunit-testresult" );
2758         if ( result ) {
2759                 result.parentNode.removeChild( result );
2760         }
2762         if ( tests ) {
2763                 tests.innerHTML = "";
2764                 result = document.createElement( "p" );
2765                 result.id = "qunit-testresult";
2766                 result.className = "result";
2767                 tests.parentNode.insertBefore( result, tests );
2768                 result.innerHTML = "Running...<br />&#160;";
2769         }
2772 function storeFixture() {
2773         var fixture = id( "qunit-fixture" );
2774         if ( fixture ) {
2775                 config.fixture = fixture.innerHTML;
2776         }
2779 function appendFilteredTest() {
2780         var testId = QUnit.config.testId;
2781         if ( !testId || testId.length <= 0 ) {
2782                 return "";
2783         }
2784         return "<div id='qunit-filteredTest'>Rerunning selected tests: " +
2785                 escapeText( testId.join( ", " ) ) +
2786                 " <a id='qunit-clearFilter' href='" +
2787                 escapeText( unfilteredUrl ) +
2788                 "'>Run all tests</a></div>";
2791 function appendUserAgent() {
2792         var userAgent = id( "qunit-userAgent" );
2794         if ( userAgent ) {
2795                 userAgent.innerHTML = "";
2796                 userAgent.appendChild(
2797                         document.createTextNode(
2798                                 "QUnit " + QUnit.version + "; " + navigator.userAgent
2799                         )
2800                 );
2801         }
2804 function appendInterface() {
2805         var qunit = id( "qunit" );
2807         if ( qunit ) {
2808                 qunit.innerHTML =
2809                         "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
2810                         "<h2 id='qunit-banner'></h2>" +
2811                         "<div id='qunit-testrunner-toolbar'></div>" +
2812                         appendFilteredTest() +
2813                         "<h2 id='qunit-userAgent'></h2>" +
2814                         "<ol id='qunit-tests'></ol>";
2815         }
2817         appendHeader();
2818         appendBanner();
2819         appendTestResults();
2820         appendUserAgent();
2821         appendToolbar();
2824 function appendTestsList( modules ) {
2825         var i, l, x, z, test, moduleObj;
2827         for ( i = 0, l = modules.length; i < l; i++ ) {
2828                 moduleObj = modules[ i ];
2830                 for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
2831                         test = moduleObj.tests[ x ];
2833                         appendTest( test.name, test.testId, moduleObj.name );
2834                 }
2835         }
2838 function appendTest( name, testId, moduleName ) {
2839         var title, rerunTrigger, testBlock, assertList,
2840                 tests = id( "qunit-tests" );
2842         if ( !tests ) {
2843                 return;
2844         }
2846         title = document.createElement( "strong" );
2847         title.innerHTML = getNameHtml( name, moduleName );
2849         rerunTrigger = document.createElement( "a" );
2850         rerunTrigger.innerHTML = "Rerun";
2851         rerunTrigger.href = setUrl( { testId: testId } );
2853         testBlock = document.createElement( "li" );
2854         testBlock.appendChild( title );
2855         testBlock.appendChild( rerunTrigger );
2856         testBlock.id = "qunit-test-output-" + testId;
2858         assertList = document.createElement( "ol" );
2859         assertList.className = "qunit-assert-list";
2861         testBlock.appendChild( assertList );
2863         tests.appendChild( testBlock );
2866 // HTML Reporter initialization and load
2867 QUnit.begin( function( details ) {
2868         var i, moduleObj, tests;
2870         // Sort modules by name for the picker
2871         for ( i = 0; i < details.modules.length; i++ ) {
2872                 moduleObj = details.modules[ i ];
2873                 if ( moduleObj.name ) {
2874                         modulesList.push( moduleObj.name );
2875                 }
2876         }
2877         modulesList.sort( function( a, b ) {
2878                 return a.localeCompare( b );
2879         } );
2881         // Capture fixture HTML from the page
2882         storeFixture();
2884         // Initialize QUnit elements
2885         appendInterface();
2886         appendTestsList( details.modules );
2887         tests = id( "qunit-tests" );
2888         if ( tests && config.hidepassed ) {
2889                 addClass( tests, "hidepass" );
2890         }
2891 } );
2893 QUnit.done( function( details ) {
2894         var i, key,
2895                 banner = id( "qunit-banner" ),
2896                 tests = id( "qunit-tests" ),
2897                 html = [
2898                         "Tests completed in ",
2899                         details.runtime,
2900                         " milliseconds.<br />",
2901                         "<span class='passed'>",
2902                         details.passed,
2903                         "</span> assertions of <span class='total'>",
2904                         details.total,
2905                         "</span> passed, <span class='failed'>",
2906                         details.failed,
2907                         "</span> failed."
2908                 ].join( "" );
2910         if ( banner ) {
2911                 banner.className = details.failed ? "qunit-fail" : "qunit-pass";
2912         }
2914         if ( tests ) {
2915                 id( "qunit-testresult" ).innerHTML = html;
2916         }
2918         if ( config.altertitle && document.title ) {
2920                 // Show âœ– for good, âœ” for bad suite result in title
2921                 // use escape sequences in case file gets loaded with non-utf-8-charset
2922                 document.title = [
2923                         ( details.failed ? "\u2716" : "\u2714" ),
2924                         document.title.replace( /^[\u2714\u2716] /i, "" )
2925                 ].join( " " );
2926         }
2928         // Clear own sessionStorage items if all tests passed
2929         if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
2930                 for ( i = 0; i < sessionStorage.length; i++ ) {
2931                         key = sessionStorage.key( i++ );
2932                         if ( key.indexOf( "qunit-test-" ) === 0 ) {
2933                                 sessionStorage.removeItem( key );
2934                         }
2935                 }
2936         }
2938         // Scroll back to top to show results
2939         if ( config.scrolltop && window.scrollTo ) {
2940                 window.scrollTo( 0, 0 );
2941         }
2942 } );
2944 function getNameHtml( name, module ) {
2945         var nameHtml = "";
2947         if ( module ) {
2948                 nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
2949         }
2951         nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
2953         return nameHtml;
2956 QUnit.testStart( function( details ) {
2957         var running, testBlock, bad;
2959         testBlock = id( "qunit-test-output-" + details.testId );
2960         if ( testBlock ) {
2961                 testBlock.className = "running";
2962         } else {
2964                 // Report later registered tests
2965                 appendTest( details.name, details.testId, details.module );
2966         }
2968         running = id( "qunit-testresult" );
2969         if ( running ) {
2970                 bad = QUnit.config.reorder && defined.sessionStorage &&
2971                         +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name );
2973                 running.innerHTML = ( bad ?
2974                         "Rerunning previously failed test: <br />" :
2975                         "Running: <br />" ) +
2976                         getNameHtml( details.name, details.module );
2977         }
2979 } );
2981 function stripHtml( string ) {
2983         // Strip tags, html entity and whitespaces
2984         return string.replace( /<\/?[^>]+(>|$)/g, "" ).replace( /\&quot;/g, "" ).replace( /\s+/g, "" );
2987 QUnit.log( function( details ) {
2988         var assertList, assertLi,
2989                 message, expected, actual, diff,
2990                 showDiff = false,
2991                 testItem = id( "qunit-test-output-" + details.testId );
2993         if ( !testItem ) {
2994                 return;
2995         }
2997         message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
2998         message = "<span class='test-message'>" + message + "</span>";
2999         message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
3001         // The pushFailure doesn't provide details.expected
3002         // when it calls, it's implicit to also not show expected and diff stuff
3003         // Also, we need to check details.expected existence, as it can exist and be undefined
3004         if ( !details.result && hasOwn.call( details, "expected" ) ) {
3005                 if ( details.negative ) {
3006                         expected = "NOT " + QUnit.dump.parse( details.expected );
3007                 } else {
3008                         expected = QUnit.dump.parse( details.expected );
3009                 }
3011                 actual = QUnit.dump.parse( details.actual );
3012                 message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
3013                         escapeText( expected ) +
3014                         "</pre></td></tr>";
3016                 if ( actual !== expected ) {
3018                         message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
3019                                 escapeText( actual ) + "</pre></td></tr>";
3021                         // Don't show diff if actual or expected are booleans
3022                         if ( !( /^(true|false)$/.test( actual ) ) &&
3023                                         !( /^(true|false)$/.test( expected ) ) ) {
3024                                 diff = QUnit.diff( expected, actual );
3025                                 showDiff = stripHtml( diff ).length !==
3026                                         stripHtml( expected ).length +
3027                                         stripHtml( actual ).length;
3028                         }
3030                         // Don't show diff if expected and actual are totally different
3031                         if ( showDiff ) {
3032                                 message += "<tr class='test-diff'><th>Diff: </th><td><pre>" +
3033                                         diff + "</pre></td></tr>";
3034                         }
3035                 } else if ( expected.indexOf( "[object Array]" ) !== -1 ||
3036                                 expected.indexOf( "[object Object]" ) !== -1 ) {
3037                         message += "<tr class='test-message'><th>Message: </th><td>" +
3038                                 "Diff suppressed as the depth of object is more than current max depth (" +
3039                                 QUnit.config.maxDepth + ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " +
3040                                 " run with a higher max depth or <a href='" +
3041                                 escapeText( setUrl( { maxDepth: -1 } ) ) + "'>" +
3042                                 "Rerun</a> without max depth.</p></td></tr>";
3043                 } else {
3044                         message += "<tr class='test-message'><th>Message: </th><td>" +
3045                                 "Diff suppressed as the expected and actual results have an equivalent" +
3046                                 " serialization</td></tr>";
3047                 }
3049                 if ( details.source ) {
3050                         message += "<tr class='test-source'><th>Source: </th><td><pre>" +
3051                                 escapeText( details.source ) + "</pre></td></tr>";
3052                 }
3054                 message += "</table>";
3056         // This occurs when pushFailure is set and we have an extracted stack trace
3057         } else if ( !details.result && details.source ) {
3058                 message += "<table>" +
3059                         "<tr class='test-source'><th>Source: </th><td><pre>" +
3060                         escapeText( details.source ) + "</pre></td></tr>" +
3061                         "</table>";
3062         }
3064         assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
3066         assertLi = document.createElement( "li" );
3067         assertLi.className = details.result ? "pass" : "fail";
3068         assertLi.innerHTML = message;
3069         assertList.appendChild( assertLi );
3070 } );
3072 QUnit.testDone( function( details ) {
3073         var testTitle, time, testItem, assertList,
3074                 good, bad, testCounts, skipped, sourceName,
3075                 tests = id( "qunit-tests" );
3077         if ( !tests ) {
3078                 return;
3079         }
3081         testItem = id( "qunit-test-output-" + details.testId );
3083         assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
3085         good = details.passed;
3086         bad = details.failed;
3088         // Store result when possible
3089         if ( config.reorder && defined.sessionStorage ) {
3090                 if ( bad ) {
3091                         sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
3092                 } else {
3093                         sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
3094                 }
3095         }
3097         if ( bad === 0 ) {
3099                 // Collapse the passing tests
3100                 addClass( assertList, "qunit-collapsed" );
3101         } else if ( bad && config.collapse && !collapseNext ) {
3103                 // Skip collapsing the first failing test
3104                 collapseNext = true;
3105         } else {
3107                 // Collapse remaining tests
3108                 addClass( assertList, "qunit-collapsed" );
3109         }
3111         // The testItem.firstChild is the test name
3112         testTitle = testItem.firstChild;
3114         testCounts = bad ?
3115                 "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
3116                 "";
3118         testTitle.innerHTML += " <b class='counts'>(" + testCounts +
3119                 details.assertions.length + ")</b>";
3121         if ( details.skipped ) {
3122                 testItem.className = "skipped";
3123                 skipped = document.createElement( "em" );
3124                 skipped.className = "qunit-skipped-label";
3125                 skipped.innerHTML = "skipped";
3126                 testItem.insertBefore( skipped, testTitle );
3127         } else {
3128                 addEvent( testTitle, "click", function() {
3129                         toggleClass( assertList, "qunit-collapsed" );
3130                 } );
3132                 testItem.className = bad ? "fail" : "pass";
3134                 time = document.createElement( "span" );
3135                 time.className = "runtime";
3136                 time.innerHTML = details.runtime + " ms";
3137                 testItem.insertBefore( time, assertList );
3138         }
3140         // Show the source of the test when showing assertions
3141         if ( details.source ) {
3142                 sourceName = document.createElement( "p" );
3143                 sourceName.innerHTML = "<strong>Source: </strong>" + details.source;
3144                 addClass( sourceName, "qunit-source" );
3145                 if ( bad === 0 ) {
3146                         addClass( sourceName, "qunit-collapsed" );
3147                 }
3148                 addEvent( testTitle, "click", function() {
3149                         toggleClass( sourceName, "qunit-collapsed" );
3150                 } );
3151                 testItem.appendChild( sourceName );
3152         }
3153 } );
3155 // Avoid readyState issue with phantomjs
3156 // Ref: #818
3157 var notPhantom = ( function( p ) {
3158         return !( p && p.version && p.version.major > 0 );
3159 } )( window.phantom );
3161 if ( notPhantom && document.readyState === "complete" ) {
3162         QUnit.load();
3163 } else {
3164         addEvent( window, "load", QUnit.load );
3168  * This file is a modified version of google-diff-match-patch's JavaScript implementation
3169  * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
3170  * modifications are licensed as more fully set forth in LICENSE.txt.
3172  * The original source of google-diff-match-patch is attributable and licensed as follows:
3174  * Copyright 2006 Google Inc.
3175  * https://code.google.com/p/google-diff-match-patch/
3177  * Licensed under the Apache License, Version 2.0 (the "License");
3178  * you may not use this file except in compliance with the License.
3179  * You may obtain a copy of the License at
3181  * https://www.apache.org/licenses/LICENSE-2.0
3183  * Unless required by applicable law or agreed to in writing, software
3184  * distributed under the License is distributed on an "AS IS" BASIS,
3185  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3186  * See the License for the specific language governing permissions and
3187  * limitations under the License.
3189  * More Info:
3190  *  https://code.google.com/p/google-diff-match-patch/
3192  * Usage: QUnit.diff(expected, actual)
3194  */
3195 QUnit.diff = ( function() {
3196         function DiffMatchPatch() {
3197         }
3199         //  DIFF FUNCTIONS
3201         /**
3202          * The data structure representing a diff is an array of tuples:
3203          * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
3204          * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
3205          */
3206         var DIFF_DELETE = -1,
3207                 DIFF_INSERT = 1,
3208                 DIFF_EQUAL = 0;
3210         /**
3211          * Find the differences between two texts.  Simplifies the problem by stripping
3212          * any common prefix or suffix off the texts before diffing.
3213          * @param {string} text1 Old string to be diffed.
3214          * @param {string} text2 New string to be diffed.
3215          * @param {boolean=} optChecklines Optional speedup flag. If present and false,
3216          *     then don't run a line-level diff first to identify the changed areas.
3217          *     Defaults to true, which does a faster, slightly less optimal diff.
3218          * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
3219          */
3220         DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) {
3221                 var deadline, checklines, commonlength,
3222                         commonprefix, commonsuffix, diffs;
3224                 // The diff must be complete in up to 1 second.
3225                 deadline = ( new Date() ).getTime() + 1000;
3227                 // Check for null inputs.
3228                 if ( text1 === null || text2 === null ) {
3229                         throw new Error( "Null input. (DiffMain)" );
3230                 }
3232                 // Check for equality (speedup).
3233                 if ( text1 === text2 ) {
3234                         if ( text1 ) {
3235                                 return [
3236                                         [ DIFF_EQUAL, text1 ]
3237                                 ];
3238                         }
3239                         return [];
3240                 }
3242                 if ( typeof optChecklines === "undefined" ) {
3243                         optChecklines = true;
3244                 }
3246                 checklines = optChecklines;
3248                 // Trim off common prefix (speedup).
3249                 commonlength = this.diffCommonPrefix( text1, text2 );
3250                 commonprefix = text1.substring( 0, commonlength );
3251                 text1 = text1.substring( commonlength );
3252                 text2 = text2.substring( commonlength );
3254                 // Trim off common suffix (speedup).
3255                 commonlength = this.diffCommonSuffix( text1, text2 );
3256                 commonsuffix = text1.substring( text1.length - commonlength );
3257                 text1 = text1.substring( 0, text1.length - commonlength );
3258                 text2 = text2.substring( 0, text2.length - commonlength );
3260                 // Compute the diff on the middle block.
3261                 diffs = this.diffCompute( text1, text2, checklines, deadline );
3263                 // Restore the prefix and suffix.
3264                 if ( commonprefix ) {
3265                         diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
3266                 }
3267                 if ( commonsuffix ) {
3268                         diffs.push( [ DIFF_EQUAL, commonsuffix ] );
3269                 }
3270                 this.diffCleanupMerge( diffs );
3271                 return diffs;
3272         };
3274         /**
3275          * Reduce the number of edits by eliminating operationally trivial equalities.
3276          * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
3277          */
3278         DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
3279                 var changes, equalities, equalitiesLength, lastequality,
3280                         pointer, preIns, preDel, postIns, postDel;
3281                 changes = false;
3282                 equalities = []; // Stack of indices where equalities are found.
3283                 equalitiesLength = 0; // Keeping our own length var is faster in JS.
3284                 /** @type {?string} */
3285                 lastequality = null;
3287                 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
3288                 pointer = 0; // Index of current position.
3290                 // Is there an insertion operation before the last equality.
3291                 preIns = false;
3293                 // Is there a deletion operation before the last equality.
3294                 preDel = false;
3296                 // Is there an insertion operation after the last equality.
3297                 postIns = false;
3299                 // Is there a deletion operation after the last equality.
3300                 postDel = false;
3301                 while ( pointer < diffs.length ) {
3303                         // Equality found.
3304                         if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) {
3305                                 if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) {
3307                                         // Candidate found.
3308                                         equalities[ equalitiesLength++ ] = pointer;
3309                                         preIns = postIns;
3310                                         preDel = postDel;
3311                                         lastequality = diffs[ pointer ][ 1 ];
3312                                 } else {
3314                                         // Not a candidate, and can never become one.
3315                                         equalitiesLength = 0;
3316                                         lastequality = null;
3317                                 }
3318                                 postIns = postDel = false;
3320                         // An insertion or deletion.
3321                         } else {
3323                                 if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
3324                                         postDel = true;
3325                                 } else {
3326                                         postIns = true;
3327                                 }
3329                                 /*
3330                                  * Five types to be split:
3331                                  * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
3332                                  * <ins>A</ins>X<ins>C</ins><del>D</del>
3333                                  * <ins>A</ins><del>B</del>X<ins>C</ins>
3334                                  * <ins>A</del>X<ins>C</ins><del>D</del>
3335                                  * <ins>A</ins><del>B</del>X<del>C</del>
3336                                  */
3337                                 if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
3338                                                 ( ( lastequality.length < 2 ) &&
3339                                                 ( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
3341                                         // Duplicate record.
3342                                         diffs.splice(
3343                                                 equalities[ equalitiesLength - 1 ],
3344                                                 0,
3345                                                 [ DIFF_DELETE, lastequality ]
3346                                         );
3348                                         // Change second copy to insert.
3349                                         diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
3350                                         equalitiesLength--; // Throw away the equality we just deleted;
3351                                         lastequality = null;
3352                                         if ( preIns && preDel ) {
3354                                                 // No changes made which could affect previous entry, keep going.
3355                                                 postIns = postDel = true;
3356                                                 equalitiesLength = 0;
3357                                         } else {
3358                                                 equalitiesLength--; // Throw away the previous equality.
3359                                                 pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
3360                                                 postIns = postDel = false;
3361                                         }
3362                                         changes = true;
3363                                 }
3364                         }
3365                         pointer++;
3366                 }
3368                 if ( changes ) {
3369                         this.diffCleanupMerge( diffs );
3370                 }
3371         };
3373         /**
3374          * Convert a diff array into a pretty HTML report.
3375          * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
3376          * @param {integer} string to be beautified.
3377          * @return {string} HTML representation.
3378          */
3379         DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
3380                 var op, data, x,
3381                         html = [];
3382                 for ( x = 0; x < diffs.length; x++ ) {
3383                         op = diffs[ x ][ 0 ]; // Operation (insert, delete, equal)
3384                         data = diffs[ x ][ 1 ]; // Text of change.
3385                         switch ( op ) {
3386                         case DIFF_INSERT:
3387                                 html[ x ] = "<ins>" + escapeText( data ) + "</ins>";
3388                                 break;
3389                         case DIFF_DELETE:
3390                                 html[ x ] = "<del>" + escapeText( data ) + "</del>";
3391                                 break;
3392                         case DIFF_EQUAL:
3393                                 html[ x ] = "<span>" + escapeText( data ) + "</span>";
3394                                 break;
3395                         }
3396                 }
3397                 return html.join( "" );
3398         };
3400         /**
3401          * Determine the common prefix of two strings.
3402          * @param {string} text1 First string.
3403          * @param {string} text2 Second string.
3404          * @return {number} The number of characters common to the start of each
3405          *     string.
3406          */
3407         DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
3408                 var pointermid, pointermax, pointermin, pointerstart;
3410                 // Quick check for common null cases.
3411                 if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) {
3412                         return 0;
3413                 }
3415                 // Binary search.
3416                 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
3417                 pointermin = 0;
3418                 pointermax = Math.min( text1.length, text2.length );
3419                 pointermid = pointermax;
3420                 pointerstart = 0;
3421                 while ( pointermin < pointermid ) {
3422                         if ( text1.substring( pointerstart, pointermid ) ===
3423                                         text2.substring( pointerstart, pointermid ) ) {
3424                                 pointermin = pointermid;
3425                                 pointerstart = pointermin;
3426                         } else {
3427                                 pointermax = pointermid;
3428                         }
3429                         pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
3430                 }
3431                 return pointermid;
3432         };
3434         /**
3435          * Determine the common suffix of two strings.
3436          * @param {string} text1 First string.
3437          * @param {string} text2 Second string.
3438          * @return {number} The number of characters common to the end of each string.
3439          */
3440         DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
3441                 var pointermid, pointermax, pointermin, pointerend;
3443                 // Quick check for common null cases.
3444                 if ( !text1 ||
3445                                 !text2 ||
3446                                 text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) {
3447                         return 0;
3448                 }
3450                 // Binary search.
3451                 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
3452                 pointermin = 0;
3453                 pointermax = Math.min( text1.length, text2.length );
3454                 pointermid = pointermax;
3455                 pointerend = 0;
3456                 while ( pointermin < pointermid ) {
3457                         if ( text1.substring( text1.length - pointermid, text1.length - pointerend ) ===
3458                                         text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {
3459                                 pointermin = pointermid;
3460                                 pointerend = pointermin;
3461                         } else {
3462                                 pointermax = pointermid;
3463                         }
3464                         pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
3465                 }
3466                 return pointermid;
3467         };
3469         /**
3470          * Find the differences between two texts.  Assumes that the texts do not
3471          * have any common prefix or suffix.
3472          * @param {string} text1 Old string to be diffed.
3473          * @param {string} text2 New string to be diffed.
3474          * @param {boolean} checklines Speedup flag.  If false, then don't run a
3475          *     line-level diff first to identify the changed areas.
3476          *     If true, then run a faster, slightly less optimal diff.
3477          * @param {number} deadline Time when the diff should be complete by.
3478          * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
3479          * @private
3480          */
3481         DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
3482                 var diffs, longtext, shorttext, i, hm,
3483                         text1A, text2A, text1B, text2B,
3484                         midCommon, diffsA, diffsB;
3486                 if ( !text1 ) {
3488                         // Just add some text (speedup).
3489                         return [
3490                                 [ DIFF_INSERT, text2 ]
3491                         ];
3492                 }
3494                 if ( !text2 ) {
3496                         // Just delete some text (speedup).
3497                         return [
3498                                 [ DIFF_DELETE, text1 ]
3499                         ];
3500                 }
3502                 longtext = text1.length > text2.length ? text1 : text2;
3503                 shorttext = text1.length > text2.length ? text2 : text1;
3504                 i = longtext.indexOf( shorttext );
3505                 if ( i !== -1 ) {
3507                         // Shorter text is inside the longer text (speedup).
3508                         diffs = [
3509                                 [ DIFF_INSERT, longtext.substring( 0, i ) ],
3510                                 [ DIFF_EQUAL, shorttext ],
3511                                 [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
3512                         ];
3514                         // Swap insertions for deletions if diff is reversed.
3515                         if ( text1.length > text2.length ) {
3516                                 diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE;
3517                         }
3518                         return diffs;
3519                 }
3521                 if ( shorttext.length === 1 ) {
3523                         // Single character string.
3524                         // After the previous speedup, the character can't be an equality.
3525                         return [
3526                                 [ DIFF_DELETE, text1 ],
3527                                 [ DIFF_INSERT, text2 ]
3528                         ];
3529                 }
3531                 // Check to see if the problem can be split in two.
3532                 hm = this.diffHalfMatch( text1, text2 );
3533                 if ( hm ) {
3535                         // A half-match was found, sort out the return data.
3536                         text1A = hm[ 0 ];
3537                         text1B = hm[ 1 ];
3538                         text2A = hm[ 2 ];
3539                         text2B = hm[ 3 ];
3540                         midCommon = hm[ 4 ];
3542                         // Send both pairs off for separate processing.
3543                         diffsA = this.DiffMain( text1A, text2A, checklines, deadline );
3544                         diffsB = this.DiffMain( text1B, text2B, checklines, deadline );
3546                         // Merge the results.
3547                         return diffsA.concat( [
3548                                 [ DIFF_EQUAL, midCommon ]
3549                         ], diffsB );
3550                 }
3552                 if ( checklines && text1.length > 100 && text2.length > 100 ) {
3553                         return this.diffLineMode( text1, text2, deadline );
3554                 }
3556                 return this.diffBisect( text1, text2, deadline );
3557         };
3559         /**
3560          * Do the two texts share a substring which is at least half the length of the
3561          * longer text?
3562          * This speedup can produce non-minimal diffs.
3563          * @param {string} text1 First string.
3564          * @param {string} text2 Second string.
3565          * @return {Array.<string>} Five element Array, containing the prefix of
3566          *     text1, the suffix of text1, the prefix of text2, the suffix of
3567          *     text2 and the common middle.  Or null if there was no match.
3568          * @private
3569          */
3570         DiffMatchPatch.prototype.diffHalfMatch = function( text1, text2 ) {
3571                 var longtext, shorttext, dmp,
3572                         text1A, text2B, text2A, text1B, midCommon,
3573                         hm1, hm2, hm;
3575                 longtext = text1.length > text2.length ? text1 : text2;
3576                 shorttext = text1.length > text2.length ? text2 : text1;
3577                 if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) {
3578                         return null; // Pointless.
3579                 }
3580                 dmp = this; // 'this' becomes 'window' in a closure.
3582                 /**
3583                  * Does a substring of shorttext exist within longtext such that the substring
3584                  * is at least half the length of longtext?
3585                  * Closure, but does not reference any external variables.
3586                  * @param {string} longtext Longer string.
3587                  * @param {string} shorttext Shorter string.
3588                  * @param {number} i Start index of quarter length substring within longtext.
3589                  * @return {Array.<string>} Five element Array, containing the prefix of
3590                  *     longtext, the suffix of longtext, the prefix of shorttext, the suffix
3591                  *     of shorttext and the common middle.  Or null if there was no match.
3592                  * @private
3593                  */
3594                 function diffHalfMatchI( longtext, shorttext, i ) {
3595                         var seed, j, bestCommon, prefixLength, suffixLength,
3596                                 bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
3598                         // Start with a 1/4 length substring at position i as a seed.
3599                         seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) );
3600                         j = -1;
3601                         bestCommon = "";
3602                         while ( ( j = shorttext.indexOf( seed, j + 1 ) ) !== -1 ) {
3603                                 prefixLength = dmp.diffCommonPrefix( longtext.substring( i ),
3604                                         shorttext.substring( j ) );
3605                                 suffixLength = dmp.diffCommonSuffix( longtext.substring( 0, i ),
3606                                         shorttext.substring( 0, j ) );
3607                                 if ( bestCommon.length < suffixLength + prefixLength ) {
3608                                         bestCommon = shorttext.substring( j - suffixLength, j ) +
3609                                                 shorttext.substring( j, j + prefixLength );
3610                                         bestLongtextA = longtext.substring( 0, i - suffixLength );
3611                                         bestLongtextB = longtext.substring( i + prefixLength );
3612                                         bestShorttextA = shorttext.substring( 0, j - suffixLength );
3613                                         bestShorttextB = shorttext.substring( j + prefixLength );
3614                                 }
3615                         }
3616                         if ( bestCommon.length * 2 >= longtext.length ) {
3617                                 return [ bestLongtextA, bestLongtextB,
3618                                         bestShorttextA, bestShorttextB, bestCommon
3619                                 ];
3620                         } else {
3621                                 return null;
3622                         }
3623                 }
3625                 // First check if the second quarter is the seed for a half-match.
3626                 hm1 = diffHalfMatchI( longtext, shorttext,
3627                         Math.ceil( longtext.length / 4 ) );
3629                 // Check again based on the third quarter.
3630                 hm2 = diffHalfMatchI( longtext, shorttext,
3631                         Math.ceil( longtext.length / 2 ) );
3632                 if ( !hm1 && !hm2 ) {
3633                         return null;
3634                 } else if ( !hm2 ) {
3635                         hm = hm1;
3636                 } else if ( !hm1 ) {
3637                         hm = hm2;
3638                 } else {
3640                         // Both matched.  Select the longest.
3641                         hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2;
3642                 }
3644                 // A half-match was found, sort out the return data.
3645                 text1A, text1B, text2A, text2B;
3646                 if ( text1.length > text2.length ) {
3647                         text1A = hm[ 0 ];
3648                         text1B = hm[ 1 ];
3649                         text2A = hm[ 2 ];
3650                         text2B = hm[ 3 ];
3651                 } else {
3652                         text2A = hm[ 0 ];
3653                         text2B = hm[ 1 ];
3654                         text1A = hm[ 2 ];
3655                         text1B = hm[ 3 ];
3656                 }
3657                 midCommon = hm[ 4 ];
3658                 return [ text1A, text1B, text2A, text2B, midCommon ];
3659         };
3661         /**
3662          * Do a quick line-level diff on both strings, then rediff the parts for
3663          * greater accuracy.
3664          * This speedup can produce non-minimal diffs.
3665          * @param {string} text1 Old string to be diffed.
3666          * @param {string} text2 New string to be diffed.
3667          * @param {number} deadline Time when the diff should be complete by.
3668          * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
3669          * @private
3670          */
3671         DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) {
3672                 var a, diffs, linearray, pointer, countInsert,
3673                         countDelete, textInsert, textDelete, j;
3675                 // Scan the text on a line-by-line basis first.
3676                 a = this.diffLinesToChars( text1, text2 );
3677                 text1 = a.chars1;
3678                 text2 = a.chars2;
3679                 linearray = a.lineArray;
3681                 diffs = this.DiffMain( text1, text2, false, deadline );
3683                 // Convert the diff back to original text.
3684                 this.diffCharsToLines( diffs, linearray );
3686                 // Eliminate freak matches (e.g. blank lines)
3687                 this.diffCleanupSemantic( diffs );
3689                 // Rediff any replacement blocks, this time character-by-character.
3690                 // Add a dummy entry at the end.
3691                 diffs.push( [ DIFF_EQUAL, "" ] );
3692                 pointer = 0;
3693                 countDelete = 0;
3694                 countInsert = 0;
3695                 textDelete = "";
3696                 textInsert = "";
3697                 while ( pointer < diffs.length ) {
3698                         switch ( diffs[ pointer ][ 0 ] ) {
3699                         case DIFF_INSERT:
3700                                 countInsert++;
3701                                 textInsert += diffs[ pointer ][ 1 ];
3702                                 break;
3703                         case DIFF_DELETE:
3704                                 countDelete++;
3705                                 textDelete += diffs[ pointer ][ 1 ];
3706                                 break;
3707                         case DIFF_EQUAL:
3709                                 // Upon reaching an equality, check for prior redundancies.
3710                                 if ( countDelete >= 1 && countInsert >= 1 ) {
3712                                         // Delete the offending records and add the merged ones.
3713                                         diffs.splice( pointer - countDelete - countInsert,
3714                                                 countDelete + countInsert );
3715                                         pointer = pointer - countDelete - countInsert;
3716                                         a = this.DiffMain( textDelete, textInsert, false, deadline );
3717                                         for ( j = a.length - 1; j >= 0; j-- ) {
3718                                                 diffs.splice( pointer, 0, a[ j ] );
3719                                         }
3720                                         pointer = pointer + a.length;
3721                                 }
3722                                 countInsert = 0;
3723                                 countDelete = 0;
3724                                 textDelete = "";
3725                                 textInsert = "";
3726                                 break;
3727                         }
3728                         pointer++;
3729                 }
3730                 diffs.pop(); // Remove the dummy entry at the end.
3732                 return diffs;
3733         };
3735         /**
3736          * Find the 'middle snake' of a diff, split the problem in two
3737          * and return the recursively constructed diff.
3738          * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
3739          * @param {string} text1 Old string to be diffed.
3740          * @param {string} text2 New string to be diffed.
3741          * @param {number} deadline Time at which to bail if not yet complete.
3742          * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
3743          * @private
3744          */
3745         DiffMatchPatch.prototype.diffBisect = function( text1, text2, deadline ) {
3746                 var text1Length, text2Length, maxD, vOffset, vLength,
3747                         v1, v2, x, delta, front, k1start, k1end, k2start,
3748                         k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
3750                 // Cache the text lengths to prevent multiple calls.
3751                 text1Length = text1.length;
3752                 text2Length = text2.length;
3753                 maxD = Math.ceil( ( text1Length + text2Length ) / 2 );
3754                 vOffset = maxD;
3755                 vLength = 2 * maxD;
3756                 v1 = new Array( vLength );
3757                 v2 = new Array( vLength );
3759                 // Setting all elements to -1 is faster in Chrome & Firefox than mixing
3760                 // integers and undefined.
3761                 for ( x = 0; x < vLength; x++ ) {
3762                         v1[ x ] = -1;
3763                         v2[ x ] = -1;
3764                 }
3765                 v1[ vOffset + 1 ] = 0;
3766                 v2[ vOffset + 1 ] = 0;
3767                 delta = text1Length - text2Length;
3769                 // If the total number of characters is odd, then the front path will collide
3770                 // with the reverse path.
3771                 front = ( delta % 2 !== 0 );
3773                 // Offsets for start and end of k loop.
3774                 // Prevents mapping of space beyond the grid.
3775                 k1start = 0;
3776                 k1end = 0;
3777                 k2start = 0;
3778                 k2end = 0;
3779                 for ( d = 0; d < maxD; d++ ) {
3781                         // Bail out if deadline is reached.
3782                         if ( ( new Date() ).getTime() > deadline ) {
3783                                 break;
3784                         }
3786                         // Walk the front path one step.
3787                         for ( k1 = -d + k1start; k1 <= d - k1end; k1 += 2 ) {
3788                                 k1Offset = vOffset + k1;
3789                                 if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {
3790                                         x1 = v1[ k1Offset + 1 ];
3791                                 } else {
3792                                         x1 = v1[ k1Offset - 1 ] + 1;
3793                                 }
3794                                 y1 = x1 - k1;
3795                                 while ( x1 < text1Length && y1 < text2Length &&
3796                                         text1.charAt( x1 ) === text2.charAt( y1 ) ) {
3797                                         x1++;
3798                                         y1++;
3799                                 }
3800                                 v1[ k1Offset ] = x1;
3801                                 if ( x1 > text1Length ) {
3803                                         // Ran off the right of the graph.
3804                                         k1end += 2;
3805                                 } else if ( y1 > text2Length ) {
3807                                         // Ran off the bottom of the graph.
3808                                         k1start += 2;
3809                                 } else if ( front ) {
3810                                         k2Offset = vOffset + delta - k1;
3811                                         if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) {
3813                                                 // Mirror x2 onto top-left coordinate system.
3814                                                 x2 = text1Length - v2[ k2Offset ];
3815                                                 if ( x1 >= x2 ) {
3817                                                         // Overlap detected.
3818                                                         return this.diffBisectSplit( text1, text2, x1, y1, deadline );
3819                                                 }
3820                                         }
3821                                 }
3822                         }
3824                         // Walk the reverse path one step.
3825                         for ( k2 = -d + k2start; k2 <= d - k2end; k2 += 2 ) {
3826                                 k2Offset = vOffset + k2;
3827                                 if ( k2 === -d || ( k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {
3828                                         x2 = v2[ k2Offset + 1 ];
3829                                 } else {
3830                                         x2 = v2[ k2Offset - 1 ] + 1;
3831                                 }
3832                                 y2 = x2 - k2;
3833                                 while ( x2 < text1Length && y2 < text2Length &&
3834                                         text1.charAt( text1Length - x2 - 1 ) ===
3835                                         text2.charAt( text2Length - y2 - 1 ) ) {
3836                                         x2++;
3837                                         y2++;
3838                                 }
3839                                 v2[ k2Offset ] = x2;
3840                                 if ( x2 > text1Length ) {
3842                                         // Ran off the left of the graph.
3843                                         k2end += 2;
3844                                 } else if ( y2 > text2Length ) {
3846                                         // Ran off the top of the graph.
3847                                         k2start += 2;
3848                                 } else if ( !front ) {
3849                                         k1Offset = vOffset + delta - k2;
3850                                         if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) {
3851                                                 x1 = v1[ k1Offset ];
3852                                                 y1 = vOffset + x1 - k1Offset;
3854                                                 // Mirror x2 onto top-left coordinate system.
3855                                                 x2 = text1Length - x2;
3856                                                 if ( x1 >= x2 ) {
3858                                                         // Overlap detected.
3859                                                         return this.diffBisectSplit( text1, text2, x1, y1, deadline );
3860                                                 }
3861                                         }
3862                                 }
3863                         }
3864                 }
3866                 // Diff took too long and hit the deadline or
3867                 // number of diffs equals number of characters, no commonality at all.
3868                 return [
3869                         [ DIFF_DELETE, text1 ],
3870                         [ DIFF_INSERT, text2 ]
3871                 ];
3872         };
3874         /**
3875          * Given the location of the 'middle snake', split the diff in two parts
3876          * and recurse.
3877          * @param {string} text1 Old string to be diffed.
3878          * @param {string} text2 New string to be diffed.
3879          * @param {number} x Index of split point in text1.
3880          * @param {number} y Index of split point in text2.
3881          * @param {number} deadline Time at which to bail if not yet complete.
3882          * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
3883          * @private
3884          */
3885         DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
3886                 var text1a, text1b, text2a, text2b, diffs, diffsb;
3887                 text1a = text1.substring( 0, x );
3888                 text2a = text2.substring( 0, y );
3889                 text1b = text1.substring( x );
3890                 text2b = text2.substring( y );
3892                 // Compute both diffs serially.
3893                 diffs = this.DiffMain( text1a, text2a, false, deadline );
3894                 diffsb = this.DiffMain( text1b, text2b, false, deadline );
3896                 return diffs.concat( diffsb );
3897         };
3899         /**
3900          * Reduce the number of edits by eliminating semantically trivial equalities.
3901          * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
3902          */
3903         DiffMatchPatch.prototype.diffCleanupSemantic = function( diffs ) {
3904                 var changes, equalities, equalitiesLength, lastequality,
3905                         pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
3906                         lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
3907                 changes = false;
3908                 equalities = []; // Stack of indices where equalities are found.
3909                 equalitiesLength = 0; // Keeping our own length var is faster in JS.
3910                 /** @type {?string} */
3911                 lastequality = null;
3913                 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
3914                 pointer = 0; // Index of current position.
3916                 // Number of characters that changed prior to the equality.
3917                 lengthInsertions1 = 0;
3918                 lengthDeletions1 = 0;
3920                 // Number of characters that changed after the equality.
3921                 lengthInsertions2 = 0;
3922                 lengthDeletions2 = 0;
3923                 while ( pointer < diffs.length ) {
3924                         if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
3925                                 equalities[ equalitiesLength++ ] = pointer;
3926                                 lengthInsertions1 = lengthInsertions2;
3927                                 lengthDeletions1 = lengthDeletions2;
3928                                 lengthInsertions2 = 0;
3929                                 lengthDeletions2 = 0;
3930                                 lastequality = diffs[ pointer ][ 1 ];
3931                         } else { // An insertion or deletion.
3932                                 if ( diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
3933                                         lengthInsertions2 += diffs[ pointer ][ 1 ].length;
3934                                 } else {
3935                                         lengthDeletions2 += diffs[ pointer ][ 1 ].length;
3936                                 }
3938                                 // Eliminate an equality that is smaller or equal to the edits on both
3939                                 // sides of it.
3940                                 if ( lastequality && ( lastequality.length <=
3941                                                 Math.max( lengthInsertions1, lengthDeletions1 ) ) &&
3942                                                 ( lastequality.length <= Math.max( lengthInsertions2,
3943                                                         lengthDeletions2 ) ) ) {
3945                                         // Duplicate record.
3946                                         diffs.splice(
3947                                                 equalities[ equalitiesLength - 1 ],
3948                                                 0,
3949                                                 [ DIFF_DELETE, lastequality ]
3950                                         );
3952                                         // Change second copy to insert.
3953                                         diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
3955                                         // Throw away the equality we just deleted.
3956                                         equalitiesLength--;
3958                                         // Throw away the previous equality (it needs to be reevaluated).
3959                                         equalitiesLength--;
3960                                         pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
3962                                         // Reset the counters.
3963                                         lengthInsertions1 = 0;
3964                                         lengthDeletions1 = 0;
3965                                         lengthInsertions2 = 0;
3966                                         lengthDeletions2 = 0;
3967                                         lastequality = null;
3968                                         changes = true;
3969                                 }
3970                         }
3971                         pointer++;
3972                 }
3974                 // Normalize the diff.
3975                 if ( changes ) {
3976                         this.diffCleanupMerge( diffs );
3977                 }
3979                 // Find any overlaps between deletions and insertions.
3980                 // e.g: <del>abcxxx</del><ins>xxxdef</ins>
3981                 //   -> <del>abc</del>xxx<ins>def</ins>
3982                 // e.g: <del>xxxabc</del><ins>defxxx</ins>
3983                 //   -> <ins>def</ins>xxx<del>abc</del>
3984                 // Only extract an overlap if it is as big as the edit ahead or behind it.
3985                 pointer = 1;
3986                 while ( pointer < diffs.length ) {
3987                         if ( diffs[ pointer - 1 ][ 0 ] === DIFF_DELETE &&
3988                                         diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
3989                                 deletion = diffs[ pointer - 1 ][ 1 ];
3990                                 insertion = diffs[ pointer ][ 1 ];
3991                                 overlapLength1 = this.diffCommonOverlap( deletion, insertion );
3992                                 overlapLength2 = this.diffCommonOverlap( insertion, deletion );
3993                                 if ( overlapLength1 >= overlapLength2 ) {
3994                                         if ( overlapLength1 >= deletion.length / 2 ||
3995                                                         overlapLength1 >= insertion.length / 2 ) {
3997                                                 // Overlap found.  Insert an equality and trim the surrounding edits.
3998                                                 diffs.splice(
3999                                                         pointer,
4000                                                         0,
4001                                                         [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ]
4002                                                 );
4003                                                 diffs[ pointer - 1 ][ 1 ] =
4004                                                         deletion.substring( 0, deletion.length - overlapLength1 );
4005                                                 diffs[ pointer + 1 ][ 1 ] = insertion.substring( overlapLength1 );
4006                                                 pointer++;
4007                                         }
4008                                 } else {
4009                                         if ( overlapLength2 >= deletion.length / 2 ||
4010                                                         overlapLength2 >= insertion.length / 2 ) {
4012                                                 // Reverse overlap found.
4013                                                 // Insert an equality and swap and trim the surrounding edits.
4014                                                 diffs.splice(
4015                                                         pointer,
4016                                                         0,
4017                                                         [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ]
4018                                                 );
4020                                                 diffs[ pointer - 1 ][ 0 ] = DIFF_INSERT;
4021                                                 diffs[ pointer - 1 ][ 1 ] =
4022                                                         insertion.substring( 0, insertion.length - overlapLength2 );
4023                                                 diffs[ pointer + 1 ][ 0 ] = DIFF_DELETE;
4024                                                 diffs[ pointer + 1 ][ 1 ] =
4025                                                         deletion.substring( overlapLength2 );
4026                                                 pointer++;
4027                                         }
4028                                 }
4029                                 pointer++;
4030                         }
4031                         pointer++;
4032                 }
4033         };
4035         /**
4036          * Determine if the suffix of one string is the prefix of another.
4037          * @param {string} text1 First string.
4038          * @param {string} text2 Second string.
4039          * @return {number} The number of characters common to the end of the first
4040          *     string and the start of the second string.
4041          * @private
4042          */
4043         DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) {
4044                 var text1Length, text2Length, textLength,
4045                         best, length, pattern, found;
4047                 // Cache the text lengths to prevent multiple calls.
4048                 text1Length = text1.length;
4049                 text2Length = text2.length;
4051                 // Eliminate the null case.
4052                 if ( text1Length === 0 || text2Length === 0 ) {
4053                         return 0;
4054                 }
4056                 // Truncate the longer string.
4057                 if ( text1Length > text2Length ) {
4058                         text1 = text1.substring( text1Length - text2Length );
4059                 } else if ( text1Length < text2Length ) {
4060                         text2 = text2.substring( 0, text1Length );
4061                 }
4062                 textLength = Math.min( text1Length, text2Length );
4064                 // Quick check for the worst case.
4065                 if ( text1 === text2 ) {
4066                         return textLength;
4067                 }
4069                 // Start by looking for a single character match
4070                 // and increase length until no match is found.
4071                 // Performance analysis: https://neil.fraser.name/news/2010/11/04/
4072                 best = 0;
4073                 length = 1;
4074                 while ( true ) {
4075                         pattern = text1.substring( textLength - length );
4076                         found = text2.indexOf( pattern );
4077                         if ( found === -1 ) {
4078                                 return best;
4079                         }
4080                         length += found;
4081                         if ( found === 0 || text1.substring( textLength - length ) ===
4082                                         text2.substring( 0, length ) ) {
4083                                 best = length;
4084                                 length++;
4085                         }
4086                 }
4087         };
4089         /**
4090          * Split two texts into an array of strings.  Reduce the texts to a string of
4091          * hashes where each Unicode character represents one line.
4092          * @param {string} text1 First string.
4093          * @param {string} text2 Second string.
4094          * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
4095          *     An object containing the encoded text1, the encoded text2 and
4096          *     the array of unique strings.
4097          *     The zeroth element of the array of unique strings is intentionally blank.
4098          * @private
4099          */
4100         DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) {
4101                 var lineArray, lineHash, chars1, chars2;
4102                 lineArray = []; // E.g. lineArray[4] === 'Hello\n'
4103                 lineHash = {};  // E.g. lineHash['Hello\n'] === 4
4105                 // '\x00' is a valid character, but various debuggers don't like it.
4106                 // So we'll insert a junk entry to avoid generating a null character.
4107                 lineArray[ 0 ] = "";
4109                 /**
4110                  * Split a text into an array of strings.  Reduce the texts to a string of
4111                  * hashes where each Unicode character represents one line.
4112                  * Modifies linearray and linehash through being a closure.
4113                  * @param {string} text String to encode.
4114                  * @return {string} Encoded string.
4115                  * @private
4116                  */
4117                 function diffLinesToCharsMunge( text ) {
4118                         var chars, lineStart, lineEnd, lineArrayLength, line;
4119                         chars = "";
4121                         // Walk the text, pulling out a substring for each line.
4122                         // text.split('\n') would would temporarily double our memory footprint.
4123                         // Modifying text would create many large strings to garbage collect.
4124                         lineStart = 0;
4125                         lineEnd = -1;
4127                         // Keeping our own length variable is faster than looking it up.
4128                         lineArrayLength = lineArray.length;
4129                         while ( lineEnd < text.length - 1 ) {
4130                                 lineEnd = text.indexOf( "\n", lineStart );
4131                                 if ( lineEnd === -1 ) {
4132                                         lineEnd = text.length - 1;
4133                                 }
4134                                 line = text.substring( lineStart, lineEnd + 1 );
4135                                 lineStart = lineEnd + 1;
4137                                 if ( lineHash.hasOwnProperty ? lineHash.hasOwnProperty( line ) :
4138                                                         ( lineHash[ line ] !== undefined ) ) {
4139                                         chars += String.fromCharCode( lineHash[ line ] );
4140                                 } else {
4141                                         chars += String.fromCharCode( lineArrayLength );
4142                                         lineHash[ line ] = lineArrayLength;
4143                                         lineArray[ lineArrayLength++ ] = line;
4144                                 }
4145                         }
4146                         return chars;
4147                 }
4149                 chars1 = diffLinesToCharsMunge( text1 );
4150                 chars2 = diffLinesToCharsMunge( text2 );
4151                 return {
4152                         chars1: chars1,
4153                         chars2: chars2,
4154                         lineArray: lineArray
4155                 };
4156         };
4158         /**
4159          * Rehydrate the text in a diff from a string of line hashes to real lines of
4160          * text.
4161          * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4162          * @param {!Array.<string>} lineArray Array of unique strings.
4163          * @private
4164          */
4165         DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {
4166                 var x, chars, text, y;
4167                 for ( x = 0; x < diffs.length; x++ ) {
4168                         chars = diffs[ x ][ 1 ];
4169                         text = [];
4170                         for ( y = 0; y < chars.length; y++ ) {
4171                                 text[ y ] = lineArray[ chars.charCodeAt( y ) ];
4172                         }
4173                         diffs[ x ][ 1 ] = text.join( "" );
4174                 }
4175         };
4177         /**
4178          * Reorder and merge like edit sections.  Merge equalities.
4179          * Any edit section can move as long as it doesn't cross an equality.
4180          * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4181          */
4182         DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) {
4183                 var pointer, countDelete, countInsert, textInsert, textDelete,
4184                         commonlength, changes, diffPointer, position;
4185                 diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
4186                 pointer = 0;
4187                 countDelete = 0;
4188                 countInsert = 0;
4189                 textDelete = "";
4190                 textInsert = "";
4191                 commonlength;
4192                 while ( pointer < diffs.length ) {
4193                         switch ( diffs[ pointer ][ 0 ] ) {
4194                         case DIFF_INSERT:
4195                                 countInsert++;
4196                                 textInsert += diffs[ pointer ][ 1 ];
4197                                 pointer++;
4198                                 break;
4199                         case DIFF_DELETE:
4200                                 countDelete++;
4201                                 textDelete += diffs[ pointer ][ 1 ];
4202                                 pointer++;
4203                                 break;
4204                         case DIFF_EQUAL:
4206                                 // Upon reaching an equality, check for prior redundancies.
4207                                 if ( countDelete + countInsert > 1 ) {
4208                                         if ( countDelete !== 0 && countInsert !== 0 ) {
4210                                                 // Factor out any common prefixes.
4211                                                 commonlength = this.diffCommonPrefix( textInsert, textDelete );
4212                                                 if ( commonlength !== 0 ) {
4213                                                         if ( ( pointer - countDelete - countInsert ) > 0 &&
4214                                                                         diffs[ pointer - countDelete - countInsert - 1 ][ 0 ] ===
4215                                                                         DIFF_EQUAL ) {
4216                                                                 diffs[ pointer - countDelete - countInsert - 1 ][ 1 ] +=
4217                                                                         textInsert.substring( 0, commonlength );
4218                                                         } else {
4219                                                                 diffs.splice( 0, 0, [ DIFF_EQUAL,
4220                                                                         textInsert.substring( 0, commonlength )
4221                                                                 ] );
4222                                                                 pointer++;
4223                                                         }
4224                                                         textInsert = textInsert.substring( commonlength );
4225                                                         textDelete = textDelete.substring( commonlength );
4226                                                 }
4228                                                 // Factor out any common suffixies.
4229                                                 commonlength = this.diffCommonSuffix( textInsert, textDelete );
4230                                                 if ( commonlength !== 0 ) {
4231                                                         diffs[ pointer ][ 1 ] = textInsert.substring( textInsert.length -
4232                                                                         commonlength ) + diffs[ pointer ][ 1 ];
4233                                                         textInsert = textInsert.substring( 0, textInsert.length -
4234                                                                 commonlength );
4235                                                         textDelete = textDelete.substring( 0, textDelete.length -
4236                                                                 commonlength );
4237                                                 }
4238                                         }
4240                                         // Delete the offending records and add the merged ones.
4241                                         if ( countDelete === 0 ) {
4242                                                 diffs.splice( pointer - countInsert,
4243                                                         countDelete + countInsert, [ DIFF_INSERT, textInsert ] );
4244                                         } else if ( countInsert === 0 ) {
4245                                                 diffs.splice( pointer - countDelete,
4246                                                         countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
4247                                         } else {
4248                                                 diffs.splice(
4249                                                         pointer - countDelete - countInsert,
4250                                                         countDelete + countInsert,
4251                                                         [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ]
4252                                                 );
4253                                         }
4254                                         pointer = pointer - countDelete - countInsert +
4255                                                 ( countDelete ? 1 : 0 ) + ( countInsert ? 1 : 0 ) + 1;
4256                                 } else if ( pointer !== 0 && diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL ) {
4258                                         // Merge this equality with the previous one.
4259                                         diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ];
4260                                         diffs.splice( pointer, 1 );
4261                                 } else {
4262                                         pointer++;
4263                                 }
4264                                 countInsert = 0;
4265                                 countDelete = 0;
4266                                 textDelete = "";
4267                                 textInsert = "";
4268                                 break;
4269                         }
4270                 }
4271                 if ( diffs[ diffs.length - 1 ][ 1 ] === "" ) {
4272                         diffs.pop(); // Remove the dummy entry at the end.
4273                 }
4275                 // Second pass: look for single edits surrounded on both sides by equalities
4276                 // which can be shifted sideways to eliminate an equality.
4277                 // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
4278                 changes = false;
4279                 pointer = 1;
4281                 // Intentionally ignore the first and last element (don't need checking).
4282                 while ( pointer < diffs.length - 1 ) {
4283                         if ( diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL &&
4284                                         diffs[ pointer + 1 ][ 0 ] === DIFF_EQUAL ) {
4286                                 diffPointer = diffs[ pointer ][ 1 ];
4287                                 position = diffPointer.substring(
4288                                         diffPointer.length - diffs[ pointer - 1 ][ 1 ].length
4289                                 );
4291                                 // This is a single edit surrounded by equalities.
4292                                 if ( position === diffs[ pointer - 1 ][ 1 ] ) {
4294                                         // Shift the edit over the previous equality.
4295                                         diffs[ pointer ][ 1 ] = diffs[ pointer - 1 ][ 1 ] +
4296                                                 diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer ][ 1 ].length -
4297                                                         diffs[ pointer - 1 ][ 1 ].length );
4298                                         diffs[ pointer + 1 ][ 1 ] =
4299                                                 diffs[ pointer - 1 ][ 1 ] + diffs[ pointer + 1 ][ 1 ];
4300                                         diffs.splice( pointer - 1, 1 );
4301                                         changes = true;
4302                                 } else if ( diffPointer.substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
4303                                                 diffs[ pointer + 1 ][ 1 ] ) {
4305                                         // Shift the edit over the next equality.
4306                                         diffs[ pointer - 1 ][ 1 ] += diffs[ pointer + 1 ][ 1 ];
4307                                         diffs[ pointer ][ 1 ] =
4308                                                 diffs[ pointer ][ 1 ].substring( diffs[ pointer + 1 ][ 1 ].length ) +
4309                                                 diffs[ pointer + 1 ][ 1 ];
4310                                         diffs.splice( pointer + 1, 1 );
4311                                         changes = true;
4312                                 }
4313                         }
4314                         pointer++;
4315                 }
4317                 // If shifts were made, the diff needs reordering and another shift sweep.
4318                 if ( changes ) {
4319                         this.diffCleanupMerge( diffs );
4320                 }
4321         };
4323         return function( o, n ) {
4324                 var diff, output, text;
4325                 diff = new DiffMatchPatch();
4326                 output = diff.DiffMain( o, n );
4327                 diff.diffCleanupEfficiency( output );
4328                 text = diff.diffPrettyHtml( output );
4330                 return text;
4331         };
4332 }() );
4334 }() );