5 * Copyright jQuery Foundation and other contributors
6 * Released under the MIT license
7 * http://jquery.org/license
9 * Date: 2015-04-03T10:23Z
17 loggingCallbacks
= {},
18 fileName
= ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ),
19 toString
= Object
.prototype.toString
,
20 hasOwn
= Object
.prototype.hasOwnProperty
,
21 // Keep a local reference to Date (GH-283)
23 now
= Date
.now
|| function() {
24 return new Date().getTime();
26 globalStartCalled
= false,
28 setTimeout
= window
.setTimeout
,
29 clearTimeout
= window
.clearTimeout
,
31 document
: window
.document
!== undefined,
32 setTimeout
: window
.setTimeout
!== undefined,
33 sessionStorage
: (function() {
34 var x
= "qunit-test-string";
36 sessionStorage
.setItem( x
, x
);
37 sessionStorage
.removeItem( x
);
45 * Provides a normalized error string, correcting an issue
46 * with IE 7 (and prior) where Error.prototype.toString is
47 * not properly implemented
49 * Based on http://es5.github.com/#x15.11.4.4
51 * @param {String|Error} error
52 * @return {String} error message
54 errorString = function( error
) {
56 errorString
= error
.toString();
57 if ( errorString
.substring( 0, 7 ) === "[object" ) {
58 name
= error
.name
? error
.name
.toString() : "Error";
59 message
= error
.message
? error
.message
.toString() : "";
60 if ( name
&& message
) {
61 return name
+ ": " + message
;
64 } else if ( message
) {
74 * Makes a clone of an object using only Array or Object as base,
75 * and copies over the own enumerable properties.
78 * @return {Object} New object with only the own properties (recursively).
80 objectValues = function( obj
) {
82 vals
= QUnit
.is( "array", obj
) ? [] : {};
84 if ( hasOwn
.call( obj
, key
) ) {
86 vals
[ key
] = val
=== Object( val
) ? objectValues( val
) : val
;
95 * Config object: Maintain internal state
96 * Later exposed as QUnit.config
97 * `config` initialized at top of scope
100 // The queue of tests to run
103 // block until document ready
106 // by default, run previously failed tests first
107 // very useful in combination with "Hide passed tests" checked
110 // by default, modify document.title when suite is done
113 // by default, scroll to top of the page when suite is done
116 // when enabled, all tests must call expect()
117 requireExpects
: false,
119 // depth up-to which object will be dumped
122 // add checkboxes that are persisted in the query-string
123 // when enabled, the id is set to `true` as a `QUnit.config` property
127 label
: "Hide passed tests",
128 tooltip
: "Only show tests and assertions that fail. Stored as query-strings."
132 label
: "Check for Globals",
133 tooltip
: "Enabling this will test if any test introduces new properties on the " +
134 "`window` object. Stored as query-strings."
138 label
: "No try-catch",
139 tooltip
: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
140 "exceptions in IE reasonable. Stored as query-strings."
144 // Set of all modules.
147 // The first unnamed module
156 // Push a loose unnamed module to the modules collection
157 config
.modules
.push( config
.currentModule
);
159 // Initialize more QUnit.config and QUnit.urlParams
162 location
= window
.location
|| { search
: "", protocol
: "file:" },
163 params
= location
.search
.slice( 1 ).split( "&" ),
164 length
= params
.length
,
168 for ( i
= 0; i
< length
; i
++ ) {
169 current
= params
[ i
].split( "=" );
170 current
[ 0 ] = decodeURIComponent( current
[ 0 ] );
172 // allow just a key to turn on a flag, e.g., test.html?noglobals
173 current
[ 1 ] = current
[ 1 ] ? decodeURIComponent( current
[ 1 ] ) : true;
174 if ( urlParams
[ current
[ 0 ] ] ) {
175 urlParams
[ current
[ 0 ] ] = [].concat( urlParams
[ current
[ 0 ] ], current
[ 1 ] );
177 urlParams
[ current
[ 0 ] ] = current
[ 1 ];
182 if ( urlParams
.filter
=== true ) {
183 delete urlParams
.filter
;
186 QUnit
.urlParams
= urlParams
;
188 // String search anywhere in moduleName+testName
189 config
.filter
= urlParams
.filter
;
191 if ( urlParams
.maxDepth
) {
192 config
.maxDepth
= parseInt( urlParams
.maxDepth
, 10 ) === -1 ?
193 Number
.POSITIVE_INFINITY
:
198 if ( urlParams
.testId
) {
200 // Ensure that urlParams.testId is an array
201 urlParams
.testId
= decodeURIComponent( urlParams
.testId
).split( "," );
202 for ( i
= 0; i
< urlParams
.testId
.length
; i
++ ) {
203 config
.testId
.push( urlParams
.testId
[ i
] );
207 // Figure out if we're running the tests from a server or not
208 QUnit
.isLocal
= location
.protocol
=== "file:";
210 // Expose the current QUnit version
211 QUnit
.version
= "1.18.0";
214 // Root QUnit object.
215 // `QUnit` initialized at top of scope
218 // call on start of module test to prepend name to all tests
219 module: function( name
, testEnvironment
) {
220 var currentModule
= {
222 testEnvironment
: testEnvironment
,
226 // DEPRECATED: handles setup/teardown functions,
227 // beforeEach and afterEach should be used instead
228 if ( testEnvironment
&& testEnvironment
.setup
) {
229 testEnvironment
.beforeEach
= testEnvironment
.setup
;
230 delete testEnvironment
.setup
;
232 if ( testEnvironment
&& testEnvironment
.teardown
) {
233 testEnvironment
.afterEach
= testEnvironment
.teardown
;
234 delete testEnvironment
.teardown
;
237 config
.modules
.push( currentModule
);
238 config
.currentModule
= currentModule
;
241 // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
242 asyncTest: function( testName
, expected
, callback
) {
243 if ( arguments
.length
=== 2 ) {
248 QUnit
.test( testName
, expected
, callback
, true );
251 test: function( testName
, expected
, callback
, async
) {
254 if ( arguments
.length
=== 2 ) {
269 skip: function( testName
) {
270 var test
= new Test({
278 // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
279 // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
280 start: function( count
) {
281 var globalStartAlreadyCalled
= globalStartCalled
;
283 if ( !config
.current
) {
284 globalStartCalled
= true;
287 throw new Error( "Called start() outside of a test context while already started" );
288 } else if ( globalStartAlreadyCalled
|| count
> 1 ) {
289 throw new Error( "Called start() outside of a test context too many times" );
290 } else if ( config
.autostart
) {
291 throw new Error( "Called start() outside of a test context when " +
292 "QUnit.config.autostart was true" );
293 } else if ( !config
.pageLoaded
) {
295 // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
296 config
.autostart
= true;
301 // If a test is running, adjust its semaphore
302 config
.current
.semaphore
-= count
|| 1;
304 // Don't start until equal number of stop-calls
305 if ( config
.current
.semaphore
> 0 ) {
309 // throw an Error if start is called more often than stop
310 if ( config
.current
.semaphore
< 0 ) {
311 config
.current
.semaphore
= 0;
314 "Called start() while already started (test's semaphore was 0 already)",
315 sourceFromStacktrace( 2 )
324 // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
325 stop: function( count
) {
327 // If there isn't a test running, don't allow QUnit.stop() to be called
328 if ( !config
.current
) {
329 throw new Error( "Called stop() outside of a test context" );
332 // If a test is running, adjust its semaphore
333 config
.current
.semaphore
+= count
|| 1;
340 // Safe object type checking
341 is: function( type
, obj
) {
342 return QUnit
.objectType( obj
) === type
;
345 objectType: function( obj
) {
346 if ( typeof obj
=== "undefined" ) {
350 // Consider: typeof null === object
351 if ( obj
=== null ) {
355 var match
= toString
.call( obj
).match( /^\[object\s(.*)\]$/ ),
356 type
= match
&& match
[ 1 ] || "";
360 if ( isNaN( obj
) ) {
370 return type
.toLowerCase();
372 if ( typeof obj
=== "object" ) {
381 config
.pageLoaded
= true;
383 // Initialize the configuration options
385 stats
: { all
: 0, bad
: 0 },
386 moduleStats
: { all
: 0, bad
: 0 },
393 config
.blocking
= false;
395 if ( config
.autostart
) {
401 // Register logging callbacks
404 callbacks
= [ "begin", "done", "log", "testStart", "testDone",
405 "moduleStart", "moduleDone" ];
407 function registerLoggingCallback( key
) {
408 var loggingCallback = function( callback
) {
409 if ( QUnit
.objectType( callback
) !== "function" ) {
411 "QUnit logging methods require a callback function as their first parameters."
415 config
.callbacks
[ key
].push( callback
);
418 // DEPRECATED: This will be removed on QUnit 2.0.0+
419 // Stores the registered functions allowing restoring
420 // at verifyLoggingCallbacks() if modified
421 loggingCallbacks
[ key
] = loggingCallback
;
423 return loggingCallback
;
426 for ( i
= 0, l
= callbacks
.length
; i
< l
; i
++ ) {
427 key
= callbacks
[ i
];
429 // Initialize key collection of logging callback
430 if ( QUnit
.objectType( config
.callbacks
[ key
] ) === "undefined" ) {
431 config
.callbacks
[ key
] = [];
434 QUnit
[ key
] = registerLoggingCallback( key
);
438 // `onErrorFnPrev` initialized at top of scope
439 // Preserve other handlers
440 onErrorFnPrev
= window
.onerror
;
442 // Cover uncaught exceptions
443 // Returning true will suppress the default browser handler,
444 // returning false will let it run.
445 window
.onerror = function( error
, filePath
, linerNr
) {
447 if ( onErrorFnPrev
) {
448 ret
= onErrorFnPrev( error
, filePath
, linerNr
);
451 // Treat return value as window.onerror itself does,
452 // Only do our handling if not suppressed.
453 if ( ret
!== true ) {
454 if ( QUnit
.config
.current
) {
455 if ( QUnit
.config
.current
.ignoreGlobalErrors
) {
458 QUnit
.pushFailure( error
, filePath
+ ":" + linerNr
);
460 QUnit
.test( "global failure", extend(function() {
461 QUnit
.pushFailure( error
, filePath
+ ":" + linerNr
);
462 }, { validTest
: true } ) );
473 config
.autorun
= true;
475 // Log the last module results
476 if ( config
.previousModule
) {
477 runLoggingCallbacks( "moduleDone", {
478 name
: config
.previousModule
.name
,
479 tests
: config
.previousModule
.tests
,
480 failed
: config
.moduleStats
.bad
,
481 passed
: config
.moduleStats
.all
- config
.moduleStats
.bad
,
482 total
: config
.moduleStats
.all
,
483 runtime
: now() - config
.moduleStats
.started
486 delete config
.previousModule
;
488 runtime
= now() - config
.started
;
489 passed
= config
.stats
.all
- config
.stats
.bad
;
491 runLoggingCallbacks( "done", {
492 failed
: config
.stats
.bad
,
494 total
: config
.stats
.all
,
499 // Doesn't support IE6 to IE9, it will return undefined on these browsers
500 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
501 function extractStacktrace( e
, offset
) {
502 offset
= offset
=== undefined ? 4 : offset
;
504 var stack
, include
, i
;
507 stack
= e
.stack
.split( "\n" );
508 if ( /^error$/i.test( stack
[ 0 ] ) ) {
513 for ( i
= offset
; i
< stack
.length
; i
++ ) {
514 if ( stack
[ i
].indexOf( fileName
) !== -1 ) {
517 include
.push( stack
[ i
] );
519 if ( include
.length
) {
520 return include
.join( "\n" );
523 return stack
[ offset
];
525 // Support: Safari <=6 only
526 } else if ( e
.sourceURL
) {
528 // exclude useless self-reference for generated Error objects
529 if ( /qunit.js$/.test( e
.sourceURL
) ) {
533 // for actual exceptions, this is useful
534 return e
.sourceURL
+ ":" + e
.line
;
538 function sourceFromStacktrace( offset
) {
539 var error
= new Error();
541 // Support: Safari <=7 only, IE <=10 - 11 only
542 // Not all browsers generate the `stack` property for `new Error()`, see also #636
543 if ( !error
.stack
) {
551 return extractStacktrace( error
, offset
);
554 function synchronize( callback
, last
) {
555 if ( QUnit
.objectType( callback
) === "array" ) {
556 while ( callback
.length
) {
557 synchronize( callback
.shift() );
561 config
.queue
.push( callback
);
563 if ( config
.autorun
&& !config
.blocking
) {
568 function process( last
) {
573 config
.depth
= ( config
.depth
|| 0 ) + 1;
575 while ( config
.queue
.length
&& !config
.blocking
) {
576 if ( !defined
.setTimeout
|| config
.updateRate
<= 0 ||
577 ( ( now() - start
) < config
.updateRate
) ) {
578 if ( config
.current
) {
580 // Reset async tracking for each phase of the Test lifecycle
581 config
.current
.usedAsync
= false;
583 config
.queue
.shift()();
585 setTimeout( next
, 13 );
590 if ( last
&& !config
.blocking
&& !config
.queue
.length
&& config
.depth
=== 0 ) {
599 // If the test run hasn't officially begun yet
600 if ( !config
.started
) {
602 // Record the time of the test run's beginning
603 config
.started
= now();
605 verifyLoggingCallbacks();
607 // Delete the loose unnamed module if unused.
608 if ( config
.modules
[ 0 ].name
=== "" && config
.modules
[ 0 ].tests
.length
=== 0 ) {
609 config
.modules
.shift();
612 // Avoid unnecessary information by not logging modules' test environments
613 for ( i
= 0, l
= config
.modules
.length
; i
< l
; i
++ ) {
615 name
: config
.modules
[ i
].name
,
616 tests
: config
.modules
[ i
].tests
620 // The test run is officially beginning now
621 runLoggingCallbacks( "begin", {
622 totalTests
: Test
.count
,
627 config
.blocking
= false;
631 function resumeProcessing() {
634 // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
635 if ( defined
.setTimeout
) {
636 setTimeout(function() {
637 if ( config
.current
&& config
.current
.semaphore
> 0 ) {
640 if ( config
.timeout
) {
641 clearTimeout( config
.timeout
);
651 function pauseProcessing() {
652 config
.blocking
= true;
654 if ( config
.testTimeout
&& defined
.setTimeout
) {
655 clearTimeout( config
.timeout
);
656 config
.timeout
= setTimeout(function() {
657 if ( config
.current
) {
658 config
.current
.semaphore
= 0;
659 QUnit
.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
661 throw new Error( "Test timed out" );
664 }, config
.testTimeout
);
668 function saveGlobal() {
669 config
.pollution
= [];
671 if ( config
.noglobals
) {
672 for ( var key
in window
) {
673 if ( hasOwn
.call( window
, key
) ) {
674 // in Opera sometimes DOM element ids show up here, ignore them
675 if ( /^qunit-test-output/.test( key
) ) {
678 config
.pollution
.push( key
);
684 function checkPollution() {
687 old
= config
.pollution
;
691 newGlobals
= diff( config
.pollution
, old
);
692 if ( newGlobals
.length
> 0 ) {
693 QUnit
.pushFailure( "Introduced global variable(s): " + newGlobals
.join( ", " ) );
696 deletedGlobals
= diff( old
, config
.pollution
);
697 if ( deletedGlobals
.length
> 0 ) {
698 QUnit
.pushFailure( "Deleted global variable(s): " + deletedGlobals
.join( ", " ) );
702 // returns a new Array with the elements that are in a but not in b
703 function diff( a
, b
) {
707 for ( i
= 0; i
< result
.length
; i
++ ) {
708 for ( j
= 0; j
< b
.length
; j
++ ) {
709 if ( result
[ i
] === b
[ j
] ) {
710 result
.splice( i
, 1 );
719 function extend( a
, b
, undefOnly
) {
720 for ( var prop
in b
) {
721 if ( hasOwn
.call( b
, prop
) ) {
723 // Avoid "Member not found" error in IE8 caused by messing with window.constructor
724 if ( !( prop
=== "constructor" && a
=== window
) ) {
725 if ( b
[ prop
] === undefined ) {
727 } else if ( !( undefOnly
&& typeof a
[ prop
] !== "undefined" ) ) {
728 a
[ prop
] = b
[ prop
];
737 function runLoggingCallbacks( key
, args
) {
740 callbacks
= config
.callbacks
[ key
];
741 for ( i
= 0, l
= callbacks
.length
; i
< l
; i
++ ) {
742 callbacks
[ i
]( args
);
746 // DEPRECATED: This will be removed on 2.0.0+
747 // This function verifies if the loggingCallbacks were modified by the user
748 // If so, it will restore it, assign the given callback and print a console warning
749 function verifyLoggingCallbacks() {
750 var loggingCallback
, userCallback
;
752 for ( loggingCallback
in loggingCallbacks
) {
753 if ( QUnit
[ loggingCallback
] !== loggingCallbacks
[ loggingCallback
] ) {
755 userCallback
= QUnit
[ loggingCallback
];
757 // Restore the callback function
758 QUnit
[ loggingCallback
] = loggingCallbacks
[ loggingCallback
];
760 // Assign the deprecated given callback
761 QUnit
[ loggingCallback
]( userCallback
);
763 if ( window
.console
&& window
.console
.warn
) {
765 "QUnit." + loggingCallback
+ " was replaced with a new value.\n" +
766 "Please, check out the documentation on how to apply logging callbacks.\n" +
767 "Reference: http://api.qunitjs.com/category/callbacks/"
775 function inArray( elem
, array
) {
776 if ( array
.indexOf
) {
777 return array
.indexOf( elem
);
780 for ( var i
= 0, length
= array
.length
; i
< length
; i
++ ) {
781 if ( array
[ i
] === elem
) {
789 function Test( settings
) {
794 extend( this, settings
);
795 this.assertions
= [];
797 this.usedAsync
= false;
798 this.module
= config
.currentModule
;
799 this.stack
= sourceFromStacktrace( 3 );
801 // Register unique strings
802 for ( i
= 0, l
= this.module
.tests
; i
< l
.length
; i
++ ) {
803 if ( this.module
.tests
[ i
].name
=== this.testName
) {
804 this.testName
+= " ";
808 this.testId
= generateHash( this.module
.name
, this.testName
);
810 this.module
.tests
.push({
815 if ( settings
.skip
) {
817 // Skipped tests will fully ignore any sent callback
818 this.callback = function() {};
822 this.assert
= new Assert( this );
832 // Emit moduleStart when we're switching from one module to another
833 this.module
!== config
.previousModule
||
835 // They could be equal (both undefined) but if the previousModule property doesn't
836 // yet exist it means this is the first test in a suite that isn't wrapped in a
837 // module, in which case we'll just emit a moduleStart event for 'undefined'.
838 // Without this, reporters can get testStart before moduleStart which is a problem.
839 !hasOwn
.call( config
, "previousModule" )
841 if ( hasOwn
.call( config
, "previousModule" ) ) {
842 runLoggingCallbacks( "moduleDone", {
843 name
: config
.previousModule
.name
,
844 tests
: config
.previousModule
.tests
,
845 failed
: config
.moduleStats
.bad
,
846 passed
: config
.moduleStats
.all
- config
.moduleStats
.bad
,
847 total
: config
.moduleStats
.all
,
848 runtime
: now() - config
.moduleStats
.started
851 config
.previousModule
= this.module
;
852 config
.moduleStats
= { all
: 0, bad
: 0, started
: now() };
853 runLoggingCallbacks( "moduleStart", {
854 name
: this.module
.name
,
855 tests
: this.module
.tests
859 config
.current
= this;
861 this.testEnvironment
= extend( {}, this.module
.testEnvironment
);
862 delete this.testEnvironment
.beforeEach
;
863 delete this.testEnvironment
.afterEach
;
865 this.started
= now();
866 runLoggingCallbacks( "testStart", {
868 module
: this.module
.name
,
872 if ( !config
.pollution
) {
880 config
.current
= this;
886 this.callbackStarted
= now();
888 if ( config
.notrycatch
) {
889 promise
= this.callback
.call( this.testEnvironment
, this.assert
);
890 this.resolvePromise( promise
);
895 promise
= this.callback
.call( this.testEnvironment
, this.assert
);
896 this.resolvePromise( promise
);
898 this.pushFailure( "Died on test #" + ( this.assertions
.length
+ 1 ) + " " +
899 this.stack
+ ": " + ( e
.message
|| e
), extractStacktrace( e
, 0 ) );
901 // else next test will carry the responsibility
904 // Restart the tests if they're blocking
905 if ( config
.blocking
) {
915 queueHook: function( hook
, hookName
) {
918 return function runHook() {
919 config
.current
= test
;
920 if ( config
.notrycatch
) {
921 promise
= hook
.call( test
.testEnvironment
, test
.assert
);
922 test
.resolvePromise( promise
, hookName
);
926 promise
= hook
.call( test
.testEnvironment
, test
.assert
);
927 test
.resolvePromise( promise
, hookName
);
929 test
.pushFailure( hookName
+ " failed on " + test
.testName
+ ": " +
930 ( error
.message
|| error
), extractStacktrace( error
, 0 ) );
935 // Currently only used for module level hooks, can be used to add global level ones
936 hooks: function( handler
) {
939 // Hooks are ignored on skipped tests
944 if ( this.module
.testEnvironment
&&
945 QUnit
.objectType( this.module
.testEnvironment
[ handler
] ) === "function" ) {
946 hooks
.push( this.queueHook( this.module
.testEnvironment
[ handler
], handler
) );
953 config
.current
= this;
954 if ( config
.requireExpects
&& this.expected
=== null ) {
955 this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
956 "not called.", this.stack
);
957 } else if ( this.expected
!== null && this.expected
!== this.assertions
.length
) {
958 this.pushFailure( "Expected " + this.expected
+ " assertions, but " +
959 this.assertions
.length
+ " were run", this.stack
);
960 } else if ( this.expected
=== null && !this.assertions
.length
) {
961 this.pushFailure( "Expected at least one assertion, but none were run - call " +
962 "expect(0) to accept zero assertions.", this.stack
);
968 this.runtime
= now() - this.started
;
969 config
.stats
.all
+= this.assertions
.length
;
970 config
.moduleStats
.all
+= this.assertions
.length
;
972 for ( i
= 0; i
< this.assertions
.length
; i
++ ) {
973 if ( !this.assertions
[ i
].result
) {
976 config
.moduleStats
.bad
++;
980 runLoggingCallbacks( "testDone", {
982 module
: this.module
.name
,
983 skipped
: !!this.skip
,
985 passed
: this.assertions
.length
- bad
,
986 total
: this.assertions
.length
,
987 runtime
: this.runtime
,
990 assertions
: this.assertions
,
993 // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
994 duration
: this.runtime
997 // QUnit.reset() is deprecated and will be replaced for a new
998 // fixture reset function on QUnit 2.0/2.1.
999 // It's still called here for backwards compatibility handling
1002 config
.current
= undefined;
1009 if ( !this.valid() ) {
1015 // each of these can by async
1021 test
.hooks( "beforeEach" ),
1027 test
.hooks( "afterEach" ).reverse(),
1038 // `bad` initialized at top of scope
1039 // defer when previous test run passed, if storage is available
1040 bad
= QUnit
.config
.reorder
&& defined
.sessionStorage
&&
1041 +sessionStorage
.getItem( "qunit-test-" + this.module
.name
+ "-" + this.testName
);
1046 synchronize( run
, true );
1050 push: function( result
, actual
, expected
, message
) {
1053 module
: this.module
.name
,
1054 name
: this.testName
,
1059 testId
: this.testId
,
1060 runtime
: now() - this.started
1064 source
= sourceFromStacktrace();
1067 details
.source
= source
;
1071 runLoggingCallbacks( "log", details
);
1073 this.assertions
.push({
1079 pushFailure: function( message
, source
, actual
) {
1080 if ( !this instanceof Test
) {
1081 throw new Error( "pushFailure() assertion outside test context, was " +
1082 sourceFromStacktrace( 2 ) );
1086 module
: this.module
.name
,
1087 name
: this.testName
,
1089 message
: message
|| "error",
1090 actual
: actual
|| null,
1091 testId
: this.testId
,
1092 runtime
: now() - this.started
1096 details
.source
= source
;
1099 runLoggingCallbacks( "log", details
);
1101 this.assertions
.push({
1107 resolvePromise: function( promise
, phase
) {
1110 if ( promise
!= null ) {
1111 then
= promise
.then
;
1112 if ( QUnit
.objectType( then
) === "function" ) {
1118 message
= "Promise rejected " +
1119 ( !phase
? "during" : phase
.replace( /Each$/, "" ) ) +
1120 " " + test
.testName
+ ": " + ( error
.message
|| error
);
1121 test
.pushFailure( message
, extractStacktrace( error
, 0 ) );
1123 // else next test will carry the responsibility
1136 filter
= config
.filter
&& config
.filter
.toLowerCase(),
1137 module
= QUnit
.urlParams
.module
&& QUnit
.urlParams
.module
.toLowerCase(),
1138 fullName
= ( this.module
.name
+ ": " + this.testName
).toLowerCase();
1140 // Internally-generated tests are always valid
1141 if ( this.callback
&& this.callback
.validTest
) {
1145 if ( config
.testId
.length
> 0 && inArray( this.testId
, config
.testId
) < 0 ) {
1149 if ( module
&& ( !this.module
.name
|| this.module
.name
.toLowerCase() !== module
) ) {
1157 include
= filter
.charAt( 0 ) !== "!";
1159 filter
= filter
.slice( 1 );
1162 // If the filter matches, we need to honour include
1163 if ( fullName
.indexOf( filter
) !== -1 ) {
1167 // Otherwise, do the opposite
1173 // Resets the test setup. Useful for tests that modify the DOM.
1175 DEPRECATED: Use multiple tests instead of resetting inside a test.
1176 Use testStart or testDone for custom cleanup.
1177 This method will throw an error in 2.0, and will be removed in 2.1
1179 QUnit
.reset = function() {
1181 // Return on non-browser environments
1182 // This is necessary to not break on node tests
1183 if ( typeof window
=== "undefined" ) {
1187 var fixture
= defined
.document
&& document
.getElementById
&&
1188 document
.getElementById( "qunit-fixture" );
1191 fixture
.innerHTML
= config
.fixture
;
1195 QUnit
.pushFailure = function() {
1196 if ( !QUnit
.config
.current
) {
1197 throw new Error( "pushFailure() assertion outside test context, in " +
1198 sourceFromStacktrace( 2 ) );
1201 // Gets current test obj
1202 var currentTest
= QUnit
.config
.current
;
1204 return currentTest
.pushFailure
.apply( currentTest
, arguments
);
1207 // Based on Java's String.hashCode, a simple but not
1208 // rigorously collision resistant hashing function
1209 function generateHash( module
, testName
) {
1213 str
= module
+ "\x1C" + testName
,
1216 for ( ; i
< len
; i
++ ) {
1217 hash
= ( ( hash
<< 5 ) - hash
) + str
.charCodeAt( i
);
1221 // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
1222 // strictly necessary but increases user understanding that the id is a SHA-like hash
1223 hex
= ( 0x100000000 + hash
).toString( 16 );
1224 if ( hex
.length
< 8 ) {
1225 hex
= "0000000" + hex
;
1228 return hex
.slice( -8 );
1231 function Assert( testContext
) {
1232 this.test
= testContext
;
1236 QUnit
.assert
= Assert
.prototype = {
1238 // Specify the number of expected assertions to guarantee that failed test
1239 // (no assertions are run at all) don't slip through.
1240 expect: function( asserts
) {
1241 if ( arguments
.length
=== 1 ) {
1242 this.test
.expected
= asserts
;
1244 return this.test
.expected
;
1248 // Increment this Test's semaphore counter, then return a single-use function that
1249 // decrements that counter a maximum of once.
1251 var test
= this.test
,
1254 test
.semaphore
+= 1;
1255 test
.usedAsync
= true;
1258 return function done() {
1260 test
.semaphore
-= 1;
1264 test
.pushFailure( "Called the callback returned from `assert.async` more than once",
1265 sourceFromStacktrace( 2 ) );
1270 // Exports test.push() to the user API
1271 push: function( /* result, actual, expected, message */ ) {
1273 currentTest
= ( assert
instanceof Assert
&& assert
.test
) || QUnit
.config
.current
;
1275 // Backwards compatibility fix.
1276 // Allows the direct use of global exported assertions and QUnit.assert.*
1277 // Although, it's use is not recommended as it can leak assertions
1278 // to other tests from async tests, because we only get a reference to the current test,
1279 // not exactly the test where assertion were intended to be called.
1280 if ( !currentTest
) {
1281 throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
1284 if ( currentTest
.usedAsync
=== true && currentTest
.semaphore
=== 0 ) {
1285 currentTest
.pushFailure( "Assertion after the final `assert.async` was resolved",
1286 sourceFromStacktrace( 2 ) );
1288 // Allow this assertion to continue running anyway...
1291 if ( !( assert
instanceof Assert
) ) {
1292 assert
= currentTest
.assert
;
1294 return assert
.test
.push
.apply( assert
.test
, arguments
);
1297 ok: function( result
, message
) {
1298 message
= message
|| ( result
? "okay" : "failed, expected argument to be truthy, was: " +
1299 QUnit
.dump
.parse( result
) );
1300 this.push( !!result
, result
, true, message
);
1303 notOk: function( result
, message
) {
1304 message
= message
|| ( !result
? "okay" : "failed, expected argument to be falsy, was: " +
1305 QUnit
.dump
.parse( result
) );
1306 this.push( !result
, result
, false, message
);
1309 equal: function( actual
, expected
, message
) {
1310 /*jshint eqeqeq:false */
1311 this.push( expected
== actual
, actual
, expected
, message
);
1314 notEqual: function( actual
, expected
, message
) {
1315 /*jshint eqeqeq:false */
1316 this.push( expected
!= actual
, actual
, expected
, message
);
1319 propEqual: function( actual
, expected
, message
) {
1320 actual
= objectValues( actual
);
1321 expected
= objectValues( expected
);
1322 this.push( QUnit
.equiv( actual
, expected
), actual
, expected
, message
);
1325 notPropEqual: function( actual
, expected
, message
) {
1326 actual
= objectValues( actual
);
1327 expected
= objectValues( expected
);
1328 this.push( !QUnit
.equiv( actual
, expected
), actual
, expected
, message
);
1331 deepEqual: function( actual
, expected
, message
) {
1332 this.push( QUnit
.equiv( actual
, expected
), actual
, expected
, message
);
1335 notDeepEqual: function( actual
, expected
, message
) {
1336 this.push( !QUnit
.equiv( actual
, expected
), actual
, expected
, message
);
1339 strictEqual: function( actual
, expected
, message
) {
1340 this.push( expected
=== actual
, actual
, expected
, message
);
1343 notStrictEqual: function( actual
, expected
, message
) {
1344 this.push( expected
!== actual
, actual
, expected
, message
);
1347 "throws": function( block
, expected
, message
) {
1348 var actual
, expectedType
,
1349 expectedOutput
= expected
,
1351 currentTest
= ( this instanceof Assert
&& this.test
) || QUnit
.config
.current
;
1353 // 'expected' is optional unless doing string comparison
1354 if ( message
== null && typeof expected
=== "string" ) {
1359 currentTest
.ignoreGlobalErrors
= true;
1361 block
.call( currentTest
.testEnvironment
);
1365 currentTest
.ignoreGlobalErrors
= false;
1368 expectedType
= QUnit
.objectType( expected
);
1370 // we don't want to validate thrown error
1373 expectedOutput
= null;
1375 // expected is a regexp
1376 } else if ( expectedType
=== "regexp" ) {
1377 ok
= expected
.test( errorString( actual
) );
1379 // expected is a string
1380 } else if ( expectedType
=== "string" ) {
1381 ok
= expected
=== errorString( actual
);
1383 // expected is a constructor, maybe an Error constructor
1384 } else if ( expectedType
=== "function" && actual
instanceof expected
) {
1387 // expected is an Error object
1388 } else if ( expectedType
=== "object" ) {
1389 ok
= actual
instanceof expected
.constructor &&
1390 actual
.name
=== expected
.name
&&
1391 actual
.message
=== expected
.message
;
1393 // expected is a validation function which returns true if validation passed
1394 } else if ( expectedType
=== "function" && expected
.call( {}, actual
) === true ) {
1395 expectedOutput
= null;
1400 currentTest
.assert
.push( ok
, actual
, expectedOutput
, message
);
1404 // Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word
1405 // Known to us are: Closure Compiler, Narwhal
1407 /*jshint sub:true */
1408 Assert
.prototype.raises
= Assert
.prototype[ "throws" ];
1411 // Test for equality any JavaScript type.
1412 // Author: Philippe Rathé <prathe@gmail.com>
1413 QUnit
.equiv
= (function() {
1415 // Call the o related callback with the given arguments.
1416 function bindCallbacks( o
, callbacks
, args
) {
1417 var prop
= QUnit
.objectType( o
);
1419 if ( QUnit
.objectType( callbacks
[ prop
] ) === "function" ) {
1420 return callbacks
[ prop
].apply( callbacks
, args
);
1422 return callbacks
[ prop
]; // or undefined
1427 // the real equiv function
1430 // stack to decide between skip/abort functions
1433 // stack to avoiding loops from circular referencing
1437 getProto
= Object
.getPrototypeOf
|| function( obj
) {
1438 /* jshint camelcase: false, proto: true */
1439 return obj
.__proto__
;
1441 callbacks
= (function() {
1443 // for string, boolean, number and null
1444 function useStrictEquality( b
, a
) {
1446 /*jshint eqeqeq:false */
1447 if ( b
instanceof a
.constructor || a
instanceof b
.constructor ) {
1449 // to catch short annotation VS 'new' annotation of a
1452 // var j = new Number(1);
1460 "string": useStrictEquality
,
1461 "boolean": useStrictEquality
,
1462 "number": useStrictEquality
,
1463 "null": useStrictEquality
,
1464 "undefined": useStrictEquality
,
1466 "nan": function( b
) {
1470 "date": function( b
, a
) {
1471 return QUnit
.objectType( b
) === "date" && a
.valueOf() === b
.valueOf();
1474 "regexp": function( b
, a
) {
1475 return QUnit
.objectType( b
) === "regexp" &&
1478 a
.source
=== b
.source
&&
1480 // and its modifiers
1481 a
.global
=== b
.global
&&
1484 a
.ignoreCase
=== b
.ignoreCase
&&
1485 a
.multiline
=== b
.multiline
&&
1486 a
.sticky
=== b
.sticky
;
1489 // - skip when the property is a method of an instance (OOP)
1490 // - abort otherwise,
1491 // initial === would have catch identical references anyway
1492 "function": function() {
1493 var caller
= callers
[ callers
.length
- 1 ];
1494 return caller
!== Object
&& typeof caller
!== "undefined";
1497 "array": function( b
, a
) {
1498 var i
, j
, len
, loop
, aCircular
, bCircular
;
1500 // b could be an object literal here
1501 if ( QUnit
.objectType( b
) !== "array" ) {
1506 if ( len
!== b
.length
) {
1511 // track reference to avoid circular references
1514 for ( i
= 0; i
< len
; i
++ ) {
1516 for ( j
= 0; j
< parents
.length
; j
++ ) {
1517 aCircular
= parents
[ j
] === a
[ i
];
1518 bCircular
= parentsB
[ j
] === b
[ i
];
1519 if ( aCircular
|| bCircular
) {
1520 if ( a
[ i
] === b
[ i
] || aCircular
&& bCircular
) {
1529 if ( !loop
&& !innerEquiv( a
[ i
], b
[ i
] ) ) {
1540 "object": function( b
, a
) {
1542 /*jshint forin:false */
1543 var i
, j
, loop
, aCircular
, bCircular
,
1549 // comparing constructors is more strict than using
1551 if ( a
.constructor !== b
.constructor ) {
1553 // Allow objects with no prototype to be equivalent to
1554 // objects with Object as their constructor.
1555 if ( !( ( getProto( a
) === null && getProto( b
) === Object
.prototype ) ||
1556 ( getProto( b
) === null && getProto( a
) === Object
.prototype ) ) ) {
1561 // stack constructor before traversing properties
1562 callers
.push( a
.constructor );
1564 // track reference to avoid circular references
1568 // be strict: don't ensure hasOwnProperty and go deep
1571 for ( j
= 0; j
< parents
.length
; j
++ ) {
1572 aCircular
= parents
[ j
] === a
[ i
];
1573 bCircular
= parentsB
[ j
] === b
[ i
];
1574 if ( aCircular
|| bCircular
) {
1575 if ( a
[ i
] === b
[ i
] || aCircular
&& bCircular
) {
1583 aProperties
.push( i
);
1584 if ( !loop
&& !innerEquiv( a
[ i
], b
[ i
] ) ) {
1592 callers
.pop(); // unstack, we are done
1595 bProperties
.push( i
); // collect b's properties
1598 // Ensures identical properties name
1599 return eq
&& innerEquiv( aProperties
.sort(), bProperties
.sort() );
1604 innerEquiv = function() { // can take multiple arguments
1605 var args
= [].slice
.apply( arguments
);
1606 if ( args
.length
< 2 ) {
1607 return true; // end transition
1610 return ( (function( a
, b
) {
1612 return true; // catch the most you can
1613 } else if ( a
=== null || b
=== null || typeof a
=== "undefined" ||
1614 typeof b
=== "undefined" ||
1615 QUnit
.objectType( a
) !== QUnit
.objectType( b
) ) {
1617 // don't lose time with error prone cases
1620 return bindCallbacks( a
, callbacks
, [ b
, a
] );
1623 // apply transition with (1..n) arguments
1624 }( args
[ 0 ], args
[ 1 ] ) ) &&
1625 innerEquiv
.apply( this, args
.splice( 1, args
.length
- 1 ) ) );
1631 // Based on jsDump by Ariel Flesler
1632 // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
1633 QUnit
.dump
= (function() {
1634 function quote( str
) {
1635 return "\"" + str
.toString().replace( /"/g, "\\\"" ) + "\"";
1637 function literal( o ) {
1640 function join( pre, arr, post ) {
1641 var s = dump.separator(),
1642 base = dump.indent(),
1643 inner = dump.indent( 1 );
1645 arr = arr.join( "," + s + inner );
1650 return [ pre, inner + arr, base + post ].join( s );
1652 function array( arr, stack ) {
1654 ret = new Array( i );
1656 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1657 return "[object Array
]";
1662 ret[ i ] = this.parse( arr[ i ], undefined, stack );
1665 return join( "[", ret, "]" );
1668 var reName = /^function (\w+)/,
1671 // objType is used mostly internally, you can fix a (custom) type in advance
1672 parse: function( obj, objType, stack ) {
1673 stack = stack || [];
1674 var res, parser, parserType,
1675 inStack = inArray( obj, stack );
1677 if ( inStack !== -1 ) {
1678 return "recursion(" + ( inStack - stack.length ) + ")";
1681 objType = objType || this.typeOf( obj );
1682 parser = this.parsers[ objType ];
1683 parserType = typeof parser;
1685 if ( parserType === "function" ) {
1687 res = parser.call( this, obj, stack );
1691 return ( parserType === "string
" ) ? parser : this.parsers.error;
1693 typeOf: function( obj ) {
1695 if ( obj === null ) {
1697 } else if ( typeof obj === "undefined" ) {
1699 } else if ( QUnit.is( "regexp
", obj ) ) {
1701 } else if ( QUnit.is( "date
", obj ) ) {
1703 } else if ( QUnit.is( "function", obj ) ) {
1705 } else if ( obj.setInterval !== undefined &&
1706 obj.document !== undefined &&
1707 obj.nodeType === undefined ) {
1709 } else if ( obj.nodeType === 9 ) {
1711 } else if ( obj.nodeType ) {
1716 toString.call( obj ) === "[object Array
]" ||
1719 ( typeof obj.length === "number
" && obj.item !== undefined &&
1720 ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
1721 obj[ 0 ] === undefined ) ) )
1724 } else if ( obj.constructor === Error.prototype.constructor ) {
1731 separator: function() {
1732 return this.multiline ? this.HTML ? "<br
/>" : "\n" : this.HTML ? " " : " ";
1734 // extra can be a number, shortcut for increasing-calling-decreasing
1735 indent: function( extra ) {
1736 if ( !this.multiline ) {
1739 var chr = this.indentChar;
1741 chr = chr.replace( /\t/g, " " ).replace( / /g, " " );
1743 return new Array( this.depth + ( extra || 0 ) ).join( chr );
1746 this.depth += a || 1;
1748 down: function( a ) {
1749 this.depth -= a || 1;
1751 setParser: function( name, parser ) {
1752 this.parsers[ name ] = parser;
1754 // The next 3 are exposed so you can use them
1760 maxDepth: QUnit.config.maxDepth,
1762 // This is the list of parsers, to modify them, use dump.setParser
1765 document: "[Document
]",
1766 error: function( error ) {
1767 return "Error(\"" + error.message + "\")";
1769 unknown: "[Unknown
]",
1771 "undefined": "undefined",
1772 "function": function( fn ) {
1773 var ret = "function",
1775 // functions never have name in IE
1776 name = "name
" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
1783 ret = [ ret, dump.parse( fn, "functionArgs
" ), "){" ].join( "" );
1784 return join( ret, dump.parse( fn, "functionCode
" ), "}" );
1789 object: function( map, stack ) {
1790 var keys, key, val, i, nonEnumerableProperties,
1793 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1794 return "[object Object
]";
1799 for ( key in map ) {
1803 // Some properties are not always enumerable on Error objects.
1804 nonEnumerableProperties = [ "message
", "name
" ];
1805 for ( i in nonEnumerableProperties ) {
1806 key = nonEnumerableProperties[ i ];
1807 if ( key in map && inArray( key, keys ) < 0 ) {
1812 for ( i = 0; i < keys.length; i++ ) {
1815 ret.push( dump.parse( key, "key
" ) + ": " +
1816 dump.parse( val, undefined, stack ) );
1819 return join( "{", ret, "}" );
1821 node: function( node ) {
1823 open = dump.HTML ? "<
;" : "<",
1824 close = dump.HTML ? ">
;" : ">",
1825 tag = node.nodeName.toLowerCase(),
1827 attrs = node.attributes;
1830 for ( i = 0, len = attrs.length; i < len; i++ ) {
1831 val = attrs[ i ].nodeValue;
1833 // IE6 includes all attributes in .attributes, even ones not explicitly
1834 // set. Those have values like undefined, null, 0, false, "" or
1836 if ( val && val !== "inherit
" ) {
1837 ret += " " + attrs[ i ].nodeName + "=" +
1838 dump.parse( val, "attribute
" );
1844 // Show content of TextNode or CDATASection
1845 if ( node.nodeType === 3 || node.nodeType === 4 ) {
1846 ret += node.nodeValue;
1849 return ret + open + "/" + tag + close;
1852 // function calls it internally, it's the arguments part of the function
1853 functionArgs: function( fn ) {
1861 args = new Array( l );
1865 args[ l ] = String.fromCharCode( 97 + l );
1867 return " " + args.join( ", " ) + " ";
1869 // object calls it internally, the key part of an item in a map
1871 // function calls it internally, it's the content of the function
1872 functionCode: "[code
]",
1873 // node calls it internally, it's an html attribute value
1881 // if true, entities are escaped ( <, >, \t, space and \n )
1885 // if true, items in a collection, are separated by a \n, else just a space.
1893 QUnit.jsDump = QUnit.dump;
1895 // For browser, export only select globals
1896 if ( typeof window !== "undefined" ) {
1899 // Extend assert methods to QUnit and Global scope through Backwards compatibility
1902 assertions = Assert.prototype;
1904 function applyCurrent( current ) {
1906 var assert = new Assert( QUnit.config.current );
1907 current.apply( assert, arguments );
1911 for ( i in assertions ) {
1912 QUnit[ i ] = applyCurrent( assertions[ i ] );
1938 for ( i = 0, l = keys.length; i < l; i++ ) {
1939 window[ keys[ i ] ] = QUnit[ keys[ i ] ];
1943 window.QUnit = QUnit;
1947 if ( typeof module !== "undefined" && module && module.exports ) {
1948 module.exports = QUnit;
1950 // For consistency with CommonJS environments' exports
1951 module.exports.QUnit = QUnit;
1954 // For CommonJS with exports, but without module.exports, like Rhino
1955 if ( typeof exports !== "undefined" && exports ) {
1956 exports.QUnit = QUnit;
1959 if ( typeof define === "function" && define.amd ) {
1960 define( function() {
1963 QUnit.config.autostart = false;
1966 // Get a reference to the global object, like window in browsers
1971 /*istanbul ignore next */
1972 // jscs:disable maximumLineLength
1974 * This file is a modified version of google-diff-match-patch's JavaScript implementation
1975 * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
1976 * modifications are licensed as more fully set forth in LICENSE.txt.
1978 * The original source of google-diff-match-patch is attributable and licensed as follows:
1980 * Copyright 2006 Google Inc.
1981 * http://code.google.com/p/google-diff-match-patch/
1983 * Licensed under the Apache License, Version 2.0 (the "License
");
1984 * you may not use this file except in compliance with the License.
1985 * You may obtain a copy of the License at
1987 * http://www.apache.org/licenses/LICENSE-2.0
1989 * Unless required by applicable law or agreed to in writing, software
1990 * distributed under the License is distributed on an "AS IS
" BASIS,
1991 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1992 * See the License for the specific language governing permissions and
1993 * limitations under the License.
1996 * https://code.google.com/p/google-diff-match-patch/
1998 * Usage: QUnit.diff(expected, actual)
2000 * QUnit.diff( "the quick brown fox jumped over
", "the quick fox jumps over
" ) === "the quick
<del
>brown
</del> fox jump<ins>s</ins><del
>ed
</del over
"
2002 QUnit.diff = (function() {
2004 function DiffMatchPatch() {
2007 // Redefine these in your program to override the defaults.
2009 // Number of seconds to map a diff before giving up (0 for infinity).
2010 this.DiffTimeout = 1.0;
2011 // Cost of an empty edit operation in terms of edit characters.
2012 this.DiffEditCost = 4;
2018 * The data structure representing a diff is an array of tuples:
2019 * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
2020 * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
2022 var DIFF_DELETE = -1,
2027 * Find the differences between two texts. Simplifies the problem by stripping
2028 * any common prefix or suffix off the texts before diffing.
2029 * @param {string} text1 Old string to be diffed.
2030 * @param {string} text2 New string to be diffed.
2031 * @param {boolean=} optChecklines Optional speedup flag. If present and false,
2032 * then don't run a line-level diff first to identify the changed areas.
2033 * Defaults to true, which does a faster, slightly less optimal diff.
2034 * @param {number} optDeadline Optional time when the diff should be complete
2035 * by. Used internally for recursive calls. Users should set DiffTimeout
2037 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2039 DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines, optDeadline ) {
2040 var deadline, checklines, commonlength,
2041 commonprefix, commonsuffix, diffs;
2042 // Set a deadline by which time the diff must be complete.
2043 if ( typeof optDeadline === "undefined" ) {
2044 if ( this.DiffTimeout <= 0 ) {
2045 optDeadline = Number.MAX_VALUE;
2047 optDeadline = ( new Date() ).getTime() + this.DiffTimeout * 1000;
2050 deadline = optDeadline;
2052 // Check for null inputs.
2053 if ( text1 === null || text2 === null ) {
2054 throw new Error( "Null input
. (DiffMain
)" );
2057 // Check for equality (speedup).
2058 if ( text1 === text2 ) {
2061 [ DIFF_EQUAL, text1 ]
2067 if ( typeof optChecklines === "undefined" ) {
2068 optChecklines = true;
2071 checklines = optChecklines;
2073 // Trim off common prefix (speedup).
2074 commonlength = this.diffCommonPrefix( text1, text2 );
2075 commonprefix = text1.substring( 0, commonlength );
2076 text1 = text1.substring( commonlength );
2077 text2 = text2.substring( commonlength );
2079 // Trim off common suffix (speedup).
2081 commonlength = this.diffCommonSuffix( text1, text2 );
2082 commonsuffix = text1.substring( text1.length - commonlength );
2083 text1 = text1.substring( 0, text1.length - commonlength );
2084 text2 = text2.substring( 0, text2.length - commonlength );
2086 // Compute the diff on the middle block.
2087 diffs = this.diffCompute( text1, text2, checklines, deadline );
2089 // Restore the prefix and suffix.
2090 if ( commonprefix ) {
2091 diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
2093 if ( commonsuffix ) {
2094 diffs.push( [ DIFF_EQUAL, commonsuffix ] );
2096 this.diffCleanupMerge( diffs );
2101 * Reduce the number of edits by eliminating operationally trivial equalities.
2102 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2104 DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
2105 var changes, equalities, equalitiesLength, lastequality,
2106 pointer, preIns, preDel, postIns, postDel;
2108 equalities = []; // Stack of indices where equalities are found.
2109 equalitiesLength = 0; // Keeping our own length var is faster in JS.
2110 /** @type {?string} */
2111 lastequality = null;
2112 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
2113 pointer = 0; // Index of current position.
2114 // Is there an insertion operation before the last equality.
2116 // Is there a deletion operation before the last equality.
2118 // Is there an insertion operation after the last equality.
2120 // Is there a deletion operation after the last equality.
2122 while ( pointer < diffs.length ) {
2123 if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
2124 if ( diffs[ pointer ][ 1 ].length < this.DiffEditCost && ( postIns || postDel ) ) {
2126 equalities[ equalitiesLength++ ] = pointer;
2129 lastequality = diffs[ pointer ][ 1 ];
2131 // Not a candidate, and can never become one.
2132 equalitiesLength = 0;
2133 lastequality = null;
2135 postIns = postDel = false;
2136 } else { // An insertion or deletion.
2137 if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
2143 * Five types to be split:
2144 * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
2145 * <ins>A</ins>X<ins>C</ins><del>D</del>
2146 * <ins>A</ins><del>B</del>X<ins>C</ins>
2147 * <ins>A</del>X<ins>C</ins><del>D</del>
2148 * <ins>A</ins><del>B</del>X<del>C</del>
2150 if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
2151 ( ( lastequality.length < this.DiffEditCost / 2 ) &&
2152 ( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
2153 // Duplicate record.
2154 diffs.splice( equalities[equalitiesLength - 1], 0, [ DIFF_DELETE, lastequality ] );
2155 // Change second copy to insert.
2156 diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
2157 equalitiesLength--; // Throw away the equality we just deleted;
2158 lastequality = null;
2159 if (preIns && preDel) {
2160 // No changes made which could affect previous entry, keep going.
2161 postIns = postDel = true;
2162 equalitiesLength = 0;
2164 equalitiesLength--; // Throw away the previous equality.
2165 pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
2166 postIns = postDel = false;
2175 this.diffCleanupMerge( diffs );
2180 * Convert a diff array into a pretty HTML report.
2181 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2182 * @param {integer} string to be beautified.
2183 * @return {string} HTML representation.
2185 DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
2186 var op, data, x, html = [];
2187 for ( x = 0; x < diffs.length; x++ ) {
2188 op = diffs[x][0]; // Operation (insert, delete, equal)
2189 data = diffs[x][1]; // Text of change.
2192 html[x] = "<ins
>" + data + "</ins
>";
2195 html[x] = "<del
>" + data + "</del
>";
2198 html[x] = "<span
>" + data + "</span
>";
2202 return html.join("");
2206 * Determine the common prefix of two strings.
2207 * @param {string} text1 First string.
2208 * @param {string} text2 Second string.
2209 * @return {number} The number of characters common to the start of each
2212 DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
2213 var pointermid, pointermax, pointermin, pointerstart;
2214 // Quick check for common null cases.
2215 if ( !text1 || !text2 || text1.charAt(0) !== text2.charAt(0) ) {
2219 // Performance analysis: http://neil.fraser.name/news/2007/10/09/
2221 pointermax = Math.min( text1.length, text2.length );
2222 pointermid = pointermax;
2224 while ( pointermin < pointermid ) {
2225 if ( text1.substring( pointerstart, pointermid ) === text2.substring( pointerstart, pointermid ) ) {
2226 pointermin = pointermid;
2227 pointerstart = pointermin;
2229 pointermax = pointermid;
2231 pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
2237 * Determine the common suffix of two strings.
2238 * @param {string} text1 First string.
2239 * @param {string} text2 Second string.
2240 * @return {number} The number of characters common to the end of each string.
2242 DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
2243 var pointermid, pointermax, pointermin, pointerend;
2244 // Quick check for common null cases.
2245 if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) {
2249 // Performance analysis: http://neil.fraser.name/news/2007/10/09/
2251 pointermax = Math.min(text1.length, text2.length);
2252 pointermid = pointermax;
2254 while ( pointermin < pointermid ) {
2255 if (text1.substring( text1.length - pointermid, text1.length - pointerend ) ===
2256 text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {
2257 pointermin = pointermid;
2258 pointerend = pointermin;
2260 pointermax = pointermid;
2262 pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
2268 * Find the differences between two texts. Assumes that the texts do not
2269 * have any common prefix or suffix.
2270 * @param {string} text1 Old string to be diffed.
2271 * @param {string} text2 New string to be diffed.
2272 * @param {boolean} checklines Speedup flag. If false, then don't run a
2273 * line-level diff first to identify the changed areas.
2274 * If true, then run a faster, slightly less optimal diff.
2275 * @param {number} deadline Time when the diff should be complete by.
2276 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2279 DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
2280 var diffs, longtext, shorttext, i, hm,
2281 text1A, text2A, text1B, text2B,
2282 midCommon, diffsA, diffsB;
2285 // Just add some text (speedup).
2287 [ DIFF_INSERT, text2 ]
2292 // Just delete some text (speedup).
2294 [ DIFF_DELETE, text1 ]
2298 longtext = text1.length > text2.length ? text1 : text2;
2299 shorttext = text1.length > text2.length ? text2 : text1;
2300 i = longtext.indexOf( shorttext );
2302 // Shorter text is inside the longer text (speedup).
2304 [ DIFF_INSERT, longtext.substring( 0, i ) ],
2305 [ DIFF_EQUAL, shorttext ],
2306 [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
2308 // Swap insertions for deletions if diff is reversed.
2309 if ( text1.length > text2.length ) {
2310 diffs[0][0] = diffs[2][0] = DIFF_DELETE;
2315 if ( shorttext.length === 1 ) {
2316 // Single character string.
2317 // After the previous speedup, the character can't be an equality.
2319 [ DIFF_DELETE, text1 ],
2320 [ DIFF_INSERT, text2 ]
2324 // Check to see if the problem can be split in two.
2325 hm = this.diffHalfMatch(text1, text2);
2327 // A half-match was found, sort out the return data.
2333 // Send both pairs off for separate processing.
2334 diffsA = this.DiffMain(text1A, text2A, checklines, deadline);
2335 diffsB = this.DiffMain(text1B, text2B, checklines, deadline);
2336 // Merge the results.
2337 return diffsA.concat([
2338 [ DIFF_EQUAL, midCommon ]
2342 if (checklines && text1.length > 100 && text2.length > 100) {
2343 return this.diffLineMode(text1, text2, deadline);
2346 return this.diffBisect(text1, text2, deadline);
2350 * Do the two texts share a substring which is at least half the length of the
2352 * This speedup can produce non-minimal diffs.
2353 * @param {string} text1 First string.
2354 * @param {string} text2 Second string.
2355 * @return {Array.<string>} Five element Array, containing the prefix of
2356 * text1, the suffix of text1, the prefix of text2, the suffix of
2357 * text2 and the common middle. Or null if there was no match.
2360 DiffMatchPatch.prototype.diffHalfMatch = function(text1, text2) {
2361 var longtext, shorttext, dmp,
2362 text1A, text2B, text2A, text1B, midCommon,
2364 if (this.DiffTimeout <= 0) {
2365 // Don't risk returning a non-optimal diff if we have unlimited time.
2368 longtext = text1.length > text2.length ? text1 : text2;
2369 shorttext = text1.length > text2.length ? text2 : text1;
2370 if (longtext.length < 4 || shorttext.length * 2 < longtext.length) {
2371 return null; // Pointless.
2373 dmp = this; // 'this' becomes 'window' in a closure.
2376 * Does a substring of shorttext exist within longtext such that the substring
2377 * is at least half the length of longtext?
2378 * Closure, but does not reference any external variables.
2379 * @param {string} longtext Longer string.
2380 * @param {string} shorttext Shorter string.
2381 * @param {number} i Start index of quarter length substring within longtext.
2382 * @return {Array.<string>} Five element Array, containing the prefix of
2383 * longtext, the suffix of longtext, the prefix of shorttext, the suffix
2384 * of shorttext and the common middle. Or null if there was no match.
2387 function diffHalfMatchI(longtext, shorttext, i) {
2388 var seed, j, bestCommon, prefixLength, suffixLength,
2389 bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
2390 // Start with a 1/4 length substring at position i as a seed.
2391 seed = longtext.substring(i, i + Math.floor(longtext.length / 4));
2394 while ((j = shorttext.indexOf(seed, j + 1)) !== -1) {
2395 prefixLength = dmp.diffCommonPrefix(longtext.substring(i),
2396 shorttext.substring(j));
2397 suffixLength = dmp.diffCommonSuffix(longtext.substring(0, i),
2398 shorttext.substring(0, j));
2399 if (bestCommon.length < suffixLength + prefixLength) {
2400 bestCommon = shorttext.substring(j - suffixLength, j) +
2401 shorttext.substring(j, j + prefixLength);
2402 bestLongtextA = longtext.substring(0, i - suffixLength);
2403 bestLongtextB = longtext.substring(i + prefixLength);
2404 bestShorttextA = shorttext.substring(0, j - suffixLength);
2405 bestShorttextB = shorttext.substring(j + prefixLength);
2408 if (bestCommon.length * 2 >= longtext.length) {
2409 return [ bestLongtextA, bestLongtextB,
2410 bestShorttextA, bestShorttextB, bestCommon
2417 // First check if the second quarter is the seed for a half-match.
2418 hm1 = diffHalfMatchI(longtext, shorttext,
2419 Math.ceil(longtext.length / 4));
2420 // Check again based on the third quarter.
2421 hm2 = diffHalfMatchI(longtext, shorttext,
2422 Math.ceil(longtext.length / 2));
2430 // Both matched. Select the longest.
2431 hm = hm1[4].length > hm2[4].length ? hm1 : hm2;
2434 // A half-match was found, sort out the return data.
2435 text1A, text1B, text2A, text2B;
2436 if (text1.length > text2.length) {
2448 return [ text1A, text1B, text2A, text2B, midCommon ];
2452 * Do a quick line-level diff on both strings, then rediff the parts for
2454 * This speedup can produce non-minimal diffs.
2455 * @param {string} text1 Old string to be diffed.
2456 * @param {string} text2 New string to be diffed.
2457 * @param {number} deadline Time when the diff should be complete by.
2458 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2461 DiffMatchPatch.prototype.diffLineMode = function(text1, text2, deadline) {
2462 var a, diffs, linearray, pointer, countInsert,
2463 countDelete, textInsert, textDelete, j;
2464 // Scan the text on a line-by-line basis first.
2465 a = this.diffLinesToChars(text1, text2);
2468 linearray = a.lineArray;
2470 diffs = this.DiffMain(text1, text2, false, deadline);
2472 // Convert the diff back to original text.
2473 this.diffCharsToLines(diffs, linearray);
2474 // Eliminate freak matches (e.g. blank lines)
2475 this.diffCleanupSemantic(diffs);
2477 // Rediff any replacement blocks, this time character-by-character.
2478 // Add a dummy entry at the end.
2479 diffs.push( [ DIFF_EQUAL, "" ] );
2485 while (pointer < diffs.length) {
2486 switch ( diffs[pointer][0] ) {
2489 textInsert += diffs[pointer][1];
2493 textDelete += diffs[pointer][1];
2496 // Upon reaching an equality, check for prior redundancies.
2497 if (countDelete >= 1 && countInsert >= 1) {
2498 // Delete the offending records and add the merged ones.
2499 diffs.splice(pointer - countDelete - countInsert,
2500 countDelete + countInsert);
2501 pointer = pointer - countDelete - countInsert;
2502 a = this.DiffMain(textDelete, textInsert, false, deadline);
2503 for (j = a.length - 1; j >= 0; j--) {
2504 diffs.splice( pointer, 0, a[j] );
2506 pointer = pointer + a.length;
2516 diffs.pop(); // Remove the dummy entry at the end.
2522 * Find the 'middle snake' of a diff, split the problem in two
2523 * and return the recursively constructed diff.
2524 * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
2525 * @param {string} text1 Old string to be diffed.
2526 * @param {string} text2 New string to be diffed.
2527 * @param {number} deadline Time at which to bail if not yet complete.
2528 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2531 DiffMatchPatch.prototype.diffBisect = function(text1, text2, deadline) {
2532 var text1Length, text2Length, maxD, vOffset, vLength,
2533 v1, v2, x, delta, front, k1start, k1end, k2start,
2534 k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
2535 // Cache the text lengths to prevent multiple calls.
2536 text1Length = text1.length;
2537 text2Length = text2.length;
2538 maxD = Math.ceil((text1Length + text2Length) / 2);
2541 v1 = new Array(vLength);
2542 v2 = new Array(vLength);
2543 // Setting all elements to -1 is faster in Chrome & Firefox than mixing
2544 // integers and undefined.
2545 for (x = 0; x < vLength; x++) {
2549 v1[vOffset + 1] = 0;
2550 v2[vOffset + 1] = 0;
2551 delta = text1Length - text2Length;
2552 // If the total number of characters is odd, then the front path will collide
2553 // with the reverse path.
2554 front = (delta % 2 !== 0);
2555 // Offsets for start and end of k loop.
2556 // Prevents mapping of space beyond the grid.
2561 for (d = 0; d < maxD; d++) {
2562 // Bail out if deadline is reached.
2563 if ((new Date()).getTime() > deadline) {
2567 // Walk the front path one step.
2568 for (k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
2569 k1Offset = vOffset + k1;
2570 if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {
2571 x1 = v1[k1Offset + 1];
2573 x1 = v1[k1Offset - 1] + 1;
2576 while (x1 < text1Length && y1 < text2Length &&
2577 text1.charAt(x1) === text2.charAt(y1)) {
2582 if (x1 > text1Length) {
2583 // Ran off the right of the graph.
2585 } else if (y1 > text2Length) {
2586 // Ran off the bottom of the graph.
2589 k2Offset = vOffset + delta - k1;
2590 if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) {
2591 // Mirror x2 onto top-left coordinate system.
2592 x2 = text1Length - v2[k2Offset];
2594 // Overlap detected.
2595 return this.diffBisectSplit(text1, text2, x1, y1, deadline);
2601 // Walk the reverse path one step.
2602 for (k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
2603 k2Offset = vOffset + k2;
2604 if ( k2 === -d || (k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {
2605 x2 = v2[k2Offset + 1];
2607 x2 = v2[k2Offset - 1] + 1;
2610 while (x2 < text1Length && y2 < text2Length &&
2611 text1.charAt(text1Length - x2 - 1) ===
2612 text2.charAt(text2Length - y2 - 1)) {
2617 if (x2 > text1Length) {
2618 // Ran off the left of the graph.
2620 } else if (y2 > text2Length) {
2621 // Ran off the top of the graph.
2623 } else if (!front) {
2624 k1Offset = vOffset + delta - k2;
2625 if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) {
2627 y1 = vOffset + x1 - k1Offset;
2628 // Mirror x2 onto top-left coordinate system.
2629 x2 = text1Length - x2;
2631 // Overlap detected.
2632 return this.diffBisectSplit(text1, text2, x1, y1, deadline);
2638 // Diff took too long and hit the deadline or
2639 // number of diffs equals number of characters, no commonality at all.
2641 [ DIFF_DELETE, text1 ],
2642 [ DIFF_INSERT, text2 ]
2647 * Given the location of the 'middle snake', split the diff in two parts
2649 * @param {string} text1 Old string to be diffed.
2650 * @param {string} text2 New string to be diffed.
2651 * @param {number} x Index of split point in text1.
2652 * @param {number} y Index of split point in text2.
2653 * @param {number} deadline Time at which to bail if not yet complete.
2654 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2657 DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
2658 var text1a, text1b, text2a, text2b, diffs, diffsb;
2659 text1a = text1.substring(0, x);
2660 text2a = text2.substring(0, y);
2661 text1b = text1.substring(x);
2662 text2b = text2.substring(y);
2664 // Compute both diffs serially.
2665 diffs = this.DiffMain(text1a, text2a, false, deadline);
2666 diffsb = this.DiffMain(text1b, text2b, false, deadline);
2668 return diffs.concat(diffsb);
2672 * Reduce the number of edits by eliminating semantically trivial equalities.
2673 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2675 DiffMatchPatch.prototype.diffCleanupSemantic = function(diffs) {
2676 var changes, equalities, equalitiesLength, lastequality,
2677 pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
2678 lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
2680 equalities = []; // Stack of indices where equalities are found.
2681 equalitiesLength = 0; // Keeping our own length var is faster in JS.
2682 /** @type {?string} */
2683 lastequality = null;
2684 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
2685 pointer = 0; // Index of current position.
2686 // Number of characters that changed prior to the equality.
2687 lengthInsertions1 = 0;
2688 lengthDeletions1 = 0;
2689 // Number of characters that changed after the equality.
2690 lengthInsertions2 = 0;
2691 lengthDeletions2 = 0;
2692 while (pointer < diffs.length) {
2693 if (diffs[pointer][0] === DIFF_EQUAL) { // Equality found.
2694 equalities[equalitiesLength++] = pointer;
2695 lengthInsertions1 = lengthInsertions2;
2696 lengthDeletions1 = lengthDeletions2;
2697 lengthInsertions2 = 0;
2698 lengthDeletions2 = 0;
2699 lastequality = diffs[pointer][1];
2700 } else { // An insertion or deletion.
2701 if (diffs[pointer][0] === DIFF_INSERT) {
2702 lengthInsertions2 += diffs[pointer][1].length;
2704 lengthDeletions2 += diffs[pointer][1].length;
2706 // Eliminate an equality that is smaller or equal to the edits on both
2708 if (lastequality && (lastequality.length <=
2709 Math.max(lengthInsertions1, lengthDeletions1)) &&
2710 (lastequality.length <= Math.max(lengthInsertions2,
2711 lengthDeletions2))) {
2712 // Duplicate record.
2713 diffs.splice( equalities[ equalitiesLength - 1 ], 0, [ DIFF_DELETE, lastequality ] );
2714 // Change second copy to insert.
2715 diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
2716 // Throw away the equality we just deleted.
2718 // Throw away the previous equality (it needs to be reevaluated).
2720 pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
2721 lengthInsertions1 = 0; // Reset the counters.
2722 lengthDeletions1 = 0;
2723 lengthInsertions2 = 0;
2724 lengthDeletions2 = 0;
2725 lastequality = null;
2732 // Normalize the diff.
2734 this.diffCleanupMerge(diffs);
2737 // Find any overlaps between deletions and insertions.
2738 // e.g: <del>abcxxx</del><ins>xxxdef</ins>
2739 // -> <del>abc</del>xxx<ins>def</ins>
2740 // e.g: <del>xxxabc</del><ins>defxxx</ins>
2741 // -> <ins>def</ins>xxx<del>abc</del>
2742 // Only extract an overlap if it is as big as the edit ahead or behind it.
2744 while (pointer < diffs.length) {
2745 if (diffs[pointer - 1][0] === DIFF_DELETE &&
2746 diffs[pointer][0] === DIFF_INSERT) {
2747 deletion = diffs[pointer - 1][1];
2748 insertion = diffs[pointer][1];
2749 overlapLength1 = this.diffCommonOverlap(deletion, insertion);
2750 overlapLength2 = this.diffCommonOverlap(insertion, deletion);
2751 if (overlapLength1 >= overlapLength2) {
2752 if (overlapLength1 >= deletion.length / 2 ||
2753 overlapLength1 >= insertion.length / 2) {
2754 // Overlap found. Insert an equality and trim the surrounding edits.
2755 diffs.splice( pointer, 0, [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ] );
2756 diffs[pointer - 1][1] =
2757 deletion.substring(0, deletion.length - overlapLength1);
2758 diffs[pointer + 1][1] = insertion.substring(overlapLength1);
2762 if (overlapLength2 >= deletion.length / 2 ||
2763 overlapLength2 >= insertion.length / 2) {
2764 // Reverse overlap found.
2765 // Insert an equality and swap and trim the surrounding edits.
2766 diffs.splice( pointer, 0, [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ] );
2767 diffs[pointer - 1][0] = DIFF_INSERT;
2768 diffs[pointer - 1][1] =
2769 insertion.substring(0, insertion.length - overlapLength2);
2770 diffs[pointer + 1][0] = DIFF_DELETE;
2771 diffs[pointer + 1][1] =
2772 deletion.substring(overlapLength2);
2783 * Determine if the suffix of one string is the prefix of another.
2784 * @param {string} text1 First string.
2785 * @param {string} text2 Second string.
2786 * @return {number} The number of characters common to the end of the first
2787 * string and the start of the second string.
2790 DiffMatchPatch.prototype.diffCommonOverlap = function(text1, text2) {
2791 var text1Length, text2Length, textLength,
2792 best, length, pattern, found;
2793 // Cache the text lengths to prevent multiple calls.
2794 text1Length = text1.length;
2795 text2Length = text2.length;
2796 // Eliminate the null case.
2797 if (text1Length === 0 || text2Length === 0) {
2800 // Truncate the longer string.
2801 if (text1Length > text2Length) {
2802 text1 = text1.substring(text1Length - text2Length);
2803 } else if (text1Length < text2Length) {
2804 text2 = text2.substring(0, text1Length);
2806 textLength = Math.min(text1Length, text2Length);
2807 // Quick check for the worst case.
2808 if (text1 === text2) {
2812 // Start by looking for a single character match
2813 // and increase length until no match is found.
2814 // Performance analysis: http://neil.fraser.name/news/2010/11/04/
2818 pattern = text1.substring(textLength - length);
2819 found = text2.indexOf(pattern);
2824 if (found === 0 || text1.substring(textLength - length) ===
2825 text2.substring(0, length)) {
2833 * Split two texts into an array of strings. Reduce the texts to a string of
2834 * hashes where each Unicode character represents one line.
2835 * @param {string} text1 First string.
2836 * @param {string} text2 Second string.
2837 * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
2838 * An object containing the encoded text1, the encoded text2 and
2839 * the array of unique strings.
2840 * The zeroth element of the array of unique strings is intentionally blank.
2843 DiffMatchPatch.prototype.diffLinesToChars = function(text1, text2) {
2844 var lineArray, lineHash, chars1, chars2;
2845 lineArray = []; // e.g. lineArray[4] === 'Hello\n'
2846 lineHash = {}; // e.g. lineHash['Hello\n'] === 4
2848 // '\x00' is a valid character, but various debuggers don't like it.
2849 // So we'll insert a junk entry to avoid generating a null character.
2853 * Split a text into an array of strings. Reduce the texts to a string of
2854 * hashes where each Unicode character represents one line.
2855 * Modifies linearray and linehash through being a closure.
2856 * @param {string} text String to encode.
2857 * @return {string} Encoded string.
2860 function diffLinesToCharsMunge(text) {
2861 var chars, lineStart, lineEnd, lineArrayLength, line;
2863 // Walk the text, pulling out a substring for each line.
2864 // text.split('\n') would would temporarily double our memory footprint.
2865 // Modifying text would create many large strings to garbage collect.
2868 // Keeping our own length variable is faster than looking it up.
2869 lineArrayLength = lineArray.length;
2870 while (lineEnd < text.length - 1) {
2871 lineEnd = text.indexOf("\n", lineStart);
2872 if (lineEnd === -1) {
2873 lineEnd = text.length - 1;
2875 line = text.substring(lineStart, lineEnd + 1);
2876 lineStart = lineEnd + 1;
2878 if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) :
2879 (lineHash[line] !== undefined)) {
2880 chars += String.fromCharCode( lineHash[ line ] );
2882 chars += String.fromCharCode(lineArrayLength);
2883 lineHash[line] = lineArrayLength;
2884 lineArray[lineArrayLength++] = line;
2890 chars1 = diffLinesToCharsMunge(text1);
2891 chars2 = diffLinesToCharsMunge(text2);
2895 lineArray: lineArray
2900 * Rehydrate the text in a diff from a string of line hashes to real lines of
2902 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2903 * @param {!Array.<string>} lineArray Array of unique strings.
2906 DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {
2907 var x, chars, text, y;
2908 for ( x = 0; x < diffs.length; x++ ) {
2909 chars = diffs[x][1];
2911 for ( y = 0; y < chars.length; y++ ) {
2912 text[y] = lineArray[chars.charCodeAt(y)];
2914 diffs[x][1] = text.join("");
2919 * Reorder and merge like edit sections. Merge equalities.
2920 * Any edit section can move as long as it doesn't cross an equality.
2921 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2923 DiffMatchPatch.prototype.diffCleanupMerge = function(diffs) {
2924 var pointer, countDelete, countInsert, textInsert, textDelete,
2925 commonlength, changes;
2926 diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
2933 while (pointer < diffs.length) {
2934 switch ( diffs[ pointer ][ 0 ] ) {
2937 textInsert += diffs[pointer][1];
2942 textDelete += diffs[pointer][1];
2946 // Upon reaching an equality, check for prior redundancies.
2947 if (countDelete + countInsert > 1) {
2948 if (countDelete !== 0 && countInsert !== 0) {
2949 // Factor out any common prefixies.
2950 commonlength = this.diffCommonPrefix(textInsert, textDelete);
2951 if (commonlength !== 0) {
2952 if ((pointer - countDelete - countInsert) > 0 &&
2953 diffs[pointer - countDelete - countInsert - 1][0] ===
2955 diffs[pointer - countDelete - countInsert - 1][1] +=
2956 textInsert.substring(0, commonlength);
2958 diffs.splice( 0, 0, [ DIFF_EQUAL,
2959 textInsert.substring( 0, commonlength )
2963 textInsert = textInsert.substring(commonlength);
2964 textDelete = textDelete.substring(commonlength);
2966 // Factor out any common suffixies.
2967 commonlength = this.diffCommonSuffix(textInsert, textDelete);
2968 if (commonlength !== 0) {
2969 diffs[pointer][1] = textInsert.substring(textInsert.length -
2970 commonlength) + diffs[pointer][1];
2971 textInsert = textInsert.substring(0, textInsert.length -
2973 textDelete = textDelete.substring(0, textDelete.length -
2977 // Delete the offending records and add the merged ones.
2978 if (countDelete === 0) {
2979 diffs.splice( pointer - countInsert,
2980 countDelete + countInsert, [ DIFF_INSERT, textInsert ] );
2981 } else if (countInsert === 0) {
2982 diffs.splice( pointer - countDelete,
2983 countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
2985 diffs.splice( pointer - countDelete - countInsert,
2986 countDelete + countInsert, [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ] );
2988 pointer = pointer - countDelete - countInsert +
2989 (countDelete ? 1 : 0) + (countInsert ? 1 : 0) + 1;
2990 } else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) {
2991 // Merge this equality with the previous one.
2992 diffs[pointer - 1][1] += diffs[pointer][1];
2993 diffs.splice(pointer, 1);
3004 if (diffs[diffs.length - 1][1] === "") {
3005 diffs.pop(); // Remove the dummy entry at the end.
3008 // Second pass: look for single edits surrounded on both sides by equalities
3009 // which can be shifted sideways to eliminate an equality.
3010 // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
3013 // Intentionally ignore the first and last element (don't need checking).
3014 while (pointer < diffs.length - 1) {
3015 if (diffs[pointer - 1][0] === DIFF_EQUAL &&
3016 diffs[pointer + 1][0] === DIFF_EQUAL) {
3017 // This is a single edit surrounded by equalities.
3018 if ( diffs[ pointer ][ 1 ].substring( diffs[ pointer ][ 1 ].length -
3019 diffs[ pointer - 1 ][ 1 ].length ) === diffs[ pointer - 1 ][ 1 ] ) {
3020 // Shift the edit over the previous equality.
3021 diffs[pointer][1] = diffs[pointer - 1][1] +
3022 diffs[pointer][1].substring(0, diffs[pointer][1].length -
3023 diffs[pointer - 1][1].length);
3024 diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
3025 diffs.splice(pointer - 1, 1);
3027 } else if ( diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
3028 diffs[ pointer + 1 ][ 1 ] ) {
3029 // Shift the edit over the next equality.
3030 diffs[pointer - 1][1] += diffs[pointer + 1][1];
3032 diffs[pointer][1].substring(diffs[pointer + 1][1].length) +
3033 diffs[pointer + 1][1];
3034 diffs.splice(pointer + 1, 1);
3040 // If shifts were made, the diff needs reordering and another shift sweep.
3042 this.diffCleanupMerge(diffs);
3046 return function(o, n) {
3047 var diff, output, text;
3048 diff = new DiffMatchPatch();
3049 output = diff.DiffMain(o, n);
3050 //console.log(output);
3051 diff.diffCleanupEfficiency(output);
3052 text = diff.diffPrettyHtml(output);
3061 // Deprecated QUnit.init - Ref #530
3062 // Re-initialize the configuration options
3063 QUnit.init = function() {
3064 var tests, banner, result, qunit,
3065 config = QUnit.config;
3067 config.stats = { all: 0, bad: 0 };
3068 config.moduleStats = { all: 0, bad: 0 };
3070 config.updateRate = 1000;
3071 config.blocking = false;
3072 config.autostart = true;
3073 config.autorun = false;
3077 // Return on non-browser environments
3078 // This is necessary to not break on node tests
3079 if ( typeof window === "undefined" ) {
3083 qunit = id( "qunit
" );
3086 "<h1 id
='qunit-header'>" + escapeText( document.title ) + "</h1
>" +
3087 "<h2 id
='qunit-banner'></h2
>" +
3088 "<div id
='qunit-testrunner-toolbar'></div
>" +
3089 "<h2 id
='qunit-userAgent'></h2
>" +
3090 "<ol id
='qunit-tests'></ol
>";
3093 tests = id( "qunit
-tests
" );
3094 banner = id( "qunit
-banner
" );
3095 result = id( "qunit
-testresult
" );
3098 tests.innerHTML = "";
3102 banner.className = "";
3106 result.parentNode.removeChild( result );
3110 result = document.createElement( "p
" );
3111 result.id = "qunit
-testresult
";
3112 result.className = "result
";
3113 tests.parentNode.insertBefore( result, tests );
3114 result.innerHTML = "Running
...<br
/> ";
3118 // Don't load the HTML Reporter on non-Browser environments
3119 if ( typeof window === "undefined" ) {
3123 var config = QUnit.config,
3124 hasOwn = Object.prototype.hasOwnProperty,
3126 document: window.document !== undefined,
3127 sessionStorage: (function() {
3128 var x = "qunit
-test
-string
";
3130 sessionStorage.setItem( x, x );
3131 sessionStorage.removeItem( x );
3141 * Escape text for attribute or text content.
3143 function escapeText( s ) {
3149 // Both single quotes and double quotes (for attributes)
3150 return s.replace( /['"<>&]/g
, function( s
) {
3167 * @param {HTMLElement} elem
3168 * @param {string} type
3169 * @param {Function} fn
3171 function addEvent( elem
, type
, fn
) {
3172 if ( elem
.addEventListener
) {
3174 // Standards-based browsers
3175 elem
.addEventListener( type
, fn
, false );
3176 } else if ( elem
.attachEvent
) {
3179 elem
.attachEvent( "on" + type
, function() {
3180 var event
= window
.event
;
3181 if ( !event
.target
) {
3182 event
.target
= event
.srcElement
|| document
;
3185 fn
.call( elem
, event
);
3191 * @param {Array|NodeList} elems
3192 * @param {string} type
3193 * @param {Function} fn
3195 function addEvents( elems
, type
, fn
) {
3196 var i
= elems
.length
;
3198 addEvent( elems
[ i
], type
, fn
);
3202 function hasClass( elem
, name
) {
3203 return ( " " + elem
.className
+ " " ).indexOf( " " + name
+ " " ) >= 0;
3206 function addClass( elem
, name
) {
3207 if ( !hasClass( elem
, name
) ) {
3208 elem
.className
+= ( elem
.className
? " " : "" ) + name
;
3212 function toggleClass( elem
, name
) {
3213 if ( hasClass( elem
, name
) ) {
3214 removeClass( elem
, name
);
3216 addClass( elem
, name
);
3220 function removeClass( elem
, name
) {
3221 var set = " " + elem
.className
+ " ";
3223 // Class name may appear multiple times
3224 while ( set.indexOf( " " + name
+ " " ) >= 0 ) {
3225 set = set.replace( " " + name
+ " ", " " );
3228 // trim for prettiness
3229 elem
.className
= typeof set.trim
=== "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
3232 function id( name
) {
3233 return defined
.document
&& document
.getElementById
&& document
.getElementById( name
);
3236 function getUrlConfigHtml() {
3238 escaped
, escapedTooltip
,
3240 len
= config
.urlConfig
.length
,
3243 for ( i
= 0; i
< len
; i
++ ) {
3244 val
= config
.urlConfig
[ i
];
3245 if ( typeof val
=== "string" ) {
3252 escaped
= escapeText( val
.id
);
3253 escapedTooltip
= escapeText( val
.tooltip
);
3255 if ( config
[ val
.id
] === undefined ) {
3256 config
[ val
.id
] = QUnit
.urlParams
[ val
.id
];
3259 if ( !val
.value
|| typeof val
.value
=== "string" ) {
3260 urlConfigHtml
+= "<input id='qunit-urlconfig-" + escaped
+
3261 "' name='" + escaped
+ "' type='checkbox'" +
3262 ( val
.value
? " value='" + escapeText( val
.value
) + "'" : "" ) +
3263 ( config
[ val
.id
] ? " checked='checked'" : "" ) +
3264 " title='" + escapedTooltip
+ "' /><label for='qunit-urlconfig-" + escaped
+
3265 "' title='" + escapedTooltip
+ "'>" + val
.label
+ "</label>";
3267 urlConfigHtml
+= "<label for='qunit-urlconfig-" + escaped
+
3268 "' title='" + escapedTooltip
+ "'>" + val
.label
+
3269 ": </label><select id='qunit-urlconfig-" + escaped
+
3270 "' name='" + escaped
+ "' title='" + escapedTooltip
+ "'><option></option>";
3272 if ( QUnit
.is( "array", val
.value
) ) {
3273 for ( j
= 0; j
< val
.value
.length
; j
++ ) {
3274 escaped
= escapeText( val
.value
[ j
] );
3275 urlConfigHtml
+= "<option value='" + escaped
+ "'" +
3276 ( config
[ val
.id
] === val
.value
[ j
] ?
3277 ( selection
= true ) && " selected='selected'" : "" ) +
3278 ">" + escaped
+ "</option>";
3281 for ( j
in val
.value
) {
3282 if ( hasOwn
.call( val
.value
, j
) ) {
3283 urlConfigHtml
+= "<option value='" + escapeText( j
) + "'" +
3284 ( config
[ val
.id
] === j
?
3285 ( selection
= true ) && " selected='selected'" : "" ) +
3286 ">" + escapeText( val
.value
[ j
] ) + "</option>";
3290 if ( config
[ val
.id
] && !selection
) {
3291 escaped
= escapeText( config
[ val
.id
] );
3292 urlConfigHtml
+= "<option value='" + escaped
+
3293 "' selected='selected' disabled='disabled'>" + escaped
+ "</option>";
3295 urlConfigHtml
+= "</select>";
3299 return urlConfigHtml
;
3302 // Handle "click" events on toolbar checkboxes and "change" for select menus.
3303 // Updates the URL with the new state of `config.urlConfig` values.
3304 function toolbarChanged() {
3305 var updatedUrl
, value
,
3309 // Detect if field is a select menu or a checkbox
3310 if ( "selectedIndex" in field
) {
3311 value
= field
.options
[ field
.selectedIndex
].value
|| undefined;
3313 value
= field
.checked
? ( field
.defaultValue
|| true ) : undefined;
3316 params
[ field
.name
] = value
;
3317 updatedUrl
= setUrl( params
);
3319 if ( "hidepassed" === field
.name
&& "replaceState" in window
.history
) {
3320 config
[ field
.name
] = value
|| false;
3322 addClass( id( "qunit-tests" ), "hidepass" );
3324 removeClass( id( "qunit-tests" ), "hidepass" );
3327 // It is not necessary to refresh the whole page
3328 window
.history
.replaceState( null, "", updatedUrl
);
3330 window
.location
= updatedUrl
;
3334 function setUrl( params
) {
3338 params
= QUnit
.extend( QUnit
.extend( {}, QUnit
.urlParams
), params
);
3340 for ( key
in params
) {
3341 if ( hasOwn
.call( params
, key
) ) {
3342 if ( params
[ key
] === undefined ) {
3345 querystring
+= encodeURIComponent( key
);
3346 if ( params
[ key
] !== true ) {
3347 querystring
+= "=" + encodeURIComponent( params
[ key
] );
3352 return location
.protocol
+ "//" + location
.host
+
3353 location
.pathname
+ querystring
.slice( 0, -1 );
3356 function applyUrlParams() {
3358 modulesList
= id( "qunit-modulefilter" ),
3359 filter
= id( "qunit-filter-input" ).value
;
3361 selectedModule
= modulesList
?
3362 decodeURIComponent( modulesList
.options
[ modulesList
.selectedIndex
].value
) :
3365 window
.location
= setUrl({
3366 module
: ( selectedModule
=== "" ) ? undefined : selectedModule
,
3367 filter
: ( filter
=== "" ) ? undefined : filter
,
3369 // Remove testId filter
3374 function toolbarUrlConfigContainer() {
3375 var urlConfigContainer
= document
.createElement( "span" );
3377 urlConfigContainer
.innerHTML
= getUrlConfigHtml();
3378 addClass( urlConfigContainer
, "qunit-url-config" );
3380 // For oldIE support:
3381 // * Add handlers to the individual elements instead of the container
3382 // * Use "click" instead of "change" for checkboxes
3383 addEvents( urlConfigContainer
.getElementsByTagName( "input" ), "click", toolbarChanged
);
3384 addEvents( urlConfigContainer
.getElementsByTagName( "select" ), "change", toolbarChanged
);
3386 return urlConfigContainer
;
3389 function toolbarLooseFilter() {
3390 var filter
= document
.createElement( "form" ),
3391 label
= document
.createElement( "label" ),
3392 input
= document
.createElement( "input" ),
3393 button
= document
.createElement( "button" );
3395 addClass( filter
, "qunit-filter" );
3397 label
.innerHTML
= "Filter: ";
3399 input
.type
= "text";
3400 input
.value
= config
.filter
|| "";
3401 input
.name
= "filter";
3402 input
.id
= "qunit-filter-input";
3404 button
.innerHTML
= "Go";
3406 label
.appendChild( input
);
3408 filter
.appendChild( label
);
3409 filter
.appendChild( button
);
3410 addEvent( filter
, "submit", function( ev
) {
3413 if ( ev
&& ev
.preventDefault
) {
3414 ev
.preventDefault();
3423 function toolbarModuleFilterHtml() {
3425 moduleFilterHtml
= "";
3427 if ( !modulesList
.length
) {
3431 modulesList
.sort(function( a
, b
) {
3432 return a
.localeCompare( b
);
3435 moduleFilterHtml
+= "<label for='qunit-modulefilter'>Module: </label>" +
3436 "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
3437 ( QUnit
.urlParams
.module
=== undefined ? "selected='selected'" : "" ) +
3438 ">< All Modules ></option>";
3440 for ( i
= 0; i
< modulesList
.length
; i
++ ) {
3441 moduleFilterHtml
+= "<option value='" +
3442 escapeText( encodeURIComponent( modulesList
[ i
] ) ) + "' " +
3443 ( QUnit
.urlParams
.module
=== modulesList
[ i
] ? "selected='selected'" : "" ) +
3444 ">" + escapeText( modulesList
[ i
] ) + "</option>";
3446 moduleFilterHtml
+= "</select>";
3448 return moduleFilterHtml
;
3451 function toolbarModuleFilter() {
3452 var toolbar
= id( "qunit-testrunner-toolbar" ),
3453 moduleFilter
= document
.createElement( "span" ),
3454 moduleFilterHtml
= toolbarModuleFilterHtml();
3456 if ( !toolbar
|| !moduleFilterHtml
) {
3460 moduleFilter
.setAttribute( "id", "qunit-modulefilter-container" );
3461 moduleFilter
.innerHTML
= moduleFilterHtml
;
3463 addEvent( moduleFilter
.lastChild
, "change", applyUrlParams
);
3465 toolbar
.appendChild( moduleFilter
);
3468 function appendToolbar() {
3469 var toolbar
= id( "qunit-testrunner-toolbar" );
3472 toolbar
.appendChild( toolbarUrlConfigContainer() );
3473 toolbar
.appendChild( toolbarLooseFilter() );
3477 function appendHeader() {
3478 var header
= id( "qunit-header" );
3481 header
.innerHTML
= "<a href='" +
3482 setUrl({ filter
: undefined, module
: undefined, testId
: undefined }) +
3483 "'>" + header
.innerHTML
+ "</a> ";
3487 function appendBanner() {
3488 var banner
= id( "qunit-banner" );
3491 banner
.className
= "";
3495 function appendTestResults() {
3496 var tests
= id( "qunit-tests" ),
3497 result
= id( "qunit-testresult" );
3500 result
.parentNode
.removeChild( result
);
3504 tests
.innerHTML
= "";
3505 result
= document
.createElement( "p" );
3506 result
.id
= "qunit-testresult";
3507 result
.className
= "result";
3508 tests
.parentNode
.insertBefore( result
, tests
);
3509 result
.innerHTML
= "Running...<br /> ";
3513 function storeFixture() {
3514 var fixture
= id( "qunit-fixture" );
3516 config
.fixture
= fixture
.innerHTML
;
3520 function appendUserAgent() {
3521 var userAgent
= id( "qunit-userAgent" );
3524 userAgent
.innerHTML
= "";
3525 userAgent
.appendChild(
3526 document
.createTextNode(
3527 "QUnit " + QUnit
.version
+ "; " + navigator
.userAgent
3533 function appendTestsList( modules
) {
3534 var i
, l
, x
, z
, test
, moduleObj
;
3536 for ( i
= 0, l
= modules
.length
; i
< l
; i
++ ) {
3537 moduleObj
= modules
[ i
];
3539 if ( moduleObj
.name
) {
3540 modulesList
.push( moduleObj
.name
);
3543 for ( x
= 0, z
= moduleObj
.tests
.length
; x
< z
; x
++ ) {
3544 test
= moduleObj
.tests
[ x
];
3546 appendTest( test
.name
, test
.testId
, moduleObj
.name
);
3551 function appendTest( name
, testId
, moduleName
) {
3552 var title
, rerunTrigger
, testBlock
, assertList
,
3553 tests
= id( "qunit-tests" );
3559 title
= document
.createElement( "strong" );
3560 title
.innerHTML
= getNameHtml( name
, moduleName
);
3562 rerunTrigger
= document
.createElement( "a" );
3563 rerunTrigger
.innerHTML
= "Rerun";
3564 rerunTrigger
.href
= setUrl({ testId
: testId
});
3566 testBlock
= document
.createElement( "li" );
3567 testBlock
.appendChild( title
);
3568 testBlock
.appendChild( rerunTrigger
);
3569 testBlock
.id
= "qunit-test-output-" + testId
;
3571 assertList
= document
.createElement( "ol" );
3572 assertList
.className
= "qunit-assert-list";
3574 testBlock
.appendChild( assertList
);
3576 tests
.appendChild( testBlock
);
3579 // HTML Reporter initialization and load
3580 QUnit
.begin(function( details
) {
3581 var qunit
= id( "qunit" );
3583 // Fixture is the only one necessary to run without the #qunit element
3588 "<h1 id='qunit-header'>" + escapeText( document
.title
) + "</h1>" +
3589 "<h2 id='qunit-banner'></h2>" +
3590 "<div id='qunit-testrunner-toolbar'></div>" +
3591 "<h2 id='qunit-userAgent'></h2>" +
3592 "<ol id='qunit-tests'></ol>";
3597 appendTestResults();
3600 appendTestsList( details
.modules
);
3601 toolbarModuleFilter();
3603 if ( qunit
&& config
.hidepassed
) {
3604 addClass( qunit
.lastChild
, "hidepass" );
3608 QUnit
.done(function( details
) {
3610 banner
= id( "qunit-banner" ),
3611 tests
= id( "qunit-tests" ),
3613 "Tests completed in ",
3615 " milliseconds.<br />",
3616 "<span class='passed'>",
3618 "</span> assertions of <span class='total'>",
3620 "</span> passed, <span class='failed'>",
3626 banner
.className
= details
.failed
? "qunit-fail" : "qunit-pass";
3630 id( "qunit-testresult" ).innerHTML
= html
;
3633 if ( config
.altertitle
&& defined
.document
&& document
.title
) {
3635 // show ✖ for good, ✔ for bad suite result in title
3636 // use escape sequences in case file gets loaded with non-utf-8-charset
3638 ( details
.failed
? "\u2716" : "\u2714" ),
3639 document
.title
.replace( /^[\u2714\u2716] /i, "" )
3643 // clear own sessionStorage items if all tests passed
3644 if ( config
.reorder
&& defined
.sessionStorage
&& details
.failed
=== 0 ) {
3645 for ( i
= 0; i
< sessionStorage
.length
; i
++ ) {
3646 key
= sessionStorage
.key( i
++ );
3647 if ( key
.indexOf( "qunit-test-" ) === 0 ) {
3648 sessionStorage
.removeItem( key
);
3653 // scroll back to top to show results
3654 if ( config
.scrolltop
&& window
.scrollTo
) {
3655 window
.scrollTo( 0, 0 );
3659 function getNameHtml( name
, module
) {
3663 nameHtml
= "<span class='module-name'>" + escapeText( module
) + "</span>: ";
3666 nameHtml
+= "<span class='test-name'>" + escapeText( name
) + "</span>";
3671 QUnit
.testStart(function( details
) {
3672 var running
, testBlock
, bad
;
3674 testBlock
= id( "qunit-test-output-" + details
.testId
);
3676 testBlock
.className
= "running";
3679 // Report later registered tests
3680 appendTest( details
.name
, details
.testId
, details
.module
);
3683 running
= id( "qunit-testresult" );
3685 bad
= QUnit
.config
.reorder
&& defined
.sessionStorage
&&
3686 +sessionStorage
.getItem( "qunit-test-" + details
.module
+ "-" + details
.name
);
3688 running
.innerHTML
= ( bad
?
3689 "Rerunning previously failed test: <br />" :
3690 "Running: <br />" ) +
3691 getNameHtml( details
.name
, details
.module
);
3696 QUnit
.log(function( details
) {
3697 var assertList
, assertLi
,
3698 message
, expected
, actual
,
3699 testItem
= id( "qunit-test-output-" + details
.testId
);
3705 message
= escapeText( details
.message
) || ( details
.result
? "okay" : "failed" );
3706 message
= "<span class='test-message'>" + message
+ "</span>";
3707 message
+= "<span class='runtime'>@ " + details
.runtime
+ " ms</span>";
3709 // pushFailure doesn't provide details.expected
3710 // when it calls, it's implicit to also not show expected and diff stuff
3711 // Also, we need to check details.expected existence, as it can exist and be undefined
3712 if ( !details
.result
&& hasOwn
.call( details
, "expected" ) ) {
3713 expected
= escapeText( QUnit
.dump
.parse( details
.expected
) );
3714 actual
= escapeText( QUnit
.dump
.parse( details
.actual
) );
3715 message
+= "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
3719 if ( actual
!== expected
) {
3720 message
+= "<tr class='test-actual'><th>Result: </th><td><pre>" +
3721 actual
+ "</pre></td></tr>" +
3722 "<tr class='test-diff'><th>Diff: </th><td><pre>" +
3723 QUnit
.diff( expected
, actual
) + "</pre></td></tr>";
3725 if ( expected
.indexOf( "[object Array]" ) !== -1 ||
3726 expected
.indexOf( "[object Object]" ) !== -1 ) {
3727 message
+= "<tr class='test-message'><th>Message: </th><td>" +
3728 "Diff suppressed as the depth of object is more than current max depth (" +
3729 QUnit
.config
.maxDepth
+ ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " +
3730 " run with a higher max depth or <a href='" + setUrl({ maxDepth
: -1 }) + "'>" +
3731 "Rerun</a> without max depth.</p></td></tr>";
3735 if ( details
.source
) {
3736 message
+= "<tr class='test-source'><th>Source: </th><td><pre>" +
3737 escapeText( details
.source
) + "</pre></td></tr>";
3740 message
+= "</table>";
3742 // this occours when pushFailure is set and we have an extracted stack trace
3743 } else if ( !details
.result
&& details
.source
) {
3744 message
+= "<table>" +
3745 "<tr class='test-source'><th>Source: </th><td><pre>" +
3746 escapeText( details
.source
) + "</pre></td></tr>" +
3750 assertList
= testItem
.getElementsByTagName( "ol" )[ 0 ];
3752 assertLi
= document
.createElement( "li" );
3753 assertLi
.className
= details
.result
? "pass" : "fail";
3754 assertLi
.innerHTML
= message
;
3755 assertList
.appendChild( assertLi
);
3758 QUnit
.testDone(function( details
) {
3759 var testTitle
, time
, testItem
, assertList
,
3760 good
, bad
, testCounts
, skipped
,
3761 tests
= id( "qunit-tests" );
3767 testItem
= id( "qunit-test-output-" + details
.testId
);
3769 assertList
= testItem
.getElementsByTagName( "ol" )[ 0 ];
3771 good
= details
.passed
;
3772 bad
= details
.failed
;
3774 // store result when possible
3775 if ( config
.reorder
&& defined
.sessionStorage
) {
3777 sessionStorage
.setItem( "qunit-test-" + details
.module
+ "-" + details
.name
, bad
);
3779 sessionStorage
.removeItem( "qunit-test-" + details
.module
+ "-" + details
.name
);
3784 addClass( assertList
, "qunit-collapsed" );
3787 // testItem.firstChild is the test name
3788 testTitle
= testItem
.firstChild
;
3791 "<b class='failed'>" + bad
+ "</b>, " + "<b class='passed'>" + good
+ "</b>, " :
3794 testTitle
.innerHTML
+= " <b class='counts'>(" + testCounts
+
3795 details
.assertions
.length
+ ")</b>";
3797 if ( details
.skipped
) {
3798 testItem
.className
= "skipped";
3799 skipped
= document
.createElement( "em" );
3800 skipped
.className
= "qunit-skipped-label";
3801 skipped
.innerHTML
= "skipped";
3802 testItem
.insertBefore( skipped
, testTitle
);
3804 addEvent( testTitle
, "click", function() {
3805 toggleClass( assertList
, "qunit-collapsed" );
3808 testItem
.className
= bad
? "fail" : "pass";
3810 time
= document
.createElement( "span" );
3811 time
.className
= "runtime";
3812 time
.innerHTML
= details
.runtime
+ " ms";
3813 testItem
.insertBefore( time
, assertList
);
3817 if ( defined
.document
) {
3818 if ( document
.readyState
=== "complete" ) {
3821 addEvent( window
, "load", QUnit
.load
);
3824 config
.pageLoaded
= true;
3825 config
.autorun
= true;