5 * Copyright jQuery Foundation and other contributors
6 * Released under the MIT license
7 * https://jquery.org/license
9 * Date: 2016-02-01T13:07Z
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;
28 document: window && window.document !== undefined,
29 setTimeout: setTimeout !== undefined,
30 sessionStorage: (function() {
31 var x = "qunit-test-string";
33 sessionStorage.setItem( x, x );
34 sessionStorage.removeItem( x );
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 ) {
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 );
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 ) {
82 * Makes a clone of an object using only Array or Object as base,
83 * and copies over the own enumerable properties.
86 * @return {Object} New object with only the own properties (recursively).
88 function objectValues ( obj ) {
90 vals = QUnit.is( "array", obj ) ? [] : {};
92 if ( hasOwn.call( obj, key ) ) {
94 vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
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 ) {
110 } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
111 a[ prop ] = b[ prop ];
120 function objectType( obj ) {
121 if ( typeof obj === "undefined" ) {
125 // Consider: typeof null === object
126 if ( obj === null ) {
130 var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
131 type = match && match[ 1 ];
135 if ( isNaN( obj ) ) {
148 return type.toLowerCase();
150 if ( typeof obj === "object" ) {
155 // Safe object type checking
156 function is( type, obj ) {
157 return QUnit.objectType( obj ) === type;
160 var getUrlParams = function() {
163 var location = window.location;
164 var params = location.search.slice( 1 ).split( "&" );
165 var length = params.length;
168 for ( i = 0; i < length; i++ ) {
169 current = params[ i ].split( "=" );
170 current[ 0 ] = decodeURIComponent( current[ 0 ] );
172 // allow just a key to turn on a flag, e.g., test.html?noglobals
173 current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
174 if ( urlParams[ current[ 0 ] ] ) {
175 urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );
177 urlParams[ current[ 0 ] ] = current[ 1 ];
185 // Doesn't support IE6 to IE9, it will return undefined on these browsers
186 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
187 function extractStacktrace( e, offset ) {
188 offset = offset === undefined ? 4 : offset;
190 var stack, include, i;
193 stack = e.stack.split( "\n" );
194 if ( /^error$/i.test( stack[ 0 ] ) ) {
199 for ( i = offset; i < stack.length; i++ ) {
200 if ( stack[ i ].indexOf( fileName ) !== -1 ) {
203 include.push( stack[ i ] );
205 if ( include.length ) {
206 return include.join( "\n" );
209 return stack[ offset ];
211 // Support: Safari <=6 only
212 } else if ( e.sourceURL ) {
214 // exclude useless self-reference for generated Error objects
215 if ( /qunit.js$/.test( e.sourceURL ) ) {
219 // for actual exceptions, this is useful
220 return e.sourceURL + ":" + e.line;
224 function sourceFromStacktrace( offset ) {
225 var error = new Error();
227 // Support: Safari <=7 only, IE <=10 - 11 only
228 // Not all browsers generate the `stack` property for `new Error()`, see also #636
229 if ( !error.stack ) {
237 return extractStacktrace( error, offset );
241 * Config object: Maintain internal state
242 * Later exposed as QUnit.config
243 * `config` initialized at top of scope
246 // The queue of tests to run
249 // block until document ready
252 // by default, run previously failed tests first
253 // very useful in combination with "Hide passed tests" checked
256 // by default, modify document.title when suite is done
259 // HTML Reporter: collapse every test except the first failing test
260 // If false, all failing tests will be expanded
263 // by default, scroll to top of the page when suite is done
266 // depth up-to which object will be dumped
269 // when enabled, all tests must call expect()
270 requireExpects: false,
272 // add checkboxes that are persisted in the query-string
273 // when enabled, the id is set to `true` as a `QUnit.config` property
277 label: "Hide passed tests",
278 tooltip: "Only show tests and assertions that fail. Stored as query-strings."
282 label: "Check for Globals",
283 tooltip: "Enabling this will test if any test introduces new properties on the " +
284 "global object (`window` in Browsers). Stored as query-strings."
288 label: "No try-catch",
289 tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
290 "exceptions in IE reasonable. Stored as query-strings."
294 // Set of all modules.
297 // Stack of nested modules
300 // The first unnamed module
309 var urlParams = defined.document ? getUrlParams() : {};
311 // Push a loose unnamed module to the modules collection
312 config.modules.push( config.currentModule );
314 if ( urlParams.filter === true ) {
315 delete urlParams.filter;
318 // String search anywhere in moduleName+testName
319 config.filter = urlParams.filter;
322 if ( urlParams.testId ) {
323 // Ensure that urlParams.testId is an array
324 urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," );
325 for (var i = 0; i < urlParams.testId.length; i++ ) {
326 config.testId.push( urlParams.testId[ i ] );
330 var loggingCallbacks = {};
332 // Register logging callbacks
333 function registerLoggingCallbacks( obj ) {
335 callbackNames = [ "begin", "done", "log", "testStart", "testDone",
336 "moduleStart", "moduleDone" ];
338 function registerLoggingCallback( key ) {
339 var loggingCallback = function( callback ) {
340 if ( objectType( callback ) !== "function" ) {
342 "QUnit logging methods require a callback function as their first parameters."
346 config.callbacks[ key ].push( callback );
349 // DEPRECATED: This will be removed on QUnit 2.0.0+
350 // Stores the registered functions allowing restoring
351 // at verifyLoggingCallbacks() if modified
352 loggingCallbacks[ key ] = loggingCallback;
354 return loggingCallback;
357 for ( i = 0, l = callbackNames.length; i < l; i++ ) {
358 key = callbackNames[ i ];
360 // Initialize key collection of logging callback
361 if ( objectType( config.callbacks[ key ] ) === "undefined" ) {
362 config.callbacks[ key ] = [];
365 obj[ key ] = registerLoggingCallback( key );
369 function runLoggingCallbacks( key, args ) {
372 callbacks = config.callbacks[ key ];
373 for ( i = 0, l = callbacks.length; i < l; i++ ) {
374 callbacks[ i ]( args );
378 // DEPRECATED: This will be removed on 2.0.0+
379 // This function verifies if the loggingCallbacks were modified by the user
380 // If so, it will restore it, assign the given callback and print a console warning
381 function verifyLoggingCallbacks() {
382 var loggingCallback, userCallback;
384 for ( loggingCallback in loggingCallbacks ) {
385 if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
387 userCallback = QUnit[ loggingCallback ];
389 // Restore the callback function
390 QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
392 // Assign the deprecated given callback
393 QUnit[ loggingCallback ]( userCallback );
395 if ( global.console && global.console.warn ) {
397 "QUnit." + loggingCallback + " was replaced with a new value.\n" +
398 "Please, check out the documentation on how to apply logging callbacks.\n" +
399 "Reference: https://api.qunitjs.com/category/callbacks/"
407 if ( !defined.document ) {
411 // `onErrorFnPrev` initialized at top of scope
412 // Preserve other handlers
413 var onErrorFnPrev = window.onerror;
415 // Cover uncaught exceptions
416 // Returning true will suppress the default browser handler,
417 // returning false will let it run.
418 window.onerror = function( error, filePath, linerNr ) {
420 if ( onErrorFnPrev ) {
421 ret = onErrorFnPrev( error, filePath, linerNr );
424 // Treat return value as window.onerror itself does,
425 // Only do our handling if not suppressed.
426 if ( ret !== true ) {
427 if ( QUnit.config.current ) {
428 if ( QUnit.config.current.ignoreGlobalErrors ) {
431 QUnit.pushFailure( error, filePath + ":" + linerNr );
433 QUnit.test( "global failure", extend(function() {
434 QUnit.pushFailure( error, filePath + ":" + linerNr );
435 }, { validTest: true } ) );
444 QUnit.urlParams = urlParams;
446 // Figure out if we're running the tests from a server or not
447 QUnit.isLocal = !( defined.document && window.location.protocol !== "file:" );
449 // Expose the current QUnit version
450 QUnit.version = "1.21.0";
454 // call on start of module test to prepend name to all tests
455 module: function( name, testEnvironment, executeNow ) {
456 var module, moduleFns;
457 var currentModule = config.currentModule;
459 if ( arguments.length === 2 ) {
460 if ( testEnvironment instanceof Function ) {
461 executeNow = testEnvironment;
462 testEnvironment = undefined;
466 // DEPRECATED: handles setup/teardown functions,
467 // beforeEach and afterEach should be used instead
468 if ( testEnvironment && testEnvironment.setup ) {
469 testEnvironment.beforeEach = testEnvironment.setup;
470 delete testEnvironment.setup;
472 if ( testEnvironment && testEnvironment.teardown ) {
473 testEnvironment.afterEach = testEnvironment.teardown;
474 delete testEnvironment.teardown;
477 module = createModule();
480 beforeEach: setHook( module, "beforeEach" ),
481 afterEach: setHook( module, "afterEach" )
484 if ( executeNow instanceof Function ) {
485 config.moduleStack.push( module );
486 setCurrentModule( module );
487 executeNow.call( module.testEnvironment, moduleFns );
488 config.moduleStack.pop();
489 module = module.parentModule || currentModule;
492 setCurrentModule( module );
494 function createModule() {
495 var parentModule = config.moduleStack.length ?
496 config.moduleStack.slice( -1 )[ 0 ] : null;
497 var moduleName = parentModule !== null ?
498 [ parentModule.name, name ].join( " > " ) : name;
501 parentModule: parentModule,
506 if ( parentModule ) {
507 extend( env, parentModule.testEnvironment );
508 delete env.beforeEach;
509 delete env.afterEach;
511 extend( env, testEnvironment );
512 module.testEnvironment = env;
514 config.modules.push( module );
518 function setCurrentModule( module ) {
519 config.currentModule = module;
524 // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
525 asyncTest: asyncTest,
533 // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
534 // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
535 start: function( count ) {
536 var globalStartAlreadyCalled = globalStartCalled;
538 if ( !config.current ) {
539 globalStartCalled = true;
542 throw new Error( "Called start() outside of a test context while already started" );
543 } else if ( globalStartAlreadyCalled || count > 1 ) {
544 throw new Error( "Called start() outside of a test context too many times" );
545 } else if ( config.autostart ) {
546 throw new Error( "Called start() outside of a test context when " +
547 "QUnit.config.autostart was true" );
548 } else if ( !config.pageLoaded ) {
550 // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
551 config.autostart = true;
556 // If a test is running, adjust its semaphore
557 config.current.semaphore -= count || 1;
559 // If semaphore is non-numeric, throw error
560 if ( isNaN( config.current.semaphore ) ) {
561 config.current.semaphore = 0;
564 "Called start() with a non-numeric decrement.",
565 sourceFromStacktrace( 2 )
570 // Don't start until equal number of stop-calls
571 if ( config.current.semaphore > 0 ) {
575 // throw an Error if start is called more often than stop
576 if ( config.current.semaphore < 0 ) {
577 config.current.semaphore = 0;
580 "Called start() while already started (test's semaphore was 0 already)",
581 sourceFromStacktrace( 2 )
590 // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
591 stop: function( count ) {
593 // If there isn't a test running, don't allow QUnit.stop() to be called
594 if ( !config.current ) {
595 throw new Error( "Called stop() outside of a test context" );
598 // If a test is running, adjust its semaphore
599 config.current.semaphore += count || 1;
608 objectType: objectType,
613 config.pageLoaded = true;
615 // Initialize the configuration options
617 stats: { all: 0, bad: 0 },
618 moduleStats: { all: 0, bad: 0 },
625 config.blocking = false;
627 if ( config.autostart ) {
632 stack: function( offset ) {
633 offset = ( offset || 0 ) + 2;
634 return sourceFromStacktrace( offset );
638 registerLoggingCallbacks( QUnit );
644 // If the test run hasn't officially begun yet
645 if ( !config.started ) {
647 // Record the time of the test run's beginning
648 config.started = now();
650 verifyLoggingCallbacks();
652 // Delete the loose unnamed module if unused.
653 if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
654 config.modules.shift();
657 // Avoid unnecessary information by not logging modules' test environments
658 for ( i = 0, l = config.modules.length; i < l; i++ ) {
660 name: config.modules[ i ].name,
661 tests: config.modules[ i ].tests
665 // The test run is officially beginning now
666 runLoggingCallbacks( "begin", {
667 totalTests: Test.count,
672 config.blocking = false;
676 function process( last ) {
681 config.depth = ( config.depth || 0 ) + 1;
683 while ( config.queue.length && !config.blocking ) {
684 if ( !defined.setTimeout || config.updateRate <= 0 ||
685 ( ( now() - start ) < config.updateRate ) ) {
686 if ( config.current ) {
688 // Reset async tracking for each phase of the Test lifecycle
689 config.current.usedAsync = false;
691 config.queue.shift()();
693 setTimeout( next, 13 );
698 if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
703 function pauseProcessing() {
704 config.blocking = true;
706 if ( config.testTimeout && defined.setTimeout ) {
707 clearTimeout( config.timeout );
708 config.timeout = setTimeout(function() {
709 if ( config.current ) {
710 config.current.semaphore = 0;
711 QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
713 throw new Error( "Test timed out" );
716 }, config.testTimeout );
720 function resumeProcessing() {
723 // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
724 if ( defined.setTimeout ) {
725 setTimeout(function() {
726 if ( config.current && config.current.semaphore > 0 ) {
729 if ( config.timeout ) {
730 clearTimeout( config.timeout );
743 config.autorun = true;
745 // Log the last module results
746 if ( config.previousModule ) {
747 runLoggingCallbacks( "moduleDone", {
748 name: config.previousModule.name,
749 tests: config.previousModule.tests,
750 failed: config.moduleStats.bad,
751 passed: config.moduleStats.all - config.moduleStats.bad,
752 total: config.moduleStats.all,
753 runtime: now() - config.moduleStats.started
756 delete config.previousModule;
758 runtime = now() - config.started;
759 passed = config.stats.all - config.stats.bad;
761 runLoggingCallbacks( "done", {
762 failed: config.stats.bad,
764 total: config.stats.all,
769 function setHook( module, hookName ) {
770 if ( module.testEnvironment === undefined ) {
771 module.testEnvironment = {};
774 return function( callback ) {
775 module.testEnvironment[ hookName ] = callback;
780 var priorityCount = 0;
782 function Test( settings ) {
787 extend( this, settings );
788 this.assertions = [];
790 this.usedAsync = false;
791 this.module = config.currentModule;
792 this.stack = sourceFromStacktrace( 3 );
794 // Register unique strings
795 for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
796 if ( this.module.tests[ i ].name === this.testName ) {
797 this.testName += " ";
801 this.testId = generateHash( this.module.name, this.testName );
803 this.module.tests.push({
808 if ( settings.skip ) {
810 // Skipped tests will fully ignore any sent callback
811 this.callback = function() {};
815 this.assert = new Assert( this );
825 // Emit moduleStart when we're switching from one module to another
826 this.module !== config.previousModule ||
828 // They could be equal (both undefined) but if the previousModule property doesn't
829 // yet exist it means this is the first test in a suite that isn't wrapped in a
830 // module, in which case we'll just emit a moduleStart event for 'undefined'.
831 // Without this, reporters can get testStart before moduleStart which is a problem.
832 !hasOwn.call( config, "previousModule" )
834 if ( hasOwn.call( config, "previousModule" ) ) {
835 runLoggingCallbacks( "moduleDone", {
836 name: config.previousModule.name,
837 tests: config.previousModule.tests,
838 failed: config.moduleStats.bad,
839 passed: config.moduleStats.all - config.moduleStats.bad,
840 total: config.moduleStats.all,
841 runtime: now() - config.moduleStats.started
844 config.previousModule = this.module;
845 config.moduleStats = { all: 0, bad: 0, started: now() };
846 runLoggingCallbacks( "moduleStart", {
847 name: this.module.name,
848 tests: this.module.tests
852 config.current = this;
854 if ( this.module.testEnvironment ) {
855 delete this.module.testEnvironment.beforeEach;
856 delete this.module.testEnvironment.afterEach;
858 this.testEnvironment = extend( {}, this.module.testEnvironment );
860 this.started = now();
861 runLoggingCallbacks( "testStart", {
863 module: this.module.name,
867 if ( !config.pollution ) {
875 config.current = this;
881 this.callbackStarted = now();
883 if ( config.notrycatch ) {
891 this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
892 this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
894 // else next test will carry the responsibility
897 // Restart the tests if they're blocking
898 if ( config.blocking ) {
903 function runTest( test ) {
904 promise = test.callback.call( test.testEnvironment, test.assert );
905 test.resolvePromise( promise );
913 queueHook: function( hook, hookName ) {
916 return function runHook() {
917 config.current = test;
918 if ( config.notrycatch ) {
925 test.pushFailure( hookName + " failed on " + test.testName + ": " +
926 ( error.message || error ), extractStacktrace( error, 0 ) );
929 function callHook() {
930 promise = hook.call( test.testEnvironment, test.assert );
931 test.resolvePromise( promise, hookName );
936 // Currently only used for module level hooks, can be used to add global level ones
937 hooks: function( handler ) {
940 function processHooks( test, module ) {
941 if ( module.parentModule ) {
942 processHooks( test, module.parentModule );
944 if ( module.testEnvironment &&
945 QUnit.objectType( module.testEnvironment[ handler ] ) === "function" ) {
946 hooks.push( test.queueHook( module.testEnvironment[ handler ], handler ) );
950 // Hooks are ignored on skipped tests
952 processHooks( this, this.module );
958 config.current = this;
959 if ( config.requireExpects && this.expected === null ) {
960 this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
961 "not called.", this.stack );
962 } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
963 this.pushFailure( "Expected " + this.expected + " assertions, but " +
964 this.assertions.length + " were run", this.stack );
965 } else if ( this.expected === null && !this.assertions.length ) {
966 this.pushFailure( "Expected at least one assertion, but none were run - call " +
967 "expect(0) to accept zero assertions.", this.stack );
973 this.runtime = now() - this.started;
974 config.stats.all += this.assertions.length;
975 config.moduleStats.all += this.assertions.length;
977 for ( i = 0; i < this.assertions.length; i++ ) {
978 if ( !this.assertions[ i ].result ) {
981 config.moduleStats.bad++;
985 runLoggingCallbacks( "testDone", {
987 module: this.module.name,
988 skipped: !!this.skip,
990 passed: this.assertions.length - bad,
991 total: this.assertions.length,
992 runtime: this.runtime,
995 assertions: this.assertions,
1001 // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
1002 duration: this.runtime
1005 // QUnit.reset() is deprecated and will be replaced for a new
1006 // fixture reset function on QUnit 2.0/2.1.
1007 // It's still called here for backwards compatibility handling
1010 config.current = undefined;
1017 if ( !this.valid() ) {
1023 // each of these can by async
1029 test.hooks( "beforeEach" ),
1034 test.hooks( "afterEach" ).reverse(),
1045 // Prioritize previously failed tests, detected from sessionStorage
1046 priority = QUnit.config.reorder && defined.sessionStorage &&
1047 +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
1049 return synchronize( run, priority );
1052 push: function( result, actual, expected, message, negative ) {
1055 module: this.module.name,
1056 name: this.testName,
1061 testId: this.testId,
1062 negative: negative || false,
1063 runtime: now() - this.started
1067 source = sourceFromStacktrace();
1070 details.source = source;
1074 runLoggingCallbacks( "log", details );
1076 this.assertions.push({
1082 pushFailure: function( message, source, actual ) {
1083 if ( !( this instanceof Test ) ) {
1084 throw new Error( "pushFailure() assertion outside test context, was " +
1085 sourceFromStacktrace( 2 ) );
1089 module: this.module.name,
1090 name: this.testName,
1092 message: message || "error",
1093 actual: actual || null,
1094 testId: this.testId,
1095 runtime: now() - this.started
1099 details.source = source;
1102 runLoggingCallbacks( "log", details );
1104 this.assertions.push({
1110 resolvePromise: function( promise, phase ) {
1113 if ( promise != null ) {
1114 then = promise.then;
1115 if ( QUnit.objectType( then ) === "function" ) {
1119 function() { QUnit.start(); },
1121 message = "Promise rejected " +
1122 ( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
1123 " " + test.testName + ": " + ( error.message || error );
1124 test.pushFailure( message, extractStacktrace( error, 0 ) );
1126 // else next test will carry the responsibility
1138 var filter = config.filter,
1139 regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec( filter ),
1140 module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
1141 fullName = ( this.module.name + ": " + this.testName );
1143 function testInModuleChain( testModule ) {
1144 var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
1145 if ( testModuleName === module ) {
1147 } else if ( testModule.parentModule ) {
1148 return testInModuleChain( testModule.parentModule );
1154 // Internally-generated tests are always valid
1155 if ( this.callback && this.callback.validTest ) {
1159 if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
1163 if ( module && !testInModuleChain( this.module ) ) {
1171 return regexFilter ?
1172 this.regexFilter( !!regexFilter[1], regexFilter[2], regexFilter[3], fullName ) :
1173 this.stringFilter( filter, fullName );
1176 regexFilter: function( exclude, pattern, flags, fullName ) {
1177 var regex = new RegExp( pattern, flags );
1178 var match = regex.test( fullName );
1180 return match !== exclude;
1183 stringFilter: function( filter, fullName ) {
1184 filter = filter.toLowerCase();
1185 fullName = fullName.toLowerCase();
1187 var include = filter.charAt( 0 ) !== "!";
1189 filter = filter.slice( 1 );
1192 // If the filter matches, we need to honour include
1193 if ( fullName.indexOf( filter ) !== -1 ) {
1197 // Otherwise, do the opposite
1202 // Resets the test setup. Useful for tests that modify the DOM.
1204 DEPRECATED: Use multiple tests instead of resetting inside a test.
1205 Use testStart or testDone for custom cleanup.
1206 This method will throw an error in 2.0, and will be removed in 2.1
1208 QUnit.reset = function() {
1210 // Return on non-browser environments
1211 // This is necessary to not break on node tests
1212 if ( !defined.document ) {
1216 var fixture = defined.document && document.getElementById &&
1217 document.getElementById( "qunit-fixture" );
1220 fixture.innerHTML = config.fixture;
1224 QUnit.pushFailure = function() {
1225 if ( !QUnit.config.current ) {
1226 throw new Error( "pushFailure() assertion outside test context, in " +
1227 sourceFromStacktrace( 2 ) );
1230 // Gets current test obj
1231 var currentTest = QUnit.config.current;
1233 return currentTest.pushFailure.apply( currentTest, arguments );
1236 // Based on Java's String.hashCode, a simple but not
1237 // rigorously collision resistant hashing function
1238 function generateHash( module, testName ) {
1242 str = module + "\x1C" + testName,
1245 for ( ; i < len; i++ ) {
1246 hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
1250 // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
1251 // strictly necessary but increases user understanding that the id is a SHA-like hash
1252 hex = ( 0x100000000 + hash ).toString( 16 );
1253 if ( hex.length < 8 ) {
1254 hex = "0000000" + hex;
1257 return hex.slice( -8 );
1260 function synchronize( callback, priority ) {
1261 var last = !priority;
1263 if ( QUnit.objectType( callback ) === "array" ) {
1264 while ( callback.length ) {
1265 synchronize( callback.shift() );
1271 config.queue.splice( priorityCount++, 0, callback );
1273 config.queue.push( callback );
1276 if ( config.autorun && !config.blocking ) {
1281 function saveGlobal() {
1282 config.pollution = [];
1284 if ( config.noglobals ) {
1285 for ( var key in global ) {
1286 if ( hasOwn.call( global, key ) ) {
1288 // in Opera sometimes DOM element ids show up here, ignore them
1289 if ( /^qunit-test-output/.test( key ) ) {
1292 config.pollution.push( key );
1298 function checkPollution() {
1301 old = config.pollution;
1305 newGlobals = diff( config.pollution, old );
1306 if ( newGlobals.length > 0 ) {
1307 QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
1310 deletedGlobals = diff( old, config.pollution );
1311 if ( deletedGlobals.length > 0 ) {
1312 QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
1316 // Will be exposed as QUnit.asyncTest
1317 function asyncTest( testName, expected, callback ) {
1318 if ( arguments.length === 2 ) {
1319 callback = expected;
1323 QUnit.test( testName, expected, callback, true );
1326 // Will be exposed as QUnit.test
1327 function test( testName, expected, callback, async ) {
1328 if ( focused ) { return; }
1332 if ( arguments.length === 2 ) {
1333 callback = expected;
1337 newTest = new Test({
1347 // Will be exposed as QUnit.skip
1348 function skip( testName ) {
1349 if ( focused ) { return; }
1351 var test = new Test({
1359 // Will be exposed as QUnit.only
1360 function only( testName, expected, callback, async ) {
1363 if ( focused ) { return; }
1365 QUnit.config.queue.length = 0;
1368 if ( arguments.length === 2 ) {
1369 callback = expected;
1373 newTest = new Test({
1383 function Assert( testContext ) {
1384 this.test = testContext;
1388 QUnit.assert = Assert.prototype = {
1390 // Specify the number of expected assertions to guarantee that failed test
1391 // (no assertions are run at all) don't slip through.
1392 expect: function( asserts ) {
1393 if ( arguments.length === 1 ) {
1394 this.test.expected = asserts;
1396 return this.test.expected;
1400 // Increment this Test's semaphore counter, then return a function that
1401 // decrements that counter a maximum of once.
1402 async: function( count ) {
1403 var test = this.test,
1405 acceptCallCount = count;
1407 if ( typeof acceptCallCount === "undefined" ) {
1408 acceptCallCount = 1;
1411 test.semaphore += 1;
1412 test.usedAsync = true;
1415 return function done() {
1418 test.pushFailure( "Too many calls to the `assert.async` callback",
1419 sourceFromStacktrace( 2 ) );
1422 acceptCallCount -= 1;
1423 if ( acceptCallCount > 0 ) {
1427 test.semaphore -= 1;
1433 // Exports test.push() to the user API
1434 push: function( /* result, actual, expected, message, negative */ ) {
1436 currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
1438 // Backwards compatibility fix.
1439 // Allows the direct use of global exported assertions and QUnit.assert.*
1440 // Although, it's use is not recommended as it can leak assertions
1441 // to other tests from async tests, because we only get a reference to the current test,
1442 // not exactly the test where assertion were intended to be called.
1443 if ( !currentTest ) {
1444 throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
1447 if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
1448 currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
1449 sourceFromStacktrace( 2 ) );
1451 // Allow this assertion to continue running anyway...
1454 if ( !( assert instanceof Assert ) ) {
1455 assert = currentTest.assert;
1457 return assert.test.push.apply( assert.test, arguments );
1460 ok: function( result, message ) {
1461 message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
1462 QUnit.dump.parse( result ) );
1463 this.push( !!result, result, true, message );
1466 notOk: function( result, message ) {
1467 message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " +
1468 QUnit.dump.parse( result ) );
1469 this.push( !result, result, false, message );
1472 equal: function( actual, expected, message ) {
1473 /*jshint eqeqeq:false */
1474 this.push( expected == actual, actual, expected, message );
1477 notEqual: function( actual, expected, message ) {
1478 /*jshint eqeqeq:false */
1479 this.push( expected != actual, actual, expected, message, true );
1482 propEqual: function( actual, expected, message ) {
1483 actual = objectValues( actual );
1484 expected = objectValues( expected );
1485 this.push( QUnit.equiv( actual, expected ), actual, expected, message );
1488 notPropEqual: function( actual, expected, message ) {
1489 actual = objectValues( actual );
1490 expected = objectValues( expected );
1491 this.push( !QUnit.equiv( actual, expected ), actual, expected, message, true );
1494 deepEqual: function( actual, expected, message ) {
1495 this.push( QUnit.equiv( actual, expected ), actual, expected, message );
1498 notDeepEqual: function( actual, expected, message ) {
1499 this.push( !QUnit.equiv( actual, expected ), actual, expected, message, true );
1502 strictEqual: function( actual, expected, message ) {
1503 this.push( expected === actual, actual, expected, message );
1506 notStrictEqual: function( actual, expected, message ) {
1507 this.push( expected !== actual, actual, expected, message, true );
1510 "throws": function( block, expected, message ) {
1511 var actual, expectedType,
1512 expectedOutput = expected,
1514 currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current;
1516 // 'expected' is optional unless doing string comparison
1517 if ( message == null && typeof expected === "string" ) {
1522 currentTest.ignoreGlobalErrors = true;
1524 block.call( currentTest.testEnvironment );
1528 currentTest.ignoreGlobalErrors = false;
1531 expectedType = QUnit.objectType( expected );
1533 // we don't want to validate thrown error
1536 expectedOutput = null;
1538 // expected is a regexp
1539 } else if ( expectedType === "regexp" ) {
1540 ok = expected.test( errorString( actual ) );
1542 // expected is a string
1543 } else if ( expectedType === "string" ) {
1544 ok = expected === errorString( actual );
1546 // expected is a constructor, maybe an Error constructor
1547 } else if ( expectedType === "function" && actual instanceof expected ) {
1550 // expected is an Error object
1551 } else if ( expectedType === "object" ) {
1552 ok = actual instanceof expected.constructor &&
1553 actual.name === expected.name &&
1554 actual.message === expected.message;
1556 // expected is a validation function which returns true if validation passed
1557 } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
1558 expectedOutput = null;
1563 currentTest.assert.push( ok, actual, expectedOutput, message );
1567 // Provide an alternative to assert.throws(), for environments that consider throws a reserved word
1568 // Known to us are: Closure Compiler, Narwhal
1570 /*jshint sub:true */
1571 Assert.prototype.raises = Assert.prototype[ "throws" ];
1574 function errorString( error ) {
1576 resultErrorString = error.toString();
1577 if ( resultErrorString.substring( 0, 7 ) === "[object" ) {
1578 name = error.name ? error.name.toString() : "Error";
1579 message = error.message ? error.message.toString() : "";
1580 if ( name && message ) {
1581 return name + ": " + message;
1582 } else if ( name ) {
1584 } else if ( message ) {
1590 return resultErrorString;
1594 // Test for equality any JavaScript type.
1595 // Author: Philippe Rathé <prathe@gmail.com>
1596 QUnit.equiv = (function() {
1598 // Stack to decide between skip/abort functions
1601 // Stack to avoiding loops from circular referencing
1605 var getProto = Object.getPrototypeOf || function( obj ) {
1607 /*jshint proto: true */
1608 return obj.__proto__;
1611 function useStrictEquality( b, a ) {
1613 // To catch short annotation VS 'new' annotation of a declaration. e.g.:
1615 // `var j = new Number(1);`
1616 if ( typeof a === "object" ) {
1619 if ( typeof b === "object" ) {
1626 function compareConstructors( a, b ) {
1627 var protoA = getProto( a );
1628 var protoB = getProto( b );
1630 // Comparing constructors is more strict than using `instanceof`
1631 if ( a.constructor === b.constructor ) {
1636 // If the obj prototype descends from a null constructor, treat it
1637 // as a null prototype.
1638 if ( protoA && protoA.constructor === null ) {
1641 if ( protoB && protoB.constructor === null ) {
1645 // Allow objects with no prototype to be equivalent to
1646 // objects with Object as their constructor.
1647 if ( ( protoA === null && protoB === Object.prototype ) ||
1648 ( protoB === null && protoA === Object.prototype ) ) {
1655 function getRegExpFlags( regexp ) {
1656 return "flags" in regexp ? regexp.flags : regexp.toString().match( /[gimuy]*$/ )[ 0 ];
1660 "string": useStrictEquality,
1661 "boolean": useStrictEquality,
1662 "number": useStrictEquality,
1663 "null": useStrictEquality,
1664 "undefined": useStrictEquality,
1665 "symbol": useStrictEquality,
1666 "date": useStrictEquality,
1672 "regexp": function( b, a ) {
1673 return a.source === b.source &&
1675 // Include flags in the comparison
1676 getRegExpFlags( a ) === getRegExpFlags( b );
1679 // - skip when the property is a method of an instance (OOP)
1680 // - abort otherwise,
1681 // initial === would have catch identical references anyway
1682 "function": function() {
1683 var caller = callers[ callers.length - 1 ];
1684 return caller !== Object && typeof caller !== "undefined";
1687 "array": function( b, a ) {
1688 var i, j, len, loop, aCircular, bCircular;
1691 if ( len !== b.length ) {
1696 // Track reference to avoid circular references
1699 for ( i = 0; i < len; i++ ) {
1701 for ( j = 0; j < parents.length; j++ ) {
1702 aCircular = parents[ j ] === a[ i ];
1703 bCircular = parentsB[ j ] === b[ i ];
1704 if ( aCircular || bCircular ) {
1705 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1714 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1725 "set": function( b, a ) {
1729 a.forEach( function( v ) {
1733 b.forEach( function( v ) {
1737 return innerEquiv( bArray, aArray );
1740 "map": function( b, a ) {
1744 a.forEach( function( v, k ) {
1745 aArray.push( [ k, v ] );
1748 b.forEach( function( v, k ) {
1749 bArray.push( [ k, v ] );
1752 return innerEquiv( bArray, aArray );
1755 "object": function( b, a ) {
1756 var i, j, loop, aCircular, bCircular;
1760 var aProperties = [];
1761 var bProperties = [];
1763 if ( compareConstructors( a, b ) === false ) {
1767 // Stack constructor before traversing properties
1768 callers.push( a.constructor );
1770 // Track reference to avoid circular references
1774 // Be strict: don't ensure hasOwnProperty and go deep
1777 for ( j = 0; j < parents.length; j++ ) {
1778 aCircular = parents[ j ] === a[ i ];
1779 bCircular = parentsB[ j ] === b[ i ];
1780 if ( aCircular || bCircular ) {
1781 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1789 aProperties.push( i );
1790 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1799 // Unstack, we are done
1804 // Collect b's properties
1805 bProperties.push( i );
1808 // Ensures identical properties name
1809 return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
1813 function typeEquiv( a, b ) {
1814 var type = QUnit.objectType( a );
1815 return QUnit.objectType( b ) === type && callbacks[ type ]( b, a );
1818 // The real equiv function
1819 function innerEquiv( a, b ) {
1821 // We're done when there's nothing more to compare
1822 if ( arguments.length < 2 ) {
1826 // Require type-specific equality
1827 return ( a === b || typeEquiv( a, b ) ) &&
1829 // ...across all consecutive argument pairs
1830 ( arguments.length === 2 || innerEquiv.apply( this, [].slice.call( arguments, 1 ) ) );
1836 // Based on jsDump by Ariel Flesler
1837 // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
1838 QUnit.dump = (function() {
1839 function quote( str ) {
1840 return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\"";
1842 function literal( o ) {
1845 function join( pre, arr, post ) {
1846 var s = dump.separator(),
1847 base = dump.indent(),
1848 inner = dump.indent( 1 );
1850 arr = arr.join( "," + s + inner );
1855 return [ pre, inner + arr, base + post ].join( s );
1857 function array( arr, stack ) {
1859 ret = new Array( i );
1861 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1862 return "[object Array]";
1867 ret[ i ] = this.parse( arr[ i ], undefined, stack );
1870 return join( "[", ret, "]" );
1873 var reName = /^function (\w+)/,
1876 // objType is used mostly internally, you can fix a (custom) type in advance
1877 parse: function( obj, objType, stack ) {
1878 stack = stack || [];
1879 var res, parser, parserType,
1880 inStack = inArray( obj, stack );
1882 if ( inStack !== -1 ) {
1883 return "recursion(" + ( inStack - stack.length ) + ")";
1886 objType = objType || this.typeOf( obj );
1887 parser = this.parsers[ objType ];
1888 parserType = typeof parser;
1890 if ( parserType === "function" ) {
1892 res = parser.call( this, obj, stack );
1896 return ( parserType === "string" ) ? parser : this.parsers.error;
1898 typeOf: function( obj ) {
1900 if ( obj === null ) {
1902 } else if ( typeof obj === "undefined" ) {
1904 } else if ( QUnit.is( "regexp", obj ) ) {
1906 } else if ( QUnit.is( "date", obj ) ) {
1908 } else if ( QUnit.is( "function", obj ) ) {
1910 } else if ( obj.setInterval !== undefined &&
1911 obj.document !== undefined &&
1912 obj.nodeType === undefined ) {
1914 } else if ( obj.nodeType === 9 ) {
1916 } else if ( obj.nodeType ) {
1921 toString.call( obj ) === "[object Array]" ||
1924 ( typeof obj.length === "number" && obj.item !== undefined &&
1925 ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
1926 obj[ 0 ] === undefined ) ) )
1929 } else if ( obj.constructor === Error.prototype.constructor ) {
1936 separator: function() {
1937 return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? " " : " ";
1939 // extra can be a number, shortcut for increasing-calling-decreasing
1940 indent: function( extra ) {
1941 if ( !this.multiline ) {
1944 var chr = this.indentChar;
1946 chr = chr.replace( /\t/g, " " ).replace( / /g, " " );
1948 return new Array( this.depth + ( extra || 0 ) ).join( chr );
1951 this.depth += a || 1;
1953 down: function( a ) {
1954 this.depth -= a || 1;
1956 setParser: function( name, parser ) {
1957 this.parsers[ name ] = parser;
1959 // The next 3 are exposed so you can use them
1965 maxDepth: QUnit.config.maxDepth,
1967 // This is the list of parsers, to modify them, use dump.setParser
1970 document: "[Document]",
1971 error: function( error ) {
1972 return "Error(\"" + error.message + "\")";
1974 unknown: "[Unknown]",
1976 "undefined": "undefined",
1977 "function": function( fn ) {
1978 var ret = "function",
1980 // functions never have name in IE
1981 name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
1988 ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
1989 return join( ret, dump.parse( fn, "functionCode" ), "}" );
1994 object: function( map, stack ) {
1995 var keys, key, val, i, nonEnumerableProperties,
1998 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1999 return "[object Object]";
2004 for ( key in map ) {
2008 // Some properties are not always enumerable on Error objects.
2009 nonEnumerableProperties = [ "message", "name" ];
2010 for ( i in nonEnumerableProperties ) {
2011 key = nonEnumerableProperties[ i ];
2012 if ( key in map && inArray( key, keys ) < 0 ) {
2017 for ( i = 0; i < keys.length; i++ ) {
2020 ret.push( dump.parse( key, "key" ) + ": " +
2021 dump.parse( val, undefined, stack ) );
2024 return join( "{", ret, "}" );
2026 node: function( node ) {
2028 open = dump.HTML ? "<" : "<",
2029 close = dump.HTML ? ">" : ">",
2030 tag = node.nodeName.toLowerCase(),
2032 attrs = node.attributes;
2035 for ( i = 0, len = attrs.length; i < len; i++ ) {
2036 val = attrs[ i ].nodeValue;
2038 // IE6 includes all attributes in .attributes, even ones not explicitly
2039 // set. Those have values like undefined, null, 0, false, "" or
2041 if ( val && val !== "inherit" ) {
2042 ret += " " + attrs[ i ].nodeName + "=" +
2043 dump.parse( val, "attribute" );
2049 // Show content of TextNode or CDATASection
2050 if ( node.nodeType === 3 || node.nodeType === 4 ) {
2051 ret += node.nodeValue;
2054 return ret + open + "/" + tag + close;
2057 // function calls it internally, it's the arguments part of the function
2058 functionArgs: function( fn ) {
2066 args = new Array( l );
2070 args[ l ] = String.fromCharCode( 97 + l );
2072 return " " + args.join( ", " ) + " ";
2074 // object calls it internally, the key part of an item in a map
2076 // function calls it internally, it's the content of the function
2077 functionCode: "[code]",
2078 // node calls it internally, it's a html attribute value
2086 // if true, entities are escaped ( <, >, \t, space and \n )
2090 // if true, items in a collection, are separated by a \n, else just a space.
2098 QUnit.jsDump = QUnit.dump;
2100 // For browser, export only select globals
2101 if ( defined.document ) {
2104 // Extend assert methods to QUnit and Global scope through Backwards compatibility
2107 assertions = Assert.prototype;
2109 function applyCurrent( current ) {
2111 var assert = new Assert( QUnit.config.current );
2112 current.apply( assert, arguments );
2116 for ( i in assertions ) {
2117 QUnit[ i ] = applyCurrent( assertions[ i ] );
2144 for ( i = 0, l = keys.length; i < l; i++ ) {
2145 window[ keys[ i ] ] = QUnit[ keys[ i ] ];
2149 window.QUnit = QUnit;
2153 if ( typeof module !== "undefined" && module && module.exports ) {
2154 module.exports = QUnit;
2156 // For consistency with CommonJS environments' exports
2157 module.exports.QUnit = QUnit;
2160 // For CommonJS with exports, but without module.exports, like Rhino
2161 if ( typeof exports !== "undefined" && exports ) {
2162 exports.QUnit = QUnit;
2165 if ( typeof define === "function" && define.amd ) {
2166 define( function() {
2169 QUnit.config.autostart = false;
2173 * This file is a modified version of google-diff-match-patch's JavaScript implementation
2174 * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
2175 * modifications are licensed as more fully set forth in LICENSE.txt.
2177 * The original source of google-diff-match-patch is attributable and licensed as follows:
2179 * Copyright 2006 Google Inc.
2180 * https://code.google.com/p/google-diff-match-patch/
2182 * Licensed under the Apache License, Version 2.0 (the "License");
2183 * you may not use this file except in compliance with the License.
2184 * You may obtain a copy of the License at
2186 * https://www.apache.org/licenses/LICENSE-2.0
2188 * Unless required by applicable law or agreed to in writing, software
2189 * distributed under the License is distributed on an "AS IS" BASIS,
2190 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2191 * See the License for the specific language governing permissions and
2192 * limitations under the License.
2195 * https://code.google.com/p/google-diff-match-patch/
2197 * Usage: QUnit.diff(expected, actual)
2200 QUnit.diff = ( function() {
2201 function DiffMatchPatch() {
2207 * The data structure representing a diff is an array of tuples:
2208 * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
2209 * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
2211 var DIFF_DELETE = -1,
2216 * Find the differences between two texts. Simplifies the problem by stripping
2217 * any common prefix or suffix off the texts before diffing.
2218 * @param {string} text1 Old string to be diffed.
2219 * @param {string} text2 New string to be diffed.
2220 * @param {boolean=} optChecklines Optional speedup flag. If present and false,
2221 * then don't run a line-level diff first to identify the changed areas.
2222 * Defaults to true, which does a faster, slightly less optimal diff.
2223 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2225 DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) {
2226 var deadline, checklines, commonlength,
2227 commonprefix, commonsuffix, diffs;
2229 // The diff must be complete in up to 1 second.
2230 deadline = ( new Date() ).getTime() + 1000;
2232 // Check for null inputs.
2233 if ( text1 === null || text2 === null ) {
2234 throw new Error( "Null input. (DiffMain)" );
2237 // Check for equality (speedup).
2238 if ( text1 === text2 ) {
2241 [ DIFF_EQUAL, text1 ]
2247 if ( typeof optChecklines === "undefined" ) {
2248 optChecklines = true;
2251 checklines = optChecklines;
2253 // Trim off common prefix (speedup).
2254 commonlength = this.diffCommonPrefix( text1, text2 );
2255 commonprefix = text1.substring( 0, commonlength );
2256 text1 = text1.substring( commonlength );
2257 text2 = text2.substring( commonlength );
2259 // Trim off common suffix (speedup).
2260 commonlength = this.diffCommonSuffix( text1, text2 );
2261 commonsuffix = text1.substring( text1.length - commonlength );
2262 text1 = text1.substring( 0, text1.length - commonlength );
2263 text2 = text2.substring( 0, text2.length - commonlength );
2265 // Compute the diff on the middle block.
2266 diffs = this.diffCompute( text1, text2, checklines, deadline );
2268 // Restore the prefix and suffix.
2269 if ( commonprefix ) {
2270 diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
2272 if ( commonsuffix ) {
2273 diffs.push( [ DIFF_EQUAL, commonsuffix ] );
2275 this.diffCleanupMerge( diffs );
2280 * Reduce the number of edits by eliminating operationally trivial equalities.
2281 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2283 DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
2284 var changes, equalities, equalitiesLength, lastequality,
2285 pointer, preIns, preDel, postIns, postDel;
2287 equalities = []; // Stack of indices where equalities are found.
2288 equalitiesLength = 0; // Keeping our own length var is faster in JS.
2289 /** @type {?string} */
2290 lastequality = null;
2291 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
2292 pointer = 0; // Index of current position.
2293 // Is there an insertion operation before the last equality.
2295 // Is there a deletion operation before the last equality.
2297 // Is there an insertion operation after the last equality.
2299 // Is there a deletion operation after the last equality.
2301 while ( pointer < diffs.length ) {
2304 if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) {
2305 if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) {
2308 equalities[ equalitiesLength++ ] = pointer;
2311 lastequality = diffs[ pointer ][ 1 ];
2314 // Not a candidate, and can never become one.
2315 equalitiesLength = 0;
2316 lastequality = null;
2318 postIns = postDel = false;
2320 // An insertion or deletion.
2323 if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
2330 * Five types to be split:
2331 * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
2332 * <ins>A</ins>X<ins>C</ins><del>D</del>
2333 * <ins>A</ins><del>B</del>X<ins>C</ins>
2334 * <ins>A</del>X<ins>C</ins><del>D</del>
2335 * <ins>A</ins><del>B</del>X<del>C</del>
2337 if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
2338 ( ( lastequality.length < 2 ) &&
2339 ( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
2341 // Duplicate record.
2343 equalities[ equalitiesLength - 1 ],
2345 [ DIFF_DELETE, lastequality ]
2348 // Change second copy to insert.
2349 diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
2350 equalitiesLength--; // Throw away the equality we just deleted;
2351 lastequality = null;
2352 if ( preIns && preDel ) {
2353 // No changes made which could affect previous entry, keep going.
2354 postIns = postDel = true;
2355 equalitiesLength = 0;
2357 equalitiesLength--; // Throw away the previous equality.
2358 pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
2359 postIns = postDel = false;
2368 this.diffCleanupMerge( diffs );
2373 * Convert a diff array into a pretty HTML report.
2374 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2375 * @param {integer} string to be beautified.
2376 * @return {string} HTML representation.
2378 DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
2381 for ( x = 0; x < diffs.length; x++ ) {
2382 op = diffs[ x ][ 0 ]; // Operation (insert, delete, equal)
2383 data = diffs[ x ][ 1 ]; // Text of change.
2386 html[ x ] = "<ins>" + data + "</ins>";
2389 html[ x ] = "<del>" + data + "</del>";
2392 html[ x ] = "<span>" + data + "</span>";
2396 return html.join( "" );
2400 * Determine the common prefix of two strings.
2401 * @param {string} text1 First string.
2402 * @param {string} text2 Second string.
2403 * @return {number} The number of characters common to the start of each
2406 DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
2407 var pointermid, pointermax, pointermin, pointerstart;
2408 // Quick check for common null cases.
2409 if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) {
2413 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
2415 pointermax = Math.min( text1.length, text2.length );
2416 pointermid = pointermax;
2418 while ( pointermin < pointermid ) {
2419 if ( text1.substring( pointerstart, pointermid ) ===
2420 text2.substring( pointerstart, pointermid ) ) {
2421 pointermin = pointermid;
2422 pointerstart = pointermin;
2424 pointermax = pointermid;
2426 pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
2432 * Determine the common suffix of two strings.
2433 * @param {string} text1 First string.
2434 * @param {string} text2 Second string.
2435 * @return {number} The number of characters common to the end of each string.
2437 DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
2438 var pointermid, pointermax, pointermin, pointerend;
2439 // Quick check for common null cases.
2442 text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) {
2446 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
2448 pointermax = Math.min( text1.length, text2.length );
2449 pointermid = pointermax;
2451 while ( pointermin < pointermid ) {
2452 if ( text1.substring( text1.length - pointermid, text1.length - pointerend ) ===
2453 text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {
2454 pointermin = pointermid;
2455 pointerend = pointermin;
2457 pointermax = pointermid;
2459 pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
2465 * Find the differences between two texts. Assumes that the texts do not
2466 * have any common prefix or suffix.
2467 * @param {string} text1 Old string to be diffed.
2468 * @param {string} text2 New string to be diffed.
2469 * @param {boolean} checklines Speedup flag. If false, then don't run a
2470 * line-level diff first to identify the changed areas.
2471 * If true, then run a faster, slightly less optimal diff.
2472 * @param {number} deadline Time when the diff should be complete by.
2473 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2476 DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
2477 var diffs, longtext, shorttext, i, hm,
2478 text1A, text2A, text1B, text2B,
2479 midCommon, diffsA, diffsB;
2482 // Just add some text (speedup).
2484 [ DIFF_INSERT, text2 ]
2489 // Just delete some text (speedup).
2491 [ DIFF_DELETE, text1 ]
2495 longtext = text1.length > text2.length ? text1 : text2;
2496 shorttext = text1.length > text2.length ? text2 : text1;
2497 i = longtext.indexOf( shorttext );
2499 // Shorter text is inside the longer text (speedup).
2501 [ DIFF_INSERT, longtext.substring( 0, i ) ],
2502 [ DIFF_EQUAL, shorttext ],
2503 [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
2505 // Swap insertions for deletions if diff is reversed.
2506 if ( text1.length > text2.length ) {
2507 diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE;
2512 if ( shorttext.length === 1 ) {
2513 // Single character string.
2514 // After the previous speedup, the character can't be an equality.
2516 [ DIFF_DELETE, text1 ],
2517 [ DIFF_INSERT, text2 ]
2521 // Check to see if the problem can be split in two.
2522 hm = this.diffHalfMatch( text1, text2 );
2524 // A half-match was found, sort out the return data.
2529 midCommon = hm[ 4 ];
2530 // Send both pairs off for separate processing.
2531 diffsA = this.DiffMain( text1A, text2A, checklines, deadline );
2532 diffsB = this.DiffMain( text1B, text2B, checklines, deadline );
2533 // Merge the results.
2534 return diffsA.concat( [
2535 [ DIFF_EQUAL, midCommon ]
2539 if ( checklines && text1.length > 100 && text2.length > 100 ) {
2540 return this.diffLineMode( text1, text2, deadline );
2543 return this.diffBisect( text1, text2, deadline );
2547 * Do the two texts share a substring which is at least half the length of the
2549 * This speedup can produce non-minimal diffs.
2550 * @param {string} text1 First string.
2551 * @param {string} text2 Second string.
2552 * @return {Array.<string>} Five element Array, containing the prefix of
2553 * text1, the suffix of text1, the prefix of text2, the suffix of
2554 * text2 and the common middle. Or null if there was no match.
2557 DiffMatchPatch.prototype.diffHalfMatch = function( text1, text2 ) {
2558 var longtext, shorttext, dmp,
2559 text1A, text2B, text2A, text1B, midCommon,
2562 longtext = text1.length > text2.length ? text1 : text2;
2563 shorttext = text1.length > text2.length ? text2 : text1;
2564 if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) {
2565 return null; // Pointless.
2567 dmp = this; // 'this' becomes 'window' in a closure.
2570 * Does a substring of shorttext exist within longtext such that the substring
2571 * is at least half the length of longtext?
2572 * Closure, but does not reference any external variables.
2573 * @param {string} longtext Longer string.
2574 * @param {string} shorttext Shorter string.
2575 * @param {number} i Start index of quarter length substring within longtext.
2576 * @return {Array.<string>} Five element Array, containing the prefix of
2577 * longtext, the suffix of longtext, the prefix of shorttext, the suffix
2578 * of shorttext and the common middle. Or null if there was no match.
2581 function diffHalfMatchI( longtext, shorttext, i ) {
2582 var seed, j, bestCommon, prefixLength, suffixLength,
2583 bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
2584 // Start with a 1/4 length substring at position i as a seed.
2585 seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) );
2588 while ( ( j = shorttext.indexOf( seed, j + 1 ) ) !== -1 ) {
2589 prefixLength = dmp.diffCommonPrefix( longtext.substring( i ),
2590 shorttext.substring( j ) );
2591 suffixLength = dmp.diffCommonSuffix( longtext.substring( 0, i ),
2592 shorttext.substring( 0, j ) );
2593 if ( bestCommon.length < suffixLength + prefixLength ) {
2594 bestCommon = shorttext.substring( j - suffixLength, j ) +
2595 shorttext.substring( j, j + prefixLength );
2596 bestLongtextA = longtext.substring( 0, i - suffixLength );
2597 bestLongtextB = longtext.substring( i + prefixLength );
2598 bestShorttextA = shorttext.substring( 0, j - suffixLength );
2599 bestShorttextB = shorttext.substring( j + prefixLength );
2602 if ( bestCommon.length * 2 >= longtext.length ) {
2603 return [ bestLongtextA, bestLongtextB,
2604 bestShorttextA, bestShorttextB, bestCommon
2611 // First check if the second quarter is the seed for a half-match.
2612 hm1 = diffHalfMatchI( longtext, shorttext,
2613 Math.ceil( longtext.length / 4 ) );
2614 // Check again based on the third quarter.
2615 hm2 = diffHalfMatchI( longtext, shorttext,
2616 Math.ceil( longtext.length / 2 ) );
2617 if ( !hm1 && !hm2 ) {
2619 } else if ( !hm2 ) {
2621 } else if ( !hm1 ) {
2624 // Both matched. Select the longest.
2625 hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2;
2628 // A half-match was found, sort out the return data.
2629 text1A, text1B, text2A, text2B;
2630 if ( text1.length > text2.length ) {
2641 midCommon = hm[ 4 ];
2642 return [ text1A, text1B, text2A, text2B, midCommon ];
2646 * Do a quick line-level diff on both strings, then rediff the parts for
2648 * This speedup can produce non-minimal diffs.
2649 * @param {string} text1 Old string to be diffed.
2650 * @param {string} text2 New string to be diffed.
2651 * @param {number} deadline Time when the diff should be complete by.
2652 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2655 DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) {
2656 var a, diffs, linearray, pointer, countInsert,
2657 countDelete, textInsert, textDelete, j;
2658 // Scan the text on a line-by-line basis first.
2659 a = this.diffLinesToChars( text1, text2 );
2662 linearray = a.lineArray;
2664 diffs = this.DiffMain( text1, text2, false, deadline );
2666 // Convert the diff back to original text.
2667 this.diffCharsToLines( diffs, linearray );
2668 // Eliminate freak matches (e.g. blank lines)
2669 this.diffCleanupSemantic( diffs );
2671 // Rediff any replacement blocks, this time character-by-character.
2672 // Add a dummy entry at the end.
2673 diffs.push( [ DIFF_EQUAL, "" ] );
2679 while ( pointer < diffs.length ) {
2680 switch ( diffs[ pointer ][ 0 ] ) {
2683 textInsert += diffs[ pointer ][ 1 ];
2687 textDelete += diffs[ pointer ][ 1 ];
2690 // Upon reaching an equality, check for prior redundancies.
2691 if ( countDelete >= 1 && countInsert >= 1 ) {
2692 // Delete the offending records and add the merged ones.
2693 diffs.splice( pointer - countDelete - countInsert,
2694 countDelete + countInsert );
2695 pointer = pointer - countDelete - countInsert;
2696 a = this.DiffMain( textDelete, textInsert, false, deadline );
2697 for ( j = a.length - 1; j >= 0; j-- ) {
2698 diffs.splice( pointer, 0, a[ j ] );
2700 pointer = pointer + a.length;
2710 diffs.pop(); // Remove the dummy entry at the end.
2716 * Find the 'middle snake' of a diff, split the problem in two
2717 * and return the recursively constructed diff.
2718 * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
2719 * @param {string} text1 Old string to be diffed.
2720 * @param {string} text2 New string to be diffed.
2721 * @param {number} deadline Time at which to bail if not yet complete.
2722 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2725 DiffMatchPatch.prototype.diffBisect = function( text1, text2, deadline ) {
2726 var text1Length, text2Length, maxD, vOffset, vLength,
2727 v1, v2, x, delta, front, k1start, k1end, k2start,
2728 k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
2729 // Cache the text lengths to prevent multiple calls.
2730 text1Length = text1.length;
2731 text2Length = text2.length;
2732 maxD = Math.ceil( ( text1Length + text2Length ) / 2 );
2735 v1 = new Array( vLength );
2736 v2 = new Array( vLength );
2737 // Setting all elements to -1 is faster in Chrome & Firefox than mixing
2738 // integers and undefined.
2739 for ( x = 0; x < vLength; x++ ) {
2743 v1[ vOffset + 1 ] = 0;
2744 v2[ vOffset + 1 ] = 0;
2745 delta = text1Length - text2Length;
2746 // If the total number of characters is odd, then the front path will collide
2747 // with the reverse path.
2748 front = ( delta % 2 !== 0 );
2749 // Offsets for start and end of k loop.
2750 // Prevents mapping of space beyond the grid.
2755 for ( d = 0; d < maxD; d++ ) {
2756 // Bail out if deadline is reached.
2757 if ( ( new Date() ).getTime() > deadline ) {
2761 // Walk the front path one step.
2762 for ( k1 = -d + k1start; k1 <= d - k1end; k1 += 2 ) {
2763 k1Offset = vOffset + k1;
2764 if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {
2765 x1 = v1[ k1Offset + 1 ];
2767 x1 = v1[ k1Offset - 1 ] + 1;
2770 while ( x1 < text1Length && y1 < text2Length &&
2771 text1.charAt( x1 ) === text2.charAt( y1 ) ) {
2775 v1[ k1Offset ] = x1;
2776 if ( x1 > text1Length ) {
2777 // Ran off the right of the graph.
2779 } else if ( y1 > text2Length ) {
2780 // Ran off the bottom of the graph.
2782 } else if ( front ) {
2783 k2Offset = vOffset + delta - k1;
2784 if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) {
2785 // Mirror x2 onto top-left coordinate system.
2786 x2 = text1Length - v2[ k2Offset ];
2788 // Overlap detected.
2789 return this.diffBisectSplit( text1, text2, x1, y1, deadline );
2795 // Walk the reverse path one step.
2796 for ( k2 = -d + k2start; k2 <= d - k2end; k2 += 2 ) {
2797 k2Offset = vOffset + k2;
2798 if ( k2 === -d || ( k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {
2799 x2 = v2[ k2Offset + 1 ];
2801 x2 = v2[ k2Offset - 1 ] + 1;
2804 while ( x2 < text1Length && y2 < text2Length &&
2805 text1.charAt( text1Length - x2 - 1 ) ===
2806 text2.charAt( text2Length - y2 - 1 ) ) {
2810 v2[ k2Offset ] = x2;
2811 if ( x2 > text1Length ) {
2812 // Ran off the left of the graph.
2814 } else if ( y2 > text2Length ) {
2815 // Ran off the top of the graph.
2817 } else if ( !front ) {
2818 k1Offset = vOffset + delta - k2;
2819 if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) {
2820 x1 = v1[ k1Offset ];
2821 y1 = vOffset + x1 - k1Offset;
2822 // Mirror x2 onto top-left coordinate system.
2823 x2 = text1Length - x2;
2825 // Overlap detected.
2826 return this.diffBisectSplit( text1, text2, x1, y1, deadline );
2832 // Diff took too long and hit the deadline or
2833 // number of diffs equals number of characters, no commonality at all.
2835 [ DIFF_DELETE, text1 ],
2836 [ DIFF_INSERT, text2 ]
2841 * Given the location of the 'middle snake', split the diff in two parts
2843 * @param {string} text1 Old string to be diffed.
2844 * @param {string} text2 New string to be diffed.
2845 * @param {number} x Index of split point in text1.
2846 * @param {number} y Index of split point in text2.
2847 * @param {number} deadline Time at which to bail if not yet complete.
2848 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2851 DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
2852 var text1a, text1b, text2a, text2b, diffs, diffsb;
2853 text1a = text1.substring( 0, x );
2854 text2a = text2.substring( 0, y );
2855 text1b = text1.substring( x );
2856 text2b = text2.substring( y );
2858 // Compute both diffs serially.
2859 diffs = this.DiffMain( text1a, text2a, false, deadline );
2860 diffsb = this.DiffMain( text1b, text2b, false, deadline );
2862 return diffs.concat( diffsb );
2866 * Reduce the number of edits by eliminating semantically trivial equalities.
2867 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2869 DiffMatchPatch.prototype.diffCleanupSemantic = function( diffs ) {
2870 var changes, equalities, equalitiesLength, lastequality,
2871 pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
2872 lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
2874 equalities = []; // Stack of indices where equalities are found.
2875 equalitiesLength = 0; // Keeping our own length var is faster in JS.
2876 /** @type {?string} */
2877 lastequality = null;
2878 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
2879 pointer = 0; // Index of current position.
2880 // Number of characters that changed prior to the equality.
2881 lengthInsertions1 = 0;
2882 lengthDeletions1 = 0;
2883 // Number of characters that changed after the equality.
2884 lengthInsertions2 = 0;
2885 lengthDeletions2 = 0;
2886 while ( pointer < diffs.length ) {
2887 if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
2888 equalities[ equalitiesLength++ ] = pointer;
2889 lengthInsertions1 = lengthInsertions2;
2890 lengthDeletions1 = lengthDeletions2;
2891 lengthInsertions2 = 0;
2892 lengthDeletions2 = 0;
2893 lastequality = diffs[ pointer ][ 1 ];
2894 } else { // An insertion or deletion.
2895 if ( diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
2896 lengthInsertions2 += diffs[ pointer ][ 1 ].length;
2898 lengthDeletions2 += diffs[ pointer ][ 1 ].length;
2900 // Eliminate an equality that is smaller or equal to the edits on both
2902 if ( lastequality && ( lastequality.length <=
2903 Math.max( lengthInsertions1, lengthDeletions1 ) ) &&
2904 ( lastequality.length <= Math.max( lengthInsertions2,
2905 lengthDeletions2 ) ) ) {
2907 // Duplicate record.
2909 equalities[ equalitiesLength - 1 ],
2911 [ DIFF_DELETE, lastequality ]
2914 // Change second copy to insert.
2915 diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
2917 // Throw away the equality we just deleted.
2920 // Throw away the previous equality (it needs to be reevaluated).
2922 pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
2924 // Reset the counters.
2925 lengthInsertions1 = 0;
2926 lengthDeletions1 = 0;
2927 lengthInsertions2 = 0;
2928 lengthDeletions2 = 0;
2929 lastequality = null;
2936 // Normalize the diff.
2938 this.diffCleanupMerge( diffs );
2941 // Find any overlaps between deletions and insertions.
2942 // e.g: <del>abcxxx</del><ins>xxxdef</ins>
2943 // -> <del>abc</del>xxx<ins>def</ins>
2944 // e.g: <del>xxxabc</del><ins>defxxx</ins>
2945 // -> <ins>def</ins>xxx<del>abc</del>
2946 // Only extract an overlap if it is as big as the edit ahead or behind it.
2948 while ( pointer < diffs.length ) {
2949 if ( diffs[ pointer - 1 ][ 0 ] === DIFF_DELETE &&
2950 diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
2951 deletion = diffs[ pointer - 1 ][ 1 ];
2952 insertion = diffs[ pointer ][ 1 ];
2953 overlapLength1 = this.diffCommonOverlap( deletion, insertion );
2954 overlapLength2 = this.diffCommonOverlap( insertion, deletion );
2955 if ( overlapLength1 >= overlapLength2 ) {
2956 if ( overlapLength1 >= deletion.length / 2 ||
2957 overlapLength1 >= insertion.length / 2 ) {
2958 // Overlap found. Insert an equality and trim the surrounding edits.
2962 [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ]
2964 diffs[ pointer - 1 ][ 1 ] =
2965 deletion.substring( 0, deletion.length - overlapLength1 );
2966 diffs[ pointer + 1 ][ 1 ] = insertion.substring( overlapLength1 );
2970 if ( overlapLength2 >= deletion.length / 2 ||
2971 overlapLength2 >= insertion.length / 2 ) {
2973 // Reverse overlap found.
2974 // Insert an equality and swap and trim the surrounding edits.
2978 [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ]
2981 diffs[ pointer - 1 ][ 0 ] = DIFF_INSERT;
2982 diffs[ pointer - 1 ][ 1 ] =
2983 insertion.substring( 0, insertion.length - overlapLength2 );
2984 diffs[ pointer + 1 ][ 0 ] = DIFF_DELETE;
2985 diffs[ pointer + 1 ][ 1 ] =
2986 deletion.substring( overlapLength2 );
2997 * Determine if the suffix of one string is the prefix of another.
2998 * @param {string} text1 First string.
2999 * @param {string} text2 Second string.
3000 * @return {number} The number of characters common to the end of the first
3001 * string and the start of the second string.
3004 DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) {
3005 var text1Length, text2Length, textLength,
3006 best, length, pattern, found;
3007 // Cache the text lengths to prevent multiple calls.
3008 text1Length = text1.length;
3009 text2Length = text2.length;
3010 // Eliminate the null case.
3011 if ( text1Length === 0 || text2Length === 0 ) {
3014 // Truncate the longer string.
3015 if ( text1Length > text2Length ) {
3016 text1 = text1.substring( text1Length - text2Length );
3017 } else if ( text1Length < text2Length ) {
3018 text2 = text2.substring( 0, text1Length );
3020 textLength = Math.min( text1Length, text2Length );
3021 // Quick check for the worst case.
3022 if ( text1 === text2 ) {
3026 // Start by looking for a single character match
3027 // and increase length until no match is found.
3028 // Performance analysis: https://neil.fraser.name/news/2010/11/04/
3032 pattern = text1.substring( textLength - length );
3033 found = text2.indexOf( pattern );
3034 if ( found === -1 ) {
3038 if ( found === 0 || text1.substring( textLength - length ) ===
3039 text2.substring( 0, length ) ) {
3047 * Split two texts into an array of strings. Reduce the texts to a string of
3048 * hashes where each Unicode character represents one line.
3049 * @param {string} text1 First string.
3050 * @param {string} text2 Second string.
3051 * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
3052 * An object containing the encoded text1, the encoded text2 and
3053 * the array of unique strings.
3054 * The zeroth element of the array of unique strings is intentionally blank.
3057 DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) {
3058 var lineArray, lineHash, chars1, chars2;
3059 lineArray = []; // e.g. lineArray[4] === 'Hello\n'
3060 lineHash = {}; // e.g. lineHash['Hello\n'] === 4
3062 // '\x00' is a valid character, but various debuggers don't like it.
3063 // So we'll insert a junk entry to avoid generating a null character.
3064 lineArray[ 0 ] = "";
3067 * Split a text into an array of strings. Reduce the texts to a string of
3068 * hashes where each Unicode character represents one line.
3069 * Modifies linearray and linehash through being a closure.
3070 * @param {string} text String to encode.
3071 * @return {string} Encoded string.
3074 function diffLinesToCharsMunge( text ) {
3075 var chars, lineStart, lineEnd, lineArrayLength, line;
3077 // Walk the text, pulling out a substring for each line.
3078 // text.split('\n') would would temporarily double our memory footprint.
3079 // Modifying text would create many large strings to garbage collect.
3082 // Keeping our own length variable is faster than looking it up.
3083 lineArrayLength = lineArray.length;
3084 while ( lineEnd < text.length - 1 ) {
3085 lineEnd = text.indexOf( "\n", lineStart );
3086 if ( lineEnd === -1 ) {
3087 lineEnd = text.length - 1;
3089 line = text.substring( lineStart, lineEnd + 1 );
3090 lineStart = lineEnd + 1;
3092 if ( lineHash.hasOwnProperty ? lineHash.hasOwnProperty( line ) :
3093 ( lineHash[ line ] !== undefined ) ) {
3094 chars += String.fromCharCode( lineHash[ line ] );
3096 chars += String.fromCharCode( lineArrayLength );
3097 lineHash[ line ] = lineArrayLength;
3098 lineArray[ lineArrayLength++ ] = line;
3104 chars1 = diffLinesToCharsMunge( text1 );
3105 chars2 = diffLinesToCharsMunge( text2 );
3109 lineArray: lineArray
3114 * Rehydrate the text in a diff from a string of line hashes to real lines of
3116 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
3117 * @param {!Array.<string>} lineArray Array of unique strings.
3120 DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {
3121 var x, chars, text, y;
3122 for ( x = 0; x < diffs.length; x++ ) {
3123 chars = diffs[ x ][ 1 ];
3125 for ( y = 0; y < chars.length; y++ ) {
3126 text[ y ] = lineArray[ chars.charCodeAt( y ) ];
3128 diffs[ x ][ 1 ] = text.join( "" );
3133 * Reorder and merge like edit sections. Merge equalities.
3134 * Any edit section can move as long as it doesn't cross an equality.
3135 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
3137 DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) {
3138 var pointer, countDelete, countInsert, textInsert, textDelete,
3139 commonlength, changes, diffPointer, position;
3140 diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
3147 while ( pointer < diffs.length ) {
3148 switch ( diffs[ pointer ][ 0 ] ) {
3151 textInsert += diffs[ pointer ][ 1 ];
3156 textDelete += diffs[ pointer ][ 1 ];
3160 // Upon reaching an equality, check for prior redundancies.
3161 if ( countDelete + countInsert > 1 ) {
3162 if ( countDelete !== 0 && countInsert !== 0 ) {
3163 // Factor out any common prefixes.
3164 commonlength = this.diffCommonPrefix( textInsert, textDelete );
3165 if ( commonlength !== 0 ) {
3166 if ( ( pointer - countDelete - countInsert ) > 0 &&
3167 diffs[ pointer - countDelete - countInsert - 1 ][ 0 ] ===
3169 diffs[ pointer - countDelete - countInsert - 1 ][ 1 ] +=
3170 textInsert.substring( 0, commonlength );
3172 diffs.splice( 0, 0, [ DIFF_EQUAL,
3173 textInsert.substring( 0, commonlength )
3177 textInsert = textInsert.substring( commonlength );
3178 textDelete = textDelete.substring( commonlength );
3180 // Factor out any common suffixies.
3181 commonlength = this.diffCommonSuffix( textInsert, textDelete );
3182 if ( commonlength !== 0 ) {
3183 diffs[ pointer ][ 1 ] = textInsert.substring( textInsert.length -
3184 commonlength ) + diffs[ pointer ][ 1 ];
3185 textInsert = textInsert.substring( 0, textInsert.length -
3187 textDelete = textDelete.substring( 0, textDelete.length -
3191 // Delete the offending records and add the merged ones.
3192 if ( countDelete === 0 ) {
3193 diffs.splice( pointer - countInsert,
3194 countDelete + countInsert, [ DIFF_INSERT, textInsert ] );
3195 } else if ( countInsert === 0 ) {
3196 diffs.splice( pointer - countDelete,
3197 countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
3200 pointer - countDelete - countInsert,
3201 countDelete + countInsert,
3202 [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ]
3205 pointer = pointer - countDelete - countInsert +
3206 ( countDelete ? 1 : 0 ) + ( countInsert ? 1 : 0 ) + 1;
3207 } else if ( pointer !== 0 && diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL ) {
3209 // Merge this equality with the previous one.
3210 diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ];
3211 diffs.splice( pointer, 1 );
3222 if ( diffs[ diffs.length - 1 ][ 1 ] === "" ) {
3223 diffs.pop(); // Remove the dummy entry at the end.
3226 // Second pass: look for single edits surrounded on both sides by equalities
3227 // which can be shifted sideways to eliminate an equality.
3228 // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
3232 // Intentionally ignore the first and last element (don't need checking).
3233 while ( pointer < diffs.length - 1 ) {
3234 if ( diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL &&
3235 diffs[ pointer + 1 ][ 0 ] === DIFF_EQUAL ) {
3237 diffPointer = diffs[ pointer ][ 1 ];
3238 position = diffPointer.substring(
3239 diffPointer.length - diffs[ pointer - 1 ][ 1 ].length
3242 // This is a single edit surrounded by equalities.
3243 if ( position === diffs[ pointer - 1 ][ 1 ] ) {
3245 // Shift the edit over the previous equality.
3246 diffs[ pointer ][ 1 ] = diffs[ pointer - 1 ][ 1 ] +
3247 diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer ][ 1 ].length -
3248 diffs[ pointer - 1 ][ 1 ].length );
3249 diffs[ pointer + 1 ][ 1 ] =
3250 diffs[ pointer - 1 ][ 1 ] + diffs[ pointer + 1 ][ 1 ];
3251 diffs.splice( pointer - 1, 1 );
3253 } else if ( diffPointer.substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
3254 diffs[ pointer + 1 ][ 1 ] ) {
3256 // Shift the edit over the next equality.
3257 diffs[ pointer - 1 ][ 1 ] += diffs[ pointer + 1 ][ 1 ];
3258 diffs[ pointer ][ 1 ] =
3259 diffs[ pointer ][ 1 ].substring( diffs[ pointer + 1 ][ 1 ].length ) +
3260 diffs[ pointer + 1 ][ 1 ];
3261 diffs.splice( pointer + 1, 1 );
3267 // If shifts were made, the diff needs reordering and another shift sweep.
3269 this.diffCleanupMerge( diffs );
3273 return function( o, n ) {
3274 var diff, output, text;
3275 diff = new DiffMatchPatch();
3276 output = diff.DiffMain( o, n );
3277 diff.diffCleanupEfficiency( output );
3278 text = diff.diffPrettyHtml( output );
3284 // Get a reference to the global object, like window in browsers
3291 // Don't load the HTML Reporter on non-Browser environments
3292 if ( typeof window === "undefined" || !window.document ) {
3296 // Deprecated QUnit.init - Ref #530
3297 // Re-initialize the configuration options
3298 QUnit.init = function() {
3299 var tests, banner, result, qunit,
3300 config = QUnit.config;
3302 config.stats = { all: 0, bad: 0 };
3303 config.moduleStats = { all: 0, bad: 0 };
3305 config.updateRate = 1000;
3306 config.blocking = false;
3307 config.autostart = true;
3308 config.autorun = false;
3312 // Return on non-browser environments
3313 // This is necessary to not break on node tests
3314 if ( typeof window === "undefined" ) {
3318 qunit = id( "qunit" );
3321 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
3322 "<h2 id='qunit-banner'></h2>" +
3323 "<div id='qunit-testrunner-toolbar'></div>" +
3324 "<h2 id='qunit-userAgent'></h2>" +
3325 "<ol id='qunit-tests'></ol>";
3328 tests = id( "qunit-tests" );
3329 banner = id( "qunit-banner" );
3330 result = id( "qunit-testresult" );
3333 tests.innerHTML = "";
3337 banner.className = "";
3341 result.parentNode.removeChild( result );
3345 result = document.createElement( "p" );
3346 result.id = "qunit-testresult";
3347 result.className = "result";
3348 tests.parentNode.insertBefore( result, tests );
3349 result.innerHTML = "Running...<br /> ";
3353 var config = QUnit.config,
3354 collapseNext = false,
3355 hasOwn = Object.prototype.hasOwnProperty,
3357 document: window.document !== undefined,
3358 sessionStorage: (function() {
3359 var x = "qunit-test-string";
3361 sessionStorage.setItem( x, x );
3362 sessionStorage.removeItem( x );
3372 * Escape text for attribute or text content.
3374 function escapeText( s ) {
3380 // Both single quotes and double quotes (for attributes)
3381 return s.replace( /['"<>&]/g, function( s ) {
3398 * @param {HTMLElement} elem
3399 * @param {string} type
3400 * @param {Function} fn
3402 function addEvent( elem, type, fn ) {
3403 if ( elem.addEventListener ) {
3405 // Standards-based browsers
3406 elem.addEventListener( type, fn, false );
3407 } else if ( elem.attachEvent ) {
3410 elem.attachEvent( "on" + type, function() {
3411 var event = window.event;
3412 if ( !event.target ) {
3413 event.target = event.srcElement || document;
3416 fn.call( elem, event );
3422 * @param {Array|NodeList} elems
3423 * @param {string} type
3424 * @param {Function} fn
3426 function addEvents( elems, type, fn ) {
3427 var i = elems.length;
3429 addEvent( elems[ i ], type, fn );
3433 function hasClass( elem, name ) {
3434 return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
3437 function addClass( elem, name ) {
3438 if ( !hasClass( elem, name ) ) {
3439 elem.className += ( elem.className ? " " : "" ) + name;
3443 function toggleClass( elem, name ) {
3444 if ( hasClass( elem, name ) ) {
3445 removeClass( elem, name );
3447 addClass( elem, name );
3451 function removeClass( elem, name ) {
3452 var set = " " + elem.className + " ";
3454 // Class name may appear multiple times
3455 while ( set.indexOf( " " + name + " " ) >= 0 ) {
3456 set = set.replace( " " + name + " ", " " );
3459 // trim for prettiness
3460 elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
3463 function id( name ) {
3464 return defined.document && document.getElementById && document.getElementById( name );
3467 function getUrlConfigHtml() {
3469 escaped, escapedTooltip,
3471 len = config.urlConfig.length,
3474 for ( i = 0; i < len; i++ ) {
3475 val = config.urlConfig[ i ];
3476 if ( typeof val === "string" ) {
3483 escaped = escapeText( val.id );
3484 escapedTooltip = escapeText( val.tooltip );
3486 if ( config[ val.id ] === undefined ) {
3487 config[ val.id ] = QUnit.urlParams[ val.id ];
3490 if ( !val.value || typeof val.value === "string" ) {
3491 urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
3492 "' name='" + escaped + "' type='checkbox'" +
3493 ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
3494 ( config[ val.id ] ? " checked='checked'" : "" ) +
3495 " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
3496 "' title='" + escapedTooltip + "'>" + val.label + "</label>";
3498 urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
3499 "' title='" + escapedTooltip + "'>" + val.label +
3500 ": </label><select id='qunit-urlconfig-" + escaped +
3501 "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
3503 if ( QUnit.is( "array", val.value ) ) {
3504 for ( j = 0; j < val.value.length; j++ ) {
3505 escaped = escapeText( val.value[ j ] );
3506 urlConfigHtml += "<option value='" + escaped + "'" +
3507 ( config[ val.id ] === val.value[ j ] ?
3508 ( selection = true ) && " selected='selected'" : "" ) +
3509 ">" + escaped + "</option>";
3512 for ( j in val.value ) {
3513 if ( hasOwn.call( val.value, j ) ) {
3514 urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
3515 ( config[ val.id ] === j ?
3516 ( selection = true ) && " selected='selected'" : "" ) +
3517 ">" + escapeText( val.value[ j ] ) + "</option>";
3521 if ( config[ val.id ] && !selection ) {
3522 escaped = escapeText( config[ val.id ] );
3523 urlConfigHtml += "<option value='" + escaped +
3524 "' selected='selected' disabled='disabled'>" + escaped + "</option>";
3526 urlConfigHtml += "</select>";
3530 return urlConfigHtml;
3533 // Handle "click" events on toolbar checkboxes and "change" for select menus.
3534 // Updates the URL with the new state of `config.urlConfig` values.
3535 function toolbarChanged() {
3536 var updatedUrl, value,
3540 // Detect if field is a select menu or a checkbox
3541 if ( "selectedIndex" in field ) {
3542 value = field.options[ field.selectedIndex ].value || undefined;
3544 value = field.checked ? ( field.defaultValue || true ) : undefined;
3547 params[ field.name ] = value;
3548 updatedUrl = setUrl( params );
3550 if ( "hidepassed" === field.name && "replaceState" in window.history ) {
3551 config[ field.name ] = value || false;
3553 addClass( id( "qunit-tests" ), "hidepass" );
3555 removeClass( id( "qunit-tests" ), "hidepass" );
3558 // It is not necessary to refresh the whole page
3559 window.history.replaceState( null, "", updatedUrl );
3561 window.location = updatedUrl;
3565 function setUrl( params ) {
3569 params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
3571 for ( key in params ) {
3572 if ( hasOwn.call( params, key ) ) {
3573 if ( params[ key ] === undefined ) {
3576 querystring += encodeURIComponent( key );
3577 if ( params[ key ] !== true ) {
3578 querystring += "=" + encodeURIComponent( params[ key ] );
3583 return location.protocol + "//" + location.host +
3584 location.pathname + querystring.slice( 0, -1 );
3587 function applyUrlParams() {
3589 modulesList = id( "qunit-modulefilter" ),
3590 filter = id( "qunit-filter-input" ).value;
3592 selectedModule = modulesList ?
3593 decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) :
3596 window.location = setUrl({
3597 module: ( selectedModule === "" ) ? undefined : selectedModule,
3598 filter: ( filter === "" ) ? undefined : filter,
3600 // Remove testId filter
3605 function toolbarUrlConfigContainer() {
3606 var urlConfigContainer = document.createElement( "span" );
3608 urlConfigContainer.innerHTML = getUrlConfigHtml();
3609 addClass( urlConfigContainer, "qunit-url-config" );
3611 // For oldIE support:
3612 // * Add handlers to the individual elements instead of the container
3613 // * Use "click" instead of "change" for checkboxes
3614 addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
3615 addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
3617 return urlConfigContainer;
3620 function toolbarLooseFilter() {
3621 var filter = document.createElement( "form" ),
3622 label = document.createElement( "label" ),
3623 input = document.createElement( "input" ),
3624 button = document.createElement( "button" );
3626 addClass( filter, "qunit-filter" );
3628 label.innerHTML = "Filter: ";
3630 input.type = "text";
3631 input.value = config.filter || "";
3632 input.name = "filter";
3633 input.id = "qunit-filter-input";
3635 button.innerHTML = "Go";
3637 label.appendChild( input );
3639 filter.appendChild( label );
3640 filter.appendChild( button );
3641 addEvent( filter, "submit", function( ev ) {
3644 if ( ev && ev.preventDefault ) {
3645 ev.preventDefault();
3654 function toolbarModuleFilterHtml() {
3656 moduleFilterHtml = "";
3658 if ( !modulesList.length ) {
3662 modulesList.sort(function( a, b ) {
3663 return a.localeCompare( b );
3666 moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
3667 "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
3668 ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
3669 ">< All Modules ></option>";
3671 for ( i = 0; i < modulesList.length; i++ ) {
3672 moduleFilterHtml += "<option value='" +
3673 escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
3674 ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
3675 ">" + escapeText( modulesList[ i ] ) + "</option>";
3677 moduleFilterHtml += "</select>";
3679 return moduleFilterHtml;
3682 function toolbarModuleFilter() {
3683 var toolbar = id( "qunit-testrunner-toolbar" ),
3684 moduleFilter = document.createElement( "span" ),
3685 moduleFilterHtml = toolbarModuleFilterHtml();
3687 if ( !toolbar || !moduleFilterHtml ) {
3691 moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
3692 moduleFilter.innerHTML = moduleFilterHtml;
3694 addEvent( moduleFilter.lastChild, "change", applyUrlParams );
3696 toolbar.appendChild( moduleFilter );
3699 function appendToolbar() {
3700 var toolbar = id( "qunit-testrunner-toolbar" );
3703 toolbar.appendChild( toolbarUrlConfigContainer() );
3704 toolbar.appendChild( toolbarLooseFilter() );
3708 function appendHeader() {
3709 var header = id( "qunit-header" );
3712 header.innerHTML = "<a href='" +
3713 setUrl({ filter: undefined, module: undefined, testId: undefined }) +
3714 "'>" + header.innerHTML + "</a> ";
3718 function appendBanner() {
3719 var banner = id( "qunit-banner" );
3722 banner.className = "";
3726 function appendTestResults() {
3727 var tests = id( "qunit-tests" ),
3728 result = id( "qunit-testresult" );
3731 result.parentNode.removeChild( result );
3735 tests.innerHTML = "";
3736 result = document.createElement( "p" );
3737 result.id = "qunit-testresult";
3738 result.className = "result";
3739 tests.parentNode.insertBefore( result, tests );
3740 result.innerHTML = "Running...<br /> ";
3744 function storeFixture() {
3745 var fixture = id( "qunit-fixture" );
3747 config.fixture = fixture.innerHTML;
3751 function appendFilteredTest() {
3752 var testId = QUnit.config.testId;
3753 if ( !testId || testId.length <= 0 ) {
3756 return "<div id='qunit-filteredTest'>Rerunning selected tests: " + testId.join(", ") +
3757 " <a id='qunit-clearFilter' href='" +
3758 setUrl({ filter: undefined, module: undefined, testId: undefined }) +
3759 "'>" + "Run all tests" + "</a></div>";
3762 function appendUserAgent() {
3763 var userAgent = id( "qunit-userAgent" );
3766 userAgent.innerHTML = "";
3767 userAgent.appendChild(
3768 document.createTextNode(
3769 "QUnit " + QUnit.version + "; " + navigator.userAgent
3775 function appendTestsList( modules ) {
3776 var i, l, x, z, test, moduleObj;
3778 for ( i = 0, l = modules.length; i < l; i++ ) {
3779 moduleObj = modules[ i ];
3781 if ( moduleObj.name ) {
3782 modulesList.push( moduleObj.name );
3785 for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
3786 test = moduleObj.tests[ x ];
3788 appendTest( test.name, test.testId, moduleObj.name );
3793 function appendTest( name, testId, moduleName ) {
3794 var title, rerunTrigger, testBlock, assertList,
3795 tests = id( "qunit-tests" );
3801 title = document.createElement( "strong" );
3802 title.innerHTML = getNameHtml( name, moduleName );
3804 rerunTrigger = document.createElement( "a" );
3805 rerunTrigger.innerHTML = "Rerun";
3806 rerunTrigger.href = setUrl({ testId: testId });
3808 testBlock = document.createElement( "li" );
3809 testBlock.appendChild( title );
3810 testBlock.appendChild( rerunTrigger );
3811 testBlock.id = "qunit-test-output-" + testId;
3813 assertList = document.createElement( "ol" );
3814 assertList.className = "qunit-assert-list";
3816 testBlock.appendChild( assertList );
3818 tests.appendChild( testBlock );
3821 // HTML Reporter initialization and load
3822 QUnit.begin(function( details ) {
3823 var qunit = id( "qunit" );
3825 // Fixture is the only one necessary to run without the #qunit element
3830 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
3831 "<h2 id='qunit-banner'></h2>" +
3832 "<div id='qunit-testrunner-toolbar'></div>" +
3833 appendFilteredTest() +
3834 "<h2 id='qunit-userAgent'></h2>" +
3835 "<ol id='qunit-tests'></ol>";
3840 appendTestResults();
3843 appendTestsList( details.modules );
3844 toolbarModuleFilter();
3846 if ( qunit && config.hidepassed ) {
3847 addClass( qunit.lastChild, "hidepass" );
3851 QUnit.done(function( details ) {
3853 banner = id( "qunit-banner" ),
3854 tests = id( "qunit-tests" ),
3856 "Tests completed in ",
3858 " milliseconds.<br />",
3859 "<span class='passed'>",
3861 "</span> assertions of <span class='total'>",
3863 "</span> passed, <span class='failed'>",
3869 banner.className = details.failed ? "qunit-fail" : "qunit-pass";
3873 id( "qunit-testresult" ).innerHTML = html;
3876 if ( config.altertitle && defined.document && document.title ) {
3878 // show ✖ for good, ✔ for bad suite result in title
3879 // use escape sequences in case file gets loaded with non-utf-8-charset
3881 ( details.failed ? "\u2716" : "\u2714" ),
3882 document.title.replace( /^[\u2714\u2716] /i, "" )
3886 // clear own sessionStorage items if all tests passed
3887 if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
3888 for ( i = 0; i < sessionStorage.length; i++ ) {
3889 key = sessionStorage.key( i++ );
3890 if ( key.indexOf( "qunit-test-" ) === 0 ) {
3891 sessionStorage.removeItem( key );
3896 // scroll back to top to show results
3897 if ( config.scrolltop && window.scrollTo ) {
3898 window.scrollTo( 0, 0 );
3902 function getNameHtml( name, module ) {
3906 nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
3909 nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
3914 QUnit.testStart(function( details ) {
3915 var running, testBlock, bad;
3917 testBlock = id( "qunit-test-output-" + details.testId );
3919 testBlock.className = "running";
3922 // Report later registered tests
3923 appendTest( details.name, details.testId, details.module );
3926 running = id( "qunit-testresult" );
3928 bad = QUnit.config.reorder && defined.sessionStorage &&
3929 +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name );
3931 running.innerHTML = ( bad ?
3932 "Rerunning previously failed test: <br />" :
3933 "Running: <br />" ) +
3934 getNameHtml( details.name, details.module );
3939 function stripHtml( string ) {
3940 // strip tags, html entity and whitespaces
3941 return string.replace(/<\/?[^>]+(>|$)/g, "").replace(/\"/g, "").replace(/\s+/g, "");
3944 QUnit.log(function( details ) {
3945 var assertList, assertLi,
3946 message, expected, actual, diff,
3948 testItem = id( "qunit-test-output-" + details.testId );
3954 message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
3955 message = "<span class='test-message'>" + message + "</span>";
3956 message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
3958 // pushFailure doesn't provide details.expected
3959 // when it calls, it's implicit to also not show expected and diff stuff
3960 // Also, we need to check details.expected existence, as it can exist and be undefined
3961 if ( !details.result && hasOwn.call( details, "expected" ) ) {
3962 if ( details.negative ) {
3963 expected = escapeText( "NOT " + QUnit.dump.parse( details.expected ) );
3965 expected = escapeText( QUnit.dump.parse( details.expected ) );
3968 actual = escapeText( QUnit.dump.parse( details.actual ) );
3969 message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
3973 if ( actual !== expected ) {
3975 message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
3976 actual + "</pre></td></tr>";
3978 // Don't show diff if actual or expected are booleans
3979 if ( !( /^(true|false)$/.test( actual ) ) &&
3980 !( /^(true|false)$/.test( expected ) ) ) {
3981 diff = QUnit.diff( expected, actual );
3982 showDiff = stripHtml( diff ).length !==
3983 stripHtml( expected ).length +
3984 stripHtml( actual ).length;
3987 // Don't show diff if expected and actual are totally different
3989 message += "<tr class='test-diff'><th>Diff: </th><td><pre>" +
3990 diff + "</pre></td></tr>";
3992 } else if ( expected.indexOf( "[object Array]" ) !== -1 ||
3993 expected.indexOf( "[object Object]" ) !== -1 ) {
3994 message += "<tr class='test-message'><th>Message: </th><td>" +
3995 "Diff suppressed as the depth of object is more than current max depth (" +
3996 QUnit.config.maxDepth + ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " +
3997 " run with a higher max depth or <a href='" + setUrl({ maxDepth: -1 }) + "'>" +
3998 "Rerun</a> without max depth.</p></td></tr>";
4001 if ( details.source ) {
4002 message += "<tr class='test-source'><th>Source: </th><td><pre>" +
4003 escapeText( details.source ) + "</pre></td></tr>";
4006 message += "</table>";
4008 // this occurs when pushFailure is set and we have an extracted stack trace
4009 } else if ( !details.result && details.source ) {
4010 message += "<table>" +
4011 "<tr class='test-source'><th>Source: </th><td><pre>" +
4012 escapeText( details.source ) + "</pre></td></tr>" +
4016 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
4018 assertLi = document.createElement( "li" );
4019 assertLi.className = details.result ? "pass" : "fail";
4020 assertLi.innerHTML = message;
4021 assertList.appendChild( assertLi );
4024 QUnit.testDone(function( details ) {
4025 var testTitle, time, testItem, assertList,
4026 good, bad, testCounts, skipped, sourceName,
4027 tests = id( "qunit-tests" );
4033 testItem = id( "qunit-test-output-" + details.testId );
4035 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
4037 good = details.passed;
4038 bad = details.failed;
4040 // store result when possible
4041 if ( config.reorder && defined.sessionStorage ) {
4043 sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
4045 sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
4051 // Collapse the passing tests
4052 addClass( assertList, "qunit-collapsed" );
4053 } else if ( bad && config.collapse && !collapseNext ) {
4055 // Skip collapsing the first failing test
4056 collapseNext = true;
4059 // Collapse remaining tests
4060 addClass( assertList, "qunit-collapsed" );
4063 // testItem.firstChild is the test name
4064 testTitle = testItem.firstChild;
4067 "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
4070 testTitle.innerHTML += " <b class='counts'>(" + testCounts +
4071 details.assertions.length + ")</b>";
4073 if ( details.skipped ) {
4074 testItem.className = "skipped";
4075 skipped = document.createElement( "em" );
4076 skipped.className = "qunit-skipped-label";
4077 skipped.innerHTML = "skipped";
4078 testItem.insertBefore( skipped, testTitle );
4080 addEvent( testTitle, "click", function() {
4081 toggleClass( assertList, "qunit-collapsed" );
4084 testItem.className = bad ? "fail" : "pass";
4086 time = document.createElement( "span" );
4087 time.className = "runtime";
4088 time.innerHTML = details.runtime + " ms";
4089 testItem.insertBefore( time, assertList );
4092 // Show the source of the test when showing assertions
4093 if ( details.source ) {
4094 sourceName = document.createElement( "p" );
4095 sourceName.innerHTML = "<strong>Source: </strong>" + details.source;
4096 addClass( sourceName, "qunit-source" );
4098 addClass( sourceName, "qunit-collapsed" );
4100 addEvent( testTitle, "click", function() {
4101 toggleClass( sourceName, "qunit-collapsed" );
4103 testItem.appendChild( sourceName );
4107 if ( defined.document ) {
4109 // Avoid readyState issue with phantomjs
4111 var notPhantom = ( function( p ) {
4112 return !( p && p.version && p.version.major > 0 );
4113 } )( window.phantom );
4115 if ( notPhantom && document.readyState === "complete" ) {
4118 addEvent( window, "load", QUnit.load );
4121 config.pageLoaded = true;
4122 config.autorun = true;