Check that file is executable in Installer::locateExecutable
[mediawiki.git] / resources / lib / qunitjs / qunit.js
blob84873ae576dc5d86085715f0a83af12e2fb18e5a
1 /*!
2 * QUnit 1.22.0
3 * https://qunitjs.com/
5 * Copyright jQuery Foundation and other contributors
6 * Released under the MIT license
7 * https://jquery.org/license
9 * Date: 2016-02-23T15:57Z
12 (function( global ) {
14 var QUnit = {};
16 var Date = global.Date;
17 var now = Date.now || function() {
18 return new Date().getTime();
21 var setTimeout = global.setTimeout;
22 var clearTimeout = global.clearTimeout;
24 // Store a local window from the global to allow direct references.
25 var window = global.window;
27 var defined = {
28 document: window && window.document !== undefined,
29 setTimeout: setTimeout !== undefined,
30 sessionStorage: (function() {
31 var x = "qunit-test-string";
32 try {
33 sessionStorage.setItem( x, x );
34 sessionStorage.removeItem( x );
35 return true;
36 } catch ( e ) {
37 return false;
39 }() )
42 var fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" );
43 var globalStartCalled = false;
44 var runStarted = false;
46 var toString = Object.prototype.toString,
47 hasOwn = Object.prototype.hasOwnProperty;
49 // returns a new Array with the elements that are in a but not in b
50 function diff( a, b ) {
51 var i, j,
52 result = a.slice();
54 for ( i = 0; i < result.length; i++ ) {
55 for ( j = 0; j < b.length; j++ ) {
56 if ( result[ i ] === b[ j ] ) {
57 result.splice( i, 1 );
58 i--;
59 break;
63 return result;
66 // from jquery.js
67 function inArray( elem, array ) {
68 if ( array.indexOf ) {
69 return array.indexOf( elem );
72 for ( var i = 0, length = array.length; i < length; i++ ) {
73 if ( array[ i ] === elem ) {
74 return i;
78 return -1;
81 /**
82 * Makes a clone of an object using only Array or Object as base,
83 * and copies over the own enumerable properties.
85 * @param {Object} obj
86 * @return {Object} New object with only the own properties (recursively).
88 function objectValues ( obj ) {
89 var key, val,
90 vals = QUnit.is( "array", obj ) ? [] : {};
91 for ( key in obj ) {
92 if ( hasOwn.call( obj, key ) ) {
93 val = obj[ key ];
94 vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
97 return vals;
100 function extend( a, b, undefOnly ) {
101 for ( var prop in b ) {
102 if ( hasOwn.call( b, prop ) ) {
104 // Avoid "Member not found" error in IE8 caused by messing with window.constructor
105 // This block runs on every environment, so `global` is being used instead of `window`
106 // to avoid errors on node.
107 if ( prop !== "constructor" || a !== global ) {
108 if ( b[ prop ] === undefined ) {
109 delete a[ prop ];
110 } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
111 a[ prop ] = b[ prop ];
117 return a;
120 function objectType( obj ) {
121 if ( typeof obj === "undefined" ) {
122 return "undefined";
125 // Consider: typeof null === object
126 if ( obj === null ) {
127 return "null";
130 var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
131 type = match && match[ 1 ];
133 switch ( type ) {
134 case "Number":
135 if ( isNaN( obj ) ) {
136 return "nan";
138 return "number";
139 case "String":
140 case "Boolean":
141 case "Array":
142 case "Set":
143 case "Map":
144 case "Date":
145 case "RegExp":
146 case "Function":
147 case "Symbol":
148 return type.toLowerCase();
150 if ( typeof obj === "object" ) {
151 return "object";
155 // Safe object type checking
156 function is( type, obj ) {
157 return QUnit.objectType( obj ) === type;
160 var getUrlParams = function() {
161 var i, param, name, value;
162 var urlParams = {};
163 var location = window.location;
164 var params = location.search.slice( 1 ).split( "&" );
165 var length = params.length;
167 for ( i = 0; i < length; i++ ) {
168 if ( params[ i ] ) {
169 param = params[ i ].split( "=" );
170 name = decodeURIComponent( param[ 0 ] );
172 // allow just a key to turn on a flag, e.g., test.html?noglobals
173 value = param.length === 1 ||
174 decodeURIComponent( param.slice( 1 ).join( "=" ) ) ;
175 if ( urlParams[ name ] ) {
176 urlParams[ name ] = [].concat( urlParams[ name ], value );
177 } else {
178 urlParams[ name ] = value;
183 return urlParams;
186 // Doesn't support IE6 to IE9, it will return undefined on these browsers
187 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
188 function extractStacktrace( e, offset ) {
189 offset = offset === undefined ? 4 : offset;
191 var stack, include, i;
193 if ( e.stack ) {
194 stack = e.stack.split( "\n" );
195 if ( /^error$/i.test( stack[ 0 ] ) ) {
196 stack.shift();
198 if ( fileName ) {
199 include = [];
200 for ( i = offset; i < stack.length; i++ ) {
201 if ( stack[ i ].indexOf( fileName ) !== -1 ) {
202 break;
204 include.push( stack[ i ] );
206 if ( include.length ) {
207 return include.join( "\n" );
210 return stack[ offset ];
212 // Support: Safari <=6 only
213 } else if ( e.sourceURL ) {
215 // exclude useless self-reference for generated Error objects
216 if ( /qunit.js$/.test( e.sourceURL ) ) {
217 return;
220 // for actual exceptions, this is useful
221 return e.sourceURL + ":" + e.line;
225 function sourceFromStacktrace( offset ) {
226 var error = new Error();
228 // Support: Safari <=7 only, IE <=10 - 11 only
229 // Not all browsers generate the `stack` property for `new Error()`, see also #636
230 if ( !error.stack ) {
231 try {
232 throw error;
233 } catch ( err ) {
234 error = err;
238 return extractStacktrace( error, offset );
242 * Config object: Maintain internal state
243 * Later exposed as QUnit.config
244 * `config` initialized at top of scope
246 var config = {
247 // The queue of tests to run
248 queue: [],
250 // block until document ready
251 blocking: true,
253 // by default, run previously failed tests first
254 // very useful in combination with "Hide passed tests" checked
255 reorder: true,
257 // by default, modify document.title when suite is done
258 altertitle: true,
260 // HTML Reporter: collapse every test except the first failing test
261 // If false, all failing tests will be expanded
262 collapse: true,
264 // by default, scroll to top of the page when suite is done
265 scrolltop: true,
267 // depth up-to which object will be dumped
268 maxDepth: 5,
270 // when enabled, all tests must call expect()
271 requireExpects: false,
273 // add checkboxes that are persisted in the query-string
274 // when enabled, the id is set to `true` as a `QUnit.config` property
275 urlConfig: [
277 id: "hidepassed",
278 label: "Hide passed tests",
279 tooltip: "Only show tests and assertions that fail. Stored as query-strings."
282 id: "noglobals",
283 label: "Check for Globals",
284 tooltip: "Enabling this will test if any test introduces new properties on the " +
285 "global object (`window` in Browsers). Stored as query-strings."
288 id: "notrycatch",
289 label: "No try-catch",
290 tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
291 "exceptions in IE reasonable. Stored as query-strings."
295 // Set of all modules.
296 modules: [],
298 // Stack of nested modules
299 moduleStack: [],
301 // The first unnamed module
302 currentModule: {
303 name: "",
304 tests: []
307 callbacks: {}
310 var urlParams = defined.document ? getUrlParams() : {};
312 // Push a loose unnamed module to the modules collection
313 config.modules.push( config.currentModule );
315 if ( urlParams.filter === true ) {
316 delete urlParams.filter;
319 // String search anywhere in moduleName+testName
320 config.filter = urlParams.filter;
322 config.testId = [];
323 if ( urlParams.testId ) {
324 // Ensure that urlParams.testId is an array
325 urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," );
326 for (var i = 0; i < urlParams.testId.length; i++ ) {
327 config.testId.push( urlParams.testId[ i ] );
331 var loggingCallbacks = {};
333 // Register logging callbacks
334 function registerLoggingCallbacks( obj ) {
335 var i, l, key,
336 callbackNames = [ "begin", "done", "log", "testStart", "testDone",
337 "moduleStart", "moduleDone" ];
339 function registerLoggingCallback( key ) {
340 var loggingCallback = function( callback ) {
341 if ( objectType( callback ) !== "function" ) {
342 throw new Error(
343 "QUnit logging methods require a callback function as their first parameters."
347 config.callbacks[ key ].push( callback );
350 // DEPRECATED: This will be removed on QUnit 2.0.0+
351 // Stores the registered functions allowing restoring
352 // at verifyLoggingCallbacks() if modified
353 loggingCallbacks[ key ] = loggingCallback;
355 return loggingCallback;
358 for ( i = 0, l = callbackNames.length; i < l; i++ ) {
359 key = callbackNames[ i ];
361 // Initialize key collection of logging callback
362 if ( objectType( config.callbacks[ key ] ) === "undefined" ) {
363 config.callbacks[ key ] = [];
366 obj[ key ] = registerLoggingCallback( key );
370 function runLoggingCallbacks( key, args ) {
371 var i, l, callbacks;
373 callbacks = config.callbacks[ key ];
374 for ( i = 0, l = callbacks.length; i < l; i++ ) {
375 callbacks[ i ]( args );
379 // DEPRECATED: This will be removed on 2.0.0+
380 // This function verifies if the loggingCallbacks were modified by the user
381 // If so, it will restore it, assign the given callback and print a console warning
382 function verifyLoggingCallbacks() {
383 var loggingCallback, userCallback;
385 for ( loggingCallback in loggingCallbacks ) {
386 if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
388 userCallback = QUnit[ loggingCallback ];
390 // Restore the callback function
391 QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
393 // Assign the deprecated given callback
394 QUnit[ loggingCallback ]( userCallback );
396 if ( global.console && global.console.warn ) {
397 global.console.warn(
398 "QUnit." + loggingCallback + " was replaced with a new value.\n" +
399 "Please, check out the documentation on how to apply logging callbacks.\n" +
400 "Reference: https://api.qunitjs.com/category/callbacks/"
407 ( function() {
408 if ( !defined.document ) {
409 return;
412 // `onErrorFnPrev` initialized at top of scope
413 // Preserve other handlers
414 var onErrorFnPrev = window.onerror;
416 // Cover uncaught exceptions
417 // Returning true will suppress the default browser handler,
418 // returning false will let it run.
419 window.onerror = function( error, filePath, linerNr ) {
420 var ret = false;
421 if ( onErrorFnPrev ) {
422 ret = onErrorFnPrev( error, filePath, linerNr );
425 // Treat return value as window.onerror itself does,
426 // Only do our handling if not suppressed.
427 if ( ret !== true ) {
428 if ( QUnit.config.current ) {
429 if ( QUnit.config.current.ignoreGlobalErrors ) {
430 return true;
432 QUnit.pushFailure( error, filePath + ":" + linerNr );
433 } else {
434 QUnit.test( "global failure", extend(function() {
435 QUnit.pushFailure( error, filePath + ":" + linerNr );
436 }, { validTest: true } ) );
438 return false;
441 return ret;
443 } )();
445 QUnit.urlParams = urlParams;
447 // Figure out if we're running the tests from a server or not
448 QUnit.isLocal = !( defined.document && window.location.protocol !== "file:" );
450 // Expose the current QUnit version
451 QUnit.version = "1.22.0";
453 extend( QUnit, {
455 // call on start of module test to prepend name to all tests
456 module: function( name, testEnvironment, executeNow ) {
457 var module, moduleFns;
458 var currentModule = config.currentModule;
460 if ( arguments.length === 2 ) {
461 if ( testEnvironment instanceof Function ) {
462 executeNow = testEnvironment;
463 testEnvironment = undefined;
467 // DEPRECATED: handles setup/teardown functions,
468 // beforeEach and afterEach should be used instead
469 if ( testEnvironment && testEnvironment.setup ) {
470 testEnvironment.beforeEach = testEnvironment.setup;
471 delete testEnvironment.setup;
473 if ( testEnvironment && testEnvironment.teardown ) {
474 testEnvironment.afterEach = testEnvironment.teardown;
475 delete testEnvironment.teardown;
478 module = createModule();
480 moduleFns = {
481 beforeEach: setHook( module, "beforeEach" ),
482 afterEach: setHook( module, "afterEach" )
485 if ( executeNow instanceof Function ) {
486 config.moduleStack.push( module );
487 setCurrentModule( module );
488 executeNow.call( module.testEnvironment, moduleFns );
489 config.moduleStack.pop();
490 module = module.parentModule || currentModule;
493 setCurrentModule( module );
495 function createModule() {
496 var parentModule = config.moduleStack.length ?
497 config.moduleStack.slice( -1 )[ 0 ] : null;
498 var moduleName = parentModule !== null ?
499 [ parentModule.name, name ].join( " > " ) : name;
500 var module = {
501 name: moduleName,
502 parentModule: parentModule,
503 tests: []
506 var env = {};
507 if ( parentModule ) {
508 extend( env, parentModule.testEnvironment );
509 delete env.beforeEach;
510 delete env.afterEach;
512 extend( env, testEnvironment );
513 module.testEnvironment = env;
515 config.modules.push( module );
516 return module;
519 function setCurrentModule( module ) {
520 config.currentModule = module;
525 // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
526 asyncTest: asyncTest,
528 test: test,
530 skip: skip,
532 only: only,
534 // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
535 // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
536 start: function( count ) {
537 var globalStartAlreadyCalled = globalStartCalled;
539 if ( !config.current ) {
540 globalStartCalled = true;
542 if ( runStarted ) {
543 throw new Error( "Called start() outside of a test context while already started" );
544 } else if ( globalStartAlreadyCalled || count > 1 ) {
545 throw new Error( "Called start() outside of a test context too many times" );
546 } else if ( config.autostart ) {
547 throw new Error( "Called start() outside of a test context when " +
548 "QUnit.config.autostart was true" );
549 } else if ( !config.pageLoaded ) {
551 // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
552 config.autostart = true;
553 return;
555 } else {
557 // If a test is running, adjust its semaphore
558 config.current.semaphore -= count || 1;
560 // If semaphore is non-numeric, throw error
561 if ( isNaN( config.current.semaphore ) ) {
562 config.current.semaphore = 0;
564 QUnit.pushFailure(
565 "Called start() with a non-numeric decrement.",
566 sourceFromStacktrace( 2 )
568 return;
571 // Don't start until equal number of stop-calls
572 if ( config.current.semaphore > 0 ) {
573 return;
576 // throw an Error if start is called more often than stop
577 if ( config.current.semaphore < 0 ) {
578 config.current.semaphore = 0;
580 QUnit.pushFailure(
581 "Called start() while already started (test's semaphore was 0 already)",
582 sourceFromStacktrace( 2 )
584 return;
588 resumeProcessing();
591 // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
592 stop: function( count ) {
594 // If there isn't a test running, don't allow QUnit.stop() to be called
595 if ( !config.current ) {
596 throw new Error( "Called stop() outside of a test context" );
599 // If a test is running, adjust its semaphore
600 config.current.semaphore += count || 1;
602 pauseProcessing();
605 config: config,
607 is: is,
609 objectType: objectType,
611 extend: extend,
613 load: function() {
614 config.pageLoaded = true;
616 // Initialize the configuration options
617 extend( config, {
618 stats: { all: 0, bad: 0 },
619 moduleStats: { all: 0, bad: 0 },
620 started: 0,
621 updateRate: 1000,
622 autostart: true,
623 filter: ""
624 }, true );
626 config.blocking = false;
628 if ( config.autostart ) {
629 resumeProcessing();
633 stack: function( offset ) {
634 offset = ( offset || 0 ) + 2;
635 return sourceFromStacktrace( offset );
639 registerLoggingCallbacks( QUnit );
641 function begin() {
642 var i, l,
643 modulesLog = [];
645 // If the test run hasn't officially begun yet
646 if ( !config.started ) {
648 // Record the time of the test run's beginning
649 config.started = now();
651 verifyLoggingCallbacks();
653 // Delete the loose unnamed module if unused.
654 if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
655 config.modules.shift();
658 // Avoid unnecessary information by not logging modules' test environments
659 for ( i = 0, l = config.modules.length; i < l; i++ ) {
660 modulesLog.push({
661 name: config.modules[ i ].name,
662 tests: config.modules[ i ].tests
666 // The test run is officially beginning now
667 runLoggingCallbacks( "begin", {
668 totalTests: Test.count,
669 modules: modulesLog
673 config.blocking = false;
674 process( true );
677 function process( last ) {
678 function next() {
679 process( last );
681 var start = now();
682 config.depth = ( config.depth || 0 ) + 1;
684 while ( config.queue.length && !config.blocking ) {
685 if ( !defined.setTimeout || config.updateRate <= 0 ||
686 ( ( now() - start ) < config.updateRate ) ) {
687 if ( config.current ) {
689 // Reset async tracking for each phase of the Test lifecycle
690 config.current.usedAsync = false;
692 config.queue.shift()();
693 } else {
694 setTimeout( next, 13 );
695 break;
698 config.depth--;
699 if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
700 done();
704 function pauseProcessing() {
705 config.blocking = true;
707 if ( config.testTimeout && defined.setTimeout ) {
708 clearTimeout( config.timeout );
709 config.timeout = setTimeout(function() {
710 if ( config.current ) {
711 config.current.semaphore = 0;
712 QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
713 } else {
714 throw new Error( "Test timed out" );
716 resumeProcessing();
717 }, config.testTimeout );
721 function resumeProcessing() {
722 runStarted = true;
724 // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
725 if ( defined.setTimeout ) {
726 setTimeout(function() {
727 if ( config.current && config.current.semaphore > 0 ) {
728 return;
730 if ( config.timeout ) {
731 clearTimeout( config.timeout );
734 begin();
735 }, 13 );
736 } else {
737 begin();
741 function done() {
742 var runtime, passed;
744 config.autorun = true;
746 // Log the last module results
747 if ( config.previousModule ) {
748 runLoggingCallbacks( "moduleDone", {
749 name: config.previousModule.name,
750 tests: config.previousModule.tests,
751 failed: config.moduleStats.bad,
752 passed: config.moduleStats.all - config.moduleStats.bad,
753 total: config.moduleStats.all,
754 runtime: now() - config.moduleStats.started
757 delete config.previousModule;
759 runtime = now() - config.started;
760 passed = config.stats.all - config.stats.bad;
762 runLoggingCallbacks( "done", {
763 failed: config.stats.bad,
764 passed: passed,
765 total: config.stats.all,
766 runtime: runtime
770 function setHook( module, hookName ) {
771 if ( module.testEnvironment === undefined ) {
772 module.testEnvironment = {};
775 return function( callback ) {
776 module.testEnvironment[ hookName ] = callback;
780 var focused = false;
781 var priorityCount = 0;
783 function Test( settings ) {
784 var i, l;
786 ++Test.count;
788 extend( this, settings );
789 this.assertions = [];
790 this.semaphore = 0;
791 this.usedAsync = false;
792 this.module = config.currentModule;
793 this.stack = sourceFromStacktrace( 3 );
795 // Register unique strings
796 for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
797 if ( this.module.tests[ i ].name === this.testName ) {
798 this.testName += " ";
802 this.testId = generateHash( this.module.name, this.testName );
804 this.module.tests.push({
805 name: this.testName,
806 testId: this.testId
809 if ( settings.skip ) {
811 // Skipped tests will fully ignore any sent callback
812 this.callback = function() {};
813 this.async = false;
814 this.expected = 0;
815 } else {
816 this.assert = new Assert( this );
820 Test.count = 0;
822 Test.prototype = {
823 before: function() {
824 if (
826 // Emit moduleStart when we're switching from one module to another
827 this.module !== config.previousModule ||
829 // They could be equal (both undefined) but if the previousModule property doesn't
830 // yet exist it means this is the first test in a suite that isn't wrapped in a
831 // module, in which case we'll just emit a moduleStart event for 'undefined'.
832 // Without this, reporters can get testStart before moduleStart which is a problem.
833 !hasOwn.call( config, "previousModule" )
835 if ( hasOwn.call( config, "previousModule" ) ) {
836 runLoggingCallbacks( "moduleDone", {
837 name: config.previousModule.name,
838 tests: config.previousModule.tests,
839 failed: config.moduleStats.bad,
840 passed: config.moduleStats.all - config.moduleStats.bad,
841 total: config.moduleStats.all,
842 runtime: now() - config.moduleStats.started
845 config.previousModule = this.module;
846 config.moduleStats = { all: 0, bad: 0, started: now() };
847 runLoggingCallbacks( "moduleStart", {
848 name: this.module.name,
849 tests: this.module.tests
853 config.current = this;
855 if ( this.module.testEnvironment ) {
856 delete this.module.testEnvironment.beforeEach;
857 delete this.module.testEnvironment.afterEach;
859 this.testEnvironment = extend( {}, this.module.testEnvironment );
861 this.started = now();
862 runLoggingCallbacks( "testStart", {
863 name: this.testName,
864 module: this.module.name,
865 testId: this.testId
868 if ( !config.pollution ) {
869 saveGlobal();
873 run: function() {
874 var promise;
876 config.current = this;
878 if ( this.async ) {
879 QUnit.stop();
882 this.callbackStarted = now();
884 if ( config.notrycatch ) {
885 runTest( this );
886 return;
889 try {
890 runTest( this );
891 } catch ( e ) {
892 this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
893 this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
895 // else next test will carry the responsibility
896 saveGlobal();
898 // Restart the tests if they're blocking
899 if ( config.blocking ) {
900 QUnit.start();
904 function runTest( test ) {
905 promise = test.callback.call( test.testEnvironment, test.assert );
906 test.resolvePromise( promise );
910 after: function() {
911 checkPollution();
914 queueHook: function( hook, hookName ) {
915 var promise,
916 test = this;
917 return function runHook() {
918 config.current = test;
919 if ( config.notrycatch ) {
920 callHook();
921 return;
923 try {
924 callHook();
925 } catch ( error ) {
926 test.pushFailure( hookName + " failed on " + test.testName + ": " +
927 ( error.message || error ), extractStacktrace( error, 0 ) );
930 function callHook() {
931 promise = hook.call( test.testEnvironment, test.assert );
932 test.resolvePromise( promise, hookName );
937 // Currently only used for module level hooks, can be used to add global level ones
938 hooks: function( handler ) {
939 var hooks = [];
941 function processHooks( test, module ) {
942 if ( module.parentModule ) {
943 processHooks( test, module.parentModule );
945 if ( module.testEnvironment &&
946 QUnit.objectType( module.testEnvironment[ handler ] ) === "function" ) {
947 hooks.push( test.queueHook( module.testEnvironment[ handler ], handler ) );
951 // Hooks are ignored on skipped tests
952 if ( !this.skip ) {
953 processHooks( this, this.module );
955 return hooks;
958 finish: function() {
959 config.current = this;
960 if ( config.requireExpects && this.expected === null ) {
961 this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
962 "not called.", this.stack );
963 } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
964 this.pushFailure( "Expected " + this.expected + " assertions, but " +
965 this.assertions.length + " were run", this.stack );
966 } else if ( this.expected === null && !this.assertions.length ) {
967 this.pushFailure( "Expected at least one assertion, but none were run - call " +
968 "expect(0) to accept zero assertions.", this.stack );
971 var i,
972 bad = 0;
974 this.runtime = now() - this.started;
975 config.stats.all += this.assertions.length;
976 config.moduleStats.all += this.assertions.length;
978 for ( i = 0; i < this.assertions.length; i++ ) {
979 if ( !this.assertions[ i ].result ) {
980 bad++;
981 config.stats.bad++;
982 config.moduleStats.bad++;
986 runLoggingCallbacks( "testDone", {
987 name: this.testName,
988 module: this.module.name,
989 skipped: !!this.skip,
990 failed: bad,
991 passed: this.assertions.length - bad,
992 total: this.assertions.length,
993 runtime: this.runtime,
995 // HTML Reporter use
996 assertions: this.assertions,
997 testId: this.testId,
999 // Source of Test
1000 source: this.stack,
1002 // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
1003 duration: this.runtime
1006 // QUnit.reset() is deprecated and will be replaced for a new
1007 // fixture reset function on QUnit 2.0/2.1.
1008 // It's still called here for backwards compatibility handling
1009 QUnit.reset();
1011 config.current = undefined;
1014 queue: function() {
1015 var priority,
1016 test = this;
1018 if ( !this.valid() ) {
1019 return;
1022 function run() {
1024 // each of these can by async
1025 synchronize([
1026 function() {
1027 test.before();
1030 test.hooks( "beforeEach" ),
1031 function() {
1032 test.run();
1035 test.hooks( "afterEach" ).reverse(),
1037 function() {
1038 test.after();
1040 function() {
1041 test.finish();
1046 // Prioritize previously failed tests, detected from sessionStorage
1047 priority = QUnit.config.reorder && defined.sessionStorage &&
1048 +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
1050 return synchronize( run, priority );
1053 pushResult: function( resultInfo ) {
1055 // resultInfo = { result, actual, expected, message, negative }
1056 var source,
1057 details = {
1058 module: this.module.name,
1059 name: this.testName,
1060 result: resultInfo.result,
1061 message: resultInfo.message,
1062 actual: resultInfo.actual,
1063 expected: resultInfo.expected,
1064 testId: this.testId,
1065 negative: resultInfo.negative || false,
1066 runtime: now() - this.started
1069 if ( !resultInfo.result ) {
1070 source = sourceFromStacktrace();
1072 if ( source ) {
1073 details.source = source;
1077 runLoggingCallbacks( "log", details );
1079 this.assertions.push({
1080 result: !!resultInfo.result,
1081 message: resultInfo.message
1085 pushFailure: function( message, source, actual ) {
1086 if ( !( this instanceof Test ) ) {
1087 throw new Error( "pushFailure() assertion outside test context, was " +
1088 sourceFromStacktrace( 2 ) );
1091 var details = {
1092 module: this.module.name,
1093 name: this.testName,
1094 result: false,
1095 message: message || "error",
1096 actual: actual || null,
1097 testId: this.testId,
1098 runtime: now() - this.started
1101 if ( source ) {
1102 details.source = source;
1105 runLoggingCallbacks( "log", details );
1107 this.assertions.push({
1108 result: false,
1109 message: message
1113 resolvePromise: function( promise, phase ) {
1114 var then, message,
1115 test = this;
1116 if ( promise != null ) {
1117 then = promise.then;
1118 if ( QUnit.objectType( then ) === "function" ) {
1119 QUnit.stop();
1120 then.call(
1121 promise,
1122 function() { QUnit.start(); },
1123 function( error ) {
1124 message = "Promise rejected " +
1125 ( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
1126 " " + test.testName + ": " + ( error.message || error );
1127 test.pushFailure( message, extractStacktrace( error, 0 ) );
1129 // else next test will carry the responsibility
1130 saveGlobal();
1132 // Unblock
1133 QUnit.start();
1140 valid: function() {
1141 var filter = config.filter,
1142 regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec( filter ),
1143 module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
1144 fullName = ( this.module.name + ": " + this.testName );
1146 function testInModuleChain( testModule ) {
1147 var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
1148 if ( testModuleName === module ) {
1149 return true;
1150 } else if ( testModule.parentModule ) {
1151 return testInModuleChain( testModule.parentModule );
1152 } else {
1153 return false;
1157 // Internally-generated tests are always valid
1158 if ( this.callback && this.callback.validTest ) {
1159 return true;
1162 if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
1163 return false;
1166 if ( module && !testInModuleChain( this.module ) ) {
1167 return false;
1170 if ( !filter ) {
1171 return true;
1174 return regexFilter ?
1175 this.regexFilter( !!regexFilter[1], regexFilter[2], regexFilter[3], fullName ) :
1176 this.stringFilter( filter, fullName );
1179 regexFilter: function( exclude, pattern, flags, fullName ) {
1180 var regex = new RegExp( pattern, flags );
1181 var match = regex.test( fullName );
1183 return match !== exclude;
1186 stringFilter: function( filter, fullName ) {
1187 filter = filter.toLowerCase();
1188 fullName = fullName.toLowerCase();
1190 var include = filter.charAt( 0 ) !== "!";
1191 if ( !include ) {
1192 filter = filter.slice( 1 );
1195 // If the filter matches, we need to honour include
1196 if ( fullName.indexOf( filter ) !== -1 ) {
1197 return include;
1200 // Otherwise, do the opposite
1201 return !include;
1205 // Resets the test setup. Useful for tests that modify the DOM.
1207 DEPRECATED: Use multiple tests instead of resetting inside a test.
1208 Use testStart or testDone for custom cleanup.
1209 This method will throw an error in 2.0, and will be removed in 2.1
1211 QUnit.reset = function() {
1213 // Return on non-browser environments
1214 // This is necessary to not break on node tests
1215 if ( !defined.document ) {
1216 return;
1219 var fixture = defined.document && document.getElementById &&
1220 document.getElementById( "qunit-fixture" );
1222 if ( fixture ) {
1223 fixture.innerHTML = config.fixture;
1227 QUnit.pushFailure = function() {
1228 if ( !QUnit.config.current ) {
1229 throw new Error( "pushFailure() assertion outside test context, in " +
1230 sourceFromStacktrace( 2 ) );
1233 // Gets current test obj
1234 var currentTest = QUnit.config.current;
1236 return currentTest.pushFailure.apply( currentTest, arguments );
1239 // Based on Java's String.hashCode, a simple but not
1240 // rigorously collision resistant hashing function
1241 function generateHash( module, testName ) {
1242 var hex,
1243 i = 0,
1244 hash = 0,
1245 str = module + "\x1C" + testName,
1246 len = str.length;
1248 for ( ; i < len; i++ ) {
1249 hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
1250 hash |= 0;
1253 // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
1254 // strictly necessary but increases user understanding that the id is a SHA-like hash
1255 hex = ( 0x100000000 + hash ).toString( 16 );
1256 if ( hex.length < 8 ) {
1257 hex = "0000000" + hex;
1260 return hex.slice( -8 );
1263 function synchronize( callback, priority ) {
1264 var last = !priority;
1266 if ( QUnit.objectType( callback ) === "array" ) {
1267 while ( callback.length ) {
1268 synchronize( callback.shift() );
1270 return;
1273 if ( priority ) {
1274 config.queue.splice( priorityCount++, 0, callback );
1275 } else {
1276 config.queue.push( callback );
1279 if ( config.autorun && !config.blocking ) {
1280 process( last );
1284 function saveGlobal() {
1285 config.pollution = [];
1287 if ( config.noglobals ) {
1288 for ( var key in global ) {
1289 if ( hasOwn.call( global, key ) ) {
1291 // in Opera sometimes DOM element ids show up here, ignore them
1292 if ( /^qunit-test-output/.test( key ) ) {
1293 continue;
1295 config.pollution.push( key );
1301 function checkPollution() {
1302 var newGlobals,
1303 deletedGlobals,
1304 old = config.pollution;
1306 saveGlobal();
1308 newGlobals = diff( config.pollution, old );
1309 if ( newGlobals.length > 0 ) {
1310 QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
1313 deletedGlobals = diff( old, config.pollution );
1314 if ( deletedGlobals.length > 0 ) {
1315 QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
1319 // Will be exposed as QUnit.asyncTest
1320 function asyncTest( testName, expected, callback ) {
1321 if ( arguments.length === 2 ) {
1322 callback = expected;
1323 expected = null;
1326 QUnit.test( testName, expected, callback, true );
1329 // Will be exposed as QUnit.test
1330 function test( testName, expected, callback, async ) {
1331 if ( focused ) { return; }
1333 var newTest;
1335 if ( arguments.length === 2 ) {
1336 callback = expected;
1337 expected = null;
1340 newTest = new Test({
1341 testName: testName,
1342 expected: expected,
1343 async: async,
1344 callback: callback
1347 newTest.queue();
1350 // Will be exposed as QUnit.skip
1351 function skip( testName ) {
1352 if ( focused ) { return; }
1354 var test = new Test({
1355 testName: testName,
1356 skip: true
1359 test.queue();
1362 // Will be exposed as QUnit.only
1363 function only( testName, expected, callback, async ) {
1364 var newTest;
1366 if ( focused ) { return; }
1368 QUnit.config.queue.length = 0;
1369 focused = true;
1371 if ( arguments.length === 2 ) {
1372 callback = expected;
1373 expected = null;
1376 newTest = new Test({
1377 testName: testName,
1378 expected: expected,
1379 async: async,
1380 callback: callback
1383 newTest.queue();
1386 function Assert( testContext ) {
1387 this.test = testContext;
1390 // Assert helpers
1391 QUnit.assert = Assert.prototype = {
1393 // Specify the number of expected assertions to guarantee that failed test
1394 // (no assertions are run at all) don't slip through.
1395 expect: function( asserts ) {
1396 if ( arguments.length === 1 ) {
1397 this.test.expected = asserts;
1398 } else {
1399 return this.test.expected;
1403 // Increment this Test's semaphore counter, then return a function that
1404 // decrements that counter a maximum of once.
1405 async: function( count ) {
1406 var test = this.test,
1407 popped = false,
1408 acceptCallCount = count;
1410 if ( typeof acceptCallCount === "undefined" ) {
1411 acceptCallCount = 1;
1414 test.semaphore += 1;
1415 test.usedAsync = true;
1416 pauseProcessing();
1418 return function done() {
1420 if ( popped ) {
1421 test.pushFailure( "Too many calls to the `assert.async` callback",
1422 sourceFromStacktrace( 2 ) );
1423 return;
1425 acceptCallCount -= 1;
1426 if ( acceptCallCount > 0 ) {
1427 return;
1430 test.semaphore -= 1;
1431 popped = true;
1432 resumeProcessing();
1436 // Exports test.push() to the user API
1437 // Alias of pushResult.
1438 push: function( result, actual, expected, message, negative ) {
1439 var currentAssert = this instanceof Assert ? this : QUnit.config.current.assert;
1440 return currentAssert.pushResult( {
1441 result: result,
1442 actual: actual,
1443 expected: expected,
1444 message: message,
1445 negative: negative
1446 } );
1449 pushResult: function( resultInfo ) {
1451 // resultInfo = { result, actual, expected, message, negative }
1452 var assert = this,
1453 currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
1455 // Backwards compatibility fix.
1456 // Allows the direct use of global exported assertions and QUnit.assert.*
1457 // Although, it's use is not recommended as it can leak assertions
1458 // to other tests from async tests, because we only get a reference to the current test,
1459 // not exactly the test where assertion were intended to be called.
1460 if ( !currentTest ) {
1461 throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
1464 if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
1465 currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
1466 sourceFromStacktrace( 2 ) );
1468 // Allow this assertion to continue running anyway...
1471 if ( !( assert instanceof Assert ) ) {
1472 assert = currentTest.assert;
1475 return assert.test.pushResult( resultInfo );
1478 ok: function( result, message ) {
1479 message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
1480 QUnit.dump.parse( result ) );
1481 this.pushResult( {
1482 result: !!result,
1483 actual: result,
1484 expected: true,
1485 message: message
1486 } );
1489 notOk: function( result, message ) {
1490 message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " +
1491 QUnit.dump.parse( result ) );
1492 this.pushResult( {
1493 result: !result,
1494 actual: result,
1495 expected: false,
1496 message: message
1497 } );
1500 equal: function( actual, expected, message ) {
1501 /*jshint eqeqeq:false */
1502 this.pushResult( {
1503 result: expected == actual,
1504 actual: actual,
1505 expected: expected,
1506 message: message
1507 } );
1510 notEqual: function( actual, expected, message ) {
1511 /*jshint eqeqeq:false */
1512 this.pushResult( {
1513 result: expected != actual,
1514 actual: actual,
1515 expected: expected,
1516 message: message,
1517 negative: true
1518 } );
1521 propEqual: function( actual, expected, message ) {
1522 actual = objectValues( actual );
1523 expected = objectValues( expected );
1524 this.pushResult( {
1525 result: QUnit.equiv( actual, expected ),
1526 actual: actual,
1527 expected: expected,
1528 message: message
1529 } );
1532 notPropEqual: function( actual, expected, message ) {
1533 actual = objectValues( actual );
1534 expected = objectValues( expected );
1535 this.pushResult( {
1536 result: !QUnit.equiv( actual, expected ),
1537 actual: actual,
1538 expected: expected,
1539 message: message,
1540 negative: true
1541 } );
1544 deepEqual: function( actual, expected, message ) {
1545 this.pushResult( {
1546 result: QUnit.equiv( actual, expected ),
1547 actual: actual,
1548 expected: expected,
1549 message: message
1550 } );
1553 notDeepEqual: function( actual, expected, message ) {
1554 this.pushResult( {
1555 result: !QUnit.equiv( actual, expected ),
1556 actual: actual,
1557 expected: expected,
1558 message: message,
1559 negative: true
1560 } );
1563 strictEqual: function( actual, expected, message ) {
1564 this.pushResult( {
1565 result: expected === actual,
1566 actual: actual,
1567 expected: expected,
1568 message: message
1569 } );
1572 notStrictEqual: function( actual, expected, message ) {
1573 this.pushResult( {
1574 result: expected !== actual,
1575 actual: actual,
1576 expected: expected,
1577 message: message,
1578 negative: true
1579 } );
1582 "throws": function( block, expected, message ) {
1583 var actual, expectedType,
1584 expectedOutput = expected,
1585 ok = false,
1586 currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current;
1588 // 'expected' is optional unless doing string comparison
1589 if ( message == null && typeof expected === "string" ) {
1590 message = expected;
1591 expected = null;
1594 currentTest.ignoreGlobalErrors = true;
1595 try {
1596 block.call( currentTest.testEnvironment );
1597 } catch (e) {
1598 actual = e;
1600 currentTest.ignoreGlobalErrors = false;
1602 if ( actual ) {
1603 expectedType = QUnit.objectType( expected );
1605 // we don't want to validate thrown error
1606 if ( !expected ) {
1607 ok = true;
1608 expectedOutput = null;
1610 // expected is a regexp
1611 } else if ( expectedType === "regexp" ) {
1612 ok = expected.test( errorString( actual ) );
1614 // expected is a string
1615 } else if ( expectedType === "string" ) {
1616 ok = expected === errorString( actual );
1618 // expected is a constructor, maybe an Error constructor
1619 } else if ( expectedType === "function" && actual instanceof expected ) {
1620 ok = true;
1622 // expected is an Error object
1623 } else if ( expectedType === "object" ) {
1624 ok = actual instanceof expected.constructor &&
1625 actual.name === expected.name &&
1626 actual.message === expected.message;
1628 // expected is a validation function which returns true if validation passed
1629 } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
1630 expectedOutput = null;
1631 ok = true;
1635 currentTest.assert.pushResult( {
1636 result: ok,
1637 actual: actual,
1638 expected: expectedOutput,
1639 message: message
1640 } );
1644 // Provide an alternative to assert.throws(), for environments that consider throws a reserved word
1645 // Known to us are: Closure Compiler, Narwhal
1646 (function() {
1647 /*jshint sub:true */
1648 Assert.prototype.raises = Assert.prototype[ "throws" ];
1649 }());
1651 function errorString( error ) {
1652 var name, message,
1653 resultErrorString = error.toString();
1654 if ( resultErrorString.substring( 0, 7 ) === "[object" ) {
1655 name = error.name ? error.name.toString() : "Error";
1656 message = error.message ? error.message.toString() : "";
1657 if ( name && message ) {
1658 return name + ": " + message;
1659 } else if ( name ) {
1660 return name;
1661 } else if ( message ) {
1662 return message;
1663 } else {
1664 return "Error";
1666 } else {
1667 return resultErrorString;
1671 // Test for equality any JavaScript type.
1672 // Author: Philippe Rathé <prathe@gmail.com>
1673 QUnit.equiv = (function() {
1675 // Stack to decide between skip/abort functions
1676 var callers = [];
1678 // Stack to avoiding loops from circular referencing
1679 var parents = [];
1680 var parentsB = [];
1682 var getProto = Object.getPrototypeOf || function( obj ) {
1684 /*jshint proto: true */
1685 return obj.__proto__;
1688 function useStrictEquality( b, a ) {
1690 // To catch short annotation VS 'new' annotation of a declaration. e.g.:
1691 // `var i = 1;`
1692 // `var j = new Number(1);`
1693 if ( typeof a === "object" ) {
1694 a = a.valueOf();
1696 if ( typeof b === "object" ) {
1697 b = b.valueOf();
1700 return a === b;
1703 function compareConstructors( a, b ) {
1704 var protoA = getProto( a );
1705 var protoB = getProto( b );
1707 // Comparing constructors is more strict than using `instanceof`
1708 if ( a.constructor === b.constructor ) {
1709 return true;
1712 // Ref #851
1713 // If the obj prototype descends from a null constructor, treat it
1714 // as a null prototype.
1715 if ( protoA && protoA.constructor === null ) {
1716 protoA = null;
1718 if ( protoB && protoB.constructor === null ) {
1719 protoB = null;
1722 // Allow objects with no prototype to be equivalent to
1723 // objects with Object as their constructor.
1724 if ( ( protoA === null && protoB === Object.prototype ) ||
1725 ( protoB === null && protoA === Object.prototype ) ) {
1726 return true;
1729 return false;
1732 function getRegExpFlags( regexp ) {
1733 return "flags" in regexp ? regexp.flags : regexp.toString().match( /[gimuy]*$/ )[ 0 ];
1736 var callbacks = {
1737 "string": useStrictEquality,
1738 "boolean": useStrictEquality,
1739 "number": useStrictEquality,
1740 "null": useStrictEquality,
1741 "undefined": useStrictEquality,
1742 "symbol": useStrictEquality,
1743 "date": useStrictEquality,
1745 "nan": function() {
1746 return true;
1749 "regexp": function( b, a ) {
1750 return a.source === b.source &&
1752 // Include flags in the comparison
1753 getRegExpFlags( a ) === getRegExpFlags( b );
1756 // - skip when the property is a method of an instance (OOP)
1757 // - abort otherwise,
1758 // initial === would have catch identical references anyway
1759 "function": function() {
1760 var caller = callers[ callers.length - 1 ];
1761 return caller !== Object && typeof caller !== "undefined";
1764 "array": function( b, a ) {
1765 var i, j, len, loop, aCircular, bCircular;
1767 len = a.length;
1768 if ( len !== b.length ) {
1769 // safe and faster
1770 return false;
1773 // Track reference to avoid circular references
1774 parents.push( a );
1775 parentsB.push( b );
1776 for ( i = 0; i < len; i++ ) {
1777 loop = false;
1778 for ( j = 0; j < parents.length; j++ ) {
1779 aCircular = parents[ j ] === a[ i ];
1780 bCircular = parentsB[ j ] === b[ i ];
1781 if ( aCircular || bCircular ) {
1782 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1783 loop = true;
1784 } else {
1785 parents.pop();
1786 parentsB.pop();
1787 return false;
1791 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1792 parents.pop();
1793 parentsB.pop();
1794 return false;
1797 parents.pop();
1798 parentsB.pop();
1799 return true;
1802 "set": function( b, a ) {
1803 var aArray, bArray;
1805 aArray = [];
1806 a.forEach( function( v ) {
1807 aArray.push( v );
1809 bArray = [];
1810 b.forEach( function( v ) {
1811 bArray.push( v );
1814 return innerEquiv( bArray, aArray );
1817 "map": function( b, a ) {
1818 var aArray, bArray;
1820 aArray = [];
1821 a.forEach( function( v, k ) {
1822 aArray.push( [ k, v ] );
1824 bArray = [];
1825 b.forEach( function( v, k ) {
1826 bArray.push( [ k, v ] );
1829 return innerEquiv( bArray, aArray );
1832 "object": function( b, a ) {
1833 var i, j, loop, aCircular, bCircular;
1835 // Default to true
1836 var eq = true;
1837 var aProperties = [];
1838 var bProperties = [];
1840 if ( compareConstructors( a, b ) === false ) {
1841 return false;
1844 // Stack constructor before traversing properties
1845 callers.push( a.constructor );
1847 // Track reference to avoid circular references
1848 parents.push( a );
1849 parentsB.push( b );
1851 // Be strict: don't ensure hasOwnProperty and go deep
1852 for ( i in a ) {
1853 loop = false;
1854 for ( j = 0; j < parents.length; j++ ) {
1855 aCircular = parents[ j ] === a[ i ];
1856 bCircular = parentsB[ j ] === b[ i ];
1857 if ( aCircular || bCircular ) {
1858 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1859 loop = true;
1860 } else {
1861 eq = false;
1862 break;
1866 aProperties.push( i );
1867 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1868 eq = false;
1869 break;
1873 parents.pop();
1874 parentsB.pop();
1876 // Unstack, we are done
1877 callers.pop();
1879 for ( i in b ) {
1881 // Collect b's properties
1882 bProperties.push( i );
1885 // Ensures identical properties name
1886 return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
1890 function typeEquiv( a, b ) {
1891 var type = QUnit.objectType( a );
1892 return QUnit.objectType( b ) === type && callbacks[ type ]( b, a );
1895 // The real equiv function
1896 function innerEquiv( a, b ) {
1898 // We're done when there's nothing more to compare
1899 if ( arguments.length < 2 ) {
1900 return true;
1903 // Require type-specific equality
1904 return ( a === b || typeEquiv( a, b ) ) &&
1906 // ...across all consecutive argument pairs
1907 ( arguments.length === 2 || innerEquiv.apply( this, [].slice.call( arguments, 1 ) ) );
1910 return innerEquiv;
1911 }());
1913 // Based on jsDump by Ariel Flesler
1914 // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
1915 QUnit.dump = (function() {
1916 function quote( str ) {
1917 return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\"";
1919 function literal( o ) {
1920 return o + "";
1922 function join( pre, arr, post ) {
1923 var s = dump.separator(),
1924 base = dump.indent(),
1925 inner = dump.indent( 1 );
1926 if ( arr.join ) {
1927 arr = arr.join( "," + s + inner );
1929 if ( !arr ) {
1930 return pre + post;
1932 return [ pre, inner + arr, base + post ].join( s );
1934 function array( arr, stack ) {
1935 var i = arr.length,
1936 ret = new Array( i );
1938 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1939 return "[object Array]";
1942 this.up();
1943 while ( i-- ) {
1944 ret[ i ] = this.parse( arr[ i ], undefined, stack );
1946 this.down();
1947 return join( "[", ret, "]" );
1950 var reName = /^function (\w+)/,
1951 dump = {
1953 // objType is used mostly internally, you can fix a (custom) type in advance
1954 parse: function( obj, objType, stack ) {
1955 stack = stack || [];
1956 var res, parser, parserType,
1957 inStack = inArray( obj, stack );
1959 if ( inStack !== -1 ) {
1960 return "recursion(" + ( inStack - stack.length ) + ")";
1963 objType = objType || this.typeOf( obj );
1964 parser = this.parsers[ objType ];
1965 parserType = typeof parser;
1967 if ( parserType === "function" ) {
1968 stack.push( obj );
1969 res = parser.call( this, obj, stack );
1970 stack.pop();
1971 return res;
1973 return ( parserType === "string" ) ? parser : this.parsers.error;
1975 typeOf: function( obj ) {
1976 var type;
1977 if ( obj === null ) {
1978 type = "null";
1979 } else if ( typeof obj === "undefined" ) {
1980 type = "undefined";
1981 } else if ( QUnit.is( "regexp", obj ) ) {
1982 type = "regexp";
1983 } else if ( QUnit.is( "date", obj ) ) {
1984 type = "date";
1985 } else if ( QUnit.is( "function", obj ) ) {
1986 type = "function";
1987 } else if ( obj.setInterval !== undefined &&
1988 obj.document !== undefined &&
1989 obj.nodeType === undefined ) {
1990 type = "window";
1991 } else if ( obj.nodeType === 9 ) {
1992 type = "document";
1993 } else if ( obj.nodeType ) {
1994 type = "node";
1995 } else if (
1997 // native arrays
1998 toString.call( obj ) === "[object Array]" ||
2000 // NodeList objects
2001 ( typeof obj.length === "number" && obj.item !== undefined &&
2002 ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
2003 obj[ 0 ] === undefined ) ) )
2005 type = "array";
2006 } else if ( obj.constructor === Error.prototype.constructor ) {
2007 type = "error";
2008 } else {
2009 type = typeof obj;
2011 return type;
2013 separator: function() {
2014 return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
2016 // extra can be a number, shortcut for increasing-calling-decreasing
2017 indent: function( extra ) {
2018 if ( !this.multiline ) {
2019 return "";
2021 var chr = this.indentChar;
2022 if ( this.HTML ) {
2023 chr = chr.replace( /\t/g, " " ).replace( / /g, "&#160;" );
2025 return new Array( this.depth + ( extra || 0 ) ).join( chr );
2027 up: function( a ) {
2028 this.depth += a || 1;
2030 down: function( a ) {
2031 this.depth -= a || 1;
2033 setParser: function( name, parser ) {
2034 this.parsers[ name ] = parser;
2036 // The next 3 are exposed so you can use them
2037 quote: quote,
2038 literal: literal,
2039 join: join,
2041 depth: 1,
2042 maxDepth: QUnit.config.maxDepth,
2044 // This is the list of parsers, to modify them, use dump.setParser
2045 parsers: {
2046 window: "[Window]",
2047 document: "[Document]",
2048 error: function( error ) {
2049 return "Error(\"" + error.message + "\")";
2051 unknown: "[Unknown]",
2052 "null": "null",
2053 "undefined": "undefined",
2054 "function": function( fn ) {
2055 var ret = "function",
2057 // functions never have name in IE
2058 name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
2060 if ( name ) {
2061 ret += " " + name;
2063 ret += "( ";
2065 ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
2066 return join( ret, dump.parse( fn, "functionCode" ), "}" );
2068 array: array,
2069 nodelist: array,
2070 "arguments": array,
2071 object: function( map, stack ) {
2072 var keys, key, val, i, nonEnumerableProperties,
2073 ret = [];
2075 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
2076 return "[object Object]";
2079 dump.up();
2080 keys = [];
2081 for ( key in map ) {
2082 keys.push( key );
2085 // Some properties are not always enumerable on Error objects.
2086 nonEnumerableProperties = [ "message", "name" ];
2087 for ( i in nonEnumerableProperties ) {
2088 key = nonEnumerableProperties[ i ];
2089 if ( key in map && inArray( key, keys ) < 0 ) {
2090 keys.push( key );
2093 keys.sort();
2094 for ( i = 0; i < keys.length; i++ ) {
2095 key = keys[ i ];
2096 val = map[ key ];
2097 ret.push( dump.parse( key, "key" ) + ": " +
2098 dump.parse( val, undefined, stack ) );
2100 dump.down();
2101 return join( "{", ret, "}" );
2103 node: function( node ) {
2104 var len, i, val,
2105 open = dump.HTML ? "&lt;" : "<",
2106 close = dump.HTML ? "&gt;" : ">",
2107 tag = node.nodeName.toLowerCase(),
2108 ret = open + tag,
2109 attrs = node.attributes;
2111 if ( attrs ) {
2112 for ( i = 0, len = attrs.length; i < len; i++ ) {
2113 val = attrs[ i ].nodeValue;
2115 // IE6 includes all attributes in .attributes, even ones not explicitly
2116 // set. Those have values like undefined, null, 0, false, "" or
2117 // "inherit".
2118 if ( val && val !== "inherit" ) {
2119 ret += " " + attrs[ i ].nodeName + "=" +
2120 dump.parse( val, "attribute" );
2124 ret += close;
2126 // Show content of TextNode or CDATASection
2127 if ( node.nodeType === 3 || node.nodeType === 4 ) {
2128 ret += node.nodeValue;
2131 return ret + open + "/" + tag + close;
2134 // function calls it internally, it's the arguments part of the function
2135 functionArgs: function( fn ) {
2136 var args,
2137 l = fn.length;
2139 if ( !l ) {
2140 return "";
2143 args = new Array( l );
2144 while ( l-- ) {
2146 // 97 is 'a'
2147 args[ l ] = String.fromCharCode( 97 + l );
2149 return " " + args.join( ", " ) + " ";
2151 // object calls it internally, the key part of an item in a map
2152 key: quote,
2153 // function calls it internally, it's the content of the function
2154 functionCode: "[code]",
2155 // node calls it internally, it's a html attribute value
2156 attribute: quote,
2157 string: quote,
2158 date: quote,
2159 regexp: literal,
2160 number: literal,
2161 "boolean": literal
2163 // if true, entities are escaped ( <, >, \t, space and \n )
2164 HTML: false,
2165 // indentation unit
2166 indentChar: " ",
2167 // if true, items in a collection, are separated by a \n, else just a space.
2168 multiline: true
2171 return dump;
2172 }());
2174 // back compat
2175 QUnit.jsDump = QUnit.dump;
2177 // Deprecated
2178 // Extend assert methods to QUnit for Backwards compatibility
2179 (function() {
2180 var i,
2181 assertions = Assert.prototype;
2183 function applyCurrent( current ) {
2184 return function() {
2185 var assert = new Assert( QUnit.config.current );
2186 current.apply( assert, arguments );
2190 for ( i in assertions ) {
2191 QUnit[ i ] = applyCurrent( assertions[ i ] );
2193 })();
2195 // For browser, export only select globals
2196 if ( defined.document ) {
2198 (function() {
2199 var i, l,
2200 keys = [
2201 "test",
2202 "module",
2203 "expect",
2204 "asyncTest",
2205 "start",
2206 "stop",
2207 "ok",
2208 "notOk",
2209 "equal",
2210 "notEqual",
2211 "propEqual",
2212 "notPropEqual",
2213 "deepEqual",
2214 "notDeepEqual",
2215 "strictEqual",
2216 "notStrictEqual",
2217 "throws",
2218 "raises"
2221 for ( i = 0, l = keys.length; i < l; i++ ) {
2222 window[ keys[ i ] ] = QUnit[ keys[ i ] ];
2224 })();
2226 window.QUnit = QUnit;
2229 // For nodejs
2230 if ( typeof module !== "undefined" && module && module.exports ) {
2231 module.exports = QUnit;
2233 // For consistency with CommonJS environments' exports
2234 module.exports.QUnit = QUnit;
2237 // For CommonJS with exports, but without module.exports, like Rhino
2238 if ( typeof exports !== "undefined" && exports ) {
2239 exports.QUnit = QUnit;
2242 if ( typeof define === "function" && define.amd ) {
2243 define( function() {
2244 return QUnit;
2245 } );
2246 QUnit.config.autostart = false;
2250 * This file is a modified version of google-diff-match-patch's JavaScript implementation
2251 * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
2252 * modifications are licensed as more fully set forth in LICENSE.txt.
2254 * The original source of google-diff-match-patch is attributable and licensed as follows:
2256 * Copyright 2006 Google Inc.
2257 * https://code.google.com/p/google-diff-match-patch/
2259 * Licensed under the Apache License, Version 2.0 (the "License");
2260 * you may not use this file except in compliance with the License.
2261 * You may obtain a copy of the License at
2263 * https://www.apache.org/licenses/LICENSE-2.0
2265 * Unless required by applicable law or agreed to in writing, software
2266 * distributed under the License is distributed on an "AS IS" BASIS,
2267 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2268 * See the License for the specific language governing permissions and
2269 * limitations under the License.
2271 * More Info:
2272 * https://code.google.com/p/google-diff-match-patch/
2274 * Usage: QUnit.diff(expected, actual)
2277 QUnit.diff = ( function() {
2278 function DiffMatchPatch() {
2281 // DIFF FUNCTIONS
2284 * The data structure representing a diff is an array of tuples:
2285 * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
2286 * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
2288 var DIFF_DELETE = -1,
2289 DIFF_INSERT = 1,
2290 DIFF_EQUAL = 0;
2293 * Find the differences between two texts. Simplifies the problem by stripping
2294 * any common prefix or suffix off the texts before diffing.
2295 * @param {string} text1 Old string to be diffed.
2296 * @param {string} text2 New string to be diffed.
2297 * @param {boolean=} optChecklines Optional speedup flag. If present and false,
2298 * then don't run a line-level diff first to identify the changed areas.
2299 * Defaults to true, which does a faster, slightly less optimal diff.
2300 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2302 DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) {
2303 var deadline, checklines, commonlength,
2304 commonprefix, commonsuffix, diffs;
2306 // The diff must be complete in up to 1 second.
2307 deadline = ( new Date() ).getTime() + 1000;
2309 // Check for null inputs.
2310 if ( text1 === null || text2 === null ) {
2311 throw new Error( "Null input. (DiffMain)" );
2314 // Check for equality (speedup).
2315 if ( text1 === text2 ) {
2316 if ( text1 ) {
2317 return [
2318 [ DIFF_EQUAL, text1 ]
2321 return [];
2324 if ( typeof optChecklines === "undefined" ) {
2325 optChecklines = true;
2328 checklines = optChecklines;
2330 // Trim off common prefix (speedup).
2331 commonlength = this.diffCommonPrefix( text1, text2 );
2332 commonprefix = text1.substring( 0, commonlength );
2333 text1 = text1.substring( commonlength );
2334 text2 = text2.substring( commonlength );
2336 // Trim off common suffix (speedup).
2337 commonlength = this.diffCommonSuffix( text1, text2 );
2338 commonsuffix = text1.substring( text1.length - commonlength );
2339 text1 = text1.substring( 0, text1.length - commonlength );
2340 text2 = text2.substring( 0, text2.length - commonlength );
2342 // Compute the diff on the middle block.
2343 diffs = this.diffCompute( text1, text2, checklines, deadline );
2345 // Restore the prefix and suffix.
2346 if ( commonprefix ) {
2347 diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
2349 if ( commonsuffix ) {
2350 diffs.push( [ DIFF_EQUAL, commonsuffix ] );
2352 this.diffCleanupMerge( diffs );
2353 return diffs;
2357 * Reduce the number of edits by eliminating operationally trivial equalities.
2358 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2360 DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
2361 var changes, equalities, equalitiesLength, lastequality,
2362 pointer, preIns, preDel, postIns, postDel;
2363 changes = false;
2364 equalities = []; // Stack of indices where equalities are found.
2365 equalitiesLength = 0; // Keeping our own length var is faster in JS.
2366 /** @type {?string} */
2367 lastequality = null;
2368 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
2369 pointer = 0; // Index of current position.
2370 // Is there an insertion operation before the last equality.
2371 preIns = false;
2372 // Is there a deletion operation before the last equality.
2373 preDel = false;
2374 // Is there an insertion operation after the last equality.
2375 postIns = false;
2376 // Is there a deletion operation after the last equality.
2377 postDel = false;
2378 while ( pointer < diffs.length ) {
2380 // Equality found.
2381 if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) {
2382 if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) {
2384 // Candidate found.
2385 equalities[ equalitiesLength++ ] = pointer;
2386 preIns = postIns;
2387 preDel = postDel;
2388 lastequality = diffs[ pointer ][ 1 ];
2389 } else {
2391 // Not a candidate, and can never become one.
2392 equalitiesLength = 0;
2393 lastequality = null;
2395 postIns = postDel = false;
2397 // An insertion or deletion.
2398 } else {
2400 if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
2401 postDel = true;
2402 } else {
2403 postIns = true;
2407 * Five types to be split:
2408 * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
2409 * <ins>A</ins>X<ins>C</ins><del>D</del>
2410 * <ins>A</ins><del>B</del>X<ins>C</ins>
2411 * <ins>A</del>X<ins>C</ins><del>D</del>
2412 * <ins>A</ins><del>B</del>X<del>C</del>
2414 if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
2415 ( ( lastequality.length < 2 ) &&
2416 ( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
2418 // Duplicate record.
2419 diffs.splice(
2420 equalities[ equalitiesLength - 1 ],
2422 [ DIFF_DELETE, lastequality ]
2425 // Change second copy to insert.
2426 diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
2427 equalitiesLength--; // Throw away the equality we just deleted;
2428 lastequality = null;
2429 if ( preIns && preDel ) {
2430 // No changes made which could affect previous entry, keep going.
2431 postIns = postDel = true;
2432 equalitiesLength = 0;
2433 } else {
2434 equalitiesLength--; // Throw away the previous equality.
2435 pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
2436 postIns = postDel = false;
2438 changes = true;
2441 pointer++;
2444 if ( changes ) {
2445 this.diffCleanupMerge( diffs );
2450 * Convert a diff array into a pretty HTML report.
2451 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2452 * @param {integer} string to be beautified.
2453 * @return {string} HTML representation.
2455 DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
2456 var op, data, x,
2457 html = [];
2458 for ( x = 0; x < diffs.length; x++ ) {
2459 op = diffs[ x ][ 0 ]; // Operation (insert, delete, equal)
2460 data = diffs[ x ][ 1 ]; // Text of change.
2461 switch ( op ) {
2462 case DIFF_INSERT:
2463 html[ x ] = "<ins>" + data + "</ins>";
2464 break;
2465 case DIFF_DELETE:
2466 html[ x ] = "<del>" + data + "</del>";
2467 break;
2468 case DIFF_EQUAL:
2469 html[ x ] = "<span>" + data + "</span>";
2470 break;
2473 return html.join( "" );
2477 * Determine the common prefix of two strings.
2478 * @param {string} text1 First string.
2479 * @param {string} text2 Second string.
2480 * @return {number} The number of characters common to the start of each
2481 * string.
2483 DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
2484 var pointermid, pointermax, pointermin, pointerstart;
2485 // Quick check for common null cases.
2486 if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) {
2487 return 0;
2489 // Binary search.
2490 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
2491 pointermin = 0;
2492 pointermax = Math.min( text1.length, text2.length );
2493 pointermid = pointermax;
2494 pointerstart = 0;
2495 while ( pointermin < pointermid ) {
2496 if ( text1.substring( pointerstart, pointermid ) ===
2497 text2.substring( pointerstart, pointermid ) ) {
2498 pointermin = pointermid;
2499 pointerstart = pointermin;
2500 } else {
2501 pointermax = pointermid;
2503 pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
2505 return pointermid;
2509 * Determine the common suffix of two strings.
2510 * @param {string} text1 First string.
2511 * @param {string} text2 Second string.
2512 * @return {number} The number of characters common to the end of each string.
2514 DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
2515 var pointermid, pointermax, pointermin, pointerend;
2516 // Quick check for common null cases.
2517 if ( !text1 ||
2518 !text2 ||
2519 text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) {
2520 return 0;
2522 // Binary search.
2523 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
2524 pointermin = 0;
2525 pointermax = Math.min( text1.length, text2.length );
2526 pointermid = pointermax;
2527 pointerend = 0;
2528 while ( pointermin < pointermid ) {
2529 if ( text1.substring( text1.length - pointermid, text1.length - pointerend ) ===
2530 text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {
2531 pointermin = pointermid;
2532 pointerend = pointermin;
2533 } else {
2534 pointermax = pointermid;
2536 pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
2538 return pointermid;
2542 * Find the differences between two texts. Assumes that the texts do not
2543 * have any common prefix or suffix.
2544 * @param {string} text1 Old string to be diffed.
2545 * @param {string} text2 New string to be diffed.
2546 * @param {boolean} checklines Speedup flag. If false, then don't run a
2547 * line-level diff first to identify the changed areas.
2548 * If true, then run a faster, slightly less optimal diff.
2549 * @param {number} deadline Time when the diff should be complete by.
2550 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2551 * @private
2553 DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
2554 var diffs, longtext, shorttext, i, hm,
2555 text1A, text2A, text1B, text2B,
2556 midCommon, diffsA, diffsB;
2558 if ( !text1 ) {
2559 // Just add some text (speedup).
2560 return [
2561 [ DIFF_INSERT, text2 ]
2565 if ( !text2 ) {
2566 // Just delete some text (speedup).
2567 return [
2568 [ DIFF_DELETE, text1 ]
2572 longtext = text1.length > text2.length ? text1 : text2;
2573 shorttext = text1.length > text2.length ? text2 : text1;
2574 i = longtext.indexOf( shorttext );
2575 if ( i !== -1 ) {
2576 // Shorter text is inside the longer text (speedup).
2577 diffs = [
2578 [ DIFF_INSERT, longtext.substring( 0, i ) ],
2579 [ DIFF_EQUAL, shorttext ],
2580 [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
2582 // Swap insertions for deletions if diff is reversed.
2583 if ( text1.length > text2.length ) {
2584 diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE;
2586 return diffs;
2589 if ( shorttext.length === 1 ) {
2590 // Single character string.
2591 // After the previous speedup, the character can't be an equality.
2592 return [
2593 [ DIFF_DELETE, text1 ],
2594 [ DIFF_INSERT, text2 ]
2598 // Check to see if the problem can be split in two.
2599 hm = this.diffHalfMatch( text1, text2 );
2600 if ( hm ) {
2601 // A half-match was found, sort out the return data.
2602 text1A = hm[ 0 ];
2603 text1B = hm[ 1 ];
2604 text2A = hm[ 2 ];
2605 text2B = hm[ 3 ];
2606 midCommon = hm[ 4 ];
2607 // Send both pairs off for separate processing.
2608 diffsA = this.DiffMain( text1A, text2A, checklines, deadline );
2609 diffsB = this.DiffMain( text1B, text2B, checklines, deadline );
2610 // Merge the results.
2611 return diffsA.concat( [
2612 [ DIFF_EQUAL, midCommon ]
2613 ], diffsB );
2616 if ( checklines && text1.length > 100 && text2.length > 100 ) {
2617 return this.diffLineMode( text1, text2, deadline );
2620 return this.diffBisect( text1, text2, deadline );
2624 * Do the two texts share a substring which is at least half the length of the
2625 * longer text?
2626 * This speedup can produce non-minimal diffs.
2627 * @param {string} text1 First string.
2628 * @param {string} text2 Second string.
2629 * @return {Array.<string>} Five element Array, containing the prefix of
2630 * text1, the suffix of text1, the prefix of text2, the suffix of
2631 * text2 and the common middle. Or null if there was no match.
2632 * @private
2634 DiffMatchPatch.prototype.diffHalfMatch = function( text1, text2 ) {
2635 var longtext, shorttext, dmp,
2636 text1A, text2B, text2A, text1B, midCommon,
2637 hm1, hm2, hm;
2639 longtext = text1.length > text2.length ? text1 : text2;
2640 shorttext = text1.length > text2.length ? text2 : text1;
2641 if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) {
2642 return null; // Pointless.
2644 dmp = this; // 'this' becomes 'window' in a closure.
2647 * Does a substring of shorttext exist within longtext such that the substring
2648 * is at least half the length of longtext?
2649 * Closure, but does not reference any external variables.
2650 * @param {string} longtext Longer string.
2651 * @param {string} shorttext Shorter string.
2652 * @param {number} i Start index of quarter length substring within longtext.
2653 * @return {Array.<string>} Five element Array, containing the prefix of
2654 * longtext, the suffix of longtext, the prefix of shorttext, the suffix
2655 * of shorttext and the common middle. Or null if there was no match.
2656 * @private
2658 function diffHalfMatchI( longtext, shorttext, i ) {
2659 var seed, j, bestCommon, prefixLength, suffixLength,
2660 bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
2661 // Start with a 1/4 length substring at position i as a seed.
2662 seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) );
2663 j = -1;
2664 bestCommon = "";
2665 while ( ( j = shorttext.indexOf( seed, j + 1 ) ) !== -1 ) {
2666 prefixLength = dmp.diffCommonPrefix( longtext.substring( i ),
2667 shorttext.substring( j ) );
2668 suffixLength = dmp.diffCommonSuffix( longtext.substring( 0, i ),
2669 shorttext.substring( 0, j ) );
2670 if ( bestCommon.length < suffixLength + prefixLength ) {
2671 bestCommon = shorttext.substring( j - suffixLength, j ) +
2672 shorttext.substring( j, j + prefixLength );
2673 bestLongtextA = longtext.substring( 0, i - suffixLength );
2674 bestLongtextB = longtext.substring( i + prefixLength );
2675 bestShorttextA = shorttext.substring( 0, j - suffixLength );
2676 bestShorttextB = shorttext.substring( j + prefixLength );
2679 if ( bestCommon.length * 2 >= longtext.length ) {
2680 return [ bestLongtextA, bestLongtextB,
2681 bestShorttextA, bestShorttextB, bestCommon
2683 } else {
2684 return null;
2688 // First check if the second quarter is the seed for a half-match.
2689 hm1 = diffHalfMatchI( longtext, shorttext,
2690 Math.ceil( longtext.length / 4 ) );
2691 // Check again based on the third quarter.
2692 hm2 = diffHalfMatchI( longtext, shorttext,
2693 Math.ceil( longtext.length / 2 ) );
2694 if ( !hm1 && !hm2 ) {
2695 return null;
2696 } else if ( !hm2 ) {
2697 hm = hm1;
2698 } else if ( !hm1 ) {
2699 hm = hm2;
2700 } else {
2701 // Both matched. Select the longest.
2702 hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2;
2705 // A half-match was found, sort out the return data.
2706 text1A, text1B, text2A, text2B;
2707 if ( text1.length > text2.length ) {
2708 text1A = hm[ 0 ];
2709 text1B = hm[ 1 ];
2710 text2A = hm[ 2 ];
2711 text2B = hm[ 3 ];
2712 } else {
2713 text2A = hm[ 0 ];
2714 text2B = hm[ 1 ];
2715 text1A = hm[ 2 ];
2716 text1B = hm[ 3 ];
2718 midCommon = hm[ 4 ];
2719 return [ text1A, text1B, text2A, text2B, midCommon ];
2723 * Do a quick line-level diff on both strings, then rediff the parts for
2724 * greater accuracy.
2725 * This speedup can produce non-minimal diffs.
2726 * @param {string} text1 Old string to be diffed.
2727 * @param {string} text2 New string to be diffed.
2728 * @param {number} deadline Time when the diff should be complete by.
2729 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2730 * @private
2732 DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) {
2733 var a, diffs, linearray, pointer, countInsert,
2734 countDelete, textInsert, textDelete, j;
2735 // Scan the text on a line-by-line basis first.
2736 a = this.diffLinesToChars( text1, text2 );
2737 text1 = a.chars1;
2738 text2 = a.chars2;
2739 linearray = a.lineArray;
2741 diffs = this.DiffMain( text1, text2, false, deadline );
2743 // Convert the diff back to original text.
2744 this.diffCharsToLines( diffs, linearray );
2745 // Eliminate freak matches (e.g. blank lines)
2746 this.diffCleanupSemantic( diffs );
2748 // Rediff any replacement blocks, this time character-by-character.
2749 // Add a dummy entry at the end.
2750 diffs.push( [ DIFF_EQUAL, "" ] );
2751 pointer = 0;
2752 countDelete = 0;
2753 countInsert = 0;
2754 textDelete = "";
2755 textInsert = "";
2756 while ( pointer < diffs.length ) {
2757 switch ( diffs[ pointer ][ 0 ] ) {
2758 case DIFF_INSERT:
2759 countInsert++;
2760 textInsert += diffs[ pointer ][ 1 ];
2761 break;
2762 case DIFF_DELETE:
2763 countDelete++;
2764 textDelete += diffs[ pointer ][ 1 ];
2765 break;
2766 case DIFF_EQUAL:
2767 // Upon reaching an equality, check for prior redundancies.
2768 if ( countDelete >= 1 && countInsert >= 1 ) {
2769 // Delete the offending records and add the merged ones.
2770 diffs.splice( pointer - countDelete - countInsert,
2771 countDelete + countInsert );
2772 pointer = pointer - countDelete - countInsert;
2773 a = this.DiffMain( textDelete, textInsert, false, deadline );
2774 for ( j = a.length - 1; j >= 0; j-- ) {
2775 diffs.splice( pointer, 0, a[ j ] );
2777 pointer = pointer + a.length;
2779 countInsert = 0;
2780 countDelete = 0;
2781 textDelete = "";
2782 textInsert = "";
2783 break;
2785 pointer++;
2787 diffs.pop(); // Remove the dummy entry at the end.
2789 return diffs;
2793 * Find the 'middle snake' of a diff, split the problem in two
2794 * and return the recursively constructed diff.
2795 * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
2796 * @param {string} text1 Old string to be diffed.
2797 * @param {string} text2 New string to be diffed.
2798 * @param {number} deadline Time at which to bail if not yet complete.
2799 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2800 * @private
2802 DiffMatchPatch.prototype.diffBisect = function( text1, text2, deadline ) {
2803 var text1Length, text2Length, maxD, vOffset, vLength,
2804 v1, v2, x, delta, front, k1start, k1end, k2start,
2805 k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
2806 // Cache the text lengths to prevent multiple calls.
2807 text1Length = text1.length;
2808 text2Length = text2.length;
2809 maxD = Math.ceil( ( text1Length + text2Length ) / 2 );
2810 vOffset = maxD;
2811 vLength = 2 * maxD;
2812 v1 = new Array( vLength );
2813 v2 = new Array( vLength );
2814 // Setting all elements to -1 is faster in Chrome & Firefox than mixing
2815 // integers and undefined.
2816 for ( x = 0; x < vLength; x++ ) {
2817 v1[ x ] = -1;
2818 v2[ x ] = -1;
2820 v1[ vOffset + 1 ] = 0;
2821 v2[ vOffset + 1 ] = 0;
2822 delta = text1Length - text2Length;
2823 // If the total number of characters is odd, then the front path will collide
2824 // with the reverse path.
2825 front = ( delta % 2 !== 0 );
2826 // Offsets for start and end of k loop.
2827 // Prevents mapping of space beyond the grid.
2828 k1start = 0;
2829 k1end = 0;
2830 k2start = 0;
2831 k2end = 0;
2832 for ( d = 0; d < maxD; d++ ) {
2833 // Bail out if deadline is reached.
2834 if ( ( new Date() ).getTime() > deadline ) {
2835 break;
2838 // Walk the front path one step.
2839 for ( k1 = -d + k1start; k1 <= d - k1end; k1 += 2 ) {
2840 k1Offset = vOffset + k1;
2841 if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {
2842 x1 = v1[ k1Offset + 1 ];
2843 } else {
2844 x1 = v1[ k1Offset - 1 ] + 1;
2846 y1 = x1 - k1;
2847 while ( x1 < text1Length && y1 < text2Length &&
2848 text1.charAt( x1 ) === text2.charAt( y1 ) ) {
2849 x1++;
2850 y1++;
2852 v1[ k1Offset ] = x1;
2853 if ( x1 > text1Length ) {
2854 // Ran off the right of the graph.
2855 k1end += 2;
2856 } else if ( y1 > text2Length ) {
2857 // Ran off the bottom of the graph.
2858 k1start += 2;
2859 } else if ( front ) {
2860 k2Offset = vOffset + delta - k1;
2861 if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) {
2862 // Mirror x2 onto top-left coordinate system.
2863 x2 = text1Length - v2[ k2Offset ];
2864 if ( x1 >= x2 ) {
2865 // Overlap detected.
2866 return this.diffBisectSplit( text1, text2, x1, y1, deadline );
2872 // Walk the reverse path one step.
2873 for ( k2 = -d + k2start; k2 <= d - k2end; k2 += 2 ) {
2874 k2Offset = vOffset + k2;
2875 if ( k2 === -d || ( k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {
2876 x2 = v2[ k2Offset + 1 ];
2877 } else {
2878 x2 = v2[ k2Offset - 1 ] + 1;
2880 y2 = x2 - k2;
2881 while ( x2 < text1Length && y2 < text2Length &&
2882 text1.charAt( text1Length - x2 - 1 ) ===
2883 text2.charAt( text2Length - y2 - 1 ) ) {
2884 x2++;
2885 y2++;
2887 v2[ k2Offset ] = x2;
2888 if ( x2 > text1Length ) {
2889 // Ran off the left of the graph.
2890 k2end += 2;
2891 } else if ( y2 > text2Length ) {
2892 // Ran off the top of the graph.
2893 k2start += 2;
2894 } else if ( !front ) {
2895 k1Offset = vOffset + delta - k2;
2896 if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) {
2897 x1 = v1[ k1Offset ];
2898 y1 = vOffset + x1 - k1Offset;
2899 // Mirror x2 onto top-left coordinate system.
2900 x2 = text1Length - x2;
2901 if ( x1 >= x2 ) {
2902 // Overlap detected.
2903 return this.diffBisectSplit( text1, text2, x1, y1, deadline );
2909 // Diff took too long and hit the deadline or
2910 // number of diffs equals number of characters, no commonality at all.
2911 return [
2912 [ DIFF_DELETE, text1 ],
2913 [ DIFF_INSERT, text2 ]
2918 * Given the location of the 'middle snake', split the diff in two parts
2919 * and recurse.
2920 * @param {string} text1 Old string to be diffed.
2921 * @param {string} text2 New string to be diffed.
2922 * @param {number} x Index of split point in text1.
2923 * @param {number} y Index of split point in text2.
2924 * @param {number} deadline Time at which to bail if not yet complete.
2925 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2926 * @private
2928 DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
2929 var text1a, text1b, text2a, text2b, diffs, diffsb;
2930 text1a = text1.substring( 0, x );
2931 text2a = text2.substring( 0, y );
2932 text1b = text1.substring( x );
2933 text2b = text2.substring( y );
2935 // Compute both diffs serially.
2936 diffs = this.DiffMain( text1a, text2a, false, deadline );
2937 diffsb = this.DiffMain( text1b, text2b, false, deadline );
2939 return diffs.concat( diffsb );
2943 * Reduce the number of edits by eliminating semantically trivial equalities.
2944 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2946 DiffMatchPatch.prototype.diffCleanupSemantic = function( diffs ) {
2947 var changes, equalities, equalitiesLength, lastequality,
2948 pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
2949 lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
2950 changes = false;
2951 equalities = []; // Stack of indices where equalities are found.
2952 equalitiesLength = 0; // Keeping our own length var is faster in JS.
2953 /** @type {?string} */
2954 lastequality = null;
2955 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
2956 pointer = 0; // Index of current position.
2957 // Number of characters that changed prior to the equality.
2958 lengthInsertions1 = 0;
2959 lengthDeletions1 = 0;
2960 // Number of characters that changed after the equality.
2961 lengthInsertions2 = 0;
2962 lengthDeletions2 = 0;
2963 while ( pointer < diffs.length ) {
2964 if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
2965 equalities[ equalitiesLength++ ] = pointer;
2966 lengthInsertions1 = lengthInsertions2;
2967 lengthDeletions1 = lengthDeletions2;
2968 lengthInsertions2 = 0;
2969 lengthDeletions2 = 0;
2970 lastequality = diffs[ pointer ][ 1 ];
2971 } else { // An insertion or deletion.
2972 if ( diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
2973 lengthInsertions2 += diffs[ pointer ][ 1 ].length;
2974 } else {
2975 lengthDeletions2 += diffs[ pointer ][ 1 ].length;
2977 // Eliminate an equality that is smaller or equal to the edits on both
2978 // sides of it.
2979 if ( lastequality && ( lastequality.length <=
2980 Math.max( lengthInsertions1, lengthDeletions1 ) ) &&
2981 ( lastequality.length <= Math.max( lengthInsertions2,
2982 lengthDeletions2 ) ) ) {
2984 // Duplicate record.
2985 diffs.splice(
2986 equalities[ equalitiesLength - 1 ],
2988 [ DIFF_DELETE, lastequality ]
2991 // Change second copy to insert.
2992 diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
2994 // Throw away the equality we just deleted.
2995 equalitiesLength--;
2997 // Throw away the previous equality (it needs to be reevaluated).
2998 equalitiesLength--;
2999 pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
3001 // Reset the counters.
3002 lengthInsertions1 = 0;
3003 lengthDeletions1 = 0;
3004 lengthInsertions2 = 0;
3005 lengthDeletions2 = 0;
3006 lastequality = null;
3007 changes = true;
3010 pointer++;
3013 // Normalize the diff.
3014 if ( changes ) {
3015 this.diffCleanupMerge( diffs );
3018 // Find any overlaps between deletions and insertions.
3019 // e.g: <del>abcxxx</del><ins>xxxdef</ins>
3020 // -> <del>abc</del>xxx<ins>def</ins>
3021 // e.g: <del>xxxabc</del><ins>defxxx</ins>
3022 // -> <ins>def</ins>xxx<del>abc</del>
3023 // Only extract an overlap if it is as big as the edit ahead or behind it.
3024 pointer = 1;
3025 while ( pointer < diffs.length ) {
3026 if ( diffs[ pointer - 1 ][ 0 ] === DIFF_DELETE &&
3027 diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
3028 deletion = diffs[ pointer - 1 ][ 1 ];
3029 insertion = diffs[ pointer ][ 1 ];
3030 overlapLength1 = this.diffCommonOverlap( deletion, insertion );
3031 overlapLength2 = this.diffCommonOverlap( insertion, deletion );
3032 if ( overlapLength1 >= overlapLength2 ) {
3033 if ( overlapLength1 >= deletion.length / 2 ||
3034 overlapLength1 >= insertion.length / 2 ) {
3035 // Overlap found. Insert an equality and trim the surrounding edits.
3036 diffs.splice(
3037 pointer,
3039 [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ]
3041 diffs[ pointer - 1 ][ 1 ] =
3042 deletion.substring( 0, deletion.length - overlapLength1 );
3043 diffs[ pointer + 1 ][ 1 ] = insertion.substring( overlapLength1 );
3044 pointer++;
3046 } else {
3047 if ( overlapLength2 >= deletion.length / 2 ||
3048 overlapLength2 >= insertion.length / 2 ) {
3050 // Reverse overlap found.
3051 // Insert an equality and swap and trim the surrounding edits.
3052 diffs.splice(
3053 pointer,
3055 [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ]
3058 diffs[ pointer - 1 ][ 0 ] = DIFF_INSERT;
3059 diffs[ pointer - 1 ][ 1 ] =
3060 insertion.substring( 0, insertion.length - overlapLength2 );
3061 diffs[ pointer + 1 ][ 0 ] = DIFF_DELETE;
3062 diffs[ pointer + 1 ][ 1 ] =
3063 deletion.substring( overlapLength2 );
3064 pointer++;
3067 pointer++;
3069 pointer++;
3074 * Determine if the suffix of one string is the prefix of another.
3075 * @param {string} text1 First string.
3076 * @param {string} text2 Second string.
3077 * @return {number} The number of characters common to the end of the first
3078 * string and the start of the second string.
3079 * @private
3081 DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) {
3082 var text1Length, text2Length, textLength,
3083 best, length, pattern, found;
3084 // Cache the text lengths to prevent multiple calls.
3085 text1Length = text1.length;
3086 text2Length = text2.length;
3087 // Eliminate the null case.
3088 if ( text1Length === 0 || text2Length === 0 ) {
3089 return 0;
3091 // Truncate the longer string.
3092 if ( text1Length > text2Length ) {
3093 text1 = text1.substring( text1Length - text2Length );
3094 } else if ( text1Length < text2Length ) {
3095 text2 = text2.substring( 0, text1Length );
3097 textLength = Math.min( text1Length, text2Length );
3098 // Quick check for the worst case.
3099 if ( text1 === text2 ) {
3100 return textLength;
3103 // Start by looking for a single character match
3104 // and increase length until no match is found.
3105 // Performance analysis: https://neil.fraser.name/news/2010/11/04/
3106 best = 0;
3107 length = 1;
3108 while ( true ) {
3109 pattern = text1.substring( textLength - length );
3110 found = text2.indexOf( pattern );
3111 if ( found === -1 ) {
3112 return best;
3114 length += found;
3115 if ( found === 0 || text1.substring( textLength - length ) ===
3116 text2.substring( 0, length ) ) {
3117 best = length;
3118 length++;
3124 * Split two texts into an array of strings. Reduce the texts to a string of
3125 * hashes where each Unicode character represents one line.
3126 * @param {string} text1 First string.
3127 * @param {string} text2 Second string.
3128 * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
3129 * An object containing the encoded text1, the encoded text2 and
3130 * the array of unique strings.
3131 * The zeroth element of the array of unique strings is intentionally blank.
3132 * @private
3134 DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) {
3135 var lineArray, lineHash, chars1, chars2;
3136 lineArray = []; // e.g. lineArray[4] === 'Hello\n'
3137 lineHash = {}; // e.g. lineHash['Hello\n'] === 4
3139 // '\x00' is a valid character, but various debuggers don't like it.
3140 // So we'll insert a junk entry to avoid generating a null character.
3141 lineArray[ 0 ] = "";
3144 * Split a text into an array of strings. Reduce the texts to a string of
3145 * hashes where each Unicode character represents one line.
3146 * Modifies linearray and linehash through being a closure.
3147 * @param {string} text String to encode.
3148 * @return {string} Encoded string.
3149 * @private
3151 function diffLinesToCharsMunge( text ) {
3152 var chars, lineStart, lineEnd, lineArrayLength, line;
3153 chars = "";
3154 // Walk the text, pulling out a substring for each line.
3155 // text.split('\n') would would temporarily double our memory footprint.
3156 // Modifying text would create many large strings to garbage collect.
3157 lineStart = 0;
3158 lineEnd = -1;
3159 // Keeping our own length variable is faster than looking it up.
3160 lineArrayLength = lineArray.length;
3161 while ( lineEnd < text.length - 1 ) {
3162 lineEnd = text.indexOf( "\n", lineStart );
3163 if ( lineEnd === -1 ) {
3164 lineEnd = text.length - 1;
3166 line = text.substring( lineStart, lineEnd + 1 );
3167 lineStart = lineEnd + 1;
3169 if ( lineHash.hasOwnProperty ? lineHash.hasOwnProperty( line ) :
3170 ( lineHash[ line ] !== undefined ) ) {
3171 chars += String.fromCharCode( lineHash[ line ] );
3172 } else {
3173 chars += String.fromCharCode( lineArrayLength );
3174 lineHash[ line ] = lineArrayLength;
3175 lineArray[ lineArrayLength++ ] = line;
3178 return chars;
3181 chars1 = diffLinesToCharsMunge( text1 );
3182 chars2 = diffLinesToCharsMunge( text2 );
3183 return {
3184 chars1: chars1,
3185 chars2: chars2,
3186 lineArray: lineArray
3191 * Rehydrate the text in a diff from a string of line hashes to real lines of
3192 * text.
3193 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
3194 * @param {!Array.<string>} lineArray Array of unique strings.
3195 * @private
3197 DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {
3198 var x, chars, text, y;
3199 for ( x = 0; x < diffs.length; x++ ) {
3200 chars = diffs[ x ][ 1 ];
3201 text = [];
3202 for ( y = 0; y < chars.length; y++ ) {
3203 text[ y ] = lineArray[ chars.charCodeAt( y ) ];
3205 diffs[ x ][ 1 ] = text.join( "" );
3210 * Reorder and merge like edit sections. Merge equalities.
3211 * Any edit section can move as long as it doesn't cross an equality.
3212 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
3214 DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) {
3215 var pointer, countDelete, countInsert, textInsert, textDelete,
3216 commonlength, changes, diffPointer, position;
3217 diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
3218 pointer = 0;
3219 countDelete = 0;
3220 countInsert = 0;
3221 textDelete = "";
3222 textInsert = "";
3223 commonlength;
3224 while ( pointer < diffs.length ) {
3225 switch ( diffs[ pointer ][ 0 ] ) {
3226 case DIFF_INSERT:
3227 countInsert++;
3228 textInsert += diffs[ pointer ][ 1 ];
3229 pointer++;
3230 break;
3231 case DIFF_DELETE:
3232 countDelete++;
3233 textDelete += diffs[ pointer ][ 1 ];
3234 pointer++;
3235 break;
3236 case DIFF_EQUAL:
3237 // Upon reaching an equality, check for prior redundancies.
3238 if ( countDelete + countInsert > 1 ) {
3239 if ( countDelete !== 0 && countInsert !== 0 ) {
3240 // Factor out any common prefixes.
3241 commonlength = this.diffCommonPrefix( textInsert, textDelete );
3242 if ( commonlength !== 0 ) {
3243 if ( ( pointer - countDelete - countInsert ) > 0 &&
3244 diffs[ pointer - countDelete - countInsert - 1 ][ 0 ] ===
3245 DIFF_EQUAL ) {
3246 diffs[ pointer - countDelete - countInsert - 1 ][ 1 ] +=
3247 textInsert.substring( 0, commonlength );
3248 } else {
3249 diffs.splice( 0, 0, [ DIFF_EQUAL,
3250 textInsert.substring( 0, commonlength )
3251 ] );
3252 pointer++;
3254 textInsert = textInsert.substring( commonlength );
3255 textDelete = textDelete.substring( commonlength );
3257 // Factor out any common suffixies.
3258 commonlength = this.diffCommonSuffix( textInsert, textDelete );
3259 if ( commonlength !== 0 ) {
3260 diffs[ pointer ][ 1 ] = textInsert.substring( textInsert.length -
3261 commonlength ) + diffs[ pointer ][ 1 ];
3262 textInsert = textInsert.substring( 0, textInsert.length -
3263 commonlength );
3264 textDelete = textDelete.substring( 0, textDelete.length -
3265 commonlength );
3268 // Delete the offending records and add the merged ones.
3269 if ( countDelete === 0 ) {
3270 diffs.splice( pointer - countInsert,
3271 countDelete + countInsert, [ DIFF_INSERT, textInsert ] );
3272 } else if ( countInsert === 0 ) {
3273 diffs.splice( pointer - countDelete,
3274 countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
3275 } else {
3276 diffs.splice(
3277 pointer - countDelete - countInsert,
3278 countDelete + countInsert,
3279 [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ]
3282 pointer = pointer - countDelete - countInsert +
3283 ( countDelete ? 1 : 0 ) + ( countInsert ? 1 : 0 ) + 1;
3284 } else if ( pointer !== 0 && diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL ) {
3286 // Merge this equality with the previous one.
3287 diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ];
3288 diffs.splice( pointer, 1 );
3289 } else {
3290 pointer++;
3292 countInsert = 0;
3293 countDelete = 0;
3294 textDelete = "";
3295 textInsert = "";
3296 break;
3299 if ( diffs[ diffs.length - 1 ][ 1 ] === "" ) {
3300 diffs.pop(); // Remove the dummy entry at the end.
3303 // Second pass: look for single edits surrounded on both sides by equalities
3304 // which can be shifted sideways to eliminate an equality.
3305 // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
3306 changes = false;
3307 pointer = 1;
3309 // Intentionally ignore the first and last element (don't need checking).
3310 while ( pointer < diffs.length - 1 ) {
3311 if ( diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL &&
3312 diffs[ pointer + 1 ][ 0 ] === DIFF_EQUAL ) {
3314 diffPointer = diffs[ pointer ][ 1 ];
3315 position = diffPointer.substring(
3316 diffPointer.length - diffs[ pointer - 1 ][ 1 ].length
3319 // This is a single edit surrounded by equalities.
3320 if ( position === diffs[ pointer - 1 ][ 1 ] ) {
3322 // Shift the edit over the previous equality.
3323 diffs[ pointer ][ 1 ] = diffs[ pointer - 1 ][ 1 ] +
3324 diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer ][ 1 ].length -
3325 diffs[ pointer - 1 ][ 1 ].length );
3326 diffs[ pointer + 1 ][ 1 ] =
3327 diffs[ pointer - 1 ][ 1 ] + diffs[ pointer + 1 ][ 1 ];
3328 diffs.splice( pointer - 1, 1 );
3329 changes = true;
3330 } else if ( diffPointer.substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
3331 diffs[ pointer + 1 ][ 1 ] ) {
3333 // Shift the edit over the next equality.
3334 diffs[ pointer - 1 ][ 1 ] += diffs[ pointer + 1 ][ 1 ];
3335 diffs[ pointer ][ 1 ] =
3336 diffs[ pointer ][ 1 ].substring( diffs[ pointer + 1 ][ 1 ].length ) +
3337 diffs[ pointer + 1 ][ 1 ];
3338 diffs.splice( pointer + 1, 1 );
3339 changes = true;
3342 pointer++;
3344 // If shifts were made, the diff needs reordering and another shift sweep.
3345 if ( changes ) {
3346 this.diffCleanupMerge( diffs );
3350 return function( o, n ) {
3351 var diff, output, text;
3352 diff = new DiffMatchPatch();
3353 output = diff.DiffMain( o, n );
3354 diff.diffCleanupEfficiency( output );
3355 text = diff.diffPrettyHtml( output );
3357 return text;
3359 }() );
3361 // Get a reference to the global object, like window in browsers
3362 }( (function() {
3363 return this;
3364 })() ));
3366 (function() {
3368 // Don't load the HTML Reporter on non-Browser environments
3369 if ( typeof window === "undefined" || !window.document ) {
3370 return;
3373 // Deprecated QUnit.init - Ref #530
3374 // Re-initialize the configuration options
3375 QUnit.init = function() {
3376 var tests, banner, result, qunit,
3377 config = QUnit.config;
3379 config.stats = { all: 0, bad: 0 };
3380 config.moduleStats = { all: 0, bad: 0 };
3381 config.started = 0;
3382 config.updateRate = 1000;
3383 config.blocking = false;
3384 config.autostart = true;
3385 config.autorun = false;
3386 config.filter = "";
3387 config.queue = [];
3389 // Return on non-browser environments
3390 // This is necessary to not break on node tests
3391 if ( typeof window === "undefined" ) {
3392 return;
3395 qunit = id( "qunit" );
3396 if ( qunit ) {
3397 qunit.innerHTML =
3398 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
3399 "<h2 id='qunit-banner'></h2>" +
3400 "<div id='qunit-testrunner-toolbar'></div>" +
3401 "<h2 id='qunit-userAgent'></h2>" +
3402 "<ol id='qunit-tests'></ol>";
3405 tests = id( "qunit-tests" );
3406 banner = id( "qunit-banner" );
3407 result = id( "qunit-testresult" );
3409 if ( tests ) {
3410 tests.innerHTML = "";
3413 if ( banner ) {
3414 banner.className = "";
3417 if ( result ) {
3418 result.parentNode.removeChild( result );
3421 if ( tests ) {
3422 result = document.createElement( "p" );
3423 result.id = "qunit-testresult";
3424 result.className = "result";
3425 tests.parentNode.insertBefore( result, tests );
3426 result.innerHTML = "Running...<br />&#160;";
3430 var config = QUnit.config,
3431 collapseNext = false,
3432 hasOwn = Object.prototype.hasOwnProperty,
3433 defined = {
3434 document: window.document !== undefined,
3435 sessionStorage: (function() {
3436 var x = "qunit-test-string";
3437 try {
3438 sessionStorage.setItem( x, x );
3439 sessionStorage.removeItem( x );
3440 return true;
3441 } catch ( e ) {
3442 return false;
3444 }())
3446 modulesList = [];
3449 * Escape text for attribute or text content.
3451 function escapeText( s ) {
3452 if ( !s ) {
3453 return "";
3455 s = s + "";
3457 // Both single quotes and double quotes (for attributes)
3458 return s.replace( /['"<>&]/g, function( s ) {
3459 switch ( s ) {
3460 case "'":
3461 return "&#039;";
3462 case "\"":
3463 return "&quot;";
3464 case "<":
3465 return "&lt;";
3466 case ">":
3467 return "&gt;";
3468 case "&":
3469 return "&amp;";
3475 * @param {HTMLElement} elem
3476 * @param {string} type
3477 * @param {Function} fn
3479 function addEvent( elem, type, fn ) {
3480 if ( elem.addEventListener ) {
3482 // Standards-based browsers
3483 elem.addEventListener( type, fn, false );
3484 } else if ( elem.attachEvent ) {
3486 // support: IE <9
3487 elem.attachEvent( "on" + type, function() {
3488 var event = window.event;
3489 if ( !event.target ) {
3490 event.target = event.srcElement || document;
3493 fn.call( elem, event );
3499 * @param {Array|NodeList} elems
3500 * @param {string} type
3501 * @param {Function} fn
3503 function addEvents( elems, type, fn ) {
3504 var i = elems.length;
3505 while ( i-- ) {
3506 addEvent( elems[ i ], type, fn );
3510 function hasClass( elem, name ) {
3511 return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
3514 function addClass( elem, name ) {
3515 if ( !hasClass( elem, name ) ) {
3516 elem.className += ( elem.className ? " " : "" ) + name;
3520 function toggleClass( elem, name ) {
3521 if ( hasClass( elem, name ) ) {
3522 removeClass( elem, name );
3523 } else {
3524 addClass( elem, name );
3528 function removeClass( elem, name ) {
3529 var set = " " + elem.className + " ";
3531 // Class name may appear multiple times
3532 while ( set.indexOf( " " + name + " " ) >= 0 ) {
3533 set = set.replace( " " + name + " ", " " );
3536 // trim for prettiness
3537 elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
3540 function id( name ) {
3541 return defined.document && document.getElementById && document.getElementById( name );
3544 function getUrlConfigHtml() {
3545 var i, j, val,
3546 escaped, escapedTooltip,
3547 selection = false,
3548 len = config.urlConfig.length,
3549 urlConfigHtml = "";
3551 for ( i = 0; i < len; i++ ) {
3552 val = config.urlConfig[ i ];
3553 if ( typeof val === "string" ) {
3554 val = {
3555 id: val,
3556 label: val
3560 escaped = escapeText( val.id );
3561 escapedTooltip = escapeText( val.tooltip );
3563 if ( config[ val.id ] === undefined ) {
3564 config[ val.id ] = QUnit.urlParams[ val.id ];
3567 if ( !val.value || typeof val.value === "string" ) {
3568 urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
3569 "' name='" + escaped + "' type='checkbox'" +
3570 ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
3571 ( config[ val.id ] ? " checked='checked'" : "" ) +
3572 " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
3573 "' title='" + escapedTooltip + "'>" + val.label + "</label>";
3574 } else {
3575 urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
3576 "' title='" + escapedTooltip + "'>" + val.label +
3577 ": </label><select id='qunit-urlconfig-" + escaped +
3578 "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
3580 if ( QUnit.is( "array", val.value ) ) {
3581 for ( j = 0; j < val.value.length; j++ ) {
3582 escaped = escapeText( val.value[ j ] );
3583 urlConfigHtml += "<option value='" + escaped + "'" +
3584 ( config[ val.id ] === val.value[ j ] ?
3585 ( selection = true ) && " selected='selected'" : "" ) +
3586 ">" + escaped + "</option>";
3588 } else {
3589 for ( j in val.value ) {
3590 if ( hasOwn.call( val.value, j ) ) {
3591 urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
3592 ( config[ val.id ] === j ?
3593 ( selection = true ) && " selected='selected'" : "" ) +
3594 ">" + escapeText( val.value[ j ] ) + "</option>";
3598 if ( config[ val.id ] && !selection ) {
3599 escaped = escapeText( config[ val.id ] );
3600 urlConfigHtml += "<option value='" + escaped +
3601 "' selected='selected' disabled='disabled'>" + escaped + "</option>";
3603 urlConfigHtml += "</select>";
3607 return urlConfigHtml;
3610 // Handle "click" events on toolbar checkboxes and "change" for select menus.
3611 // Updates the URL with the new state of `config.urlConfig` values.
3612 function toolbarChanged() {
3613 var updatedUrl, value,
3614 field = this,
3615 params = {};
3617 // Detect if field is a select menu or a checkbox
3618 if ( "selectedIndex" in field ) {
3619 value = field.options[ field.selectedIndex ].value || undefined;
3620 } else {
3621 value = field.checked ? ( field.defaultValue || true ) : undefined;
3624 params[ field.name ] = value;
3625 updatedUrl = setUrl( params );
3627 if ( "hidepassed" === field.name && "replaceState" in window.history ) {
3628 config[ field.name ] = value || false;
3629 if ( value ) {
3630 addClass( id( "qunit-tests" ), "hidepass" );
3631 } else {
3632 removeClass( id( "qunit-tests" ), "hidepass" );
3635 // It is not necessary to refresh the whole page
3636 window.history.replaceState( null, "", updatedUrl );
3637 } else {
3638 window.location = updatedUrl;
3642 function setUrl( params ) {
3643 var key,
3644 querystring = "?";
3646 params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
3648 for ( key in params ) {
3649 if ( hasOwn.call( params, key ) ) {
3650 if ( params[ key ] === undefined ) {
3651 continue;
3653 querystring += encodeURIComponent( key );
3654 if ( params[ key ] !== true ) {
3655 querystring += "=" + encodeURIComponent( params[ key ] );
3657 querystring += "&";
3660 return location.protocol + "//" + location.host +
3661 location.pathname + querystring.slice( 0, -1 );
3664 function applyUrlParams() {
3665 var selectedModule,
3666 modulesList = id( "qunit-modulefilter" ),
3667 filter = id( "qunit-filter-input" ).value;
3669 selectedModule = modulesList ?
3670 decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) :
3671 undefined;
3673 window.location = setUrl({
3674 module: ( selectedModule === "" ) ? undefined : selectedModule,
3675 filter: ( filter === "" ) ? undefined : filter,
3677 // Remove testId filter
3678 testId: undefined
3682 function toolbarUrlConfigContainer() {
3683 var urlConfigContainer = document.createElement( "span" );
3685 urlConfigContainer.innerHTML = getUrlConfigHtml();
3686 addClass( urlConfigContainer, "qunit-url-config" );
3688 // For oldIE support:
3689 // * Add handlers to the individual elements instead of the container
3690 // * Use "click" instead of "change" for checkboxes
3691 addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
3692 addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
3694 return urlConfigContainer;
3697 function toolbarLooseFilter() {
3698 var filter = document.createElement( "form" ),
3699 label = document.createElement( "label" ),
3700 input = document.createElement( "input" ),
3701 button = document.createElement( "button" );
3703 addClass( filter, "qunit-filter" );
3705 label.innerHTML = "Filter: ";
3707 input.type = "text";
3708 input.value = config.filter || "";
3709 input.name = "filter";
3710 input.id = "qunit-filter-input";
3712 button.innerHTML = "Go";
3714 label.appendChild( input );
3716 filter.appendChild( label );
3717 filter.appendChild( button );
3718 addEvent( filter, "submit", function( ev ) {
3719 applyUrlParams();
3721 if ( ev && ev.preventDefault ) {
3722 ev.preventDefault();
3725 return false;
3728 return filter;
3731 function toolbarModuleFilterHtml() {
3732 var i,
3733 moduleFilterHtml = "";
3735 if ( !modulesList.length ) {
3736 return false;
3739 modulesList.sort(function( a, b ) {
3740 return a.localeCompare( b );
3743 moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
3744 "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
3745 ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
3746 ">< All Modules ></option>";
3748 for ( i = 0; i < modulesList.length; i++ ) {
3749 moduleFilterHtml += "<option value='" +
3750 escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
3751 ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
3752 ">" + escapeText( modulesList[ i ] ) + "</option>";
3754 moduleFilterHtml += "</select>";
3756 return moduleFilterHtml;
3759 function toolbarModuleFilter() {
3760 var toolbar = id( "qunit-testrunner-toolbar" ),
3761 moduleFilter = document.createElement( "span" ),
3762 moduleFilterHtml = toolbarModuleFilterHtml();
3764 if ( !toolbar || !moduleFilterHtml ) {
3765 return false;
3768 moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
3769 moduleFilter.innerHTML = moduleFilterHtml;
3771 addEvent( moduleFilter.lastChild, "change", applyUrlParams );
3773 toolbar.appendChild( moduleFilter );
3776 function appendToolbar() {
3777 var toolbar = id( "qunit-testrunner-toolbar" );
3779 if ( toolbar ) {
3780 toolbar.appendChild( toolbarUrlConfigContainer() );
3781 toolbar.appendChild( toolbarLooseFilter() );
3785 function appendHeader() {
3786 var header = id( "qunit-header" );
3788 if ( header ) {
3789 header.innerHTML = "<a href='" +
3790 escapeText( setUrl( { filter: undefined, module: undefined, testId: undefined } ) ) +
3791 "'>" + header.innerHTML + "</a> ";
3795 function appendBanner() {
3796 var banner = id( "qunit-banner" );
3798 if ( banner ) {
3799 banner.className = "";
3803 function appendTestResults() {
3804 var tests = id( "qunit-tests" ),
3805 result = id( "qunit-testresult" );
3807 if ( result ) {
3808 result.parentNode.removeChild( result );
3811 if ( tests ) {
3812 tests.innerHTML = "";
3813 result = document.createElement( "p" );
3814 result.id = "qunit-testresult";
3815 result.className = "result";
3816 tests.parentNode.insertBefore( result, tests );
3817 result.innerHTML = "Running...<br />&#160;";
3821 function storeFixture() {
3822 var fixture = id( "qunit-fixture" );
3823 if ( fixture ) {
3824 config.fixture = fixture.innerHTML;
3828 function appendFilteredTest() {
3829 var testId = QUnit.config.testId;
3830 if ( !testId || testId.length <= 0 ) {
3831 return "";
3833 return "<div id='qunit-filteredTest'>Rerunning selected tests: " +
3834 escapeText( testId.join(", ") ) +
3835 " <a id='qunit-clearFilter' href='" +
3836 escapeText( setUrl( { filter: undefined, module: undefined, testId: undefined } ) ) +
3837 "'>" + "Run all tests" + "</a></div>";
3840 function appendUserAgent() {
3841 var userAgent = id( "qunit-userAgent" );
3843 if ( userAgent ) {
3844 userAgent.innerHTML = "";
3845 userAgent.appendChild(
3846 document.createTextNode(
3847 "QUnit " + QUnit.version + "; " + navigator.userAgent
3853 function appendTestsList( modules ) {
3854 var i, l, x, z, test, moduleObj;
3856 for ( i = 0, l = modules.length; i < l; i++ ) {
3857 moduleObj = modules[ i ];
3859 if ( moduleObj.name ) {
3860 modulesList.push( moduleObj.name );
3863 for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
3864 test = moduleObj.tests[ x ];
3866 appendTest( test.name, test.testId, moduleObj.name );
3871 function appendTest( name, testId, moduleName ) {
3872 var title, rerunTrigger, testBlock, assertList,
3873 tests = id( "qunit-tests" );
3875 if ( !tests ) {
3876 return;
3879 title = document.createElement( "strong" );
3880 title.innerHTML = getNameHtml( name, moduleName );
3882 rerunTrigger = document.createElement( "a" );
3883 rerunTrigger.innerHTML = "Rerun";
3884 rerunTrigger.href = setUrl({ testId: testId });
3886 testBlock = document.createElement( "li" );
3887 testBlock.appendChild( title );
3888 testBlock.appendChild( rerunTrigger );
3889 testBlock.id = "qunit-test-output-" + testId;
3891 assertList = document.createElement( "ol" );
3892 assertList.className = "qunit-assert-list";
3894 testBlock.appendChild( assertList );
3896 tests.appendChild( testBlock );
3899 // HTML Reporter initialization and load
3900 QUnit.begin(function( details ) {
3901 var qunit = id( "qunit" );
3903 // Fixture is the only one necessary to run without the #qunit element
3904 storeFixture();
3906 if ( qunit ) {
3907 qunit.innerHTML =
3908 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
3909 "<h2 id='qunit-banner'></h2>" +
3910 "<div id='qunit-testrunner-toolbar'></div>" +
3911 appendFilteredTest() +
3912 "<h2 id='qunit-userAgent'></h2>" +
3913 "<ol id='qunit-tests'></ol>";
3916 appendHeader();
3917 appendBanner();
3918 appendTestResults();
3919 appendUserAgent();
3920 appendToolbar();
3921 appendTestsList( details.modules );
3922 toolbarModuleFilter();
3924 if ( qunit && config.hidepassed ) {
3925 addClass( qunit.lastChild, "hidepass" );
3929 QUnit.done(function( details ) {
3930 var i, key,
3931 banner = id( "qunit-banner" ),
3932 tests = id( "qunit-tests" ),
3933 html = [
3934 "Tests completed in ",
3935 details.runtime,
3936 " milliseconds.<br />",
3937 "<span class='passed'>",
3938 details.passed,
3939 "</span> assertions of <span class='total'>",
3940 details.total,
3941 "</span> passed, <span class='failed'>",
3942 details.failed,
3943 "</span> failed."
3944 ].join( "" );
3946 if ( banner ) {
3947 banner.className = details.failed ? "qunit-fail" : "qunit-pass";
3950 if ( tests ) {
3951 id( "qunit-testresult" ).innerHTML = html;
3954 if ( config.altertitle && defined.document && document.title ) {
3956 // show ✖ for good, ✔ for bad suite result in title
3957 // use escape sequences in case file gets loaded with non-utf-8-charset
3958 document.title = [
3959 ( details.failed ? "\u2716" : "\u2714" ),
3960 document.title.replace( /^[\u2714\u2716] /i, "" )
3961 ].join( " " );
3964 // clear own sessionStorage items if all tests passed
3965 if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
3966 for ( i = 0; i < sessionStorage.length; i++ ) {
3967 key = sessionStorage.key( i++ );
3968 if ( key.indexOf( "qunit-test-" ) === 0 ) {
3969 sessionStorage.removeItem( key );
3974 // scroll back to top to show results
3975 if ( config.scrolltop && window.scrollTo ) {
3976 window.scrollTo( 0, 0 );
3980 function getNameHtml( name, module ) {
3981 var nameHtml = "";
3983 if ( module ) {
3984 nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
3987 nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
3989 return nameHtml;
3992 QUnit.testStart(function( details ) {
3993 var running, testBlock, bad;
3995 testBlock = id( "qunit-test-output-" + details.testId );
3996 if ( testBlock ) {
3997 testBlock.className = "running";
3998 } else {
4000 // Report later registered tests
4001 appendTest( details.name, details.testId, details.module );
4004 running = id( "qunit-testresult" );
4005 if ( running ) {
4006 bad = QUnit.config.reorder && defined.sessionStorage &&
4007 +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name );
4009 running.innerHTML = ( bad ?
4010 "Rerunning previously failed test: <br />" :
4011 "Running: <br />" ) +
4012 getNameHtml( details.name, details.module );
4017 function stripHtml( string ) {
4018 // strip tags, html entity and whitespaces
4019 return string.replace(/<\/?[^>]+(>|$)/g, "").replace(/\&quot;/g, "").replace(/\s+/g, "");
4022 QUnit.log(function( details ) {
4023 var assertList, assertLi,
4024 message, expected, actual, diff,
4025 showDiff = false,
4026 testItem = id( "qunit-test-output-" + details.testId );
4028 if ( !testItem ) {
4029 return;
4032 message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
4033 message = "<span class='test-message'>" + message + "</span>";
4034 message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
4036 // pushFailure doesn't provide details.expected
4037 // when it calls, it's implicit to also not show expected and diff stuff
4038 // Also, we need to check details.expected existence, as it can exist and be undefined
4039 if ( !details.result && hasOwn.call( details, "expected" ) ) {
4040 if ( details.negative ) {
4041 expected = escapeText( "NOT " + QUnit.dump.parse( details.expected ) );
4042 } else {
4043 expected = escapeText( QUnit.dump.parse( details.expected ) );
4046 actual = escapeText( QUnit.dump.parse( details.actual ) );
4047 message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
4048 expected +
4049 "</pre></td></tr>";
4051 if ( actual !== expected ) {
4053 message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
4054 actual + "</pre></td></tr>";
4056 // Don't show diff if actual or expected are booleans
4057 if ( !( /^(true|false)$/.test( actual ) ) &&
4058 !( /^(true|false)$/.test( expected ) ) ) {
4059 diff = QUnit.diff( expected, actual );
4060 showDiff = stripHtml( diff ).length !==
4061 stripHtml( expected ).length +
4062 stripHtml( actual ).length;
4065 // Don't show diff if expected and actual are totally different
4066 if ( showDiff ) {
4067 message += "<tr class='test-diff'><th>Diff: </th><td><pre>" +
4068 diff + "</pre></td></tr>";
4070 } else if ( expected.indexOf( "[object Array]" ) !== -1 ||
4071 expected.indexOf( "[object Object]" ) !== -1 ) {
4072 message += "<tr class='test-message'><th>Message: </th><td>" +
4073 "Diff suppressed as the depth of object is more than current max depth (" +
4074 QUnit.config.maxDepth + ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " +
4075 " run with a higher max depth or <a href='" +
4076 escapeText( setUrl( { maxDepth: -1 } ) ) + "'>" +
4077 "Rerun</a> without max depth.</p></td></tr>";
4080 if ( details.source ) {
4081 message += "<tr class='test-source'><th>Source: </th><td><pre>" +
4082 escapeText( details.source ) + "</pre></td></tr>";
4085 message += "</table>";
4087 // this occurs when pushFailure is set and we have an extracted stack trace
4088 } else if ( !details.result && details.source ) {
4089 message += "<table>" +
4090 "<tr class='test-source'><th>Source: </th><td><pre>" +
4091 escapeText( details.source ) + "</pre></td></tr>" +
4092 "</table>";
4095 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
4097 assertLi = document.createElement( "li" );
4098 assertLi.className = details.result ? "pass" : "fail";
4099 assertLi.innerHTML = message;
4100 assertList.appendChild( assertLi );
4103 QUnit.testDone(function( details ) {
4104 var testTitle, time, testItem, assertList,
4105 good, bad, testCounts, skipped, sourceName,
4106 tests = id( "qunit-tests" );
4108 if ( !tests ) {
4109 return;
4112 testItem = id( "qunit-test-output-" + details.testId );
4114 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
4116 good = details.passed;
4117 bad = details.failed;
4119 // store result when possible
4120 if ( config.reorder && defined.sessionStorage ) {
4121 if ( bad ) {
4122 sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
4123 } else {
4124 sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
4128 if ( bad === 0 ) {
4130 // Collapse the passing tests
4131 addClass( assertList, "qunit-collapsed" );
4132 } else if ( bad && config.collapse && !collapseNext ) {
4134 // Skip collapsing the first failing test
4135 collapseNext = true;
4136 } else {
4138 // Collapse remaining tests
4139 addClass( assertList, "qunit-collapsed" );
4142 // testItem.firstChild is the test name
4143 testTitle = testItem.firstChild;
4145 testCounts = bad ?
4146 "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
4149 testTitle.innerHTML += " <b class='counts'>(" + testCounts +
4150 details.assertions.length + ")</b>";
4152 if ( details.skipped ) {
4153 testItem.className = "skipped";
4154 skipped = document.createElement( "em" );
4155 skipped.className = "qunit-skipped-label";
4156 skipped.innerHTML = "skipped";
4157 testItem.insertBefore( skipped, testTitle );
4158 } else {
4159 addEvent( testTitle, "click", function() {
4160 toggleClass( assertList, "qunit-collapsed" );
4163 testItem.className = bad ? "fail" : "pass";
4165 time = document.createElement( "span" );
4166 time.className = "runtime";
4167 time.innerHTML = details.runtime + " ms";
4168 testItem.insertBefore( time, assertList );
4171 // Show the source of the test when showing assertions
4172 if ( details.source ) {
4173 sourceName = document.createElement( "p" );
4174 sourceName.innerHTML = "<strong>Source: </strong>" + details.source;
4175 addClass( sourceName, "qunit-source" );
4176 if ( bad === 0 ) {
4177 addClass( sourceName, "qunit-collapsed" );
4179 addEvent( testTitle, "click", function() {
4180 toggleClass( sourceName, "qunit-collapsed" );
4182 testItem.appendChild( sourceName );
4186 if ( defined.document ) {
4188 // Avoid readyState issue with phantomjs
4189 // Ref: #818
4190 var notPhantom = ( function( p ) {
4191 return !( p && p.version && p.version.major > 0 );
4192 } )( window.phantom );
4194 if ( notPhantom && document.readyState === "complete" ) {
4195 QUnit.load();
4196 } else {
4197 addEvent( window, "load", QUnit.load );
4199 } else {
4200 config.pageLoaded = true;
4201 config.autorun = true;
4204 })();