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