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