Merge "Add more $wgPoolCounterConf comments"
[mediawiki.git] / resources / lib / qunitjs / qunit.js
blob5df0822ea428345513a05ff23e78c395c402d06f
1 /*!
2 * QUnit 1.23.1
3 * https://qunitjs.com/
5 * Copyright jQuery Foundation and other contributors
6 * Released under the MIT license
7 * https://jquery.org/license
9 * Date: 2016-04-12T17:29Z
12 ( function( global ) {
14 var QUnit = {};
16 var Date = global.Date;
17 var now = Date.now || function() {
18 return new Date().getTime();
21 var setTimeout = global.setTimeout;
22 var clearTimeout = global.clearTimeout;
24 // Store a local window from the global to allow direct references.
25 var window = global.window;
27 var defined = {
28 document: window && window.document !== undefined,
29 setTimeout: setTimeout !== undefined,
30 sessionStorage: ( function() {
31 var x = "qunit-test-string";
32 try {
33 sessionStorage.setItem( x, x );
34 sessionStorage.removeItem( x );
35 return true;
36 } catch ( e ) {
37 return false;
39 }() )
42 var fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" );
43 var globalStartCalled = false;
44 var runStarted = false;
46 var toString = Object.prototype.toString,
47 hasOwn = Object.prototype.hasOwnProperty;
49 // Returns a new Array with the elements that are in a but not in b
50 function diff( a, b ) {
51 var i, j,
52 result = a.slice();
54 for ( i = 0; i < result.length; i++ ) {
55 for ( j = 0; j < b.length; j++ ) {
56 if ( result[ i ] === b[ j ] ) {
57 result.splice( i, 1 );
58 i--;
59 break;
63 return result;
66 // From jquery.js
67 function inArray( elem, array ) {
68 if ( array.indexOf ) {
69 return array.indexOf( elem );
72 for ( var i = 0, length = array.length; i < length; i++ ) {
73 if ( array[ i ] === elem ) {
74 return i;
78 return -1;
81 /**
82 * Makes a clone of an object using only Array or Object as base,
83 * and copies over the own enumerable properties.
85 * @param {Object} obj
86 * @return {Object} New object with only the own properties (recursively).
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;
97 return vals;
100 function extend( a, b, undefOnly ) {
101 for ( var prop in b ) {
102 if ( hasOwn.call( b, prop ) ) {
104 // Avoid "Member not found" error in IE8 caused by messing with window.constructor
105 // This block runs on every environment, so `global` is being used instead of `window`
106 // to avoid errors on node.
107 if ( prop !== "constructor" || a !== global ) {
108 if ( b[ prop ] === undefined ) {
109 delete a[ prop ];
110 } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
111 a[ prop ] = b[ prop ];
117 return a;
120 function objectType( obj ) {
121 if ( typeof obj === "undefined" ) {
122 return "undefined";
125 // Consider: typeof null === object
126 if ( obj === null ) {
127 return "null";
130 var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
131 type = match && match[ 1 ];
133 switch ( type ) {
134 case "Number":
135 if ( isNaN( obj ) ) {
136 return "nan";
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();
150 if ( typeof obj === "object" ) {
151 return "object";
155 // Safe object type checking
156 function is( type, obj ) {
157 return QUnit.objectType( obj ) === type;
160 // Doesn't support IE6 to IE9, it will return undefined on these browsers
161 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
162 function extractStacktrace( e, offset ) {
163 offset = offset === undefined ? 4 : offset;
165 var stack, include, i;
167 if ( e.stack ) {
168 stack = e.stack.split( "\n" );
169 if ( /^error$/i.test( stack[ 0 ] ) ) {
170 stack.shift();
172 if ( fileName ) {
173 include = [];
174 for ( i = offset; i < stack.length; i++ ) {
175 if ( stack[ i ].indexOf( fileName ) !== -1 ) {
176 break;
178 include.push( stack[ i ] );
180 if ( include.length ) {
181 return include.join( "\n" );
184 return stack[ offset ];
186 // Support: Safari <=6 only
187 } else if ( e.sourceURL ) {
189 // Exclude useless self-reference for generated Error objects
190 if ( /qunit.js$/.test( e.sourceURL ) ) {
191 return;
194 // For actual exceptions, this is useful
195 return e.sourceURL + ":" + e.line;
199 function sourceFromStacktrace( offset ) {
200 var error = new Error();
202 // Support: Safari <=7 only, IE <=10 - 11 only
203 // Not all browsers generate the `stack` property for `new Error()`, see also #636
204 if ( !error.stack ) {
205 try {
206 throw error;
207 } catch ( err ) {
208 error = err;
212 return extractStacktrace( error, offset );
216 * Config object: Maintain internal state
217 * Later exposed as QUnit.config
218 * `config` initialized at top of scope
220 var config = {
222 // The queue of tests to run
223 queue: [],
225 // Block until document ready
226 blocking: true,
228 // By default, run previously failed tests first
229 // very useful in combination with "Hide passed tests" checked
230 reorder: true,
232 // By default, modify document.title when suite is done
233 altertitle: true,
235 // HTML Reporter: collapse every test except the first failing test
236 // If false, all failing tests will be expanded
237 collapse: true,
239 // By default, scroll to top of the page when suite is done
240 scrolltop: true,
242 // Depth up-to which object will be dumped
243 maxDepth: 5,
245 // When enabled, all tests must call expect()
246 requireExpects: false,
248 // Placeholder for user-configurable form-exposed URL parameters
249 urlConfig: [],
251 // Set of all modules.
252 modules: [],
254 // Stack of nested modules
255 moduleStack: [],
257 // The first unnamed module
258 currentModule: {
259 name: "",
260 tests: []
263 callbacks: {}
266 // Push a loose unnamed module to the modules collection
267 config.modules.push( config.currentModule );
269 var loggingCallbacks = {};
271 // Register logging callbacks
272 function registerLoggingCallbacks( obj ) {
273 var i, l, key,
274 callbackNames = [ "begin", "done", "log", "testStart", "testDone",
275 "moduleStart", "moduleDone" ];
277 function registerLoggingCallback( key ) {
278 var loggingCallback = function( callback ) {
279 if ( objectType( callback ) !== "function" ) {
280 throw new Error(
281 "QUnit logging methods require a callback function as their first parameters."
285 config.callbacks[ key ].push( callback );
288 // DEPRECATED: This will be removed on QUnit 2.0.0+
289 // Stores the registered functions allowing restoring
290 // at verifyLoggingCallbacks() if modified
291 loggingCallbacks[ key ] = loggingCallback;
293 return loggingCallback;
296 for ( i = 0, l = callbackNames.length; i < l; i++ ) {
297 key = callbackNames[ i ];
299 // Initialize key collection of logging callback
300 if ( objectType( config.callbacks[ key ] ) === "undefined" ) {
301 config.callbacks[ key ] = [];
304 obj[ key ] = registerLoggingCallback( key );
308 function runLoggingCallbacks( key, args ) {
309 var i, l, callbacks;
311 callbacks = config.callbacks[ key ];
312 for ( i = 0, l = callbacks.length; i < l; i++ ) {
313 callbacks[ i ]( args );
317 // DEPRECATED: This will be removed on 2.0.0+
318 // This function verifies if the loggingCallbacks were modified by the user
319 // If so, it will restore it, assign the given callback and print a console warning
320 function verifyLoggingCallbacks() {
321 var loggingCallback, userCallback;
323 for ( loggingCallback in loggingCallbacks ) {
324 if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
326 userCallback = QUnit[ loggingCallback ];
328 // Restore the callback function
329 QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
331 // Assign the deprecated given callback
332 QUnit[ loggingCallback ]( userCallback );
334 if ( global.console && global.console.warn ) {
335 global.console.warn(
336 "QUnit." + loggingCallback + " was replaced with a new value.\n" +
337 "Please, check out the documentation on how to apply logging callbacks.\n" +
338 "Reference: https://api.qunitjs.com/category/callbacks/"
345 ( function() {
346 if ( !defined.document ) {
347 return;
350 // `onErrorFnPrev` initialized at top of scope
351 // Preserve other handlers
352 var onErrorFnPrev = window.onerror;
354 // Cover uncaught exceptions
355 // Returning true will suppress the default browser handler,
356 // returning false will let it run.
357 window.onerror = function( error, filePath, linerNr ) {
358 var ret = false;
359 if ( onErrorFnPrev ) {
360 ret = onErrorFnPrev( error, filePath, linerNr );
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;
370 QUnit.pushFailure( error, filePath + ":" + linerNr );
371 } else {
372 QUnit.test( "global failure", extend( function() {
373 QUnit.pushFailure( error, filePath + ":" + linerNr );
374 }, { validTest: true } ) );
376 return false;
379 return ret;
381 }() );
383 // Figure out if we're running the tests from a server or not
384 QUnit.isLocal = !( defined.document && window.location.protocol !== "file:" );
386 // Expose the current QUnit version
387 QUnit.version = "1.23.1";
389 extend( QUnit, {
391 // Call on start of module test to prepend name to all tests
392 module: function( name, testEnvironment, executeNow ) {
393 var module, moduleFns;
394 var currentModule = config.currentModule;
396 if ( arguments.length === 2 ) {
397 if ( objectType( testEnvironment ) === "function" ) {
398 executeNow = testEnvironment;
399 testEnvironment = undefined;
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;
409 if ( testEnvironment && testEnvironment.teardown ) {
410 testEnvironment.afterEach = testEnvironment.teardown;
411 delete testEnvironment.teardown;
414 module = createModule();
416 moduleFns = {
417 beforeEach: setHook( module, "beforeEach" ),
418 afterEach: setHook( module, "afterEach" )
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;
429 setCurrentModule( module );
431 function createModule() {
432 var parentModule = config.moduleStack.length ?
433 config.moduleStack.slice( -1 )[ 0 ] : null;
434 var moduleName = parentModule !== null ?
435 [ parentModule.name, name ].join( " > " ) : name;
436 var module = {
437 name: moduleName,
438 parentModule: parentModule,
439 tests: [],
440 moduleId: generateHash( moduleName )
443 var env = {};
444 if ( parentModule ) {
445 extend( env, parentModule.testEnvironment );
446 delete env.beforeEach;
447 delete env.afterEach;
449 extend( env, testEnvironment );
450 module.testEnvironment = env;
452 config.modules.push( module );
453 return module;
456 function setCurrentModule( module ) {
457 config.currentModule = module;
462 // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
463 asyncTest: asyncTest,
465 test: test,
467 skip: skip,
469 only: only,
471 // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
472 // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
473 start: function( count ) {
474 var globalStartAlreadyCalled = globalStartCalled;
476 if ( !config.current ) {
477 globalStartCalled = true;
479 if ( runStarted ) {
480 throw new Error( "Called start() outside of a test context while already started" );
481 } else if ( globalStartAlreadyCalled || count > 1 ) {
482 throw new Error( "Called start() outside of a test context too many times" );
483 } else if ( config.autostart ) {
484 throw new Error( "Called start() outside of a test context when " +
485 "QUnit.config.autostart was true" );
486 } else if ( !config.pageLoaded ) {
488 // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
489 config.autostart = true;
490 return;
492 } else {
494 // If a test is running, adjust its semaphore
495 config.current.semaphore -= count || 1;
497 // If semaphore is non-numeric, throw error
498 if ( isNaN( config.current.semaphore ) ) {
499 config.current.semaphore = 0;
501 QUnit.pushFailure(
502 "Called start() with a non-numeric decrement.",
503 sourceFromStacktrace( 2 )
505 return;
508 // Don't start until equal number of stop-calls
509 if ( config.current.semaphore > 0 ) {
510 return;
513 // Throw an Error if start is called more often than stop
514 if ( config.current.semaphore < 0 ) {
515 config.current.semaphore = 0;
517 QUnit.pushFailure(
518 "Called start() while already started (test's semaphore was 0 already)",
519 sourceFromStacktrace( 2 )
521 return;
525 resumeProcessing();
528 // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
529 stop: function( count ) {
531 // If there isn't a test running, don't allow QUnit.stop() to be called
532 if ( !config.current ) {
533 throw new Error( "Called stop() outside of a test context" );
536 // If a test is running, adjust its semaphore
537 config.current.semaphore += count || 1;
539 pauseProcessing();
542 config: config,
544 is: is,
546 objectType: objectType,
548 extend: extend,
550 load: function() {
551 config.pageLoaded = true;
553 // Initialize the configuration options
554 extend( config, {
555 stats: { all: 0, bad: 0 },
556 moduleStats: { all: 0, bad: 0 },
557 started: 0,
558 updateRate: 1000,
559 autostart: true,
560 filter: ""
561 }, true );
563 config.blocking = false;
565 if ( config.autostart ) {
566 resumeProcessing();
570 stack: function( offset ) {
571 offset = ( offset || 0 ) + 2;
572 return sourceFromStacktrace( offset );
574 } );
576 registerLoggingCallbacks( QUnit );
578 function begin() {
579 var i, l,
580 modulesLog = [];
582 // If the test run hasn't officially begun yet
583 if ( !config.started ) {
585 // Record the time of the test run's beginning
586 config.started = now();
588 verifyLoggingCallbacks();
590 // Delete the loose unnamed module if unused.
591 if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
592 config.modules.shift();
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 } );
603 // The test run is officially beginning now
604 runLoggingCallbacks( "begin", {
605 totalTests: Test.count,
606 modules: modulesLog
607 } );
610 config.blocking = false;
611 process( true );
614 function process( last ) {
615 function next() {
616 process( last );
618 var start = now();
619 config.depth = ( config.depth || 0 ) + 1;
621 while ( config.queue.length && !config.blocking ) {
622 if ( !defined.setTimeout || config.updateRate <= 0 ||
623 ( ( now() - start ) < config.updateRate ) ) {
624 if ( config.current ) {
626 // Reset async tracking for each phase of the Test lifecycle
627 config.current.usedAsync = false;
629 config.queue.shift()();
630 } else {
631 setTimeout( next, 13 );
632 break;
635 config.depth--;
636 if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
637 done();
641 function pauseProcessing() {
642 config.blocking = true;
644 if ( config.testTimeout && defined.setTimeout ) {
645 clearTimeout( config.timeout );
646 config.timeout = setTimeout( function() {
647 if ( config.current ) {
648 config.current.semaphore = 0;
649 QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
650 } else {
651 throw new Error( "Test timed out" );
653 resumeProcessing();
654 }, config.testTimeout );
658 function resumeProcessing() {
659 runStarted = true;
661 // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
662 if ( defined.setTimeout ) {
663 setTimeout( function() {
664 if ( config.current && config.current.semaphore > 0 ) {
665 return;
667 if ( config.timeout ) {
668 clearTimeout( config.timeout );
671 begin();
672 }, 13 );
673 } else {
674 begin();
678 function done() {
679 var runtime, passed;
681 config.autorun = true;
683 // Log the last module results
684 if ( config.previousModule ) {
685 runLoggingCallbacks( "moduleDone", {
686 name: config.previousModule.name,
687 tests: config.previousModule.tests,
688 failed: config.moduleStats.bad,
689 passed: config.moduleStats.all - config.moduleStats.bad,
690 total: config.moduleStats.all,
691 runtime: now() - config.moduleStats.started
692 } );
694 delete config.previousModule;
696 runtime = now() - config.started;
697 passed = config.stats.all - config.stats.bad;
699 runLoggingCallbacks( "done", {
700 failed: config.stats.bad,
701 passed: passed,
702 total: config.stats.all,
703 runtime: runtime
704 } );
707 function setHook( module, hookName ) {
708 if ( module.testEnvironment === undefined ) {
709 module.testEnvironment = {};
712 return function( callback ) {
713 module.testEnvironment[ hookName ] = callback;
717 var focused = false;
718 var priorityCount = 0;
719 var unitSampler;
721 function Test( settings ) {
722 var i, l;
724 ++Test.count;
726 extend( this, settings );
727 this.assertions = [];
728 this.semaphore = 0;
729 this.usedAsync = false;
730 this.module = config.currentModule;
731 this.stack = sourceFromStacktrace( 3 );
733 // Register unique strings
734 for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
735 if ( this.module.tests[ i ].name === this.testName ) {
736 this.testName += " ";
740 this.testId = generateHash( this.module.name, this.testName );
742 this.module.tests.push( {
743 name: this.testName,
744 testId: this.testId
745 } );
747 if ( settings.skip ) {
749 // Skipped tests will fully ignore any sent callback
750 this.callback = function() {};
751 this.async = false;
752 this.expected = 0;
753 } else {
754 this.assert = new Assert( this );
758 Test.count = 0;
760 Test.prototype = {
761 before: function() {
762 if (
764 // Emit moduleStart when we're switching from one module to another
765 this.module !== config.previousModule ||
767 // They could be equal (both undefined) but if the previousModule property doesn't
768 // yet exist it means this is the first test in a suite that isn't wrapped in a
769 // module, in which case we'll just emit a moduleStart event for 'undefined'.
770 // Without this, reporters can get testStart before moduleStart which is a problem.
771 !hasOwn.call( config, "previousModule" )
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 } );
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 } );
791 config.current = this;
793 if ( this.module.testEnvironment ) {
794 delete this.module.testEnvironment.beforeEach;
795 delete this.module.testEnvironment.afterEach;
797 this.testEnvironment = extend( {}, this.module.testEnvironment );
799 this.started = now();
800 runLoggingCallbacks( "testStart", {
801 name: this.testName,
802 module: this.module.name,
803 testId: this.testId
804 } );
806 if ( !config.pollution ) {
807 saveGlobal();
811 run: function() {
812 var promise;
814 config.current = this;
816 if ( this.async ) {
817 QUnit.stop();
820 this.callbackStarted = now();
822 if ( config.notrycatch ) {
823 runTest( this );
824 return;
827 try {
828 runTest( this );
829 } catch ( e ) {
830 this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
831 this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
833 // Else next test will carry the responsibility
834 saveGlobal();
836 // Restart the tests if they're blocking
837 if ( config.blocking ) {
838 QUnit.start();
842 function runTest( test ) {
843 promise = test.callback.call( test.testEnvironment, test.assert );
844 test.resolvePromise( promise );
848 after: function() {
849 checkPollution();
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;
861 try {
862 callHook();
863 } catch ( error ) {
864 test.pushFailure( hookName + " failed on " + test.testName + ": " +
865 ( error.message || error ), extractStacktrace( error, 0 ) );
868 function callHook() {
869 promise = hook.call( test.testEnvironment, test.assert );
870 test.resolvePromise( promise, hookName );
875 // Currently only used for module level hooks, can be used to add global level ones
876 hooks: function( handler ) {
877 var hooks = [];
879 function processHooks( test, module ) {
880 if ( module.parentModule ) {
881 processHooks( test, module.parentModule );
883 if ( module.testEnvironment &&
884 QUnit.objectType( module.testEnvironment[ handler ] ) === "function" ) {
885 hooks.push( test.queueHook( module.testEnvironment[ handler ], handler ) );
889 // Hooks are ignored on skipped tests
890 if ( !this.skip ) {
891 processHooks( this, this.module );
893 return hooks;
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 );
909 var i,
910 bad = 0;
912 this.runtime = now() - this.started;
913 config.stats.all += this.assertions.length;
914 config.moduleStats.all += this.assertions.length;
916 for ( i = 0; i < this.assertions.length; i++ ) {
917 if ( !this.assertions[ i ].result ) {
918 bad++;
919 config.stats.bad++;
920 config.moduleStats.bad++;
924 runLoggingCallbacks( "testDone", {
925 name: this.testName,
926 module: this.module.name,
927 skipped: !!this.skip,
928 failed: bad,
929 passed: this.assertions.length - bad,
930 total: this.assertions.length,
931 runtime: this.runtime,
933 // HTML Reporter use
934 assertions: this.assertions,
935 testId: this.testId,
937 // Source of Test
938 source: this.stack,
940 // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
941 duration: this.runtime
942 } );
944 // QUnit.reset() is deprecated and will be replaced for a new
945 // fixture reset function on QUnit 2.0/2.1.
946 // It's still called here for backwards compatibility handling
947 QUnit.reset();
949 config.current = undefined;
952 queue: function() {
953 var priority,
954 test = this;
956 if ( !this.valid() ) {
957 return;
960 function run() {
962 // Each of these can by async
963 synchronize( [
964 function() {
965 test.before();
968 test.hooks( "beforeEach" ),
969 function() {
970 test.run();
973 test.hooks( "afterEach" ).reverse(),
975 function() {
976 test.after();
978 function() {
979 test.finish();
981 ] );
984 // Prioritize previously failed tests, detected from sessionStorage
985 priority = QUnit.config.reorder && defined.sessionStorage &&
986 +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
988 return synchronize( run, priority, config.seed );
991 pushResult: function( resultInfo ) {
993 // Destructure of resultInfo = { result, actual, expected, message, negative }
994 var source,
995 details = {
996 module: this.module.name,
997 name: this.testName,
998 result: resultInfo.result,
999 message: resultInfo.message,
1000 actual: resultInfo.actual,
1001 expected: resultInfo.expected,
1002 testId: this.testId,
1003 negative: resultInfo.negative || false,
1004 runtime: now() - this.started
1007 if ( !resultInfo.result ) {
1008 source = sourceFromStacktrace();
1010 if ( source ) {
1011 details.source = source;
1015 runLoggingCallbacks( "log", details );
1017 this.assertions.push( {
1018 result: !!resultInfo.result,
1019 message: resultInfo.message
1020 } );
1023 pushFailure: function( message, source, actual ) {
1024 if ( !( this instanceof Test ) ) {
1025 throw new Error( "pushFailure() assertion outside test context, was " +
1026 sourceFromStacktrace( 2 ) );
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
1039 if ( source ) {
1040 details.source = source;
1043 runLoggingCallbacks( "log", details );
1045 this.assertions.push( {
1046 result: false,
1047 message: message
1048 } );
1051 resolvePromise: function( promise, phase ) {
1052 var then, message,
1053 test = this;
1054 if ( promise != null ) {
1055 then = promise.then;
1056 if ( QUnit.objectType( then ) === "function" ) {
1057 QUnit.stop();
1058 then.call(
1059 promise,
1060 function() { QUnit.start(); },
1061 function( error ) {
1062 message = "Promise rejected " +
1063 ( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
1064 " " + test.testName + ": " + ( error.message || error );
1065 test.pushFailure( message, extractStacktrace( error, 0 ) );
1067 // Else next test will carry the responsibility
1068 saveGlobal();
1070 // Unblock
1071 QUnit.start();
1078 valid: function() {
1079 var filter = config.filter,
1080 regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec( filter ),
1081 module = config.module && config.module.toLowerCase(),
1082 fullName = ( this.module.name + ": " + this.testName );
1084 function moduleChainNameMatch( testModule ) {
1085 var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
1086 if ( testModuleName === module ) {
1087 return true;
1088 } else if ( testModule.parentModule ) {
1089 return moduleChainNameMatch( testModule.parentModule );
1090 } else {
1091 return false;
1095 function moduleChainIdMatch( testModule ) {
1096 return inArray( testModule.moduleId, config.moduleId ) > -1 ||
1097 testModule.parentModule && moduleChainIdMatch( testModule.parentModule );
1100 // Internally-generated tests are always valid
1101 if ( this.callback && this.callback.validTest ) {
1102 return true;
1105 if ( config.moduleId && config.moduleId.length > 0 &&
1106 !moduleChainIdMatch( this.module ) ) {
1108 return false;
1111 if ( config.testId && config.testId.length > 0 &&
1112 inArray( this.testId, config.testId ) < 0 ) {
1114 return false;
1117 if ( module && !moduleChainNameMatch( this.module ) ) {
1118 return false;
1121 if ( !filter ) {
1122 return true;
1125 return regexFilter ?
1126 this.regexFilter( !!regexFilter[ 1 ], regexFilter[ 2 ], regexFilter[ 3 ], fullName ) :
1127 this.stringFilter( filter, fullName );
1130 regexFilter: function( exclude, pattern, flags, fullName ) {
1131 var regex = new RegExp( pattern, flags );
1132 var match = regex.test( fullName );
1134 return match !== exclude;
1137 stringFilter: function( filter, fullName ) {
1138 filter = filter.toLowerCase();
1139 fullName = fullName.toLowerCase();
1141 var include = filter.charAt( 0 ) !== "!";
1142 if ( !include ) {
1143 filter = filter.slice( 1 );
1146 // If the filter matches, we need to honour include
1147 if ( fullName.indexOf( filter ) !== -1 ) {
1148 return include;
1151 // Otherwise, do the opposite
1152 return !include;
1156 // Resets the test setup. Useful for tests that modify the DOM.
1158 DEPRECATED: Use multiple tests instead of resetting inside a test.
1159 Use testStart or testDone for custom cleanup.
1160 This method will throw an error in 2.0, and will be removed in 2.1
1162 QUnit.reset = function() {
1164 // Return on non-browser environments
1165 // This is necessary to not break on node tests
1166 if ( !defined.document ) {
1167 return;
1170 var fixture = defined.document && document.getElementById &&
1171 document.getElementById( "qunit-fixture" );
1173 if ( fixture ) {
1174 fixture.innerHTML = config.fixture;
1178 QUnit.pushFailure = function() {
1179 if ( !QUnit.config.current ) {
1180 throw new Error( "pushFailure() assertion outside test context, in " +
1181 sourceFromStacktrace( 2 ) );
1184 // Gets current test obj
1185 var currentTest = QUnit.config.current;
1187 return currentTest.pushFailure.apply( currentTest, arguments );
1190 // Based on Java's String.hashCode, a simple but not
1191 // rigorously collision resistant hashing function
1192 function generateHash( module, testName ) {
1193 var hex,
1194 i = 0,
1195 hash = 0,
1196 str = module + "\x1C" + testName,
1197 len = str.length;
1199 for ( ; i < len; i++ ) {
1200 hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
1201 hash |= 0;
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;
1211 return hex.slice( -8 );
1214 function synchronize( callback, priority, seed ) {
1215 var last = !priority,
1216 index;
1218 if ( QUnit.objectType( callback ) === "array" ) {
1219 while ( callback.length ) {
1220 synchronize( callback.shift() );
1222 return;
1225 if ( priority ) {
1226 config.queue.splice( priorityCount++, 0, callback );
1227 } else if ( seed ) {
1228 if ( !unitSampler ) {
1229 unitSampler = unitSamplerGenerator( seed );
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 );
1239 if ( config.autorun && !config.blocking ) {
1240 process( last );
1244 function unitSamplerGenerator( seed ) {
1246 // 32-bit xorshift, requires only a nonzero seed
1247 // http://excamera.com/sphinx/article-xorshift.html
1248 var sample = parseInt( generateHash( seed ), 16 ) || -1;
1249 return function() {
1250 sample ^= sample << 13;
1251 sample ^= sample >>> 17;
1252 sample ^= sample << 5;
1254 // ECMAScript has no unsigned number type
1255 if ( sample < 0 ) {
1256 sample += 0x100000000;
1259 return sample / 0x100000000;
1263 function saveGlobal() {
1264 config.pollution = [];
1266 if ( config.noglobals ) {
1267 for ( var key in global ) {
1268 if ( hasOwn.call( global, key ) ) {
1270 // In Opera sometimes DOM element ids show up here, ignore them
1271 if ( /^qunit-test-output/.test( key ) ) {
1272 continue;
1274 config.pollution.push( key );
1280 function checkPollution() {
1281 var newGlobals,
1282 deletedGlobals,
1283 old = config.pollution;
1285 saveGlobal();
1287 newGlobals = diff( config.pollution, old );
1288 if ( newGlobals.length > 0 ) {
1289 QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
1292 deletedGlobals = diff( old, config.pollution );
1293 if ( deletedGlobals.length > 0 ) {
1294 QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
1298 // Will be exposed as QUnit.asyncTest
1299 function asyncTest( testName, expected, callback ) {
1300 if ( arguments.length === 2 ) {
1301 callback = expected;
1302 expected = null;
1305 QUnit.test( testName, expected, callback, true );
1308 // Will be exposed as QUnit.test
1309 function test( testName, expected, callback, async ) {
1310 if ( focused ) { return; }
1312 var newTest;
1314 if ( arguments.length === 2 ) {
1315 callback = expected;
1316 expected = null;
1319 newTest = new Test( {
1320 testName: testName,
1321 expected: expected,
1322 async: async,
1323 callback: callback
1324 } );
1326 newTest.queue();
1329 // Will be exposed as QUnit.skip
1330 function skip( testName ) {
1331 if ( focused ) { return; }
1333 var test = new Test( {
1334 testName: testName,
1335 skip: true
1336 } );
1338 test.queue();
1341 // Will be exposed as QUnit.only
1342 function only( testName, expected, callback, async ) {
1343 var newTest;
1345 if ( focused ) { return; }
1347 QUnit.config.queue.length = 0;
1348 focused = true;
1350 if ( arguments.length === 2 ) {
1351 callback = expected;
1352 expected = null;
1355 newTest = new Test( {
1356 testName: testName,
1357 expected: expected,
1358 async: async,
1359 callback: callback
1360 } );
1362 newTest.queue();
1365 function Assert( testContext ) {
1366 this.test = testContext;
1369 // Assert helpers
1370 QUnit.assert = Assert.prototype = {
1372 // Specify the number of expected assertions to guarantee that failed test
1373 // (no assertions are run at all) don't slip through.
1374 expect: function( asserts ) {
1375 if ( arguments.length === 1 ) {
1376 this.test.expected = asserts;
1377 } else {
1378 return this.test.expected;
1382 // Increment this Test's semaphore counter, then return a function that
1383 // decrements that counter a maximum of once.
1384 async: function( count ) {
1385 var test = this.test,
1386 popped = false,
1387 acceptCallCount = count;
1389 if ( typeof acceptCallCount === "undefined" ) {
1390 acceptCallCount = 1;
1393 test.semaphore += 1;
1394 test.usedAsync = true;
1395 pauseProcessing();
1397 return function done() {
1399 if ( popped ) {
1400 test.pushFailure( "Too many calls to the `assert.async` callback",
1401 sourceFromStacktrace( 2 ) );
1402 return;
1404 acceptCallCount -= 1;
1405 if ( acceptCallCount > 0 ) {
1406 return;
1409 test.semaphore -= 1;
1410 popped = true;
1411 resumeProcessing();
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 } );
1428 pushResult: function( resultInfo ) {
1430 // Destructure of resultInfo = { result, actual, expected, message, negative }
1431 var assert = this,
1432 currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
1434 // Backwards compatibility fix.
1435 // Allows the direct use of global exported assertions and QUnit.assert.*
1436 // Although, it's use is not recommended as it can leak assertions
1437 // to other tests from async tests, because we only get a reference to the current test,
1438 // not exactly the test where assertion were intended to be called.
1439 if ( !currentTest ) {
1440 throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
1443 if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
1444 currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
1445 sourceFromStacktrace( 2 ) );
1447 // Allow this assertion to continue running anyway...
1450 if ( !( assert instanceof Assert ) ) {
1451 assert = currentTest.assert;
1454 return assert.test.pushResult( resultInfo );
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 } );
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 } );
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 } );
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 } );
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 } );
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 } );
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 } );
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 } );
1542 strictEqual: function( actual, expected, message ) {
1543 this.pushResult( {
1544 result: expected === actual,
1545 actual: actual,
1546 expected: expected,
1547 message: message
1548 } );
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 } );
1561 "throws": function( block, expected, message ) {
1562 var actual, expectedType,
1563 expectedOutput = expected,
1564 ok = false,
1565 currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current;
1567 // 'expected' is optional unless doing string comparison
1568 if ( message == null && typeof expected === "string" ) {
1569 message = expected;
1570 expected = null;
1573 currentTest.ignoreGlobalErrors = true;
1574 try {
1575 block.call( currentTest.testEnvironment );
1576 } catch ( e ) {
1577 actual = e;
1579 currentTest.ignoreGlobalErrors = false;
1581 if ( actual ) {
1582 expectedType = QUnit.objectType( expected );
1584 // We don't want to validate thrown error
1585 if ( !expected ) {
1586 ok = true;
1587 expectedOutput = null;
1589 // Expected is a regexp
1590 } else if ( expectedType === "regexp" ) {
1591 ok = expected.test( errorString( actual ) );
1593 // Expected is a string
1594 } else if ( expectedType === "string" ) {
1595 ok = expected === errorString( actual );
1597 // Expected is a constructor, maybe an Error constructor
1598 } else if ( expectedType === "function" && actual instanceof expected ) {
1599 ok = true;
1601 // Expected is an Error object
1602 } else if ( expectedType === "object" ) {
1603 ok = actual instanceof expected.constructor &&
1604 actual.name === expected.name &&
1605 actual.message === expected.message;
1607 // Expected is a validation function which returns true if validation passed
1608 } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
1609 expectedOutput = null;
1610 ok = true;
1614 currentTest.assert.pushResult( {
1615 result: ok,
1616 actual: actual,
1617 expected: expectedOutput,
1618 message: message
1619 } );
1623 // Provide an alternative to assert.throws(), for environments that consider throws a reserved word
1624 // Known to us are: Closure Compiler, Narwhal
1625 ( function() {
1626 /*jshint sub:true */
1627 Assert.prototype.raises = Assert.prototype [ "throws" ]; //jscs:ignore requireDotNotation
1628 }() );
1630 function errorString( error ) {
1631 var name, message,
1632 resultErrorString = error.toString();
1633 if ( resultErrorString.substring( 0, 7 ) === "[object" ) {
1634 name = error.name ? error.name.toString() : "Error";
1635 message = error.message ? error.message.toString() : "";
1636 if ( name && message ) {
1637 return name + ": " + message;
1638 } else if ( name ) {
1639 return name;
1640 } else if ( message ) {
1641 return message;
1642 } else {
1643 return "Error";
1645 } else {
1646 return resultErrorString;
1650 // Test for equality any JavaScript type.
1651 // Author: Philippe Rathé <prathe@gmail.com>
1652 QUnit.equiv = ( function() {
1654 // Stack to decide between skip/abort functions
1655 var callers = [];
1657 // Stack to avoiding loops from circular referencing
1658 var parents = [];
1659 var parentsB = [];
1661 var getProto = Object.getPrototypeOf || function( obj ) {
1663 /*jshint proto: true */
1664 return obj.__proto__;
1667 function useStrictEquality( b, a ) {
1669 // To catch short annotation VS 'new' annotation of a declaration. e.g.:
1670 // `var i = 1;`
1671 // `var j = new Number(1);`
1672 if ( typeof a === "object" ) {
1673 a = a.valueOf();
1675 if ( typeof b === "object" ) {
1676 b = b.valueOf();
1679 return a === b;
1682 function compareConstructors( a, b ) {
1683 var protoA = getProto( a );
1684 var protoB = getProto( b );
1686 // Comparing constructors is more strict than using `instanceof`
1687 if ( a.constructor === b.constructor ) {
1688 return true;
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;
1697 if ( protoB && protoB.constructor === null ) {
1698 protoB = null;
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;
1708 return false;
1711 function getRegExpFlags( regexp ) {
1712 return "flags" in regexp ? regexp.flags : regexp.toString().match( /[gimuy]*$/ )[ 0 ];
1715 var callbacks = {
1716 "string": useStrictEquality,
1717 "boolean": useStrictEquality,
1718 "number": useStrictEquality,
1719 "null": useStrictEquality,
1720 "undefined": useStrictEquality,
1721 "symbol": useStrictEquality,
1722 "date": useStrictEquality,
1724 "nan": function() {
1725 return true;
1728 "regexp": function( b, a ) {
1729 return a.source === b.source &&
1731 // Include flags in the comparison
1732 getRegExpFlags( a ) === getRegExpFlags( b );
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";
1743 "array": function( b, a ) {
1744 var i, j, len, loop, aCircular, bCircular;
1746 len = a.length;
1747 if ( len !== b.length ) {
1749 // Safe and faster
1750 return false;
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;
1771 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1772 parents.pop();
1773 parentsB.pop();
1774 return false;
1777 parents.pop();
1778 parentsB.pop();
1779 return true;
1782 "set": function( b, a ) {
1783 var innerEq,
1784 outerEq = true;
1786 if ( a.size !== b.size ) {
1787 return false;
1790 a.forEach( function( aVal ) {
1791 innerEq = false;
1793 b.forEach( function( bVal ) {
1794 if ( innerEquiv( bVal, aVal ) ) {
1795 innerEq = true;
1797 } );
1799 if ( !innerEq ) {
1800 outerEq = false;
1802 } );
1804 return outerEq;
1807 "map": function( b, a ) {
1808 var innerEq,
1809 outerEq = true;
1811 if ( a.size !== b.size ) {
1812 return false;
1815 a.forEach( function( aVal, aKey ) {
1816 innerEq = false;
1818 b.forEach( function( bVal, bKey ) {
1819 if ( innerEquiv( [ bVal, bKey ], [ aVal, aKey ] ) ) {
1820 innerEq = true;
1822 } );
1824 if ( !innerEq ) {
1825 outerEq = false;
1827 } );
1829 return outerEq;
1832 "object": function( b, a ) {
1833 var i, j, loop, aCircular, bCircular;
1835 // Default to true
1836 var eq = true;
1837 var aProperties = [];
1838 var bProperties = [];
1840 if ( compareConstructors( a, b ) === false ) {
1841 return false;
1844 // Stack constructor before traversing properties
1845 callers.push( a.constructor );
1847 // Track reference to avoid circular references
1848 parents.push( a );
1849 parentsB.push( b );
1851 // Be strict: don't ensure hasOwnProperty and go deep
1852 for ( i in a ) {
1853 loop = false;
1854 for ( j = 0; j < parents.length; j++ ) {
1855 aCircular = parents[ j ] === a[ i ];
1856 bCircular = parentsB[ j ] === b[ i ];
1857 if ( aCircular || bCircular ) {
1858 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1859 loop = true;
1860 } else {
1861 eq = false;
1862 break;
1866 aProperties.push( i );
1867 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1868 eq = false;
1869 break;
1873 parents.pop();
1874 parentsB.pop();
1876 // Unstack, we are done
1877 callers.pop();
1879 for ( i in b ) {
1881 // Collect b's properties
1882 bProperties.push( i );
1885 // Ensures identical properties name
1886 return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
1890 function typeEquiv( a, b ) {
1891 var type = QUnit.objectType( a );
1892 return QUnit.objectType( b ) === type && callbacks[ type ]( b, a );
1895 // The real equiv function
1896 function innerEquiv( a, b ) {
1898 // We're done when there's nothing more to compare
1899 if ( arguments.length < 2 ) {
1900 return true;
1903 // Require type-specific equality
1904 return ( a === b || typeEquiv( a, b ) ) &&
1906 // ...across all consecutive argument pairs
1907 ( arguments.length === 2 || innerEquiv.apply( this, [].slice.call( arguments, 1 ) ) );
1910 return innerEquiv;
1911 }() );
1913 // Based on jsDump by Ariel Flesler
1914 // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
1915 QUnit.dump = ( function() {
1916 function quote( str ) {
1917 return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\"";
1919 function literal( o ) {
1920 return o + "";
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 );
1929 if ( !arr ) {
1930 return pre + post;
1932 return [ pre, inner + arr, base + post ].join( s );
1934 function array( arr, stack ) {
1935 var i = arr.length,
1936 ret = new Array( i );
1938 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1939 return "[object Array]";
1942 this.up();
1943 while ( i-- ) {
1944 ret[ i ] = this.parse( arr[ i ], undefined, stack );
1946 this.down();
1947 return join( "[", ret, "]" );
1950 var reName = /^function (\w+)/,
1951 dump = {
1953 // The objType is used mostly internally, you can fix a (custom) type in advance
1954 parse: function( obj, objType, stack ) {
1955 stack = stack || [];
1956 var res, parser, parserType,
1957 inStack = inArray( obj, stack );
1959 if ( inStack !== -1 ) {
1960 return "recursion(" + ( inStack - stack.length ) + ")";
1963 objType = objType || this.typeOf( obj );
1964 parser = this.parsers[ objType ];
1965 parserType = typeof parser;
1967 if ( parserType === "function" ) {
1968 stack.push( obj );
1969 res = parser.call( this, obj, stack );
1970 stack.pop();
1971 return res;
1973 return ( parserType === "string" ) ? parser : this.parsers.error;
1975 typeOf: function( obj ) {
1976 var type;
1977 if ( obj === null ) {
1978 type = "null";
1979 } else if ( typeof obj === "undefined" ) {
1980 type = "undefined";
1981 } else if ( QUnit.is( "regexp", obj ) ) {
1982 type = "regexp";
1983 } else if ( QUnit.is( "date", obj ) ) {
1984 type = "date";
1985 } else if ( QUnit.is( "function", obj ) ) {
1986 type = "function";
1987 } else if ( obj.setInterval !== undefined &&
1988 obj.document !== undefined &&
1989 obj.nodeType === undefined ) {
1990 type = "window";
1991 } else if ( obj.nodeType === 9 ) {
1992 type = "document";
1993 } else if ( obj.nodeType ) {
1994 type = "node";
1995 } else if (
1997 // Native arrays
1998 toString.call( obj ) === "[object Array]" ||
2000 // NodeList objects
2001 ( typeof obj.length === "number" && obj.item !== undefined &&
2002 ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
2003 obj[ 0 ] === undefined ) ) )
2005 type = "array";
2006 } else if ( obj.constructor === Error.prototype.constructor ) {
2007 type = "error";
2008 } else {
2009 type = typeof obj;
2011 return type;
2014 separator: function() {
2015 return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
2018 // Extra can be a number, shortcut for increasing-calling-decreasing
2019 indent: function( extra ) {
2020 if ( !this.multiline ) {
2021 return "";
2023 var chr = this.indentChar;
2024 if ( this.HTML ) {
2025 chr = chr.replace( /\t/g, " " ).replace( / /g, "&#160;" );
2027 return new Array( this.depth + ( extra || 0 ) ).join( chr );
2029 up: function( a ) {
2030 this.depth += a || 1;
2032 down: function( a ) {
2033 this.depth -= a || 1;
2035 setParser: function( name, parser ) {
2036 this.parsers[ name ] = parser;
2039 // The next 3 are exposed so you can use them
2040 quote: quote,
2041 literal: literal,
2042 join: join,
2043 depth: 1,
2044 maxDepth: QUnit.config.maxDepth,
2046 // This is the list of parsers, to modify them, use dump.setParser
2047 parsers: {
2048 window: "[Window]",
2049 document: "[Document]",
2050 error: function( error ) {
2051 return "Error(\"" + error.message + "\")";
2053 unknown: "[Unknown]",
2054 "null": "null",
2055 "undefined": "undefined",
2056 "function": function( fn ) {
2057 var ret = "function",
2059 // Functions never have name in IE
2060 name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
2062 if ( name ) {
2063 ret += " " + name;
2065 ret += "(";
2067 ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
2068 return join( ret, dump.parse( fn, "functionCode" ), "}" );
2070 array: array,
2071 nodelist: array,
2072 "arguments": array,
2073 object: function( map, stack ) {
2074 var keys, key, val, i, nonEnumerableProperties,
2075 ret = [];
2077 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
2078 return "[object Object]";
2081 dump.up();
2082 keys = [];
2083 for ( key in map ) {
2084 keys.push( key );
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 );
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 ) );
2102 dump.down();
2103 return join( "{", ret, "}" );
2105 node: function( node ) {
2106 var len, i, val,
2107 open = dump.HTML ? "&lt;" : "<",
2108 close = dump.HTML ? "&gt;" : ">",
2109 tag = node.nodeName.toLowerCase(),
2110 ret = open + tag,
2111 attrs = node.attributes;
2113 if ( attrs ) {
2114 for ( i = 0, len = attrs.length; i < len; i++ ) {
2115 val = attrs[ i ].nodeValue;
2117 // IE6 includes all attributes in .attributes, even ones not explicitly
2118 // set. Those have values like undefined, null, 0, false, "" or
2119 // "inherit".
2120 if ( val && val !== "inherit" ) {
2121 ret += " " + attrs[ i ].nodeName + "=" +
2122 dump.parse( val, "attribute" );
2126 ret += close;
2128 // Show content of TextNode or CDATASection
2129 if ( node.nodeType === 3 || node.nodeType === 4 ) {
2130 ret += node.nodeValue;
2133 return ret + open + "/" + tag + close;
2136 // Function calls it internally, it's the arguments part of the function
2137 functionArgs: function( fn ) {
2138 var args,
2139 l = fn.length;
2141 if ( !l ) {
2142 return "";
2145 args = new Array( l );
2146 while ( l-- ) {
2148 // 97 is 'a'
2149 args[ l ] = String.fromCharCode( 97 + l );
2151 return " " + args.join( ", " ) + " ";
2154 // Object calls it internally, the key part of an item in a map
2155 key: quote,
2157 // Function calls it internally, it's the content of the function
2158 functionCode: "[code]",
2160 // Node calls it internally, it's a html attribute value
2161 attribute: quote,
2162 string: quote,
2163 date: quote,
2164 regexp: literal,
2165 number: literal,
2166 "boolean": literal
2169 // If true, entities are escaped ( <, >, \t, space and \n )
2170 HTML: false,
2172 // Indentation unit
2173 indentChar: " ",
2175 // If true, items in a collection, are separated by a \n, else just a space.
2176 multiline: true
2179 return dump;
2180 }() );
2182 // Back compat
2183 QUnit.jsDump = QUnit.dump;
2185 // Deprecated
2186 // Extend assert methods to QUnit for Backwards compatibility
2187 ( function() {
2188 var i,
2189 assertions = Assert.prototype;
2191 function applyCurrent( current ) {
2192 return function() {
2193 var assert = new Assert( QUnit.config.current );
2194 current.apply( assert, arguments );
2198 for ( i in assertions ) {
2199 QUnit[ i ] = applyCurrent( assertions[ i ] );
2201 }() );
2203 // For browser, export only select globals
2204 if ( defined.document ) {
2206 ( function() {
2207 var i, l,
2208 keys = [
2209 "test",
2210 "module",
2211 "expect",
2212 "asyncTest",
2213 "start",
2214 "stop",
2215 "ok",
2216 "notOk",
2217 "equal",
2218 "notEqual",
2219 "propEqual",
2220 "notPropEqual",
2221 "deepEqual",
2222 "notDeepEqual",
2223 "strictEqual",
2224 "notStrictEqual",
2225 "throws",
2226 "raises"
2229 for ( i = 0, l = keys.length; i < l; i++ ) {
2230 window[ keys[ i ] ] = QUnit[ keys[ i ] ];
2232 }() );
2234 window.QUnit = QUnit;
2237 // For nodejs
2238 if ( typeof module !== "undefined" && module && module.exports ) {
2239 module.exports = QUnit;
2241 // For consistency with CommonJS environments' exports
2242 module.exports.QUnit = QUnit;
2245 // For CommonJS with exports, but without module.exports, like Rhino
2246 if ( typeof exports !== "undefined" && exports ) {
2247 exports.QUnit = QUnit;
2250 if ( typeof define === "function" && define.amd ) {
2251 define( function() {
2252 return QUnit;
2253 } );
2254 QUnit.config.autostart = false;
2257 // Get a reference to the global object, like window in browsers
2258 }( ( function() {
2259 return this;
2260 }() ) ) );
2262 ( function() {
2264 // Only interact with URLs via window.location
2265 var location = typeof window !== "undefined" && window.location;
2266 if ( !location ) {
2267 return;
2270 var urlParams = getUrlParams();
2272 QUnit.urlParams = urlParams;
2274 // Match module/test by inclusion in an array
2275 QUnit.config.moduleId = [].concat( urlParams.moduleId || [] );
2276 QUnit.config.testId = [].concat( urlParams.testId || [] );
2278 // Exact case-insensitive match of the module name
2279 QUnit.config.module = urlParams.module;
2281 // Regular expression or case-insenstive substring match against "moduleName: testName"
2282 QUnit.config.filter = urlParams.filter;
2284 // Test order randomization
2285 if ( urlParams.seed === true ) {
2287 // Generate a random seed if the option is specified without a value
2288 QUnit.config.seed = Math.random().toString( 36 ).slice( 2 );
2289 } else if ( urlParams.seed ) {
2290 QUnit.config.seed = urlParams.seed;
2293 // Add URL-parameter-mapped config values with UI form rendering data
2294 QUnit.config.urlConfig.push(
2296 id: "hidepassed",
2297 label: "Hide passed tests",
2298 tooltip: "Only show tests and assertions that fail. Stored as query-strings."
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."
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."
2314 QUnit.begin( function() {
2315 var i, option,
2316 urlConfig = QUnit.config.urlConfig;
2318 for ( i = 0; i < urlConfig.length; i++ ) {
2320 // Options can be either strings or objects with nonempty "id" properties
2321 option = QUnit.config.urlConfig[ i ];
2322 if ( typeof option !== "string" ) {
2323 option = option.id;
2326 if ( QUnit.config[ option ] === undefined ) {
2327 QUnit.config[ option ] = urlParams[ option ];
2330 } );
2332 function getUrlParams() {
2333 var i, param, name, value;
2334 var urlParams = {};
2335 var params = location.search.slice( 1 ).split( "&" );
2336 var length = params.length;
2338 for ( i = 0; i < length; i++ ) {
2339 if ( params[ i ] ) {
2340 param = params[ i ].split( "=" );
2341 name = decodeURIComponent( param[ 0 ] );
2343 // Allow just a key to turn on a flag, e.g., test.html?noglobals
2344 value = param.length === 1 ||
2345 decodeURIComponent( param.slice( 1 ).join( "=" ) ) ;
2346 if ( urlParams[ name ] ) {
2347 urlParams[ name ] = [].concat( urlParams[ name ], value );
2348 } else {
2349 urlParams[ name ] = value;
2354 return urlParams;
2357 // Don't load the HTML Reporter on non-browser environments
2358 if ( typeof window === "undefined" || !window.document ) {
2359 return;
2362 // Deprecated QUnit.init - Ref #530
2363 // Re-initialize the configuration options
2364 QUnit.init = function() {
2365 var config = QUnit.config;
2367 config.stats = { all: 0, bad: 0 };
2368 config.moduleStats = { all: 0, bad: 0 };
2369 config.started = 0;
2370 config.updateRate = 1000;
2371 config.blocking = false;
2372 config.autostart = true;
2373 config.autorun = false;
2374 config.filter = "";
2375 config.queue = [];
2377 appendInterface();
2380 var config = QUnit.config,
2381 document = window.document,
2382 collapseNext = false,
2383 hasOwn = Object.prototype.hasOwnProperty,
2384 unfilteredUrl = setUrl( { filter: undefined, module: undefined,
2385 moduleId: undefined, testId: undefined } ),
2386 defined = {
2387 sessionStorage: ( function() {
2388 var x = "qunit-test-string";
2389 try {
2390 sessionStorage.setItem( x, x );
2391 sessionStorage.removeItem( x );
2392 return true;
2393 } catch ( e ) {
2394 return false;
2396 }() )
2398 modulesList = [];
2401 * Escape text for attribute or text content.
2403 function escapeText( s ) {
2404 if ( !s ) {
2405 return "";
2407 s = s + "";
2409 // Both single quotes and double quotes (for attributes)
2410 return s.replace( /['"<>&]/g, function( s ) {
2411 switch ( s ) {
2412 case "'":
2413 return "&#039;";
2414 case "\"":
2415 return "&quot;";
2416 case "<":
2417 return "&lt;";
2418 case ">":
2419 return "&gt;";
2420 case "&":
2421 return "&amp;";
2423 } );
2427 * @param {HTMLElement} elem
2428 * @param {string} type
2429 * @param {Function} fn
2431 function addEvent( elem, type, fn ) {
2432 if ( elem.addEventListener ) {
2434 // Standards-based browsers
2435 elem.addEventListener( type, fn, false );
2436 } else if ( elem.attachEvent ) {
2438 // Support: IE <9
2439 elem.attachEvent( "on" + type, function() {
2440 var event = window.event;
2441 if ( !event.target ) {
2442 event.target = event.srcElement || document;
2445 fn.call( elem, event );
2446 } );
2451 * @param {Array|NodeList} elems
2452 * @param {string} type
2453 * @param {Function} fn
2455 function addEvents( elems, type, fn ) {
2456 var i = elems.length;
2457 while ( i-- ) {
2458 addEvent( elems[ i ], type, fn );
2462 function hasClass( elem, name ) {
2463 return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
2466 function addClass( elem, name ) {
2467 if ( !hasClass( elem, name ) ) {
2468 elem.className += ( elem.className ? " " : "" ) + name;
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 );
2480 function removeClass( elem, name ) {
2481 var set = " " + elem.className + " ";
2483 // Class name may appear multiple times
2484 while ( set.indexOf( " " + name + " " ) >= 0 ) {
2485 set = set.replace( " " + name + " ", " " );
2488 // Trim for prettiness
2489 elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
2492 function id( name ) {
2493 return document.getElementById && document.getElementById( name );
2496 function getUrlConfigHtml() {
2497 var i, j, val,
2498 escaped, escapedTooltip,
2499 selection = false,
2500 urlConfig = config.urlConfig,
2501 urlConfigHtml = "";
2503 for ( i = 0; i < urlConfig.length; i++ ) {
2505 // Options can be either strings or objects with nonempty "id" properties
2506 val = config.urlConfig[ i ];
2507 if ( typeof val === "string" ) {
2508 val = {
2509 id: val,
2510 label: val
2514 escaped = escapeText( val.id );
2515 escapedTooltip = escapeText( val.tooltip );
2517 if ( !val.value || typeof val.value === "string" ) {
2518 urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
2519 "' name='" + escaped + "' type='checkbox'" +
2520 ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
2521 ( config[ val.id ] ? " checked='checked'" : "" ) +
2522 " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
2523 "' title='" + escapedTooltip + "'>" + val.label + "</label>";
2524 } else {
2525 urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
2526 "' title='" + escapedTooltip + "'>" + val.label +
2527 ": </label><select id='qunit-urlconfig-" + escaped +
2528 "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
2530 if ( QUnit.is( "array", val.value ) ) {
2531 for ( j = 0; j < val.value.length; j++ ) {
2532 escaped = escapeText( val.value[ j ] );
2533 urlConfigHtml += "<option value='" + escaped + "'" +
2534 ( config[ val.id ] === val.value[ j ] ?
2535 ( selection = true ) && " selected='selected'" : "" ) +
2536 ">" + escaped + "</option>";
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>";
2548 if ( config[ val.id ] && !selection ) {
2549 escaped = escapeText( config[ val.id ] );
2550 urlConfigHtml += "<option value='" + escaped +
2551 "' selected='selected' disabled='disabled'>" + escaped + "</option>";
2553 urlConfigHtml += "</select>";
2557 return urlConfigHtml;
2560 // Handle "click" events on toolbar checkboxes and "change" for select menus.
2561 // Updates the URL with the new state of `config.urlConfig` values.
2562 function toolbarChanged() {
2563 var updatedUrl, value, tests,
2564 field = this,
2565 params = {};
2567 // Detect if field is a select menu or a checkbox
2568 if ( "selectedIndex" in field ) {
2569 value = field.options[ field.selectedIndex ].value || undefined;
2570 } else {
2571 value = field.checked ? ( field.defaultValue || true ) : undefined;
2574 params[ field.name ] = value;
2575 updatedUrl = setUrl( params );
2577 // Check if we can apply the change without a page refresh
2578 if ( "hidepassed" === field.name && "replaceState" in window.history ) {
2579 QUnit.urlParams[ field.name ] = value;
2580 config[ field.name ] = value || false;
2581 tests = id( "qunit-tests" );
2582 if ( tests ) {
2583 toggleClass( tests, "hidepass", value || false );
2585 window.history.replaceState( null, "", updatedUrl );
2586 } else {
2587 window.location = updatedUrl;
2591 function setUrl( params ) {
2592 var key, arrValue, i,
2593 querystring = "?",
2594 location = window.location;
2596 params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
2598 for ( key in params ) {
2600 // Skip inherited or undefined properties
2601 if ( hasOwn.call( params, key ) && params[ key ] !== undefined ) {
2603 // Output a parameter for each value of this key (but usually just one)
2604 arrValue = [].concat( params[ key ] );
2605 for ( i = 0; i < arrValue.length; i++ ) {
2606 querystring += encodeURIComponent( key );
2607 if ( arrValue[ i ] !== true ) {
2608 querystring += "=" + encodeURIComponent( arrValue[ i ] );
2610 querystring += "&";
2614 return location.protocol + "//" + location.host +
2615 location.pathname + querystring.slice( 0, -1 );
2618 function applyUrlParams() {
2619 var selectedModule,
2620 modulesList = id( "qunit-modulefilter" ),
2621 filter = id( "qunit-filter-input" ).value;
2623 selectedModule = modulesList ?
2624 decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) :
2625 undefined;
2627 window.location = setUrl( {
2628 module: ( selectedModule === "" ) ? undefined : selectedModule,
2629 filter: ( filter === "" ) ? undefined : filter,
2631 // Remove moduleId and testId filters
2632 moduleId: undefined,
2633 testId: undefined
2634 } );
2637 function toolbarUrlConfigContainer() {
2638 var urlConfigContainer = document.createElement( "span" );
2640 urlConfigContainer.innerHTML = getUrlConfigHtml();
2641 addClass( urlConfigContainer, "qunit-url-config" );
2643 // For oldIE support:
2644 // * Add handlers to the individual elements instead of the container
2645 // * Use "click" instead of "change" for checkboxes
2646 addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
2647 addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
2649 return urlConfigContainer;
2652 function toolbarLooseFilter() {
2653 var filter = document.createElement( "form" ),
2654 label = document.createElement( "label" ),
2655 input = document.createElement( "input" ),
2656 button = document.createElement( "button" );
2658 addClass( filter, "qunit-filter" );
2660 label.innerHTML = "Filter: ";
2662 input.type = "text";
2663 input.value = config.filter || "";
2664 input.name = "filter";
2665 input.id = "qunit-filter-input";
2667 button.innerHTML = "Go";
2669 label.appendChild( input );
2671 filter.appendChild( label );
2672 filter.appendChild( button );
2673 addEvent( filter, "submit", function( ev ) {
2674 applyUrlParams();
2676 if ( ev && ev.preventDefault ) {
2677 ev.preventDefault();
2680 return false;
2681 } );
2683 return filter;
2686 function toolbarModuleFilterHtml() {
2687 var i,
2688 moduleFilterHtml = "";
2690 if ( !modulesList.length ) {
2691 return false;
2694 moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
2695 "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
2696 ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
2697 ">< All Modules ></option>";
2699 for ( i = 0; i < modulesList.length; i++ ) {
2700 moduleFilterHtml += "<option value='" +
2701 escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
2702 ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
2703 ">" + escapeText( modulesList[ i ] ) + "</option>";
2705 moduleFilterHtml += "</select>";
2707 return moduleFilterHtml;
2710 function toolbarModuleFilter() {
2711 var toolbar = id( "qunit-testrunner-toolbar" ),
2712 moduleFilter = document.createElement( "span" ),
2713 moduleFilterHtml = toolbarModuleFilterHtml();
2715 if ( !toolbar || !moduleFilterHtml ) {
2716 return false;
2719 moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
2720 moduleFilter.innerHTML = moduleFilterHtml;
2722 addEvent( moduleFilter.lastChild, "change", applyUrlParams );
2724 toolbar.appendChild( moduleFilter );
2727 function appendToolbar() {
2728 var toolbar = id( "qunit-testrunner-toolbar" );
2730 if ( toolbar ) {
2731 toolbar.appendChild( toolbarUrlConfigContainer() );
2732 toolbar.appendChild( toolbarLooseFilter() );
2733 toolbarModuleFilter();
2737 function appendHeader() {
2738 var header = id( "qunit-header" );
2740 if ( header ) {
2741 header.innerHTML = "<a href='" + escapeText( unfilteredUrl ) + "'>" + header.innerHTML +
2742 "</a> ";
2746 function appendBanner() {
2747 var banner = id( "qunit-banner" );
2749 if ( banner ) {
2750 banner.className = "";
2754 function appendTestResults() {
2755 var tests = id( "qunit-tests" ),
2756 result = id( "qunit-testresult" );
2758 if ( result ) {
2759 result.parentNode.removeChild( result );
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;";
2772 function storeFixture() {
2773 var fixture = id( "qunit-fixture" );
2774 if ( fixture ) {
2775 config.fixture = fixture.innerHTML;
2779 function appendFilteredTest() {
2780 var testId = QUnit.config.testId;
2781 if ( !testId || testId.length <= 0 ) {
2782 return "";
2784 return "<div id='qunit-filteredTest'>Rerunning selected tests: " +
2785 escapeText( testId.join( ", " ) ) +
2786 " <a id='qunit-clearFilter' href='" +
2787 escapeText( unfilteredUrl ) +
2788 "'>Run all tests</a></div>";
2791 function appendUserAgent() {
2792 var userAgent = id( "qunit-userAgent" );
2794 if ( userAgent ) {
2795 userAgent.innerHTML = "";
2796 userAgent.appendChild(
2797 document.createTextNode(
2798 "QUnit " + QUnit.version + "; " + navigator.userAgent
2804 function appendInterface() {
2805 var qunit = id( "qunit" );
2807 if ( qunit ) {
2808 qunit.innerHTML =
2809 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
2810 "<h2 id='qunit-banner'></h2>" +
2811 "<div id='qunit-testrunner-toolbar'></div>" +
2812 appendFilteredTest() +
2813 "<h2 id='qunit-userAgent'></h2>" +
2814 "<ol id='qunit-tests'></ol>";
2817 appendHeader();
2818 appendBanner();
2819 appendTestResults();
2820 appendUserAgent();
2821 appendToolbar();
2824 function appendTestsList( modules ) {
2825 var i, l, x, z, test, moduleObj;
2827 for ( i = 0, l = modules.length; i < l; i++ ) {
2828 moduleObj = modules[ i ];
2830 for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
2831 test = moduleObj.tests[ x ];
2833 appendTest( test.name, test.testId, moduleObj.name );
2838 function appendTest( name, testId, moduleName ) {
2839 var title, rerunTrigger, testBlock, assertList,
2840 tests = id( "qunit-tests" );
2842 if ( !tests ) {
2843 return;
2846 title = document.createElement( "strong" );
2847 title.innerHTML = getNameHtml( name, moduleName );
2849 rerunTrigger = document.createElement( "a" );
2850 rerunTrigger.innerHTML = "Rerun";
2851 rerunTrigger.href = setUrl( { testId: testId } );
2853 testBlock = document.createElement( "li" );
2854 testBlock.appendChild( title );
2855 testBlock.appendChild( rerunTrigger );
2856 testBlock.id = "qunit-test-output-" + testId;
2858 assertList = document.createElement( "ol" );
2859 assertList.className = "qunit-assert-list";
2861 testBlock.appendChild( assertList );
2863 tests.appendChild( testBlock );
2866 // HTML Reporter initialization and load
2867 QUnit.begin( function( details ) {
2868 var i, moduleObj, tests;
2870 // Sort modules by name for the picker
2871 for ( i = 0; i < details.modules.length; i++ ) {
2872 moduleObj = details.modules[ i ];
2873 if ( moduleObj.name ) {
2874 modulesList.push( moduleObj.name );
2877 modulesList.sort( function( a, b ) {
2878 return a.localeCompare( b );
2879 } );
2881 // Capture fixture HTML from the page
2882 storeFixture();
2884 // Initialize QUnit elements
2885 appendInterface();
2886 appendTestsList( details.modules );
2887 tests = id( "qunit-tests" );
2888 if ( tests && config.hidepassed ) {
2889 addClass( tests, "hidepass" );
2891 } );
2893 QUnit.done( function( details ) {
2894 var i, key,
2895 banner = id( "qunit-banner" ),
2896 tests = id( "qunit-tests" ),
2897 html = [
2898 "Tests completed in ",
2899 details.runtime,
2900 " milliseconds.<br />",
2901 "<span class='passed'>",
2902 details.passed,
2903 "</span> assertions of <span class='total'>",
2904 details.total,
2905 "</span> passed, <span class='failed'>",
2906 details.failed,
2907 "</span> failed."
2908 ].join( "" );
2910 if ( banner ) {
2911 banner.className = details.failed ? "qunit-fail" : "qunit-pass";
2914 if ( tests ) {
2915 id( "qunit-testresult" ).innerHTML = html;
2918 if ( config.altertitle && document.title ) {
2920 // Show ✖ for good, ✔ for bad suite result in title
2921 // use escape sequences in case file gets loaded with non-utf-8-charset
2922 document.title = [
2923 ( details.failed ? "\u2716" : "\u2714" ),
2924 document.title.replace( /^[\u2714\u2716] /i, "" )
2925 ].join( " " );
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 );
2938 // Scroll back to top to show results
2939 if ( config.scrolltop && window.scrollTo ) {
2940 window.scrollTo( 0, 0 );
2942 } );
2944 function getNameHtml( name, module ) {
2945 var nameHtml = "";
2947 if ( module ) {
2948 nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
2951 nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
2953 return nameHtml;
2956 QUnit.testStart( function( details ) {
2957 var running, testBlock, bad;
2959 testBlock = id( "qunit-test-output-" + details.testId );
2960 if ( testBlock ) {
2961 testBlock.className = "running";
2962 } else {
2964 // Report later registered tests
2965 appendTest( details.name, details.testId, details.module );
2968 running = id( "qunit-testresult" );
2969 if ( running ) {
2970 bad = QUnit.config.reorder && defined.sessionStorage &&
2971 +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name );
2973 running.innerHTML = ( bad ?
2974 "Rerunning previously failed test: <br />" :
2975 "Running: <br />" ) +
2976 getNameHtml( details.name, details.module );
2979 } );
2981 function stripHtml( string ) {
2983 // Strip tags, html entity and whitespaces
2984 return string.replace( /<\/?[^>]+(>|$)/g, "" ).replace( /\&quot;/g, "" ).replace( /\s+/g, "" );
2987 QUnit.log( function( details ) {
2988 var assertList, assertLi,
2989 message, expected, actual, diff,
2990 showDiff = false,
2991 testItem = id( "qunit-test-output-" + details.testId );
2993 if ( !testItem ) {
2994 return;
2997 message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
2998 message = "<span class='test-message'>" + message + "</span>";
2999 message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
3001 // The pushFailure doesn't provide details.expected
3002 // when it calls, it's implicit to also not show expected and diff stuff
3003 // Also, we need to check details.expected existence, as it can exist and be undefined
3004 if ( !details.result && hasOwn.call( details, "expected" ) ) {
3005 if ( details.negative ) {
3006 expected = "NOT " + QUnit.dump.parse( details.expected );
3007 } else {
3008 expected = QUnit.dump.parse( details.expected );
3011 actual = QUnit.dump.parse( details.actual );
3012 message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
3013 escapeText( expected ) +
3014 "</pre></td></tr>";
3016 if ( actual !== expected ) {
3018 message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
3019 escapeText( actual ) + "</pre></td></tr>";
3021 // Don't show diff if actual or expected are booleans
3022 if ( !( /^(true|false)$/.test( actual ) ) &&
3023 !( /^(true|false)$/.test( expected ) ) ) {
3024 diff = QUnit.diff( expected, actual );
3025 showDiff = stripHtml( diff ).length !==
3026 stripHtml( expected ).length +
3027 stripHtml( actual ).length;
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>";
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>";
3049 if ( details.source ) {
3050 message += "<tr class='test-source'><th>Source: </th><td><pre>" +
3051 escapeText( details.source ) + "</pre></td></tr>";
3054 message += "</table>";
3056 // This occurs when pushFailure is set and we have an extracted stack trace
3057 } else if ( !details.result && details.source ) {
3058 message += "<table>" +
3059 "<tr class='test-source'><th>Source: </th><td><pre>" +
3060 escapeText( details.source ) + "</pre></td></tr>" +
3061 "</table>";
3064 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
3066 assertLi = document.createElement( "li" );
3067 assertLi.className = details.result ? "pass" : "fail";
3068 assertLi.innerHTML = message;
3069 assertList.appendChild( assertLi );
3070 } );
3072 QUnit.testDone( function( details ) {
3073 var testTitle, time, testItem, assertList,
3074 good, bad, testCounts, skipped, sourceName,
3075 tests = id( "qunit-tests" );
3077 if ( !tests ) {
3078 return;
3081 testItem = id( "qunit-test-output-" + details.testId );
3083 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
3085 good = details.passed;
3086 bad = details.failed;
3088 // Store result when possible
3089 if ( config.reorder && defined.sessionStorage ) {
3090 if ( bad ) {
3091 sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
3092 } else {
3093 sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
3097 if ( bad === 0 ) {
3099 // Collapse the passing tests
3100 addClass( assertList, "qunit-collapsed" );
3101 } else if ( bad && config.collapse && !collapseNext ) {
3103 // Skip collapsing the first failing test
3104 collapseNext = true;
3105 } else {
3107 // Collapse remaining tests
3108 addClass( assertList, "qunit-collapsed" );
3111 // The testItem.firstChild is the test name
3112 testTitle = testItem.firstChild;
3114 testCounts = bad ?
3115 "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
3118 testTitle.innerHTML += " <b class='counts'>(" + testCounts +
3119 details.assertions.length + ")</b>";
3121 if ( details.skipped ) {
3122 testItem.className = "skipped";
3123 skipped = document.createElement( "em" );
3124 skipped.className = "qunit-skipped-label";
3125 skipped.innerHTML = "skipped";
3126 testItem.insertBefore( skipped, testTitle );
3127 } else {
3128 addEvent( testTitle, "click", function() {
3129 toggleClass( assertList, "qunit-collapsed" );
3130 } );
3132 testItem.className = bad ? "fail" : "pass";
3134 time = document.createElement( "span" );
3135 time.className = "runtime";
3136 time.innerHTML = details.runtime + " ms";
3137 testItem.insertBefore( time, assertList );
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" );
3148 addEvent( testTitle, "click", function() {
3149 toggleClass( sourceName, "qunit-collapsed" );
3150 } );
3151 testItem.appendChild( sourceName );
3153 } );
3155 // Avoid readyState issue with phantomjs
3156 // Ref: #818
3157 var notPhantom = ( function( p ) {
3158 return !( p && p.version && p.version.major > 0 );
3159 } )( window.phantom );
3161 if ( notPhantom && document.readyState === "complete" ) {
3162 QUnit.load();
3163 } else {
3164 addEvent( window, "load", QUnit.load );
3168 * This file is a modified version of google-diff-match-patch's JavaScript implementation
3169 * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
3170 * modifications are licensed as more fully set forth in LICENSE.txt.
3172 * The original source of google-diff-match-patch is attributable and licensed as follows:
3174 * Copyright 2006 Google Inc.
3175 * https://code.google.com/p/google-diff-match-patch/
3177 * Licensed under the Apache License, Version 2.0 (the "License");
3178 * you may not use this file except in compliance with the License.
3179 * You may obtain a copy of the License at
3181 * https://www.apache.org/licenses/LICENSE-2.0
3183 * Unless required by applicable law or agreed to in writing, software
3184 * distributed under the License is distributed on an "AS IS" BASIS,
3185 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3186 * See the License for the specific language governing permissions and
3187 * limitations under the License.
3189 * More Info:
3190 * https://code.google.com/p/google-diff-match-patch/
3192 * Usage: QUnit.diff(expected, actual)
3195 QUnit.diff = ( function() {
3196 function DiffMatchPatch() {
3199 // DIFF FUNCTIONS
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.'
3206 var DIFF_DELETE = -1,
3207 DIFF_INSERT = 1,
3208 DIFF_EQUAL = 0;
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.
3220 DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) {
3221 var deadline, checklines, commonlength,
3222 commonprefix, commonsuffix, diffs;
3224 // The diff must be complete in up to 1 second.
3225 deadline = ( new Date() ).getTime() + 1000;
3227 // Check for null inputs.
3228 if ( text1 === null || text2 === null ) {
3229 throw new Error( "Null input. (DiffMain)" );
3232 // Check for equality (speedup).
3233 if ( text1 === text2 ) {
3234 if ( text1 ) {
3235 return [
3236 [ DIFF_EQUAL, text1 ]
3239 return [];
3242 if ( typeof optChecklines === "undefined" ) {
3243 optChecklines = true;
3246 checklines = optChecklines;
3248 // Trim off common prefix (speedup).
3249 commonlength = this.diffCommonPrefix( text1, text2 );
3250 commonprefix = text1.substring( 0, commonlength );
3251 text1 = text1.substring( commonlength );
3252 text2 = text2.substring( commonlength );
3254 // Trim off common suffix (speedup).
3255 commonlength = this.diffCommonSuffix( text1, text2 );
3256 commonsuffix = text1.substring( text1.length - commonlength );
3257 text1 = text1.substring( 0, text1.length - commonlength );
3258 text2 = text2.substring( 0, text2.length - commonlength );
3260 // Compute the diff on the middle block.
3261 diffs = this.diffCompute( text1, text2, checklines, deadline );
3263 // Restore the prefix and suffix.
3264 if ( commonprefix ) {
3265 diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
3267 if ( commonsuffix ) {
3268 diffs.push( [ DIFF_EQUAL, commonsuffix ] );
3270 this.diffCleanupMerge( diffs );
3271 return diffs;
3275 * Reduce the number of edits by eliminating operationally trivial equalities.
3276 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
3278 DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
3279 var changes, equalities, equalitiesLength, lastequality,
3280 pointer, preIns, preDel, postIns, postDel;
3281 changes = false;
3282 equalities = []; // Stack of indices where equalities are found.
3283 equalitiesLength = 0; // Keeping our own length var is faster in JS.
3284 /** @type {?string} */
3285 lastequality = null;
3287 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
3288 pointer = 0; // Index of current position.
3290 // Is there an insertion operation before the last equality.
3291 preIns = false;
3293 // Is there a deletion operation before the last equality.
3294 preDel = false;
3296 // Is there an insertion operation after the last equality.
3297 postIns = false;
3299 // Is there a deletion operation after the last equality.
3300 postDel = false;
3301 while ( pointer < diffs.length ) {
3303 // Equality found.
3304 if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) {
3305 if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) {
3307 // Candidate found.
3308 equalities[ equalitiesLength++ ] = pointer;
3309 preIns = postIns;
3310 preDel = postDel;
3311 lastequality = diffs[ pointer ][ 1 ];
3312 } else {
3314 // Not a candidate, and can never become one.
3315 equalitiesLength = 0;
3316 lastequality = null;
3318 postIns = postDel = false;
3320 // An insertion or deletion.
3321 } else {
3323 if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
3324 postDel = true;
3325 } else {
3326 postIns = true;
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>
3337 if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
3338 ( ( lastequality.length < 2 ) &&
3339 ( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
3341 // Duplicate record.
3342 diffs.splice(
3343 equalities[ equalitiesLength - 1 ],
3345 [ DIFF_DELETE, lastequality ]
3348 // Change second copy to insert.
3349 diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
3350 equalitiesLength--; // Throw away the equality we just deleted;
3351 lastequality = null;
3352 if ( preIns && preDel ) {
3354 // No changes made which could affect previous entry, keep going.
3355 postIns = postDel = true;
3356 equalitiesLength = 0;
3357 } else {
3358 equalitiesLength--; // Throw away the previous equality.
3359 pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
3360 postIns = postDel = false;
3362 changes = true;
3365 pointer++;
3368 if ( changes ) {
3369 this.diffCleanupMerge( diffs );
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.
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;
3397 return html.join( "" );
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.
3407 DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
3408 var pointermid, pointermax, pointermin, pointerstart;
3410 // Quick check for common null cases.
3411 if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) {
3412 return 0;
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;
3429 pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
3431 return pointermid;
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.
3440 DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
3441 var pointermid, pointermax, pointermin, pointerend;
3443 // Quick check for common null cases.
3444 if ( !text1 ||
3445 !text2 ||
3446 text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) {
3447 return 0;
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;
3464 pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
3466 return pointermid;
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
3481 DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
3482 var diffs, longtext, shorttext, i, hm,
3483 text1A, text2A, text1B, text2B,
3484 midCommon, diffsA, diffsB;
3486 if ( !text1 ) {
3488 // Just add some text (speedup).
3489 return [
3490 [ DIFF_INSERT, text2 ]
3494 if ( !text2 ) {
3496 // Just delete some text (speedup).
3497 return [
3498 [ DIFF_DELETE, text1 ]
3502 longtext = text1.length > text2.length ? text1 : text2;
3503 shorttext = text1.length > text2.length ? text2 : text1;
3504 i = longtext.indexOf( shorttext );
3505 if ( i !== -1 ) {
3507 // Shorter text is inside the longer text (speedup).
3508 diffs = [
3509 [ DIFF_INSERT, longtext.substring( 0, i ) ],
3510 [ DIFF_EQUAL, shorttext ],
3511 [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
3514 // Swap insertions for deletions if diff is reversed.
3515 if ( text1.length > text2.length ) {
3516 diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE;
3518 return diffs;
3521 if ( shorttext.length === 1 ) {
3523 // Single character string.
3524 // After the previous speedup, the character can't be an equality.
3525 return [
3526 [ DIFF_DELETE, text1 ],
3527 [ DIFF_INSERT, text2 ]
3531 // Check to see if the problem can be split in two.
3532 hm = this.diffHalfMatch( text1, text2 );
3533 if ( hm ) {
3535 // A half-match was found, sort out the return data.
3536 text1A = hm[ 0 ];
3537 text1B = hm[ 1 ];
3538 text2A = hm[ 2 ];
3539 text2B = hm[ 3 ];
3540 midCommon = hm[ 4 ];
3542 // Send both pairs off for separate processing.
3543 diffsA = this.DiffMain( text1A, text2A, checklines, deadline );
3544 diffsB = this.DiffMain( text1B, text2B, checklines, deadline );
3546 // Merge the results.
3547 return diffsA.concat( [
3548 [ DIFF_EQUAL, midCommon ]
3549 ], diffsB );
3552 if ( checklines && text1.length > 100 && text2.length > 100 ) {
3553 return this.diffLineMode( text1, text2, deadline );
3556 return this.diffBisect( text1, text2, deadline );
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
3570 DiffMatchPatch.prototype.diffHalfMatch = function( text1, text2 ) {
3571 var longtext, shorttext, dmp,
3572 text1A, text2B, text2A, text1B, midCommon,
3573 hm1, hm2, hm;
3575 longtext = text1.length > text2.length ? text1 : text2;
3576 shorttext = text1.length > text2.length ? text2 : text1;
3577 if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) {
3578 return null; // Pointless.
3580 dmp = this; // 'this' becomes 'window' in a closure.
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
3594 function diffHalfMatchI( longtext, shorttext, i ) {
3595 var seed, j, bestCommon, prefixLength, suffixLength,
3596 bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
3598 // Start with a 1/4 length substring at position i as a seed.
3599 seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) );
3600 j = -1;
3601 bestCommon = "";
3602 while ( ( j = shorttext.indexOf( seed, j + 1 ) ) !== -1 ) {
3603 prefixLength = dmp.diffCommonPrefix( longtext.substring( i ),
3604 shorttext.substring( j ) );
3605 suffixLength = dmp.diffCommonSuffix( longtext.substring( 0, i ),
3606 shorttext.substring( 0, j ) );
3607 if ( bestCommon.length < suffixLength + prefixLength ) {
3608 bestCommon = shorttext.substring( j - suffixLength, j ) +
3609 shorttext.substring( j, j + prefixLength );
3610 bestLongtextA = longtext.substring( 0, i - suffixLength );
3611 bestLongtextB = longtext.substring( i + prefixLength );
3612 bestShorttextA = shorttext.substring( 0, j - suffixLength );
3613 bestShorttextB = shorttext.substring( j + prefixLength );
3616 if ( bestCommon.length * 2 >= longtext.length ) {
3617 return [ bestLongtextA, bestLongtextB,
3618 bestShorttextA, bestShorttextB, bestCommon
3620 } else {
3621 return null;
3625 // First check if the second quarter is the seed for a half-match.
3626 hm1 = diffHalfMatchI( longtext, shorttext,
3627 Math.ceil( longtext.length / 4 ) );
3629 // Check again based on the third quarter.
3630 hm2 = diffHalfMatchI( longtext, shorttext,
3631 Math.ceil( longtext.length / 2 ) );
3632 if ( !hm1 && !hm2 ) {
3633 return null;
3634 } else if ( !hm2 ) {
3635 hm = hm1;
3636 } else if ( !hm1 ) {
3637 hm = hm2;
3638 } else {
3640 // Both matched. Select the longest.
3641 hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2;
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 ];
3657 midCommon = hm[ 4 ];
3658 return [ text1A, text1B, text2A, text2B, midCommon ];
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
3671 DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) {
3672 var a, diffs, linearray, pointer, countInsert,
3673 countDelete, textInsert, textDelete, j;
3675 // Scan the text on a line-by-line basis first.
3676 a = this.diffLinesToChars( text1, text2 );
3677 text1 = a.chars1;
3678 text2 = a.chars2;
3679 linearray = a.lineArray;
3681 diffs = this.DiffMain( text1, text2, false, deadline );
3683 // Convert the diff back to original text.
3684 this.diffCharsToLines( diffs, linearray );
3686 // Eliminate freak matches (e.g. blank lines)
3687 this.diffCleanupSemantic( diffs );
3689 // Rediff any replacement blocks, this time character-by-character.
3690 // Add a dummy entry at the end.
3691 diffs.push( [ DIFF_EQUAL, "" ] );
3692 pointer = 0;
3693 countDelete = 0;
3694 countInsert = 0;
3695 textDelete = "";
3696 textInsert = "";
3697 while ( pointer < diffs.length ) {
3698 switch ( diffs[ pointer ][ 0 ] ) {
3699 case DIFF_INSERT:
3700 countInsert++;
3701 textInsert += diffs[ pointer ][ 1 ];
3702 break;
3703 case DIFF_DELETE:
3704 countDelete++;
3705 textDelete += diffs[ pointer ][ 1 ];
3706 break;
3707 case DIFF_EQUAL:
3709 // Upon reaching an equality, check for prior redundancies.
3710 if ( countDelete >= 1 && countInsert >= 1 ) {
3712 // Delete the offending records and add the merged ones.
3713 diffs.splice( pointer - countDelete - countInsert,
3714 countDelete + countInsert );
3715 pointer = pointer - countDelete - countInsert;
3716 a = this.DiffMain( textDelete, textInsert, false, deadline );
3717 for ( j = a.length - 1; j >= 0; j-- ) {
3718 diffs.splice( pointer, 0, a[ j ] );
3720 pointer = pointer + a.length;
3722 countInsert = 0;
3723 countDelete = 0;
3724 textDelete = "";
3725 textInsert = "";
3726 break;
3728 pointer++;
3730 diffs.pop(); // Remove the dummy entry at the end.
3732 return diffs;
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
3745 DiffMatchPatch.prototype.diffBisect = function( text1, text2, deadline ) {
3746 var text1Length, text2Length, maxD, vOffset, vLength,
3747 v1, v2, x, delta, front, k1start, k1end, k2start,
3748 k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
3750 // Cache the text lengths to prevent multiple calls.
3751 text1Length = text1.length;
3752 text2Length = text2.length;
3753 maxD = Math.ceil( ( text1Length + text2Length ) / 2 );
3754 vOffset = maxD;
3755 vLength = 2 * maxD;
3756 v1 = new Array( vLength );
3757 v2 = new Array( vLength );
3759 // Setting all elements to -1 is faster in Chrome & Firefox than mixing
3760 // integers and undefined.
3761 for ( x = 0; x < vLength; x++ ) {
3762 v1[ x ] = -1;
3763 v2[ x ] = -1;
3765 v1[ vOffset + 1 ] = 0;
3766 v2[ vOffset + 1 ] = 0;
3767 delta = text1Length - text2Length;
3769 // If the total number of characters is odd, then the front path will collide
3770 // with the reverse path.
3771 front = ( delta % 2 !== 0 );
3773 // Offsets for start and end of k loop.
3774 // Prevents mapping of space beyond the grid.
3775 k1start = 0;
3776 k1end = 0;
3777 k2start = 0;
3778 k2end = 0;
3779 for ( d = 0; d < maxD; d++ ) {
3781 // Bail out if deadline is reached.
3782 if ( ( new Date() ).getTime() > deadline ) {
3783 break;
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;
3794 y1 = x1 - k1;
3795 while ( x1 < text1Length && y1 < text2Length &&
3796 text1.charAt( x1 ) === text2.charAt( y1 ) ) {
3797 x1++;
3798 y1++;
3800 v1[ k1Offset ] = x1;
3801 if ( x1 > text1Length ) {
3803 // Ran off the right of the graph.
3804 k1end += 2;
3805 } else if ( y1 > text2Length ) {
3807 // Ran off the bottom of the graph.
3808 k1start += 2;
3809 } else if ( front ) {
3810 k2Offset = vOffset + delta - k1;
3811 if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) {
3813 // Mirror x2 onto top-left coordinate system.
3814 x2 = text1Length - v2[ k2Offset ];
3815 if ( x1 >= x2 ) {
3817 // Overlap detected.
3818 return this.diffBisectSplit( text1, text2, x1, y1, deadline );
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;
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++;
3839 v2[ k2Offset ] = x2;
3840 if ( x2 > text1Length ) {
3842 // Ran off the left of the graph.
3843 k2end += 2;
3844 } else if ( y2 > text2Length ) {
3846 // Ran off the top of the graph.
3847 k2start += 2;
3848 } else if ( !front ) {
3849 k1Offset = vOffset + delta - k2;
3850 if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) {
3851 x1 = v1[ k1Offset ];
3852 y1 = vOffset + x1 - k1Offset;
3854 // Mirror x2 onto top-left coordinate system.
3855 x2 = text1Length - x2;
3856 if ( x1 >= x2 ) {
3858 // Overlap detected.
3859 return this.diffBisectSplit( text1, text2, x1, y1, deadline );
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 ]
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
3885 DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
3886 var text1a, text1b, text2a, text2b, diffs, diffsb;
3887 text1a = text1.substring( 0, x );
3888 text2a = text2.substring( 0, y );
3889 text1b = text1.substring( x );
3890 text2b = text2.substring( y );
3892 // Compute both diffs serially.
3893 diffs = this.DiffMain( text1a, text2a, false, deadline );
3894 diffsb = this.DiffMain( text1b, text2b, false, deadline );
3896 return diffs.concat( diffsb );
3900 * Reduce the number of edits by eliminating semantically trivial equalities.
3901 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
3903 DiffMatchPatch.prototype.diffCleanupSemantic = function( diffs ) {
3904 var changes, equalities, equalitiesLength, lastequality,
3905 pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
3906 lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
3907 changes = false;
3908 equalities = []; // Stack of indices where equalities are found.
3909 equalitiesLength = 0; // Keeping our own length var is faster in JS.
3910 /** @type {?string} */
3911 lastequality = null;
3913 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
3914 pointer = 0; // Index of current position.
3916 // Number of characters that changed prior to the equality.
3917 lengthInsertions1 = 0;
3918 lengthDeletions1 = 0;
3920 // Number of characters that changed after the equality.
3921 lengthInsertions2 = 0;
3922 lengthDeletions2 = 0;
3923 while ( pointer < diffs.length ) {
3924 if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
3925 equalities[ equalitiesLength++ ] = pointer;
3926 lengthInsertions1 = lengthInsertions2;
3927 lengthDeletions1 = lengthDeletions2;
3928 lengthInsertions2 = 0;
3929 lengthDeletions2 = 0;
3930 lastequality = diffs[ pointer ][ 1 ];
3931 } else { // An insertion or deletion.
3932 if ( diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
3933 lengthInsertions2 += diffs[ pointer ][ 1 ].length;
3934 } else {
3935 lengthDeletions2 += diffs[ pointer ][ 1 ].length;
3938 // Eliminate an equality that is smaller or equal to the edits on both
3939 // sides of it.
3940 if ( lastequality && ( lastequality.length <=
3941 Math.max( lengthInsertions1, lengthDeletions1 ) ) &&
3942 ( lastequality.length <= Math.max( lengthInsertions2,
3943 lengthDeletions2 ) ) ) {
3945 // Duplicate record.
3946 diffs.splice(
3947 equalities[ equalitiesLength - 1 ],
3949 [ DIFF_DELETE, lastequality ]
3952 // Change second copy to insert.
3953 diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
3955 // Throw away the equality we just deleted.
3956 equalitiesLength--;
3958 // Throw away the previous equality (it needs to be reevaluated).
3959 equalitiesLength--;
3960 pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
3962 // Reset the counters.
3963 lengthInsertions1 = 0;
3964 lengthDeletions1 = 0;
3965 lengthInsertions2 = 0;
3966 lengthDeletions2 = 0;
3967 lastequality = null;
3968 changes = true;
3971 pointer++;
3974 // Normalize the diff.
3975 if ( changes ) {
3976 this.diffCleanupMerge( diffs );
3979 // Find any overlaps between deletions and insertions.
3980 // e.g: <del>abcxxx</del><ins>xxxdef</ins>
3981 // -> <del>abc</del>xxx<ins>def</ins>
3982 // e.g: <del>xxxabc</del><ins>defxxx</ins>
3983 // -> <ins>def</ins>xxx<del>abc</del>
3984 // Only extract an overlap if it is as big as the edit ahead or behind it.
3985 pointer = 1;
3986 while ( pointer < diffs.length ) {
3987 if ( diffs[ pointer - 1 ][ 0 ] === DIFF_DELETE &&
3988 diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
3989 deletion = diffs[ pointer - 1 ][ 1 ];
3990 insertion = diffs[ pointer ][ 1 ];
3991 overlapLength1 = this.diffCommonOverlap( deletion, insertion );
3992 overlapLength2 = this.diffCommonOverlap( insertion, deletion );
3993 if ( overlapLength1 >= overlapLength2 ) {
3994 if ( overlapLength1 >= deletion.length / 2 ||
3995 overlapLength1 >= insertion.length / 2 ) {
3997 // Overlap found. Insert an equality and trim the surrounding edits.
3998 diffs.splice(
3999 pointer,
4001 [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ]
4003 diffs[ pointer - 1 ][ 1 ] =
4004 deletion.substring( 0, deletion.length - overlapLength1 );
4005 diffs[ pointer + 1 ][ 1 ] = insertion.substring( overlapLength1 );
4006 pointer++;
4008 } else {
4009 if ( overlapLength2 >= deletion.length / 2 ||
4010 overlapLength2 >= insertion.length / 2 ) {
4012 // Reverse overlap found.
4013 // Insert an equality and swap and trim the surrounding edits.
4014 diffs.splice(
4015 pointer,
4017 [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ]
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++;
4029 pointer++;
4031 pointer++;
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
4043 DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) {
4044 var text1Length, text2Length, textLength,
4045 best, length, pattern, found;
4047 // Cache the text lengths to prevent multiple calls.
4048 text1Length = text1.length;
4049 text2Length = text2.length;
4051 // Eliminate the null case.
4052 if ( text1Length === 0 || text2Length === 0 ) {
4053 return 0;
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 );
4062 textLength = Math.min( text1Length, text2Length );
4064 // Quick check for the worst case.
4065 if ( text1 === text2 ) {
4066 return textLength;
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;
4080 length += found;
4081 if ( found === 0 || text1.substring( textLength - length ) ===
4082 text2.substring( 0, length ) ) {
4083 best = length;
4084 length++;
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
4100 DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) {
4101 var lineArray, lineHash, chars1, chars2;
4102 lineArray = []; // E.g. lineArray[4] === 'Hello\n'
4103 lineHash = {}; // E.g. lineHash['Hello\n'] === 4
4105 // '\x00' is a valid character, but various debuggers don't like it.
4106 // So we'll insert a junk entry to avoid generating a null character.
4107 lineArray[ 0 ] = "";
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
4117 function diffLinesToCharsMunge( text ) {
4118 var chars, lineStart, lineEnd, lineArrayLength, line;
4119 chars = "";
4121 // Walk the text, pulling out a substring for each line.
4122 // text.split('\n') would would temporarily double our memory footprint.
4123 // Modifying text would create many large strings to garbage collect.
4124 lineStart = 0;
4125 lineEnd = -1;
4127 // Keeping our own length variable is faster than looking it up.
4128 lineArrayLength = lineArray.length;
4129 while ( lineEnd < text.length - 1 ) {
4130 lineEnd = text.indexOf( "\n", lineStart );
4131 if ( lineEnd === -1 ) {
4132 lineEnd = text.length - 1;
4134 line = text.substring( lineStart, lineEnd + 1 );
4135 lineStart = lineEnd + 1;
4137 if ( lineHash.hasOwnProperty ? lineHash.hasOwnProperty( line ) :
4138 ( lineHash[ line ] !== undefined ) ) {
4139 chars += String.fromCharCode( lineHash[ line ] );
4140 } else {
4141 chars += String.fromCharCode( lineArrayLength );
4142 lineHash[ line ] = lineArrayLength;
4143 lineArray[ lineArrayLength++ ] = line;
4146 return chars;
4149 chars1 = diffLinesToCharsMunge( text1 );
4150 chars2 = diffLinesToCharsMunge( text2 );
4151 return {
4152 chars1: chars1,
4153 chars2: chars2,
4154 lineArray: lineArray
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
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 ) ];
4173 diffs[ x ][ 1 ] = text.join( "" );
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.
4182 DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) {
4183 var pointer, countDelete, countInsert, textInsert, textDelete,
4184 commonlength, changes, diffPointer, position;
4185 diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
4186 pointer = 0;
4187 countDelete = 0;
4188 countInsert = 0;
4189 textDelete = "";
4190 textInsert = "";
4191 commonlength;
4192 while ( pointer < diffs.length ) {
4193 switch ( diffs[ pointer ][ 0 ] ) {
4194 case DIFF_INSERT:
4195 countInsert++;
4196 textInsert += diffs[ pointer ][ 1 ];
4197 pointer++;
4198 break;
4199 case DIFF_DELETE:
4200 countDelete++;
4201 textDelete += diffs[ pointer ][ 1 ];
4202 pointer++;
4203 break;
4204 case DIFF_EQUAL:
4206 // Upon reaching an equality, check for prior redundancies.
4207 if ( countDelete + countInsert > 1 ) {
4208 if ( countDelete !== 0 && countInsert !== 0 ) {
4210 // Factor out any common prefixes.
4211 commonlength = this.diffCommonPrefix( textInsert, textDelete );
4212 if ( commonlength !== 0 ) {
4213 if ( ( pointer - countDelete - countInsert ) > 0 &&
4214 diffs[ pointer - countDelete - countInsert - 1 ][ 0 ] ===
4215 DIFF_EQUAL ) {
4216 diffs[ pointer - countDelete - countInsert - 1 ][ 1 ] +=
4217 textInsert.substring( 0, commonlength );
4218 } else {
4219 diffs.splice( 0, 0, [ DIFF_EQUAL,
4220 textInsert.substring( 0, commonlength )
4221 ] );
4222 pointer++;
4224 textInsert = textInsert.substring( commonlength );
4225 textDelete = textDelete.substring( commonlength );
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 );
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 ]
4254 pointer = pointer - countDelete - countInsert +
4255 ( countDelete ? 1 : 0 ) + ( countInsert ? 1 : 0 ) + 1;
4256 } else if ( pointer !== 0 && diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL ) {
4258 // Merge this equality with the previous one.
4259 diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ];
4260 diffs.splice( pointer, 1 );
4261 } else {
4262 pointer++;
4264 countInsert = 0;
4265 countDelete = 0;
4266 textDelete = "";
4267 textInsert = "";
4268 break;
4271 if ( diffs[ diffs.length - 1 ][ 1 ] === "" ) {
4272 diffs.pop(); // Remove the dummy entry at the end.
4275 // Second pass: look for single edits surrounded on both sides by equalities
4276 // which can be shifted sideways to eliminate an equality.
4277 // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
4278 changes = false;
4279 pointer = 1;
4281 // Intentionally ignore the first and last element (don't need checking).
4282 while ( pointer < diffs.length - 1 ) {
4283 if ( diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL &&
4284 diffs[ pointer + 1 ][ 0 ] === DIFF_EQUAL ) {
4286 diffPointer = diffs[ pointer ][ 1 ];
4287 position = diffPointer.substring(
4288 diffPointer.length - diffs[ pointer - 1 ][ 1 ].length
4291 // This is a single edit surrounded by equalities.
4292 if ( position === diffs[ pointer - 1 ][ 1 ] ) {
4294 // Shift the edit over the previous equality.
4295 diffs[ pointer ][ 1 ] = diffs[ pointer - 1 ][ 1 ] +
4296 diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer ][ 1 ].length -
4297 diffs[ pointer - 1 ][ 1 ].length );
4298 diffs[ pointer + 1 ][ 1 ] =
4299 diffs[ pointer - 1 ][ 1 ] + diffs[ pointer + 1 ][ 1 ];
4300 diffs.splice( pointer - 1, 1 );
4301 changes = true;
4302 } else if ( diffPointer.substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
4303 diffs[ pointer + 1 ][ 1 ] ) {
4305 // Shift the edit over the next equality.
4306 diffs[ pointer - 1 ][ 1 ] += diffs[ pointer + 1 ][ 1 ];
4307 diffs[ pointer ][ 1 ] =
4308 diffs[ pointer ][ 1 ].substring( diffs[ pointer + 1 ][ 1 ].length ) +
4309 diffs[ pointer + 1 ][ 1 ];
4310 diffs.splice( pointer + 1, 1 );
4311 changes = true;
4314 pointer++;
4317 // If shifts were made, the diff needs reordering and another shift sweep.
4318 if ( changes ) {
4319 this.diffCleanupMerge( diffs );
4323 return function( o, n ) {
4324 var diff, output, text;
4325 diff = new DiffMatchPatch();
4326 output = diff.DiffMain( o, n );
4327 diff.diffCleanupEfficiency( output );
4328 text = diff.diffPrettyHtml( output );
4330 return text;
4332 }() );
4334 }() );