5 * Copyright jQuery Foundation and other contributors
6 * Released under the MIT license
7 * https://jquery.org/license
9 * Date: 2016-07-23T19:39Z
12 ( function( global
) {
16 var Date
= global
.Date
;
17 var now
= Date
.now
|| function() {
18 return new Date().getTime();
21 var setTimeout
= global
.setTimeout
;
22 var clearTimeout
= global
.clearTimeout
;
24 // Store a local window from the global to allow direct references.
25 var window
= global
.window
;
28 document
: window
&& window
.document
!== undefined,
29 setTimeout
: setTimeout
!== undefined,
30 sessionStorage
: ( function() {
31 var x
= "qunit-test-string";
33 sessionStorage
.setItem( x
, x
);
34 sessionStorage
.removeItem( x
);
42 var fileName
= ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" );
43 var globalStartCalled
= false;
44 var runStarted
= false;
48 var toString
= Object
.prototype.toString
,
49 hasOwn
= Object
.prototype.hasOwnProperty
;
51 // Returns a new Array with the elements that are in a but not in b
52 function diff( a
, b
) {
56 for ( i
= 0; i
< result
.length
; i
++ ) {
57 for ( j
= 0; j
< b
.length
; j
++ ) {
58 if ( result
[ i
] === b
[ j
] ) {
59 result
.splice( i
, 1 );
69 function inArray( elem
, array
) {
70 if ( array
.indexOf
) {
71 return array
.indexOf( elem
);
74 for ( var i
= 0, length
= array
.length
; i
< length
; i
++ ) {
75 if ( array
[ i
] === elem
) {
84 * Makes a clone of an object using only Array or Object as base,
85 * and copies over the own enumerable properties.
88 * @return {Object} New object with only the own properties (recursively).
90 function objectValues ( obj
) {
92 vals
= QUnit
.is( "array", obj
) ? [] : {};
94 if ( hasOwn
.call( obj
, key
) ) {
96 vals
[ key
] = val
=== Object( val
) ? objectValues( val
) : val
;
102 function extend( a
, b
, undefOnly
) {
103 for ( var prop
in b
) {
104 if ( hasOwn
.call( b
, prop
) ) {
105 if ( b
[ prop
] === undefined ) {
107 } else if ( !( undefOnly
&& typeof a
[ prop
] !== "undefined" ) ) {
108 a
[ prop
] = b
[ prop
];
116 function objectType( obj
) {
117 if ( typeof obj
=== "undefined" ) {
121 // Consider: typeof null === object
122 if ( obj
=== null ) {
126 var match
= toString
.call( obj
).match( /^\[object\s(.*)\]$/ ),
127 type
= match
&& match
[ 1 ];
131 if ( isNaN( obj
) ) {
144 return type
.toLowerCase();
146 if ( typeof obj
=== "object" ) {
151 // Safe object type checking
152 function is( type
, obj
) {
153 return QUnit
.objectType( obj
) === type
;
156 // Doesn't support IE9, it will return undefined on these browsers
157 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
158 function extractStacktrace( e
, offset
) {
159 offset
= offset
=== undefined ? 4 : offset
;
161 var stack
, include
, i
;
164 stack
= e
.stack
.split( "\n" );
165 if ( /^error$/i.test( stack
[ 0 ] ) ) {
170 for ( i
= offset
; i
< stack
.length
; i
++ ) {
171 if ( stack
[ i
].indexOf( fileName
) !== -1 ) {
174 include
.push( stack
[ i
] );
176 if ( include
.length
) {
177 return include
.join( "\n" );
180 return stack
[ offset
];
184 function sourceFromStacktrace( offset
) {
185 var error
= new Error();
187 // Support: Safari <=7 only, IE <=10 - 11 only
188 // Not all browsers generate the `stack` property for `new Error()`, see also #636
189 if ( !error
.stack
) {
197 return extractStacktrace( error
, offset
);
201 * Config object: Maintain internal state
202 * Later exposed as QUnit.config
203 * `config` initialized at top of scope
207 // The queue of tests to run
210 // Block until document ready
213 // By default, run previously failed tests first
214 // very useful in combination with "Hide passed tests" checked
217 // By default, modify document.title when suite is done
220 // HTML Reporter: collapse every test except the first failing test
221 // If false, all failing tests will be expanded
224 // By default, scroll to top of the page when suite is done
227 // Depth up-to which object will be dumped
230 // When enabled, all tests must call expect()
231 requireExpects
: false,
233 // Placeholder for user-configurable form-exposed URL parameters
236 // Set of all modules.
239 // Stack of nested modules
242 // The first unnamed module
251 // Push a loose unnamed module to the modules collection
252 config
.modules
.push( config
.currentModule
);
254 // Register logging callbacks
255 function registerLoggingCallbacks( obj
) {
257 callbackNames
= [ "begin", "done", "log", "testStart", "testDone",
258 "moduleStart", "moduleDone" ];
260 function registerLoggingCallback( key
) {
261 var loggingCallback = function( callback
) {
262 if ( objectType( callback
) !== "function" ) {
264 "QUnit logging methods require a callback function as their first parameters."
268 config
.callbacks
[ key
].push( callback
);
271 return loggingCallback
;
274 for ( i
= 0, l
= callbackNames
.length
; i
< l
; i
++ ) {
275 key
= callbackNames
[ i
];
277 // Initialize key collection of logging callback
278 if ( objectType( config
.callbacks
[ key
] ) === "undefined" ) {
279 config
.callbacks
[ key
] = [];
282 obj
[ key
] = registerLoggingCallback( key
);
286 function runLoggingCallbacks( key
, args
) {
289 callbacks
= config
.callbacks
[ key
];
290 for ( i
= 0, l
= callbacks
.length
; i
< l
; i
++ ) {
291 callbacks
[ i
]( args
);
296 if ( !defined
.document
) {
300 // `onErrorFnPrev` initialized at top of scope
301 // Preserve other handlers
302 var onErrorFnPrev
= window
.onerror
;
304 // Cover uncaught exceptions
305 // Returning true will suppress the default browser handler,
306 // returning false will let it run.
307 window
.onerror = function( error
, filePath
, linerNr
) {
309 if ( onErrorFnPrev
) {
310 ret
= onErrorFnPrev( error
, filePath
, linerNr
);
313 // Treat return value as window.onerror itself does,
314 // Only do our handling if not suppressed.
315 if ( ret
!== true ) {
316 if ( QUnit
.config
.current
) {
317 if ( QUnit
.config
.current
.ignoreGlobalErrors
) {
320 QUnit
.pushFailure( error
, filePath
+ ":" + linerNr
);
322 QUnit
.test( "global failure", extend( function() {
323 QUnit
.pushFailure( error
, filePath
+ ":" + linerNr
);
324 }, { validTest
: true } ) );
333 // Figure out if we're running the tests from a server or not
334 QUnit
.isLocal
= !( defined
.document
&& window
.location
.protocol
!== "file:" );
336 // Expose the current QUnit version
337 QUnit
.version
= "2.0.1";
341 // Call on start of module test to prepend name to all tests
342 module: function( name
, testEnvironment
, executeNow
) {
343 var module
, moduleFns
;
344 var currentModule
= config
.currentModule
;
346 if ( arguments
.length
=== 2 ) {
347 if ( objectType( testEnvironment
) === "function" ) {
348 executeNow
= testEnvironment
;
349 testEnvironment
= undefined;
353 module
= createModule();
355 if ( testEnvironment
&& ( testEnvironment
.setup
|| testEnvironment
.teardown
) ) {
357 "Module's `setup` and `teardown` are not hooks anymore on QUnit 2.0, use " +
358 "`beforeEach` and `afterEach` instead\n" +
359 "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/"
364 before
: setHook( module
, "before" ),
365 beforeEach
: setHook( module
, "beforeEach" ),
366 afterEach
: setHook( module
, "afterEach" ),
367 after
: setHook( module
, "after" )
370 if ( objectType( executeNow
) === "function" ) {
371 config
.moduleStack
.push( module
);
372 setCurrentModule( module
);
373 executeNow
.call( module
.testEnvironment
, moduleFns
);
374 config
.moduleStack
.pop();
375 module
= module
.parentModule
|| currentModule
;
378 setCurrentModule( module
);
380 function createModule() {
381 var parentModule
= config
.moduleStack
.length
?
382 config
.moduleStack
.slice( -1 )[ 0 ] : null;
383 var moduleName
= parentModule
!== null ?
384 [ parentModule
.name
, name
].join( " > " ) : name
;
387 parentModule
: parentModule
,
389 moduleId
: generateHash( moduleName
),
394 if ( parentModule
) {
395 parentModule
.childModule
= module
;
396 extend( env
, parentModule
.testEnvironment
);
397 delete env
.beforeEach
;
398 delete env
.afterEach
;
400 extend( env
, testEnvironment
);
401 module
.testEnvironment
= env
;
403 config
.modules
.push( module
);
407 function setCurrentModule( module
) {
408 config
.currentModule
= module
;
419 start: function( count
) {
420 var globalStartAlreadyCalled
= globalStartCalled
;
422 if ( !config
.current
) {
423 globalStartCalled
= true;
426 throw new Error( "Called start() while test already started running" );
427 } else if ( globalStartAlreadyCalled
|| count
> 1 ) {
428 throw new Error( "Called start() outside of a test context too many times" );
429 } else if ( config
.autostart
) {
430 throw new Error( "Called start() outside of a test context when " +
431 "QUnit.config.autostart was true" );
432 } else if ( !config
.pageLoaded
) {
434 // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
435 config
.autostart
= true;
440 "QUnit.start cannot be called inside a test context. This feature is removed in " +
441 "QUnit 2.0. For async tests, use QUnit.test() with assert.async() instead.\n" +
442 "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/"
453 objectType
: objectType
,
458 config
.pageLoaded
= true;
460 // Initialize the configuration options
462 stats
: { all
: 0, bad
: 0 },
463 moduleStats
: { all
: 0, bad
: 0 },
471 config
.blocking
= false;
473 if ( config
.autostart
) {
479 stack: function( offset
) {
480 offset
= ( offset
|| 0 ) + 2;
481 return sourceFromStacktrace( offset
);
485 registerLoggingCallbacks( QUnit
);
487 function scheduleBegin() {
491 // Add a slight delay to allow definition of more modules and tests.
492 if ( defined
.setTimeout
) {
493 setTimeout( function() {
505 // If the test run hasn't officially begun yet
506 if ( !config
.started
) {
508 // Record the time of the test run's beginning
509 config
.started
= now();
511 // Delete the loose unnamed module if unused.
512 if ( config
.modules
[ 0 ].name
=== "" && config
.modules
[ 0 ].tests
.length
=== 0 ) {
513 config
.modules
.shift();
516 // Avoid unnecessary information by not logging modules' test environments
517 for ( i
= 0, l
= config
.modules
.length
; i
< l
; i
++ ) {
519 name
: config
.modules
[ i
].name
,
520 tests
: config
.modules
[ i
].tests
524 // The test run is officially beginning now
525 runLoggingCallbacks( "begin", {
526 totalTests
: Test
.count
,
531 config
.blocking
= false;
535 function process( last
) {
540 config
.depth
= ( config
.depth
|| 0 ) + 1;
542 while ( config
.queue
.length
&& !config
.blocking
) {
543 if ( !defined
.setTimeout
|| config
.updateRate
<= 0 ||
544 ( ( now() - start
) < config
.updateRate
) ) {
545 if ( config
.current
) {
547 // Reset async tracking for each phase of the Test lifecycle
548 config
.current
.usedAsync
= false;
550 config
.queue
.shift()();
552 setTimeout( next
, 13 );
557 if ( last
&& !config
.blocking
&& !config
.queue
.length
&& config
.depth
=== 0 ) {
567 // Log the last module results
568 if ( config
.previousModule
) {
569 runLoggingCallbacks( "moduleDone", {
570 name
: config
.previousModule
.name
,
571 tests
: config
.previousModule
.tests
,
572 failed
: config
.moduleStats
.bad
,
573 passed
: config
.moduleStats
.all
- config
.moduleStats
.bad
,
574 total
: config
.moduleStats
.all
,
575 runtime
: now() - config
.moduleStats
.started
578 delete config
.previousModule
;
580 runtime
= now() - config
.started
;
581 passed
= config
.stats
.all
- config
.stats
.bad
;
583 runLoggingCallbacks( "done", {
584 failed
: config
.stats
.bad
,
586 total
: config
.stats
.all
,
591 function setHook( module
, hookName
) {
592 if ( module
.testEnvironment
=== undefined ) {
593 module
.testEnvironment
= {};
596 return function( callback
) {
597 module
.testEnvironment
[ hookName
] = callback
;
605 function Test( settings
) {
610 this.expected
= null;
611 extend( this, settings
);
612 this.assertions
= [];
614 this.usedAsync
= false;
615 this.module
= config
.currentModule
;
616 this.stack
= sourceFromStacktrace( 3 );
618 // Register unique strings
619 for ( i
= 0, l
= this.module
.tests
; i
< l
.length
; i
++ ) {
620 if ( this.module
.tests
[ i
].name
=== this.testName
) {
621 this.testName
+= " ";
625 this.testId
= generateHash( this.module
.name
, this.testName
);
627 this.module
.tests
.push( {
632 if ( settings
.skip
) {
634 // Skipped tests will fully ignore any sent callback
635 this.callback = function() {};
639 this.assert
= new Assert( this );
649 // Emit moduleStart when we're switching from one module to another
650 this.module
!== config
.previousModule
||
652 // They could be equal (both undefined) but if the previousModule property doesn't
653 // yet exist it means this is the first test in a suite that isn't wrapped in a
654 // module, in which case we'll just emit a moduleStart event for 'undefined'.
655 // Without this, reporters can get testStart before moduleStart which is a problem.
656 !hasOwn
.call( config
, "previousModule" )
658 if ( hasOwn
.call( config
, "previousModule" ) ) {
659 runLoggingCallbacks( "moduleDone", {
660 name
: config
.previousModule
.name
,
661 tests
: config
.previousModule
.tests
,
662 failed
: config
.moduleStats
.bad
,
663 passed
: config
.moduleStats
.all
- config
.moduleStats
.bad
,
664 total
: config
.moduleStats
.all
,
665 runtime
: now() - config
.moduleStats
.started
668 config
.previousModule
= this.module
;
669 config
.moduleStats
= { all
: 0, bad
: 0, started
: now() };
670 runLoggingCallbacks( "moduleStart", {
671 name
: this.module
.name
,
672 tests
: this.module
.tests
676 config
.current
= this;
678 if ( this.module
.testEnvironment
) {
679 delete this.module
.testEnvironment
.before
;
680 delete this.module
.testEnvironment
.beforeEach
;
681 delete this.module
.testEnvironment
.afterEach
;
682 delete this.module
.testEnvironment
.after
;
684 this.testEnvironment
= extend( {}, this.module
.testEnvironment
);
686 this.started
= now();
687 runLoggingCallbacks( "testStart", {
689 module
: this.module
.name
,
693 if ( !config
.pollution
) {
701 config
.current
= this;
703 this.callbackStarted
= now();
705 if ( config
.notrycatch
) {
713 this.pushFailure( "Died on test #" + ( this.assertions
.length
+ 1 ) + " " +
714 this.stack
+ ": " + ( e
.message
|| e
), extractStacktrace( e
, 0 ) );
716 // Else next test will carry the responsibility
719 // Restart the tests if they're blocking
720 if ( config
.blocking
) {
721 internalRecover( this );
725 function runTest( test
) {
726 promise
= test
.callback
.call( test
.testEnvironment
, test
.assert
);
727 test
.resolvePromise( promise
);
735 queueHook: function( hook
, hookName
, hookOwner
) {
738 return function runHook() {
739 if ( hookName
=== "before" ) {
740 if ( hookOwner
.testsRun
!== 0 ) {
744 test
.preserveEnvironment
= true;
747 if ( hookName
=== "after" && hookOwner
.testsRun
!== numberOfTests( hookOwner
) - 1 ) {
751 config
.current
= test
;
752 if ( config
.notrycatch
) {
759 test
.pushFailure( hookName
+ " failed on " + test
.testName
+ ": " +
760 ( error
.message
|| error
), extractStacktrace( error
, 0 ) );
763 function callHook() {
764 promise
= hook
.call( test
.testEnvironment
, test
.assert
);
765 test
.resolvePromise( promise
, hookName
);
770 // Currently only used for module level hooks, can be used to add global level ones
771 hooks: function( handler
) {
774 function processHooks( test
, module
) {
775 if ( module
.parentModule
) {
776 processHooks( test
, module
.parentModule
);
778 if ( module
.testEnvironment
&&
779 QUnit
.objectType( module
.testEnvironment
[ handler
] ) === "function" ) {
780 hooks
.push( test
.queueHook( module
.testEnvironment
[ handler
], handler
, module
) );
784 // Hooks are ignored on skipped tests
786 processHooks( this, this.module
);
792 config
.current
= this;
793 if ( config
.requireExpects
&& this.expected
=== null ) {
794 this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
795 "not called.", this.stack
);
796 } else if ( this.expected
!== null && this.expected
!== this.assertions
.length
) {
797 this.pushFailure( "Expected " + this.expected
+ " assertions, but " +
798 this.assertions
.length
+ " were run", this.stack
);
799 } else if ( this.expected
=== null && !this.assertions
.length
) {
800 this.pushFailure( "Expected at least one assertion, but none were run - call " +
801 "expect(0) to accept zero assertions.", this.stack
);
805 skipped
= !!this.skip
,
808 this.runtime
= now() - this.started
;
810 config
.stats
.all
+= this.assertions
.length
;
811 config
.moduleStats
.all
+= this.assertions
.length
;
813 for ( i
= 0; i
< this.assertions
.length
; i
++ ) {
814 if ( !this.assertions
[ i
].result
) {
817 config
.moduleStats
.bad
++;
821 notifyTestsRan( this.module
);
822 runLoggingCallbacks( "testDone", {
824 module
: this.module
.name
,
827 passed
: this.assertions
.length
- bad
,
828 total
: this.assertions
.length
,
829 runtime
: skipped
? 0 : this.runtime
,
832 assertions
: this.assertions
,
839 config
.current
= undefined;
842 preserveTestEnvironment: function() {
843 if ( this.preserveEnvironment
) {
844 this.module
.testEnvironment
= this.testEnvironment
;
845 this.testEnvironment
= extend( {}, this.module
.testEnvironment
);
853 if ( !this.valid() ) {
859 // Each of these can by async
865 test
.hooks( "before" ),
868 test
.preserveTestEnvironment();
871 test
.hooks( "beforeEach" ),
877 test
.hooks( "afterEach" ).reverse(),
878 test
.hooks( "after" ).reverse(),
890 // Prioritize previously failed tests, detected from sessionStorage
891 priority
= QUnit
.config
.reorder
&& defined
.sessionStorage
&&
892 +sessionStorage
.getItem( "qunit-test-" + this.module
.name
+ "-" + this.testName
);
894 return synchronize( run
, priority
, config
.seed
);
897 pushResult: function( resultInfo
) {
899 // Destructure of resultInfo = { result, actual, expected, message, negative }
902 module
: this.module
.name
,
904 result
: resultInfo
.result
,
905 message
: resultInfo
.message
,
906 actual
: resultInfo
.actual
,
907 expected
: resultInfo
.expected
,
909 negative
: resultInfo
.negative
|| false,
910 runtime
: now() - this.started
913 if ( !resultInfo
.result
) {
914 source
= sourceFromStacktrace();
917 details
.source
= source
;
921 runLoggingCallbacks( "log", details
);
923 this.assertions
.push( {
924 result
: !!resultInfo
.result
,
925 message
: resultInfo
.message
929 pushFailure: function( message
, source
, actual
) {
930 if ( !( this instanceof Test
) ) {
931 throw new Error( "pushFailure() assertion outside test context, was " +
932 sourceFromStacktrace( 2 ) );
936 module
: this.module
.name
,
939 message
: message
|| "error",
940 actual
: actual
|| null,
942 runtime
: now() - this.started
946 details
.source
= source
;
949 runLoggingCallbacks( "log", details
);
951 this.assertions
.push( {
957 resolvePromise: function( promise
, phase
) {
958 var then
, resume
, message
,
960 if ( promise
!= null ) {
962 if ( QUnit
.objectType( then
) === "function" ) {
963 resume
= internalStop( test
);
966 function() { resume(); },
968 message
= "Promise rejected " +
969 ( !phase
? "during" : phase
.replace( /Each$/, "" ) ) +
970 " " + test
.testName
+ ": " + ( error
.message
|| error
);
971 test
.pushFailure( message
, extractStacktrace( error
, 0 ) );
973 // Else next test will carry the responsibility
985 var filter
= config
.filter
,
986 regexFilter
= /^(!?)\/([\w\W]*)\/(i?$)/.exec( filter
),
987 module
= config
.module
&& config
.module
.toLowerCase(),
988 fullName
= ( this.module
.name
+ ": " + this.testName
);
990 function moduleChainNameMatch( testModule
) {
991 var testModuleName
= testModule
.name
? testModule
.name
.toLowerCase() : null;
992 if ( testModuleName
=== module
) {
994 } else if ( testModule
.parentModule
) {
995 return moduleChainNameMatch( testModule
.parentModule
);
1001 function moduleChainIdMatch( testModule
) {
1002 return inArray( testModule
.moduleId
, config
.moduleId
) > -1 ||
1003 testModule
.parentModule
&& moduleChainIdMatch( testModule
.parentModule
);
1006 // Internally-generated tests are always valid
1007 if ( this.callback
&& this.callback
.validTest
) {
1011 if ( config
.moduleId
&& config
.moduleId
.length
> 0 &&
1012 !moduleChainIdMatch( this.module
) ) {
1017 if ( config
.testId
&& config
.testId
.length
> 0 &&
1018 inArray( this.testId
, config
.testId
) < 0 ) {
1023 if ( module
&& !moduleChainNameMatch( this.module
) ) {
1031 return regexFilter
?
1032 this.regexFilter( !!regexFilter
[ 1 ], regexFilter
[ 2 ], regexFilter
[ 3 ], fullName
) :
1033 this.stringFilter( filter
, fullName
);
1036 regexFilter: function( exclude
, pattern
, flags
, fullName
) {
1037 var regex
= new RegExp( pattern
, flags
);
1038 var match
= regex
.test( fullName
);
1040 return match
!== exclude
;
1043 stringFilter: function( filter
, fullName
) {
1044 filter
= filter
.toLowerCase();
1045 fullName
= fullName
.toLowerCase();
1047 var include
= filter
.charAt( 0 ) !== "!";
1049 filter
= filter
.slice( 1 );
1052 // If the filter matches, we need to honour include
1053 if ( fullName
.indexOf( filter
) !== -1 ) {
1057 // Otherwise, do the opposite
1062 QUnit
.pushFailure = function() {
1063 if ( !QUnit
.config
.current
) {
1064 throw new Error( "pushFailure() assertion outside test context, in " +
1065 sourceFromStacktrace( 2 ) );
1068 // Gets current test obj
1069 var currentTest
= QUnit
.config
.current
;
1071 return currentTest
.pushFailure
.apply( currentTest
, arguments
);
1074 // Based on Java's String.hashCode, a simple but not
1075 // rigorously collision resistant hashing function
1076 function generateHash( module
, testName
) {
1080 str
= module
+ "\x1C" + testName
,
1083 for ( ; i
< len
; i
++ ) {
1084 hash
= ( ( hash
<< 5 ) - hash
) + str
.charCodeAt( i
);
1088 // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
1089 // strictly necessary but increases user understanding that the id is a SHA-like hash
1090 hex
= ( 0x100000000 + hash
).toString( 16 );
1091 if ( hex
.length
< 8 ) {
1092 hex
= "0000000" + hex
;
1095 return hex
.slice( -8 );
1098 function synchronize( callback
, priority
, seed
) {
1099 var last
= !priority
,
1102 if ( QUnit
.objectType( callback
) === "array" ) {
1103 while ( callback
.length
) {
1104 synchronize( callback
.shift() );
1110 config
.queue
.splice( priorityCount
++, 0, callback
);
1111 } else if ( seed
) {
1112 if ( !unitSampler
) {
1113 unitSampler
= unitSamplerGenerator( seed
);
1116 // Insert into a random position after all priority items
1117 index
= Math
.floor( unitSampler() * ( config
.queue
.length
- priorityCount
+ 1 ) );
1118 config
.queue
.splice( priorityCount
+ index
, 0, callback
);
1120 config
.queue
.push( callback
);
1123 if ( autorun
&& !config
.blocking
) {
1128 function unitSamplerGenerator( seed
) {
1130 // 32-bit xorshift, requires only a nonzero seed
1131 // http://excamera.com/sphinx/article-xorshift.html
1132 var sample
= parseInt( generateHash( seed
), 16 ) || -1;
1134 sample
^= sample
<< 13;
1135 sample
^= sample
>>> 17;
1136 sample
^= sample
<< 5;
1138 // ECMAScript has no unsigned number type
1140 sample
+= 0x100000000;
1143 return sample
/ 0x100000000;
1147 function saveGlobal() {
1148 config
.pollution
= [];
1150 if ( config
.noglobals
) {
1151 for ( var key
in global
) {
1152 if ( hasOwn
.call( global
, key
) ) {
1154 // In Opera sometimes DOM element ids show up here, ignore them
1155 if ( /^qunit-test-output/.test( key
) ) {
1158 config
.pollution
.push( key
);
1164 function checkPollution() {
1167 old
= config
.pollution
;
1171 newGlobals
= diff( config
.pollution
, old
);
1172 if ( newGlobals
.length
> 0 ) {
1173 QUnit
.pushFailure( "Introduced global variable(s): " + newGlobals
.join( ", " ) );
1176 deletedGlobals
= diff( old
, config
.pollution
);
1177 if ( deletedGlobals
.length
> 0 ) {
1178 QUnit
.pushFailure( "Deleted global variable(s): " + deletedGlobals
.join( ", " ) );
1182 // Will be exposed as QUnit.test
1183 function test( testName
, callback
) {
1184 if ( focused
) { return; }
1188 newTest
= new Test( {
1196 // Will be exposed as QUnit.skip
1197 function skip( testName
) {
1198 if ( focused
) { return; }
1200 var test
= new Test( {
1208 // Will be exposed as QUnit.only
1209 function only( testName
, callback
) {
1212 if ( focused
) { return; }
1214 QUnit
.config
.queue
.length
= 0;
1217 newTest
= new Test( {
1225 // Put a hold on processing and return a function that will release it.
1226 function internalStop( test
) {
1227 var released
= false;
1229 test
.semaphore
+= 1;
1230 config
.blocking
= true;
1232 // Set a recovery timeout, if so configured.
1233 if ( config
.testTimeout
&& defined
.setTimeout
) {
1234 clearTimeout( config
.timeout
);
1235 config
.timeout
= setTimeout( function() {
1236 QUnit
.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
1237 internalRecover( test
);
1238 }, config
.testTimeout
);
1241 return function resume() {
1247 test
.semaphore
-= 1;
1248 internalStart( test
);
1252 // Forcefully release all processing holds.
1253 function internalRecover( test
) {
1255 internalStart( test
);
1258 // Release a processing hold, scheduling a resumption attempt if no holds remain.
1259 function internalStart( test
) {
1261 // If semaphore is non-numeric, throw error
1262 if ( isNaN( test
.semaphore
) ) {
1266 "Invalid value on test.semaphore",
1267 sourceFromStacktrace( 2 )
1272 // Don't start until equal number of stop-calls
1273 if ( test
.semaphore
> 0 ) {
1277 // Throw an Error if start is called more often than stop
1278 if ( test
.semaphore
< 0 ) {
1282 "Tried to restart test while already started (test's semaphore was 0 already)",
1283 sourceFromStacktrace( 2 )
1288 // Add a slight delay to allow more assertions etc.
1289 if ( defined
.setTimeout
) {
1290 if ( config
.timeout
) {
1291 clearTimeout( config
.timeout
);
1293 config
.timeout
= setTimeout( function() {
1294 if ( test
.semaphore
> 0 ) {
1298 if ( config
.timeout
) {
1299 clearTimeout( config
.timeout
);
1309 function numberOfTests( module
) {
1310 var count
= module
.tests
.length
;
1311 while ( module
= module
.childModule
) {
1312 count
+= module
.tests
.length
;
1317 function notifyTestsRan( module
) {
1319 while ( module
= module
.parentModule
) {
1324 function Assert( testContext
) {
1325 this.test
= testContext
;
1329 QUnit
.assert
= Assert
.prototype = {
1331 // Specify the number of expected assertions to guarantee that failed test
1332 // (no assertions are run at all) don't slip through.
1333 expect: function( asserts
) {
1334 if ( arguments
.length
=== 1 ) {
1335 this.test
.expected
= asserts
;
1337 return this.test
.expected
;
1341 // Put a hold on processing and return a function that will release it a maximum of once.
1342 async: function( count
) {
1346 acceptCallCount
= count
;
1348 if ( typeof acceptCallCount
=== "undefined" ) {
1349 acceptCallCount
= 1;
1352 test
.usedAsync
= true;
1353 resume
= internalStop( test
);
1355 return function done() {
1358 test
.pushFailure( "Too many calls to the `assert.async` callback",
1359 sourceFromStacktrace( 2 ) );
1362 acceptCallCount
-= 1;
1363 if ( acceptCallCount
> 0 ) {
1372 // Exports test.push() to the user API
1373 // Alias of pushResult.
1374 push: function( result
, actual
, expected
, message
, negative
) {
1375 var currentAssert
= this instanceof Assert
? this : QUnit
.config
.current
.assert
;
1376 return currentAssert
.pushResult( {
1385 pushResult: function( resultInfo
) {
1387 // Destructure of resultInfo = { result, actual, expected, message, negative }
1389 currentTest
= ( assert
instanceof Assert
&& assert
.test
) || QUnit
.config
.current
;
1391 // Backwards compatibility fix.
1392 // Allows the direct use of global exported assertions and QUnit.assert.*
1393 // Although, it's use is not recommended as it can leak assertions
1394 // to other tests from async tests, because we only get a reference to the current test,
1395 // not exactly the test where assertion were intended to be called.
1396 if ( !currentTest
) {
1397 throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
1400 if ( currentTest
.usedAsync
=== true && currentTest
.semaphore
=== 0 ) {
1401 currentTest
.pushFailure( "Assertion after the final `assert.async` was resolved",
1402 sourceFromStacktrace( 2 ) );
1404 // Allow this assertion to continue running anyway...
1407 if ( !( assert
instanceof Assert
) ) {
1408 assert
= currentTest
.assert
;
1411 return assert
.test
.pushResult( resultInfo
);
1414 ok: function( result
, message
) {
1415 message
= message
|| ( result
? "okay" : "failed, expected argument to be truthy, was: " +
1416 QUnit
.dump
.parse( result
) );
1425 notOk: function( result
, message
) {
1426 message
= message
|| ( !result
? "okay" : "failed, expected argument to be falsy, was: " +
1427 QUnit
.dump
.parse( result
) );
1436 equal: function( actual
, expected
, message
) {
1437 /*jshint eqeqeq:false */
1439 result
: expected
== actual
,
1446 notEqual: function( actual
, expected
, message
) {
1447 /*jshint eqeqeq:false */
1449 result
: expected
!= actual
,
1457 propEqual: function( actual
, expected
, message
) {
1458 actual
= objectValues( actual
);
1459 expected
= objectValues( expected
);
1461 result
: QUnit
.equiv( actual
, expected
),
1468 notPropEqual: function( actual
, expected
, message
) {
1469 actual
= objectValues( actual
);
1470 expected
= objectValues( expected
);
1472 result
: !QUnit
.equiv( actual
, expected
),
1480 deepEqual: function( actual
, expected
, message
) {
1482 result
: QUnit
.equiv( actual
, expected
),
1489 notDeepEqual: function( actual
, expected
, message
) {
1491 result
: !QUnit
.equiv( actual
, expected
),
1499 strictEqual: function( actual
, expected
, message
) {
1501 result
: expected
=== actual
,
1508 notStrictEqual: function( actual
, expected
, message
) {
1510 result
: expected
!== actual
,
1518 "throws": function( block
, expected
, message
) {
1519 var actual
, expectedType
,
1520 expectedOutput
= expected
,
1522 currentTest
= ( this instanceof Assert
&& this.test
) || QUnit
.config
.current
;
1524 // 'expected' is optional unless doing string comparison
1525 if ( QUnit
.objectType( expected
) === "string" ) {
1526 if ( message
== null ) {
1531 "throws/raises does not accept a string value for the expected argument.\n" +
1532 "Use a non-string object value (e.g. regExp) instead if it's necessary." +
1533 "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/"
1538 currentTest
.ignoreGlobalErrors
= true;
1540 block
.call( currentTest
.testEnvironment
);
1544 currentTest
.ignoreGlobalErrors
= false;
1547 expectedType
= QUnit
.objectType( expected
);
1549 // We don't want to validate thrown error
1552 expectedOutput
= null;
1554 // Expected is a regexp
1555 } else if ( expectedType
=== "regexp" ) {
1556 ok
= expected
.test( errorString( actual
) );
1558 // Expected is a constructor, maybe an Error constructor
1559 } else if ( expectedType
=== "function" && actual
instanceof expected
) {
1562 // Expected is an Error object
1563 } else if ( expectedType
=== "object" ) {
1564 ok
= actual
instanceof expected
.constructor &&
1565 actual
.name
=== expected
.name
&&
1566 actual
.message
=== expected
.message
;
1568 // Expected is a validation function which returns true if validation passed
1569 } else if ( expectedType
=== "function" && expected
.call( {}, actual
) === true ) {
1570 expectedOutput
= null;
1575 currentTest
.assert
.pushResult( {
1578 expected
: expectedOutput
,
1584 // Provide an alternative to assert.throws(), for environments that consider throws a reserved word
1585 // Known to us are: Closure Compiler, Narwhal
1587 /*jshint sub:true */
1588 Assert
.prototype.raises
= Assert
.prototype [ "throws" ]; //jscs:ignore requireDotNotation
1591 function errorString( error
) {
1593 resultErrorString
= error
.toString();
1594 if ( resultErrorString
.substring( 0, 7 ) === "[object" ) {
1595 name
= error
.name
? error
.name
.toString() : "Error";
1596 message
= error
.message
? error
.message
.toString() : "";
1597 if ( name
&& message
) {
1598 return name
+ ": " + message
;
1599 } else if ( name
) {
1601 } else if ( message
) {
1607 return resultErrorString
;
1611 // Test for equality any JavaScript type.
1612 // Author: Philippe Rathé <prathe@gmail.com>
1613 QUnit
.equiv
= ( function() {
1615 // Stack to decide between skip/abort functions
1618 // Stack to avoiding loops from circular referencing
1622 var getProto
= Object
.getPrototypeOf
|| function( obj
) {
1624 /*jshint proto: true */
1625 return obj
.__proto__
;
1628 function useStrictEquality( b
, a
) {
1630 // To catch short annotation VS 'new' annotation of a declaration. e.g.:
1632 // `var j = new Number(1);`
1633 if ( typeof a
=== "object" ) {
1636 if ( typeof b
=== "object" ) {
1643 function compareConstructors( a
, b
) {
1644 var protoA
= getProto( a
);
1645 var protoB
= getProto( b
);
1647 // Comparing constructors is more strict than using `instanceof`
1648 if ( a
.constructor === b
.constructor ) {
1653 // If the obj prototype descends from a null constructor, treat it
1654 // as a null prototype.
1655 if ( protoA
&& protoA
.constructor === null ) {
1658 if ( protoB
&& protoB
.constructor === null ) {
1662 // Allow objects with no prototype to be equivalent to
1663 // objects with Object as their constructor.
1664 if ( ( protoA
=== null && protoB
=== Object
.prototype ) ||
1665 ( protoB
=== null && protoA
=== Object
.prototype ) ) {
1672 function getRegExpFlags( regexp
) {
1673 return "flags" in regexp
? regexp
.flags
: regexp
.toString().match( /[gimuy]*$/ )[ 0 ];
1677 "string": useStrictEquality
,
1678 "boolean": useStrictEquality
,
1679 "number": useStrictEquality
,
1680 "null": useStrictEquality
,
1681 "undefined": useStrictEquality
,
1682 "symbol": useStrictEquality
,
1683 "date": useStrictEquality
,
1689 "regexp": function( b
, a
) {
1690 return a
.source
=== b
.source
&&
1692 // Include flags in the comparison
1693 getRegExpFlags( a
) === getRegExpFlags( b
);
1696 // - skip when the property is a method of an instance (OOP)
1697 // - abort otherwise,
1698 // initial === would have catch identical references anyway
1699 "function": function() {
1700 var caller
= callers
[ callers
.length
- 1 ];
1701 return caller
!== Object
&& typeof caller
!== "undefined";
1704 "array": function( b
, a
) {
1705 var i
, j
, len
, loop
, aCircular
, bCircular
;
1708 if ( len
!== b
.length
) {
1714 // Track reference to avoid circular references
1717 for ( i
= 0; i
< len
; i
++ ) {
1719 for ( j
= 0; j
< parents
.length
; j
++ ) {
1720 aCircular
= parents
[ j
] === a
[ i
];
1721 bCircular
= parentsB
[ j
] === b
[ i
];
1722 if ( aCircular
|| bCircular
) {
1723 if ( a
[ i
] === b
[ i
] || aCircular
&& bCircular
) {
1732 if ( !loop
&& !innerEquiv( a
[ i
], b
[ i
] ) ) {
1743 "set": function( b
, a
) {
1747 if ( a
.size
!== b
.size
) {
1751 a
.forEach( function( aVal
) {
1754 b
.forEach( function( bVal
) {
1755 if ( innerEquiv( bVal
, aVal
) ) {
1768 "map": function( b
, a
) {
1772 if ( a
.size
!== b
.size
) {
1776 a
.forEach( function( aVal
, aKey
) {
1779 b
.forEach( function( bVal
, bKey
) {
1780 if ( innerEquiv( [ bVal
, bKey
], [ aVal
, aKey
] ) ) {
1793 "object": function( b
, a
) {
1794 var i
, j
, loop
, aCircular
, bCircular
;
1798 var aProperties
= [];
1799 var bProperties
= [];
1801 if ( compareConstructors( a
, b
) === false ) {
1805 // Stack constructor before traversing properties
1806 callers
.push( a
.constructor );
1808 // Track reference to avoid circular references
1812 // Be strict: don't ensure hasOwnProperty and go deep
1815 for ( j
= 0; j
< parents
.length
; j
++ ) {
1816 aCircular
= parents
[ j
] === a
[ i
];
1817 bCircular
= parentsB
[ j
] === b
[ i
];
1818 if ( aCircular
|| bCircular
) {
1819 if ( a
[ i
] === b
[ i
] || aCircular
&& bCircular
) {
1827 aProperties
.push( i
);
1828 if ( !loop
&& !innerEquiv( a
[ i
], b
[ i
] ) ) {
1837 // Unstack, we are done
1842 // Collect b's properties
1843 bProperties
.push( i
);
1846 // Ensures identical properties name
1847 return eq
&& innerEquiv( aProperties
.sort(), bProperties
.sort() );
1851 function typeEquiv( a
, b
) {
1852 var type
= QUnit
.objectType( a
);
1853 return QUnit
.objectType( b
) === type
&& callbacks
[ type
]( b
, a
);
1856 // The real equiv function
1857 function innerEquiv( a
, b
) {
1859 // We're done when there's nothing more to compare
1860 if ( arguments
.length
< 2 ) {
1864 // Require type-specific equality
1865 return ( a
=== b
|| typeEquiv( a
, b
) ) &&
1867 // ...across all consecutive argument pairs
1868 ( arguments
.length
=== 2 || innerEquiv
.apply( this, [].slice
.call( arguments
, 1 ) ) );
1874 // Based on jsDump by Ariel Flesler
1875 // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
1876 QUnit
.dump
= ( function() {
1877 function quote( str
) {
1878 return "\"" + str
.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\"";
1880 function literal( o ) {
1883 function join( pre, arr, post ) {
1884 var s = dump.separator(),
1885 base = dump.indent(),
1886 inner = dump.indent( 1 );
1888 arr = arr.join( "," + s + inner );
1893 return [ pre, inner + arr, base + post ].join( s );
1895 function array( arr, stack ) {
1897 ret = new Array( i );
1899 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1900 return "[object Array
]";
1905 ret[ i ] = this.parse( arr[ i ], undefined, stack );
1908 return join( "[", ret, "]" );
1911 function isArray( obj ) {
1915 toString.call( obj ) === "[object Array
]" ||
1918 ( typeof obj.length === "number
" && obj.item !== undefined ) &&
1920 obj.item( 0 ) === obj[ 0 ] :
1921 ( obj.item( 0 ) === null && obj[ 0 ] === undefined )
1926 var reName = /^function (\w+)/,
1929 // The objType is used mostly internally, you can fix a (custom) type in advance
1930 parse: function( obj, objType, stack ) {
1931 stack = stack || [];
1932 var res, parser, parserType,
1933 inStack = inArray( obj, stack );
1935 if ( inStack !== -1 ) {
1936 return "recursion(" + ( inStack - stack.length ) + ")";
1939 objType = objType || this.typeOf( obj );
1940 parser = this.parsers[ objType ];
1941 parserType = typeof parser;
1943 if ( parserType === "function" ) {
1945 res = parser.call( this, obj, stack );
1949 return ( parserType === "string
" ) ? parser : this.parsers.error;
1951 typeOf: function( obj ) {
1954 if ( obj === null ) {
1956 } else if ( typeof obj === "undefined" ) {
1958 } else if ( QUnit.is( "regexp
", obj ) ) {
1960 } else if ( QUnit.is( "date
", obj ) ) {
1962 } else if ( QUnit.is( "function", obj ) ) {
1964 } else if ( obj.setInterval !== undefined &&
1965 obj.document !== undefined &&
1966 obj.nodeType === undefined ) {
1968 } else if ( obj.nodeType === 9 ) {
1970 } else if ( obj.nodeType ) {
1972 } else if ( isArray( obj ) ) {
1974 } else if ( obj.constructor === Error.prototype.constructor ) {
1982 separator: function() {
1983 return this.multiline ? this.HTML ? "<br
/>" : "\n" : this.HTML ? " " : " ";
1986 // Extra can be a number, shortcut for increasing-calling-decreasing
1987 indent: function( extra ) {
1988 if ( !this.multiline ) {
1991 var chr = this.indentChar;
1993 chr = chr.replace( /\t/g, " " ).replace( / /g, " " );
1995 return new Array( this.depth + ( extra || 0 ) ).join( chr );
1998 this.depth += a || 1;
2000 down: function( a ) {
2001 this.depth -= a || 1;
2003 setParser: function( name, parser ) {
2004 this.parsers[ name ] = parser;
2007 // The next 3 are exposed so you can use them
2012 maxDepth: QUnit.config.maxDepth,
2014 // This is the list of parsers, to modify them, use dump.setParser
2017 document: "[Document
]",
2018 error: function( error ) {
2019 return "Error(\"" + error.message + "\")";
2021 unknown: "[Unknown
]",
2023 "undefined": "undefined",
2024 "function": function( fn ) {
2025 var ret = "function",
2027 // Functions never have name in IE
2028 name = "name
" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
2035 ret = [ ret, dump.parse( fn, "functionArgs
" ), "){" ].join( "" );
2036 return join( ret, dump.parse( fn, "functionCode
" ), "}" );
2041 object: function( map, stack ) {
2042 var keys, key, val, i, nonEnumerableProperties,
2045 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
2046 return "[object Object
]";
2051 for ( key in map ) {
2055 // Some properties are not always enumerable on Error objects.
2056 nonEnumerableProperties = [ "message
", "name
" ];
2057 for ( i in nonEnumerableProperties ) {
2058 key = nonEnumerableProperties[ i ];
2059 if ( key in map && inArray( key, keys ) < 0 ) {
2064 for ( i = 0; i < keys.length; i++ ) {
2067 ret.push( dump.parse( key, "key
" ) + ": " +
2068 dump.parse( val, undefined, stack ) );
2071 return join( "{", ret, "}" );
2073 node: function( node ) {
2075 open = dump.HTML ? "<
;" : "<",
2076 close = dump.HTML ? ">
;" : ">",
2077 tag = node.nodeName.toLowerCase(),
2079 attrs = node.attributes;
2082 for ( i = 0, len = attrs.length; i < len; i++ ) {
2083 val = attrs[ i ].nodeValue;
2085 // IE6 includes all attributes in .attributes, even ones not explicitly
2086 // set. Those have values like undefined, null, 0, false, "" or
2088 if ( val && val !== "inherit
" ) {
2089 ret += " " + attrs[ i ].nodeName + "=" +
2090 dump.parse( val, "attribute
" );
2096 // Show content of TextNode or CDATASection
2097 if ( node.nodeType === 3 || node.nodeType === 4 ) {
2098 ret += node.nodeValue;
2101 return ret + open + "/" + tag + close;
2104 // Function calls it internally, it's the arguments part of the function
2105 functionArgs: function( fn ) {
2113 args = new Array( l );
2117 args[ l ] = String.fromCharCode( 97 + l );
2119 return " " + args.join( ", " ) + " ";
2122 // Object calls it internally, the key part of an item in a map
2125 // Function calls it internally, it's the content of the function
2126 functionCode: "[code
]",
2128 // Node calls it internally, it's a html attribute value
2135 symbol: function( sym ) {
2136 return sym.toString();
2140 // If true, entities are escaped ( <, >, \t, space and \n )
2146 // If true, items in a collection, are separated by a \n, else just a space.
2154 QUnit.jsDump = QUnit.dump;
2156 function applyDeprecated( name ) {
2159 name + " is removed
in QUnit
2.0.\n" +
2160 "Details
in our upgrade guide at https
://qunitjs.com/upgrade-guide-2.x/"
2165 Object
.keys( Assert
.prototype ).forEach( function( key
) {
2166 QUnit
[ key
] = applyDeprecated( "`QUnit." + key
+ "`" );
2169 QUnit
.asyncTest = function() {
2171 "asyncTest is removed in QUnit 2.0, use QUnit.test() with assert.async() instead.\n" +
2172 "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/"
2176 QUnit
.stop = function() {
2178 "QUnit.stop is removed in QUnit 2.0, use QUnit.test() with assert.async() instead.\n" +
2179 "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/"
2183 function resetThrower() {
2185 "QUnit.reset is removed in QUnit 2.0 without replacement.\n" +
2186 "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/"
2190 Object
.defineProperty( QUnit
, "reset", {
2192 return resetThrower
;
2197 if ( defined
.document
) {
2198 if ( window
.QUnit
) {
2199 throw new Error( "QUnit has already been defined." );
2219 ].forEach( function( key
) {
2220 window
[ key
] = applyDeprecated( "The global `" + key
+ "`" );
2223 window
.QUnit
= QUnit
;
2227 if ( typeof module
!== "undefined" && module
&& module
.exports
) {
2228 module
.exports
= QUnit
;
2230 // For consistency with CommonJS environments' exports
2231 module
.exports
.QUnit
= QUnit
;
2234 // For CommonJS with exports, but without module.exports, like Rhino
2235 if ( typeof exports
!== "undefined" && exports
) {
2236 exports
.QUnit
= QUnit
;
2239 if ( typeof define
=== "function" && define
.amd
) {
2240 define( function() {
2243 QUnit
.config
.autostart
= false;
2246 // Get a reference to the global object, like window in browsers
2253 if ( typeof window
=== "undefined" || !window
.document
) {
2257 var config
= QUnit
.config
,
2258 hasOwn
= Object
.prototype.hasOwnProperty
;
2260 // Stores fixture HTML for resetting later
2261 function storeFixture() {
2263 // Avoid overwriting user-defined values
2264 if ( hasOwn
.call( config
, "fixture" ) ) {
2268 var fixture
= document
.getElementById( "qunit-fixture" );
2270 config
.fixture
= fixture
.innerHTML
;
2274 QUnit
.begin( storeFixture
);
2276 // Resets the fixture DOM element if available.
2277 function resetFixture() {
2278 if ( config
.fixture
== null ) {
2282 var fixture
= document
.getElementById( "qunit-fixture" );
2284 fixture
.innerHTML
= config
.fixture
;
2288 QUnit
.testStart( resetFixture
);
2294 // Only interact with URLs via window.location
2295 var location
= typeof window
!== "undefined" && window
.location
;
2300 var urlParams
= getUrlParams();
2302 QUnit
.urlParams
= urlParams
;
2304 // Match module/test by inclusion in an array
2305 QUnit
.config
.moduleId
= [].concat( urlParams
.moduleId
|| [] );
2306 QUnit
.config
.testId
= [].concat( urlParams
.testId
|| [] );
2308 // Exact case-insensitive match of the module name
2309 QUnit
.config
.module
= urlParams
.module
;
2311 // Regular expression or case-insenstive substring match against "moduleName: testName"
2312 QUnit
.config
.filter
= urlParams
.filter
;
2314 // Test order randomization
2315 if ( urlParams
.seed
=== true ) {
2317 // Generate a random seed if the option is specified without a value
2318 QUnit
.config
.seed
= Math
.random().toString( 36 ).slice( 2 );
2319 } else if ( urlParams
.seed
) {
2320 QUnit
.config
.seed
= urlParams
.seed
;
2323 // Add URL-parameter-mapped config values with UI form rendering data
2324 QUnit
.config
.urlConfig
.push(
2327 label
: "Hide passed tests",
2328 tooltip
: "Only show tests and assertions that fail. Stored as query-strings."
2332 label
: "Check for Globals",
2333 tooltip
: "Enabling this will test if any test introduces new properties on the " +
2334 "global object (`window` in Browsers). Stored as query-strings."
2338 label
: "No try-catch",
2339 tooltip
: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
2340 "exceptions in IE reasonable. Stored as query-strings."
2344 QUnit
.begin( function() {
2346 urlConfig
= QUnit
.config
.urlConfig
;
2348 for ( i
= 0; i
< urlConfig
.length
; i
++ ) {
2350 // Options can be either strings or objects with nonempty "id" properties
2351 option
= QUnit
.config
.urlConfig
[ i
];
2352 if ( typeof option
!== "string" ) {
2356 if ( QUnit
.config
[ option
] === undefined ) {
2357 QUnit
.config
[ option
] = urlParams
[ option
];
2362 function getUrlParams() {
2363 var i
, param
, name
, value
;
2365 var params
= location
.search
.slice( 1 ).split( "&" );
2366 var length
= params
.length
;
2368 for ( i
= 0; i
< length
; i
++ ) {
2369 if ( params
[ i
] ) {
2370 param
= params
[ i
].split( "=" );
2371 name
= decodeQueryParam( param
[ 0 ] );
2373 // Allow just a key to turn on a flag, e.g., test.html?noglobals
2374 value
= param
.length
=== 1 ||
2375 decodeQueryParam( param
.slice( 1 ).join( "=" ) ) ;
2376 if ( urlParams
[ name
] ) {
2377 urlParams
[ name
] = [].concat( urlParams
[ name
], value
);
2379 urlParams
[ name
] = value
;
2387 function decodeQueryParam( param
) {
2388 return decodeURIComponent( param
.replace( /\+/g, "%20" ) );
2391 // Don't load the HTML Reporter on non-browser environments
2392 if ( typeof window
=== "undefined" || !window
.document
) {
2396 QUnit
.init = function() {
2398 "QUnit.init is removed in QUnit 2.0, use QUnit.test() with assert.async() instead.\n" +
2399 "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/"
2403 var config
= QUnit
.config
,
2404 document
= window
.document
,
2405 collapseNext
= false,
2406 hasOwn
= Object
.prototype.hasOwnProperty
,
2407 unfilteredUrl
= setUrl( { filter
: undefined, module
: undefined,
2408 moduleId
: undefined, testId
: undefined } ),
2410 sessionStorage
: ( function() {
2411 var x
= "qunit-test-string";
2413 sessionStorage
.setItem( x
, x
);
2414 sessionStorage
.removeItem( x
);
2423 // Escape text for attribute or text content.
2424 function escapeText( s
) {
2430 // Both single quotes and double quotes (for attributes)
2431 return s
.replace( /['"<>&]/g, function( s
) {
2447 function addEvent( elem
, type
, fn
) {
2448 elem
.addEventListener( type
, fn
, false );
2451 function removeEvent( elem
, type
, fn
) {
2452 elem
.removeEventListener( type
, fn
, false );
2455 function addEvents( elems
, type
, fn
) {
2456 var i
= elems
.length
;
2458 addEvent( elems
[ i
], type
, fn
);
2462 function hasClass( elem
, name
) {
2463 return ( " " + elem
.className
+ " " ).indexOf( " " + name
+ " " ) >= 0;
2466 function addClass( elem
, name
) {
2467 if ( !hasClass( elem
, name
) ) {
2468 elem
.className
+= ( elem
.className
? " " : "" ) + name
;
2472 function toggleClass( elem
, name
, force
) {
2473 if ( force
|| typeof force
=== "undefined" && !hasClass( elem
, name
) ) {
2474 addClass( elem
, name
);
2476 removeClass( elem
, name
);
2480 function removeClass( elem
, name
) {
2481 var set = " " + elem
.className
+ " ";
2483 // Class name may appear multiple times
2484 while ( set.indexOf( " " + name
+ " " ) >= 0 ) {
2485 set = set.replace( " " + name
+ " ", " " );
2488 // Trim for prettiness
2489 elem
.className
= typeof set.trim
=== "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
2492 function id( name
) {
2493 return document
.getElementById
&& document
.getElementById( name
);
2496 function interceptNavigation( ev
) {
2499 if ( ev
&& ev
.preventDefault
) {
2500 ev
.preventDefault();
2506 function getUrlConfigHtml() {
2508 escaped
, escapedTooltip
,
2510 urlConfig
= config
.urlConfig
,
2513 for ( i
= 0; i
< urlConfig
.length
; i
++ ) {
2515 // Options can be either strings or objects with nonempty "id" properties
2516 val
= config
.urlConfig
[ i
];
2517 if ( typeof val
=== "string" ) {
2524 escaped
= escapeText( val
.id
);
2525 escapedTooltip
= escapeText( val
.tooltip
);
2527 if ( !val
.value
|| typeof val
.value
=== "string" ) {
2528 urlConfigHtml
+= "<label for='qunit-urlconfig-" + escaped
+
2529 "' title='" + escapedTooltip
+ "'><input id='qunit-urlconfig-" + escaped
+
2530 "' name='" + escaped
+ "' type='checkbox'" +
2531 ( val
.value
? " value='" + escapeText( val
.value
) + "'" : "" ) +
2532 ( config
[ val
.id
] ? " checked='checked'" : "" ) +
2533 " title='" + escapedTooltip
+ "' />" + escapeText( val
.label
) + "</label>";
2535 urlConfigHtml
+= "<label for='qunit-urlconfig-" + escaped
+
2536 "' title='" + escapedTooltip
+ "'>" + val
.label
+
2537 ": </label><select id='qunit-urlconfig-" + escaped
+
2538 "' name='" + escaped
+ "' title='" + escapedTooltip
+ "'><option></option>";
2540 if ( QUnit
.is( "array", val
.value
) ) {
2541 for ( j
= 0; j
< val
.value
.length
; j
++ ) {
2542 escaped
= escapeText( val
.value
[ j
] );
2543 urlConfigHtml
+= "<option value='" + escaped
+ "'" +
2544 ( config
[ val
.id
] === val
.value
[ j
] ?
2545 ( selection
= true ) && " selected='selected'" : "" ) +
2546 ">" + escaped
+ "</option>";
2549 for ( j
in val
.value
) {
2550 if ( hasOwn
.call( val
.value
, j
) ) {
2551 urlConfigHtml
+= "<option value='" + escapeText( j
) + "'" +
2552 ( config
[ val
.id
] === j
?
2553 ( selection
= true ) && " selected='selected'" : "" ) +
2554 ">" + escapeText( val
.value
[ j
] ) + "</option>";
2558 if ( config
[ val
.id
] && !selection
) {
2559 escaped
= escapeText( config
[ val
.id
] );
2560 urlConfigHtml
+= "<option value='" + escaped
+
2561 "' selected='selected' disabled='disabled'>" + escaped
+ "</option>";
2563 urlConfigHtml
+= "</select>";
2567 return urlConfigHtml
;
2570 // Handle "click" events on toolbar checkboxes and "change" for select menus.
2571 // Updates the URL with the new state of `config.urlConfig` values.
2572 function toolbarChanged() {
2573 var updatedUrl
, value
, tests
,
2577 // Detect if field is a select menu or a checkbox
2578 if ( "selectedIndex" in field
) {
2579 value
= field
.options
[ field
.selectedIndex
].value
|| undefined;
2581 value
= field
.checked
? ( field
.defaultValue
|| true ) : undefined;
2584 params
[ field
.name
] = value
;
2585 updatedUrl
= setUrl( params
);
2587 // Check if we can apply the change without a page refresh
2588 if ( "hidepassed" === field
.name
&& "replaceState" in window
.history
) {
2589 QUnit
.urlParams
[ field
.name
] = value
;
2590 config
[ field
.name
] = value
|| false;
2591 tests
= id( "qunit-tests" );
2593 toggleClass( tests
, "hidepass", value
|| false );
2595 window
.history
.replaceState( null, "", updatedUrl
);
2597 window
.location
= updatedUrl
;
2601 function setUrl( params
) {
2602 var key
, arrValue
, i
,
2604 location
= window
.location
;
2606 params
= QUnit
.extend( QUnit
.extend( {}, QUnit
.urlParams
), params
);
2608 for ( key
in params
) {
2610 // Skip inherited or undefined properties
2611 if ( hasOwn
.call( params
, key
) && params
[ key
] !== undefined ) {
2613 // Output a parameter for each value of this key (but usually just one)
2614 arrValue
= [].concat( params
[ key
] );
2615 for ( i
= 0; i
< arrValue
.length
; i
++ ) {
2616 querystring
+= encodeURIComponent( key
);
2617 if ( arrValue
[ i
] !== true ) {
2618 querystring
+= "=" + encodeURIComponent( arrValue
[ i
] );
2624 return location
.protocol
+ "//" + location
.host
+
2625 location
.pathname
+ querystring
.slice( 0, -1 );
2628 function applyUrlParams() {
2630 selectedModules
= [],
2631 modulesList
= id( "qunit-modulefilter-dropdown-list" ).getElementsByTagName( "input" ),
2632 filter
= id( "qunit-filter-input" ).value
;
2634 for ( i
= 0; i
< modulesList
.length
; i
++ ) {
2635 if ( modulesList
[ i
].checked
) {
2636 selectedModules
.push( modulesList
[ i
].value
);
2640 window
.location
= setUrl( {
2641 filter
: ( filter
=== "" ) ? undefined : filter
,
2642 moduleId
: ( selectedModules
.length
=== 0 ) ? undefined : selectedModules
,
2644 // Remove module and testId filter
2650 function toolbarUrlConfigContainer() {
2651 var urlConfigContainer
= document
.createElement( "span" );
2653 urlConfigContainer
.innerHTML
= getUrlConfigHtml();
2654 addClass( urlConfigContainer
, "qunit-url-config" );
2656 addEvents( urlConfigContainer
.getElementsByTagName( "input" ), "change", toolbarChanged
);
2657 addEvents( urlConfigContainer
.getElementsByTagName( "select" ), "change", toolbarChanged
);
2659 return urlConfigContainer
;
2662 function toolbarLooseFilter() {
2663 var filter
= document
.createElement( "form" ),
2664 label
= document
.createElement( "label" ),
2665 input
= document
.createElement( "input" ),
2666 button
= document
.createElement( "button" );
2668 addClass( filter
, "qunit-filter" );
2670 label
.innerHTML
= "Filter: ";
2672 input
.type
= "text";
2673 input
.value
= config
.filter
|| "";
2674 input
.name
= "filter";
2675 input
.id
= "qunit-filter-input";
2677 button
.innerHTML
= "Go";
2679 label
.appendChild( input
);
2681 filter
.appendChild( label
);
2682 filter
.appendChild( document
.createTextNode( " " ) );
2683 filter
.appendChild( button
);
2684 addEvent( filter
, "submit", interceptNavigation
);
2689 function moduleListHtml () {
2693 for ( i
= 0; i
< config
.modules
.length
; i
++ ) {
2694 if ( config
.modules
[ i
].name
!== "" ) {
2695 checked
= config
.moduleId
.indexOf( config
.modules
[ i
].moduleId
) > -1;
2696 html
+= "<li><label class='clickable" + ( checked
? " checked" : "" ) +
2697 "'><input type='checkbox' " + "value='" + config
.modules
[ i
].moduleId
+ "'" +
2698 ( checked
? " checked='checked'" : "" ) + " />" +
2699 escapeText( config
.modules
[ i
].name
) + "</label></li>";
2706 function toolbarModuleFilter () {
2707 var allCheckbox
, commit
, reset
,
2708 moduleFilter
= document
.createElement( "form" ),
2709 label
= document
.createElement( "label" ),
2710 moduleSearch
= document
.createElement( "input" ),
2711 dropDown
= document
.createElement( "div" ),
2712 actions
= document
.createElement( "span" ),
2713 dropDownList
= document
.createElement( "ul" ),
2716 moduleSearch
.id
= "qunit-modulefilter-search";
2717 addEvent( moduleSearch
, "input", searchInput
);
2718 addEvent( moduleSearch
, "input", searchFocus
);
2719 addEvent( moduleSearch
, "focus", searchFocus
);
2720 addEvent( moduleSearch
, "click", searchFocus
);
2722 label
.id
= "qunit-modulefilter-search-container";
2723 label
.innerHTML
= "Module: ";
2724 label
.appendChild( moduleSearch
);
2726 actions
.id
= "qunit-modulefilter-actions";
2728 "<button style='display:none'>Apply</button>" +
2729 "<button type='reset' style='display:none'>Reset</button>" +
2730 "<label class='clickable" +
2731 ( config
.moduleId
.length
? "" : " checked" ) +
2732 "'><input type='checkbox'" + ( config
.moduleId
.length
? "" : " checked='checked'" ) +
2733 ">All modules</label>";
2734 allCheckbox
= actions
.lastChild
.firstChild
;
2735 commit
= actions
.firstChild
;
2736 reset
= commit
.nextSibling
;
2737 addEvent( commit
, "click", applyUrlParams
);
2739 dropDownList
.id
= "qunit-modulefilter-dropdown-list";
2740 dropDownList
.innerHTML
= moduleListHtml();
2742 dropDown
.id
= "qunit-modulefilter-dropdown";
2743 dropDown
.style
.display
= "none";
2744 dropDown
.appendChild( actions
);
2745 dropDown
.appendChild( dropDownList
);
2746 addEvent( dropDown
, "change", selectionChange
);
2749 moduleFilter
.id
= "qunit-modulefilter";
2750 moduleFilter
.appendChild( label
);
2751 moduleFilter
.appendChild( dropDown
) ;
2752 addEvent( moduleFilter
, "submit", interceptNavigation
);
2753 addEvent( moduleFilter
, "reset", function() {
2755 // Let the reset happen, then update styles
2756 window
.setTimeout( selectionChange
);
2759 // Enables show/hide for the dropdown
2760 function searchFocus() {
2761 if ( dropDown
.style
.display
!== "none" ) {
2765 dropDown
.style
.display
= "block";
2766 addEvent( document
, "click", hideHandler
);
2767 addEvent( document
, "keydown", hideHandler
);
2769 // Hide on Escape keydown or outside-container click
2770 function hideHandler( e
) {
2771 var inContainer
= moduleFilter
.contains( e
.target
);
2773 if ( e
.keyCode
=== 27 || !inContainer
) {
2774 if ( e
.keyCode
=== 27 && inContainer
) {
2775 moduleSearch
.focus();
2777 dropDown
.style
.display
= "none";
2778 removeEvent( document
, "click", hideHandler
);
2779 removeEvent( document
, "keydown", hideHandler
);
2780 moduleSearch
.value
= "";
2786 // Processes module search box input
2787 function searchInput() {
2789 searchText
= moduleSearch
.value
.toLowerCase(),
2790 listItems
= dropDownList
.children
;
2792 for ( i
= 0; i
< listItems
.length
; i
++ ) {
2793 item
= listItems
[ i
];
2794 if ( !searchText
|| item
.textContent
.toLowerCase().indexOf( searchText
) > -1 ) {
2795 item
.style
.display
= "";
2797 item
.style
.display
= "none";
2802 // Processes selection changes
2803 function selectionChange( evt
) {
2805 checkbox
= evt
&& evt
.target
|| allCheckbox
,
2806 modulesList
= dropDownList
.getElementsByTagName( "input" ),
2809 toggleClass( checkbox
.parentNode
, "checked", checkbox
.checked
);
2812 if ( checkbox
.checked
&& checkbox
!== allCheckbox
) {
2813 allCheckbox
.checked
= false;
2814 removeClass( allCheckbox
.parentNode
, "checked" );
2816 for ( i
= 0; i
< modulesList
.length
; i
++ ) {
2817 item
= modulesList
[ i
];
2819 toggleClass( item
.parentNode
, "checked", item
.checked
);
2820 } else if ( checkbox
=== allCheckbox
&& checkbox
.checked
) {
2821 item
.checked
= false;
2822 removeClass( item
.parentNode
, "checked" );
2824 dirty
= dirty
|| ( item
.checked
!== item
.defaultChecked
);
2825 if ( item
.checked
) {
2826 selectedNames
.push( item
.parentNode
.textContent
);
2830 commit
.style
.display
= reset
.style
.display
= dirty
? "" : "none";
2831 moduleSearch
.placeholder
= selectedNames
.join( ", " ) || allCheckbox
.parentNode
.textContent
;
2832 moduleSearch
.title
= "Type to filter list. Current selection:\n" +
2833 ( selectedNames
.join( "\n" ) || allCheckbox
.parentNode
.textContent
);
2836 return moduleFilter
;
2839 function appendToolbar() {
2840 var toolbar
= id( "qunit-testrunner-toolbar" );
2843 toolbar
.appendChild( toolbarUrlConfigContainer() );
2844 toolbar
.appendChild( toolbarModuleFilter() );
2845 toolbar
.appendChild( toolbarLooseFilter() );
2846 toolbar
.appendChild( document
.createElement( "div" ) ).className
= "clearfix";
2850 function appendHeader() {
2851 var header
= id( "qunit-header" );
2854 header
.innerHTML
= "<a href='" + escapeText( unfilteredUrl
) + "'>" + header
.innerHTML
+
2859 function appendBanner() {
2860 var banner
= id( "qunit-banner" );
2863 banner
.className
= "";
2867 function appendTestResults() {
2868 var tests
= id( "qunit-tests" ),
2869 result
= id( "qunit-testresult" );
2872 result
.parentNode
.removeChild( result
);
2876 tests
.innerHTML
= "";
2877 result
= document
.createElement( "p" );
2878 result
.id
= "qunit-testresult";
2879 result
.className
= "result";
2880 tests
.parentNode
.insertBefore( result
, tests
);
2881 result
.innerHTML
= "Running...<br /> ";
2885 function appendFilteredTest() {
2886 var testId
= QUnit
.config
.testId
;
2887 if ( !testId
|| testId
.length
<= 0 ) {
2890 return "<div id='qunit-filteredTest'>Rerunning selected tests: " +
2891 escapeText( testId
.join( ", " ) ) +
2892 " <a id='qunit-clearFilter' href='" +
2893 escapeText( unfilteredUrl
) +
2894 "'>Run all tests</a></div>";
2897 function appendUserAgent() {
2898 var userAgent
= id( "qunit-userAgent" );
2901 userAgent
.innerHTML
= "";
2902 userAgent
.appendChild(
2903 document
.createTextNode(
2904 "QUnit " + QUnit
.version
+ "; " + navigator
.userAgent
2910 function appendInterface() {
2911 var qunit
= id( "qunit" );
2915 "<h1 id='qunit-header'>" + escapeText( document
.title
) + "</h1>" +
2916 "<h2 id='qunit-banner'></h2>" +
2917 "<div id='qunit-testrunner-toolbar'></div>" +
2918 appendFilteredTest() +
2919 "<h2 id='qunit-userAgent'></h2>" +
2920 "<ol id='qunit-tests'></ol>";
2925 appendTestResults();
2930 function appendTestsList( modules
) {
2931 var i
, l
, x
, z
, test
, moduleObj
;
2933 for ( i
= 0, l
= modules
.length
; i
< l
; i
++ ) {
2934 moduleObj
= modules
[ i
];
2936 for ( x
= 0, z
= moduleObj
.tests
.length
; x
< z
; x
++ ) {
2937 test
= moduleObj
.tests
[ x
];
2939 appendTest( test
.name
, test
.testId
, moduleObj
.name
);
2944 function appendTest( name
, testId
, moduleName
) {
2945 var title
, rerunTrigger
, testBlock
, assertList
,
2946 tests
= id( "qunit-tests" );
2952 title
= document
.createElement( "strong" );
2953 title
.innerHTML
= getNameHtml( name
, moduleName
);
2955 rerunTrigger
= document
.createElement( "a" );
2956 rerunTrigger
.innerHTML
= "Rerun";
2957 rerunTrigger
.href
= setUrl( { testId
: testId
} );
2959 testBlock
= document
.createElement( "li" );
2960 testBlock
.appendChild( title
);
2961 testBlock
.appendChild( rerunTrigger
);
2962 testBlock
.id
= "qunit-test-output-" + testId
;
2964 assertList
= document
.createElement( "ol" );
2965 assertList
.className
= "qunit-assert-list";
2967 testBlock
.appendChild( assertList
);
2969 tests
.appendChild( testBlock
);
2972 // HTML Reporter initialization and load
2973 QUnit
.begin( function( details
) {
2974 var i
, moduleObj
, tests
;
2976 // Sort modules by name for the picker
2977 for ( i
= 0; i
< details
.modules
.length
; i
++ ) {
2978 moduleObj
= details
.modules
[ i
];
2979 if ( moduleObj
.name
) {
2980 modulesList
.push( moduleObj
.name
);
2983 modulesList
.sort( function( a
, b
) {
2984 return a
.localeCompare( b
);
2987 // Initialize QUnit elements
2989 appendTestsList( details
.modules
);
2990 tests
= id( "qunit-tests" );
2991 if ( tests
&& config
.hidepassed
) {
2992 addClass( tests
, "hidepass" );
2996 QUnit
.done( function( details
) {
2998 banner
= id( "qunit-banner" ),
2999 tests
= id( "qunit-tests" ),
3001 "Tests completed in ",
3003 " milliseconds.<br />",
3004 "<span class='passed'>",
3006 "</span> assertions of <span class='total'>",
3008 "</span> passed, <span class='failed'>",
3014 banner
.className
= details
.failed
? "qunit-fail" : "qunit-pass";
3018 id( "qunit-testresult" ).innerHTML
= html
;
3021 if ( config
.altertitle
&& document
.title
) {
3023 // Show ✖ for good, ✔ for bad suite result in title
3024 // use escape sequences in case file gets loaded with non-utf-8-charset
3026 ( details
.failed
? "\u2716" : "\u2714" ),
3027 document
.title
.replace( /^[\u2714\u2716] /i, "" )
3031 // Clear own sessionStorage items if all tests passed
3032 if ( config
.reorder
&& defined
.sessionStorage
&& details
.failed
=== 0 ) {
3033 for ( i
= 0; i
< sessionStorage
.length
; i
++ ) {
3034 key
= sessionStorage
.key( i
++ );
3035 if ( key
.indexOf( "qunit-test-" ) === 0 ) {
3036 sessionStorage
.removeItem( key
);
3041 // Scroll back to top to show results
3042 if ( config
.scrolltop
&& window
.scrollTo
) {
3043 window
.scrollTo( 0, 0 );
3047 function getNameHtml( name
, module
) {
3051 nameHtml
= "<span class='module-name'>" + escapeText( module
) + "</span>: ";
3054 nameHtml
+= "<span class='test-name'>" + escapeText( name
) + "</span>";
3059 QUnit
.testStart( function( details
) {
3060 var running
, testBlock
, bad
;
3062 testBlock
= id( "qunit-test-output-" + details
.testId
);
3064 testBlock
.className
= "running";
3067 // Report later registered tests
3068 appendTest( details
.name
, details
.testId
, details
.module
);
3071 running
= id( "qunit-testresult" );
3073 bad
= QUnit
.config
.reorder
&& defined
.sessionStorage
&&
3074 +sessionStorage
.getItem( "qunit-test-" + details
.module
+ "-" + details
.name
);
3076 running
.innerHTML
= ( bad
?
3077 "Rerunning previously failed test: <br />" :
3078 "Running: <br />" ) +
3079 getNameHtml( details
.name
, details
.module
);
3084 function stripHtml( string
) {
3086 // Strip tags, html entity and whitespaces
3087 return string
.replace( /<\/?[^>]+(>|$)/g, "" ).replace( /\"/g, "" ).replace( /\s+/g, "" );
3090 QUnit
.log( function( details
) {
3091 var assertList
, assertLi
,
3092 message
, expected
, actual
, diff
,
3094 testItem
= id( "qunit-test-output-" + details
.testId
);
3100 message
= escapeText( details
.message
) || ( details
.result
? "okay" : "failed" );
3101 message
= "<span class='test-message'>" + message
+ "</span>";
3102 message
+= "<span class='runtime'>@ " + details
.runtime
+ " ms</span>";
3104 // The pushFailure doesn't provide details.expected
3105 // when it calls, it's implicit to also not show expected and diff stuff
3106 // Also, we need to check details.expected existence, as it can exist and be undefined
3107 if ( !details
.result
&& hasOwn
.call( details
, "expected" ) ) {
3108 if ( details
.negative
) {
3109 expected
= "NOT " + QUnit
.dump
.parse( details
.expected
);
3111 expected
= QUnit
.dump
.parse( details
.expected
);
3114 actual
= QUnit
.dump
.parse( details
.actual
);
3115 message
+= "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
3116 escapeText( expected
) +
3119 if ( actual
!== expected
) {
3121 message
+= "<tr class='test-actual'><th>Result: </th><td><pre>" +
3122 escapeText( actual
) + "</pre></td></tr>";
3124 // Don't show diff if actual or expected are booleans
3125 if ( !( /^(true|false)$/.test( actual
) ) &&
3126 !( /^(true|false)$/.test( expected
) ) ) {
3127 diff
= QUnit
.diff( expected
, actual
);
3128 showDiff
= stripHtml( diff
).length
!==
3129 stripHtml( expected
).length
+
3130 stripHtml( actual
).length
;
3133 // Don't show diff if expected and actual are totally different
3135 message
+= "<tr class='test-diff'><th>Diff: </th><td><pre>" +
3136 diff
+ "</pre></td></tr>";
3138 } else if ( expected
.indexOf( "[object Array]" ) !== -1 ||
3139 expected
.indexOf( "[object Object]" ) !== -1 ) {
3140 message
+= "<tr class='test-message'><th>Message: </th><td>" +
3141 "Diff suppressed as the depth of object is more than current max depth (" +
3142 QUnit
.config
.maxDepth
+ ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " +
3143 " run with a higher max depth or <a href='" +
3144 escapeText( setUrl( { maxDepth
: -1 } ) ) + "'>" +
3145 "Rerun</a> without max depth.</p></td></tr>";
3147 message
+= "<tr class='test-message'><th>Message: </th><td>" +
3148 "Diff suppressed as the expected and actual results have an equivalent" +
3149 " serialization</td></tr>";
3152 if ( details
.source
) {
3153 message
+= "<tr class='test-source'><th>Source: </th><td><pre>" +
3154 escapeText( details
.source
) + "</pre></td></tr>";
3157 message
+= "</table>";
3159 // This occurs when pushFailure is set and we have an extracted stack trace
3160 } else if ( !details
.result
&& details
.source
) {
3161 message
+= "<table>" +
3162 "<tr class='test-source'><th>Source: </th><td><pre>" +
3163 escapeText( details
.source
) + "</pre></td></tr>" +
3167 assertList
= testItem
.getElementsByTagName( "ol" )[ 0 ];
3169 assertLi
= document
.createElement( "li" );
3170 assertLi
.className
= details
.result
? "pass" : "fail";
3171 assertLi
.innerHTML
= message
;
3172 assertList
.appendChild( assertLi
);
3175 QUnit
.testDone( function( details
) {
3176 var testTitle
, time
, testItem
, assertList
,
3177 good
, bad
, testCounts
, skipped
, sourceName
,
3178 tests
= id( "qunit-tests" );
3184 testItem
= id( "qunit-test-output-" + details
.testId
);
3186 assertList
= testItem
.getElementsByTagName( "ol" )[ 0 ];
3188 good
= details
.passed
;
3189 bad
= details
.failed
;
3191 // Store result when possible
3192 if ( config
.reorder
&& defined
.sessionStorage
) {
3194 sessionStorage
.setItem( "qunit-test-" + details
.module
+ "-" + details
.name
, bad
);
3196 sessionStorage
.removeItem( "qunit-test-" + details
.module
+ "-" + details
.name
);
3202 // Collapse the passing tests
3203 addClass( assertList
, "qunit-collapsed" );
3204 } else if ( bad
&& config
.collapse
&& !collapseNext
) {
3206 // Skip collapsing the first failing test
3207 collapseNext
= true;
3210 // Collapse remaining tests
3211 addClass( assertList
, "qunit-collapsed" );
3214 // The testItem.firstChild is the test name
3215 testTitle
= testItem
.firstChild
;
3218 "<b class='failed'>" + bad
+ "</b>, " + "<b class='passed'>" + good
+ "</b>, " :
3221 testTitle
.innerHTML
+= " <b class='counts'>(" + testCounts
+
3222 details
.assertions
.length
+ ")</b>";
3224 if ( details
.skipped
) {
3225 testItem
.className
= "skipped";
3226 skipped
= document
.createElement( "em" );
3227 skipped
.className
= "qunit-skipped-label";
3228 skipped
.innerHTML
= "skipped";
3229 testItem
.insertBefore( skipped
, testTitle
);
3231 addEvent( testTitle
, "click", function() {
3232 toggleClass( assertList
, "qunit-collapsed" );
3235 testItem
.className
= bad
? "fail" : "pass";
3237 time
= document
.createElement( "span" );
3238 time
.className
= "runtime";
3239 time
.innerHTML
= details
.runtime
+ " ms";
3240 testItem
.insertBefore( time
, assertList
);
3243 // Show the source of the test when showing assertions
3244 if ( details
.source
) {
3245 sourceName
= document
.createElement( "p" );
3246 sourceName
.innerHTML
= "<strong>Source: </strong>" + details
.source
;
3247 addClass( sourceName
, "qunit-source" );
3249 addClass( sourceName
, "qunit-collapsed" );
3251 addEvent( testTitle
, "click", function() {
3252 toggleClass( sourceName
, "qunit-collapsed" );
3254 testItem
.appendChild( sourceName
);
3258 // Avoid readyState issue with phantomjs
3260 var notPhantom
= ( function( p
) {
3261 return !( p
&& p
.version
&& p
.version
.major
> 0 );
3262 } )( window
.phantom
);
3264 if ( notPhantom
&& document
.readyState
=== "complete" ) {
3267 addEvent( window
, "load", QUnit
.load
);
3271 * This file is a modified version of google-diff-match-patch's JavaScript implementation
3272 * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
3273 * modifications are licensed as more fully set forth in LICENSE.txt.
3275 * The original source of google-diff-match-patch is attributable and licensed as follows:
3277 * Copyright 2006 Google Inc.
3278 * https://code.google.com/p/google-diff-match-patch/
3280 * Licensed under the Apache License, Version 2.0 (the "License");
3281 * you may not use this file except in compliance with the License.
3282 * You may obtain a copy of the License at
3284 * https://www.apache.org/licenses/LICENSE-2.0
3286 * Unless required by applicable law or agreed to in writing, software
3287 * distributed under the License is distributed on an "AS IS" BASIS,
3288 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3289 * See the License for the specific language governing permissions and
3290 * limitations under the License.
3293 * https://code.google.com/p/google-diff-match-patch/
3295 * Usage: QUnit.diff(expected, actual)
3298 QUnit
.diff
= ( function() {
3299 function DiffMatchPatch() {
3305 * The data structure representing a diff is an array of tuples:
3306 * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
3307 * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
3309 var DIFF_DELETE
= -1,
3314 * Find the differences between two texts. Simplifies the problem by stripping
3315 * any common prefix or suffix off the texts before diffing.
3316 * @param {string} text1 Old string to be diffed.
3317 * @param {string} text2 New string to be diffed.
3318 * @param {boolean=} optChecklines Optional speedup flag. If present and false,
3319 * then don't run a line-level diff first to identify the changed areas.
3320 * Defaults to true, which does a faster, slightly less optimal diff.
3321 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
3323 DiffMatchPatch
.prototype.DiffMain = function( text1
, text2
, optChecklines
) {
3324 var deadline
, checklines
, commonlength
,
3325 commonprefix
, commonsuffix
, diffs
;
3327 // The diff must be complete in up to 1 second.
3328 deadline
= ( new Date() ).getTime() + 1000;
3330 // Check for null inputs.
3331 if ( text1
=== null || text2
=== null ) {
3332 throw new Error( "Null input. (DiffMain)" );
3335 // Check for equality (speedup).
3336 if ( text1
=== text2
) {
3339 [ DIFF_EQUAL
, text1
]
3345 if ( typeof optChecklines
=== "undefined" ) {
3346 optChecklines
= true;
3349 checklines
= optChecklines
;
3351 // Trim off common prefix (speedup).
3352 commonlength
= this.diffCommonPrefix( text1
, text2
);
3353 commonprefix
= text1
.substring( 0, commonlength
);
3354 text1
= text1
.substring( commonlength
);
3355 text2
= text2
.substring( commonlength
);
3357 // Trim off common suffix (speedup).
3358 commonlength
= this.diffCommonSuffix( text1
, text2
);
3359 commonsuffix
= text1
.substring( text1
.length
- commonlength
);
3360 text1
= text1
.substring( 0, text1
.length
- commonlength
);
3361 text2
= text2
.substring( 0, text2
.length
- commonlength
);
3363 // Compute the diff on the middle block.
3364 diffs
= this.diffCompute( text1
, text2
, checklines
, deadline
);
3366 // Restore the prefix and suffix.
3367 if ( commonprefix
) {
3368 diffs
.unshift( [ DIFF_EQUAL
, commonprefix
] );
3370 if ( commonsuffix
) {
3371 diffs
.push( [ DIFF_EQUAL
, commonsuffix
] );
3373 this.diffCleanupMerge( diffs
);
3378 * Reduce the number of edits by eliminating operationally trivial equalities.
3379 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
3381 DiffMatchPatch
.prototype.diffCleanupEfficiency = function( diffs
) {
3382 var changes
, equalities
, equalitiesLength
, lastequality
,
3383 pointer
, preIns
, preDel
, postIns
, postDel
;
3385 equalities
= []; // Stack of indices where equalities are found.
3386 equalitiesLength
= 0; // Keeping our own length var is faster in JS.
3387 /** @type {?string} */
3388 lastequality
= null;
3390 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
3391 pointer
= 0; // Index of current position.
3393 // Is there an insertion operation before the last equality.
3396 // Is there a deletion operation before the last equality.
3399 // Is there an insertion operation after the last equality.
3402 // Is there a deletion operation after the last equality.
3404 while ( pointer
< diffs
.length
) {
3407 if ( diffs
[ pointer
][ 0 ] === DIFF_EQUAL
) {
3408 if ( diffs
[ pointer
][ 1 ].length
< 4 && ( postIns
|| postDel
) ) {
3411 equalities
[ equalitiesLength
++ ] = pointer
;
3414 lastequality
= diffs
[ pointer
][ 1 ];
3417 // Not a candidate, and can never become one.
3418 equalitiesLength
= 0;
3419 lastequality
= null;
3421 postIns
= postDel
= false;
3423 // An insertion or deletion.
3426 if ( diffs
[ pointer
][ 0 ] === DIFF_DELETE
) {
3433 * Five types to be split:
3434 * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
3435 * <ins>A</ins>X<ins>C</ins><del>D</del>
3436 * <ins>A</ins><del>B</del>X<ins>C</ins>
3437 * <ins>A</del>X<ins>C</ins><del>D</del>
3438 * <ins>A</ins><del>B</del>X<del>C</del>
3440 if ( lastequality
&& ( ( preIns
&& preDel
&& postIns
&& postDel
) ||
3441 ( ( lastequality
.length
< 2 ) &&
3442 ( preIns
+ preDel
+ postIns
+ postDel
) === 3 ) ) ) {
3444 // Duplicate record.
3446 equalities
[ equalitiesLength
- 1 ],
3448 [ DIFF_DELETE
, lastequality
]
3451 // Change second copy to insert.
3452 diffs
[ equalities
[ equalitiesLength
- 1 ] + 1 ][ 0 ] = DIFF_INSERT
;
3453 equalitiesLength
--; // Throw away the equality we just deleted;
3454 lastequality
= null;
3455 if ( preIns
&& preDel
) {
3457 // No changes made which could affect previous entry, keep going.
3458 postIns
= postDel
= true;
3459 equalitiesLength
= 0;
3461 equalitiesLength
--; // Throw away the previous equality.
3462 pointer
= equalitiesLength
> 0 ? equalities
[ equalitiesLength
- 1 ] : -1;
3463 postIns
= postDel
= false;
3472 this.diffCleanupMerge( diffs
);
3477 * Convert a diff array into a pretty HTML report.
3478 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
3479 * @param {integer} string to be beautified.
3480 * @return {string} HTML representation.
3482 DiffMatchPatch
.prototype.diffPrettyHtml = function( diffs
) {
3485 for ( x
= 0; x
< diffs
.length
; x
++ ) {
3486 op
= diffs
[ x
][ 0 ]; // Operation (insert, delete, equal)
3487 data
= diffs
[ x
][ 1 ]; // Text of change.
3490 html
[ x
] = "<ins>" + escapeText( data
) + "</ins>";
3493 html
[ x
] = "<del>" + escapeText( data
) + "</del>";
3496 html
[ x
] = "<span>" + escapeText( data
) + "</span>";
3500 return html
.join( "" );
3504 * Determine the common prefix of two strings.
3505 * @param {string} text1 First string.
3506 * @param {string} text2 Second string.
3507 * @return {number} The number of characters common to the start of each
3510 DiffMatchPatch
.prototype.diffCommonPrefix = function( text1
, text2
) {
3511 var pointermid
, pointermax
, pointermin
, pointerstart
;
3513 // Quick check for common null cases.
3514 if ( !text1
|| !text2
|| text1
.charAt( 0 ) !== text2
.charAt( 0 ) ) {
3519 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
3521 pointermax
= Math
.min( text1
.length
, text2
.length
);
3522 pointermid
= pointermax
;
3524 while ( pointermin
< pointermid
) {
3525 if ( text1
.substring( pointerstart
, pointermid
) ===
3526 text2
.substring( pointerstart
, pointermid
) ) {
3527 pointermin
= pointermid
;
3528 pointerstart
= pointermin
;
3530 pointermax
= pointermid
;
3532 pointermid
= Math
.floor( ( pointermax
- pointermin
) / 2 + pointermin
);
3538 * Determine the common suffix of two strings.
3539 * @param {string} text1 First string.
3540 * @param {string} text2 Second string.
3541 * @return {number} The number of characters common to the end of each string.
3543 DiffMatchPatch
.prototype.diffCommonSuffix = function( text1
, text2
) {
3544 var pointermid
, pointermax
, pointermin
, pointerend
;
3546 // Quick check for common null cases.
3549 text1
.charAt( text1
.length
- 1 ) !== text2
.charAt( text2
.length
- 1 ) ) {
3554 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
3556 pointermax
= Math
.min( text1
.length
, text2
.length
);
3557 pointermid
= pointermax
;
3559 while ( pointermin
< pointermid
) {
3560 if ( text1
.substring( text1
.length
- pointermid
, text1
.length
- pointerend
) ===
3561 text2
.substring( text2
.length
- pointermid
, text2
.length
- pointerend
) ) {
3562 pointermin
= pointermid
;
3563 pointerend
= pointermin
;
3565 pointermax
= pointermid
;
3567 pointermid
= Math
.floor( ( pointermax
- pointermin
) / 2 + pointermin
);
3573 * Find the differences between two texts. Assumes that the texts do not
3574 * have any common prefix or suffix.
3575 * @param {string} text1 Old string to be diffed.
3576 * @param {string} text2 New string to be diffed.
3577 * @param {boolean} checklines Speedup flag. If false, then don't run a
3578 * line-level diff first to identify the changed areas.
3579 * If true, then run a faster, slightly less optimal diff.
3580 * @param {number} deadline Time when the diff should be complete by.
3581 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
3584 DiffMatchPatch
.prototype.diffCompute = function( text1
, text2
, checklines
, deadline
) {
3585 var diffs
, longtext
, shorttext
, i
, hm
,
3586 text1A
, text2A
, text1B
, text2B
,
3587 midCommon
, diffsA
, diffsB
;
3591 // Just add some text (speedup).
3593 [ DIFF_INSERT
, text2
]
3599 // Just delete some text (speedup).
3601 [ DIFF_DELETE
, text1
]
3605 longtext
= text1
.length
> text2
.length
? text1
: text2
;
3606 shorttext
= text1
.length
> text2
.length
? text2
: text1
;
3607 i
= longtext
.indexOf( shorttext
);
3610 // Shorter text is inside the longer text (speedup).
3612 [ DIFF_INSERT
, longtext
.substring( 0, i
) ],
3613 [ DIFF_EQUAL
, shorttext
],
3614 [ DIFF_INSERT
, longtext
.substring( i
+ shorttext
.length
) ]
3617 // Swap insertions for deletions if diff is reversed.
3618 if ( text1
.length
> text2
.length
) {
3619 diffs
[ 0 ][ 0 ] = diffs
[ 2 ][ 0 ] = DIFF_DELETE
;
3624 if ( shorttext
.length
=== 1 ) {
3626 // Single character string.
3627 // After the previous speedup, the character can't be an equality.
3629 [ DIFF_DELETE
, text1
],
3630 [ DIFF_INSERT
, text2
]
3634 // Check to see if the problem can be split in two.
3635 hm
= this.diffHalfMatch( text1
, text2
);
3638 // A half-match was found, sort out the return data.
3643 midCommon
= hm
[ 4 ];
3645 // Send both pairs off for separate processing.
3646 diffsA
= this.DiffMain( text1A
, text2A
, checklines
, deadline
);
3647 diffsB
= this.DiffMain( text1B
, text2B
, checklines
, deadline
);
3649 // Merge the results.
3650 return diffsA
.concat( [
3651 [ DIFF_EQUAL
, midCommon
]
3655 if ( checklines
&& text1
.length
> 100 && text2
.length
> 100 ) {
3656 return this.diffLineMode( text1
, text2
, deadline
);
3659 return this.diffBisect( text1
, text2
, deadline
);
3663 * Do the two texts share a substring which is at least half the length of the
3665 * This speedup can produce non-minimal diffs.
3666 * @param {string} text1 First string.
3667 * @param {string} text2 Second string.
3668 * @return {Array.<string>} Five element Array, containing the prefix of
3669 * text1, the suffix of text1, the prefix of text2, the suffix of
3670 * text2 and the common middle. Or null if there was no match.
3673 DiffMatchPatch
.prototype.diffHalfMatch = function( text1
, text2
) {
3674 var longtext
, shorttext
, dmp
,
3675 text1A
, text2B
, text2A
, text1B
, midCommon
,
3678 longtext
= text1
.length
> text2
.length
? text1
: text2
;
3679 shorttext
= text1
.length
> text2
.length
? text2
: text1
;
3680 if ( longtext
.length
< 4 || shorttext
.length
* 2 < longtext
.length
) {
3681 return null; // Pointless.
3683 dmp
= this; // 'this' becomes 'window' in a closure.
3686 * Does a substring of shorttext exist within longtext such that the substring
3687 * is at least half the length of longtext?
3688 * Closure, but does not reference any external variables.
3689 * @param {string} longtext Longer string.
3690 * @param {string} shorttext Shorter string.
3691 * @param {number} i Start index of quarter length substring within longtext.
3692 * @return {Array.<string>} Five element Array, containing the prefix of
3693 * longtext, the suffix of longtext, the prefix of shorttext, the suffix
3694 * of shorttext and the common middle. Or null if there was no match.
3697 function diffHalfMatchI( longtext
, shorttext
, i
) {
3698 var seed
, j
, bestCommon
, prefixLength
, suffixLength
,
3699 bestLongtextA
, bestLongtextB
, bestShorttextA
, bestShorttextB
;
3701 // Start with a 1/4 length substring at position i as a seed.
3702 seed
= longtext
.substring( i
, i
+ Math
.floor( longtext
.length
/ 4 ) );
3705 while ( ( j
= shorttext
.indexOf( seed
, j
+ 1 ) ) !== -1 ) {
3706 prefixLength
= dmp
.diffCommonPrefix( longtext
.substring( i
),
3707 shorttext
.substring( j
) );
3708 suffixLength
= dmp
.diffCommonSuffix( longtext
.substring( 0, i
),
3709 shorttext
.substring( 0, j
) );
3710 if ( bestCommon
.length
< suffixLength
+ prefixLength
) {
3711 bestCommon
= shorttext
.substring( j
- suffixLength
, j
) +
3712 shorttext
.substring( j
, j
+ prefixLength
);
3713 bestLongtextA
= longtext
.substring( 0, i
- suffixLength
);
3714 bestLongtextB
= longtext
.substring( i
+ prefixLength
);
3715 bestShorttextA
= shorttext
.substring( 0, j
- suffixLength
);
3716 bestShorttextB
= shorttext
.substring( j
+ prefixLength
);
3719 if ( bestCommon
.length
* 2 >= longtext
.length
) {
3720 return [ bestLongtextA
, bestLongtextB
,
3721 bestShorttextA
, bestShorttextB
, bestCommon
3728 // First check if the second quarter is the seed for a half-match.
3729 hm1
= diffHalfMatchI( longtext
, shorttext
,
3730 Math
.ceil( longtext
.length
/ 4 ) );
3732 // Check again based on the third quarter.
3733 hm2
= diffHalfMatchI( longtext
, shorttext
,
3734 Math
.ceil( longtext
.length
/ 2 ) );
3735 if ( !hm1
&& !hm2
) {
3737 } else if ( !hm2
) {
3739 } else if ( !hm1
) {
3743 // Both matched. Select the longest.
3744 hm
= hm1
[ 4 ].length
> hm2
[ 4 ].length
? hm1
: hm2
;
3747 // A half-match was found, sort out the return data.
3748 text1A
, text1B
, text2A
, text2B
;
3749 if ( text1
.length
> text2
.length
) {
3760 midCommon
= hm
[ 4 ];
3761 return [ text1A
, text1B
, text2A
, text2B
, midCommon
];
3765 * Do a quick line-level diff on both strings, then rediff the parts for
3767 * This speedup can produce non-minimal diffs.
3768 * @param {string} text1 Old string to be diffed.
3769 * @param {string} text2 New string to be diffed.
3770 * @param {number} deadline Time when the diff should be complete by.
3771 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
3774 DiffMatchPatch
.prototype.diffLineMode = function( text1
, text2
, deadline
) {
3775 var a
, diffs
, linearray
, pointer
, countInsert
,
3776 countDelete
, textInsert
, textDelete
, j
;
3778 // Scan the text on a line-by-line basis first.
3779 a
= this.diffLinesToChars( text1
, text2
);
3782 linearray
= a
.lineArray
;
3784 diffs
= this.DiffMain( text1
, text2
, false, deadline
);
3786 // Convert the diff back to original text.
3787 this.diffCharsToLines( diffs
, linearray
);
3789 // Eliminate freak matches (e.g. blank lines)
3790 this.diffCleanupSemantic( diffs
);
3792 // Rediff any replacement blocks, this time character-by-character.
3793 // Add a dummy entry at the end.
3794 diffs
.push( [ DIFF_EQUAL
, "" ] );
3800 while ( pointer
< diffs
.length
) {
3801 switch ( diffs
[ pointer
][ 0 ] ) {
3804 textInsert
+= diffs
[ pointer
][ 1 ];
3808 textDelete
+= diffs
[ pointer
][ 1 ];
3812 // Upon reaching an equality, check for prior redundancies.
3813 if ( countDelete
>= 1 && countInsert
>= 1 ) {
3815 // Delete the offending records and add the merged ones.
3816 diffs
.splice( pointer
- countDelete
- countInsert
,
3817 countDelete
+ countInsert
);
3818 pointer
= pointer
- countDelete
- countInsert
;
3819 a
= this.DiffMain( textDelete
, textInsert
, false, deadline
);
3820 for ( j
= a
.length
- 1; j
>= 0; j
-- ) {
3821 diffs
.splice( pointer
, 0, a
[ j
] );
3823 pointer
= pointer
+ a
.length
;
3833 diffs
.pop(); // Remove the dummy entry at the end.
3839 * Find the 'middle snake' of a diff, split the problem in two
3840 * and return the recursively constructed diff.
3841 * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
3842 * @param {string} text1 Old string to be diffed.
3843 * @param {string} text2 New string to be diffed.
3844 * @param {number} deadline Time at which to bail if not yet complete.
3845 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
3848 DiffMatchPatch
.prototype.diffBisect = function( text1
, text2
, deadline
) {
3849 var text1Length
, text2Length
, maxD
, vOffset
, vLength
,
3850 v1
, v2
, x
, delta
, front
, k1start
, k1end
, k2start
,
3851 k2end
, k2Offset
, k1Offset
, x1
, x2
, y1
, y2
, d
, k1
, k2
;
3853 // Cache the text lengths to prevent multiple calls.
3854 text1Length
= text1
.length
;
3855 text2Length
= text2
.length
;
3856 maxD
= Math
.ceil( ( text1Length
+ text2Length
) / 2 );
3859 v1
= new Array( vLength
);
3860 v2
= new Array( vLength
);
3862 // Setting all elements to -1 is faster in Chrome & Firefox than mixing
3863 // integers and undefined.
3864 for ( x
= 0; x
< vLength
; x
++ ) {
3868 v1
[ vOffset
+ 1 ] = 0;
3869 v2
[ vOffset
+ 1 ] = 0;
3870 delta
= text1Length
- text2Length
;
3872 // If the total number of characters is odd, then the front path will collide
3873 // with the reverse path.
3874 front
= ( delta
% 2 !== 0 );
3876 // Offsets for start and end of k loop.
3877 // Prevents mapping of space beyond the grid.
3882 for ( d
= 0; d
< maxD
; d
++ ) {
3884 // Bail out if deadline is reached.
3885 if ( ( new Date() ).getTime() > deadline
) {
3889 // Walk the front path one step.
3890 for ( k1
= -d
+ k1start
; k1
<= d
- k1end
; k1
+= 2 ) {
3891 k1Offset
= vOffset
+ k1
;
3892 if ( k1
=== -d
|| ( k1
!== d
&& v1
[ k1Offset
- 1 ] < v1
[ k1Offset
+ 1 ] ) ) {
3893 x1
= v1
[ k1Offset
+ 1 ];
3895 x1
= v1
[ k1Offset
- 1 ] + 1;
3898 while ( x1
< text1Length
&& y1
< text2Length
&&
3899 text1
.charAt( x1
) === text2
.charAt( y1
) ) {
3903 v1
[ k1Offset
] = x1
;
3904 if ( x1
> text1Length
) {
3906 // Ran off the right of the graph.
3908 } else if ( y1
> text2Length
) {
3910 // Ran off the bottom of the graph.
3912 } else if ( front
) {
3913 k2Offset
= vOffset
+ delta
- k1
;
3914 if ( k2Offset
>= 0 && k2Offset
< vLength
&& v2
[ k2Offset
] !== -1 ) {
3916 // Mirror x2 onto top-left coordinate system.
3917 x2
= text1Length
- v2
[ k2Offset
];
3920 // Overlap detected.
3921 return this.diffBisectSplit( text1
, text2
, x1
, y1
, deadline
);
3927 // Walk the reverse path one step.
3928 for ( k2
= -d
+ k2start
; k2
<= d
- k2end
; k2
+= 2 ) {
3929 k2Offset
= vOffset
+ k2
;
3930 if ( k2
=== -d
|| ( k2
!== d
&& v2
[ k2Offset
- 1 ] < v2
[ k2Offset
+ 1 ] ) ) {
3931 x2
= v2
[ k2Offset
+ 1 ];
3933 x2
= v2
[ k2Offset
- 1 ] + 1;
3936 while ( x2
< text1Length
&& y2
< text2Length
&&
3937 text1
.charAt( text1Length
- x2
- 1 ) ===
3938 text2
.charAt( text2Length
- y2
- 1 ) ) {
3942 v2
[ k2Offset
] = x2
;
3943 if ( x2
> text1Length
) {
3945 // Ran off the left of the graph.
3947 } else if ( y2
> text2Length
) {
3949 // Ran off the top of the graph.
3951 } else if ( !front
) {
3952 k1Offset
= vOffset
+ delta
- k2
;
3953 if ( k1Offset
>= 0 && k1Offset
< vLength
&& v1
[ k1Offset
] !== -1 ) {
3954 x1
= v1
[ k1Offset
];
3955 y1
= vOffset
+ x1
- k1Offset
;
3957 // Mirror x2 onto top-left coordinate system.
3958 x2
= text1Length
- x2
;
3961 // Overlap detected.
3962 return this.diffBisectSplit( text1
, text2
, x1
, y1
, deadline
);
3969 // Diff took too long and hit the deadline or
3970 // number of diffs equals number of characters, no commonality at all.
3972 [ DIFF_DELETE
, text1
],
3973 [ DIFF_INSERT
, text2
]
3978 * Given the location of the 'middle snake', split the diff in two parts
3980 * @param {string} text1 Old string to be diffed.
3981 * @param {string} text2 New string to be diffed.
3982 * @param {number} x Index of split point in text1.
3983 * @param {number} y Index of split point in text2.
3984 * @param {number} deadline Time at which to bail if not yet complete.
3985 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
3988 DiffMatchPatch
.prototype.diffBisectSplit = function( text1
, text2
, x
, y
, deadline
) {
3989 var text1a
, text1b
, text2a
, text2b
, diffs
, diffsb
;
3990 text1a
= text1
.substring( 0, x
);
3991 text2a
= text2
.substring( 0, y
);
3992 text1b
= text1
.substring( x
);
3993 text2b
= text2
.substring( y
);
3995 // Compute both diffs serially.
3996 diffs
= this.DiffMain( text1a
, text2a
, false, deadline
);
3997 diffsb
= this.DiffMain( text1b
, text2b
, false, deadline
);
3999 return diffs
.concat( diffsb
);
4003 * Reduce the number of edits by eliminating semantically trivial equalities.
4004 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4006 DiffMatchPatch
.prototype.diffCleanupSemantic = function( diffs
) {
4007 var changes
, equalities
, equalitiesLength
, lastequality
,
4008 pointer
, lengthInsertions2
, lengthDeletions2
, lengthInsertions1
,
4009 lengthDeletions1
, deletion
, insertion
, overlapLength1
, overlapLength2
;
4011 equalities
= []; // Stack of indices where equalities are found.
4012 equalitiesLength
= 0; // Keeping our own length var is faster in JS.
4013 /** @type {?string} */
4014 lastequality
= null;
4016 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
4017 pointer
= 0; // Index of current position.
4019 // Number of characters that changed prior to the equality.
4020 lengthInsertions1
= 0;
4021 lengthDeletions1
= 0;
4023 // Number of characters that changed after the equality.
4024 lengthInsertions2
= 0;
4025 lengthDeletions2
= 0;
4026 while ( pointer
< diffs
.length
) {
4027 if ( diffs
[ pointer
][ 0 ] === DIFF_EQUAL
) { // Equality found.
4028 equalities
[ equalitiesLength
++ ] = pointer
;
4029 lengthInsertions1
= lengthInsertions2
;
4030 lengthDeletions1
= lengthDeletions2
;
4031 lengthInsertions2
= 0;
4032 lengthDeletions2
= 0;
4033 lastequality
= diffs
[ pointer
][ 1 ];
4034 } else { // An insertion or deletion.
4035 if ( diffs
[ pointer
][ 0 ] === DIFF_INSERT
) {
4036 lengthInsertions2
+= diffs
[ pointer
][ 1 ].length
;
4038 lengthDeletions2
+= diffs
[ pointer
][ 1 ].length
;
4041 // Eliminate an equality that is smaller or equal to the edits on both
4043 if ( lastequality
&& ( lastequality
.length
<=
4044 Math
.max( lengthInsertions1
, lengthDeletions1
) ) &&
4045 ( lastequality
.length
<= Math
.max( lengthInsertions2
,
4046 lengthDeletions2
) ) ) {
4048 // Duplicate record.
4050 equalities
[ equalitiesLength
- 1 ],
4052 [ DIFF_DELETE
, lastequality
]
4055 // Change second copy to insert.
4056 diffs
[ equalities
[ equalitiesLength
- 1 ] + 1 ][ 0 ] = DIFF_INSERT
;
4058 // Throw away the equality we just deleted.
4061 // Throw away the previous equality (it needs to be reevaluated).
4063 pointer
= equalitiesLength
> 0 ? equalities
[ equalitiesLength
- 1 ] : -1;
4065 // Reset the counters.
4066 lengthInsertions1
= 0;
4067 lengthDeletions1
= 0;
4068 lengthInsertions2
= 0;
4069 lengthDeletions2
= 0;
4070 lastequality
= null;
4077 // Normalize the diff.
4079 this.diffCleanupMerge( diffs
);
4082 // Find any overlaps between deletions and insertions.
4083 // e.g: <del>abcxxx</del><ins>xxxdef</ins>
4084 // -> <del>abc</del>xxx<ins>def</ins>
4085 // e.g: <del>xxxabc</del><ins>defxxx</ins>
4086 // -> <ins>def</ins>xxx<del>abc</del>
4087 // Only extract an overlap if it is as big as the edit ahead or behind it.
4089 while ( pointer
< diffs
.length
) {
4090 if ( diffs
[ pointer
- 1 ][ 0 ] === DIFF_DELETE
&&
4091 diffs
[ pointer
][ 0 ] === DIFF_INSERT
) {
4092 deletion
= diffs
[ pointer
- 1 ][ 1 ];
4093 insertion
= diffs
[ pointer
][ 1 ];
4094 overlapLength1
= this.diffCommonOverlap( deletion
, insertion
);
4095 overlapLength2
= this.diffCommonOverlap( insertion
, deletion
);
4096 if ( overlapLength1
>= overlapLength2
) {
4097 if ( overlapLength1
>= deletion
.length
/ 2 ||
4098 overlapLength1
>= insertion
.length
/ 2 ) {
4100 // Overlap found. Insert an equality and trim the surrounding edits.
4104 [ DIFF_EQUAL
, insertion
.substring( 0, overlapLength1
) ]
4106 diffs
[ pointer
- 1 ][ 1 ] =
4107 deletion
.substring( 0, deletion
.length
- overlapLength1
);
4108 diffs
[ pointer
+ 1 ][ 1 ] = insertion
.substring( overlapLength1
);
4112 if ( overlapLength2
>= deletion
.length
/ 2 ||
4113 overlapLength2
>= insertion
.length
/ 2 ) {
4115 // Reverse overlap found.
4116 // Insert an equality and swap and trim the surrounding edits.
4120 [ DIFF_EQUAL
, deletion
.substring( 0, overlapLength2
) ]
4123 diffs
[ pointer
- 1 ][ 0 ] = DIFF_INSERT
;
4124 diffs
[ pointer
- 1 ][ 1 ] =
4125 insertion
.substring( 0, insertion
.length
- overlapLength2
);
4126 diffs
[ pointer
+ 1 ][ 0 ] = DIFF_DELETE
;
4127 diffs
[ pointer
+ 1 ][ 1 ] =
4128 deletion
.substring( overlapLength2
);
4139 * Determine if the suffix of one string is the prefix of another.
4140 * @param {string} text1 First string.
4141 * @param {string} text2 Second string.
4142 * @return {number} The number of characters common to the end of the first
4143 * string and the start of the second string.
4146 DiffMatchPatch
.prototype.diffCommonOverlap = function( text1
, text2
) {
4147 var text1Length
, text2Length
, textLength
,
4148 best
, length
, pattern
, found
;
4150 // Cache the text lengths to prevent multiple calls.
4151 text1Length
= text1
.length
;
4152 text2Length
= text2
.length
;
4154 // Eliminate the null case.
4155 if ( text1Length
=== 0 || text2Length
=== 0 ) {
4159 // Truncate the longer string.
4160 if ( text1Length
> text2Length
) {
4161 text1
= text1
.substring( text1Length
- text2Length
);
4162 } else if ( text1Length
< text2Length
) {
4163 text2
= text2
.substring( 0, text1Length
);
4165 textLength
= Math
.min( text1Length
, text2Length
);
4167 // Quick check for the worst case.
4168 if ( text1
=== text2
) {
4172 // Start by looking for a single character match
4173 // and increase length until no match is found.
4174 // Performance analysis: https://neil.fraser.name/news/2010/11/04/
4178 pattern
= text1
.substring( textLength
- length
);
4179 found
= text2
.indexOf( pattern
);
4180 if ( found
=== -1 ) {
4184 if ( found
=== 0 || text1
.substring( textLength
- length
) ===
4185 text2
.substring( 0, length
) ) {
4193 * Split two texts into an array of strings. Reduce the texts to a string of
4194 * hashes where each Unicode character represents one line.
4195 * @param {string} text1 First string.
4196 * @param {string} text2 Second string.
4197 * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
4198 * An object containing the encoded text1, the encoded text2 and
4199 * the array of unique strings.
4200 * The zeroth element of the array of unique strings is intentionally blank.
4203 DiffMatchPatch
.prototype.diffLinesToChars = function( text1
, text2
) {
4204 var lineArray
, lineHash
, chars1
, chars2
;
4205 lineArray
= []; // E.g. lineArray[4] === 'Hello\n'
4206 lineHash
= {}; // E.g. lineHash['Hello\n'] === 4
4208 // '\x00' is a valid character, but various debuggers don't like it.
4209 // So we'll insert a junk entry to avoid generating a null character.
4210 lineArray
[ 0 ] = "";
4213 * Split a text into an array of strings. Reduce the texts to a string of
4214 * hashes where each Unicode character represents one line.
4215 * Modifies linearray and linehash through being a closure.
4216 * @param {string} text String to encode.
4217 * @return {string} Encoded string.
4220 function diffLinesToCharsMunge( text
) {
4221 var chars
, lineStart
, lineEnd
, lineArrayLength
, line
;
4224 // Walk the text, pulling out a substring for each line.
4225 // text.split('\n') would would temporarily double our memory footprint.
4226 // Modifying text would create many large strings to garbage collect.
4230 // Keeping our own length variable is faster than looking it up.
4231 lineArrayLength
= lineArray
.length
;
4232 while ( lineEnd
< text
.length
- 1 ) {
4233 lineEnd
= text
.indexOf( "\n", lineStart
);
4234 if ( lineEnd
=== -1 ) {
4235 lineEnd
= text
.length
- 1;
4237 line
= text
.substring( lineStart
, lineEnd
+ 1 );
4238 lineStart
= lineEnd
+ 1;
4240 if ( lineHash
.hasOwnProperty
? lineHash
.hasOwnProperty( line
) :
4241 ( lineHash
[ line
] !== undefined ) ) {
4242 chars
+= String
.fromCharCode( lineHash
[ line
] );
4244 chars
+= String
.fromCharCode( lineArrayLength
);
4245 lineHash
[ line
] = lineArrayLength
;
4246 lineArray
[ lineArrayLength
++ ] = line
;
4252 chars1
= diffLinesToCharsMunge( text1
);
4253 chars2
= diffLinesToCharsMunge( text2
);
4257 lineArray
: lineArray
4262 * Rehydrate the text in a diff from a string of line hashes to real lines of
4264 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4265 * @param {!Array.<string>} lineArray Array of unique strings.
4268 DiffMatchPatch
.prototype.diffCharsToLines = function( diffs
, lineArray
) {
4269 var x
, chars
, text
, y
;
4270 for ( x
= 0; x
< diffs
.length
; x
++ ) {
4271 chars
= diffs
[ x
][ 1 ];
4273 for ( y
= 0; y
< chars
.length
; y
++ ) {
4274 text
[ y
] = lineArray
[ chars
.charCodeAt( y
) ];
4276 diffs
[ x
][ 1 ] = text
.join( "" );
4281 * Reorder and merge like edit sections. Merge equalities.
4282 * Any edit section can move as long as it doesn't cross an equality.
4283 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4285 DiffMatchPatch
.prototype.diffCleanupMerge = function( diffs
) {
4286 var pointer
, countDelete
, countInsert
, textInsert
, textDelete
,
4287 commonlength
, changes
, diffPointer
, position
;
4288 diffs
.push( [ DIFF_EQUAL
, "" ] ); // Add a dummy entry at the end.
4295 while ( pointer
< diffs
.length
) {
4296 switch ( diffs
[ pointer
][ 0 ] ) {
4299 textInsert
+= diffs
[ pointer
][ 1 ];
4304 textDelete
+= diffs
[ pointer
][ 1 ];
4309 // Upon reaching an equality, check for prior redundancies.
4310 if ( countDelete
+ countInsert
> 1 ) {
4311 if ( countDelete
!== 0 && countInsert
!== 0 ) {
4313 // Factor out any common prefixes.
4314 commonlength
= this.diffCommonPrefix( textInsert
, textDelete
);
4315 if ( commonlength
!== 0 ) {
4316 if ( ( pointer
- countDelete
- countInsert
) > 0 &&
4317 diffs
[ pointer
- countDelete
- countInsert
- 1 ][ 0 ] ===
4319 diffs
[ pointer
- countDelete
- countInsert
- 1 ][ 1 ] +=
4320 textInsert
.substring( 0, commonlength
);
4322 diffs
.splice( 0, 0, [ DIFF_EQUAL
,
4323 textInsert
.substring( 0, commonlength
)
4327 textInsert
= textInsert
.substring( commonlength
);
4328 textDelete
= textDelete
.substring( commonlength
);
4331 // Factor out any common suffixies.
4332 commonlength
= this.diffCommonSuffix( textInsert
, textDelete
);
4333 if ( commonlength
!== 0 ) {
4334 diffs
[ pointer
][ 1 ] = textInsert
.substring( textInsert
.length
-
4335 commonlength
) + diffs
[ pointer
][ 1 ];
4336 textInsert
= textInsert
.substring( 0, textInsert
.length
-
4338 textDelete
= textDelete
.substring( 0, textDelete
.length
-
4343 // Delete the offending records and add the merged ones.
4344 if ( countDelete
=== 0 ) {
4345 diffs
.splice( pointer
- countInsert
,
4346 countDelete
+ countInsert
, [ DIFF_INSERT
, textInsert
] );
4347 } else if ( countInsert
=== 0 ) {
4348 diffs
.splice( pointer
- countDelete
,
4349 countDelete
+ countInsert
, [ DIFF_DELETE
, textDelete
] );
4352 pointer
- countDelete
- countInsert
,
4353 countDelete
+ countInsert
,
4354 [ DIFF_DELETE
, textDelete
], [ DIFF_INSERT
, textInsert
]
4357 pointer
= pointer
- countDelete
- countInsert
+
4358 ( countDelete
? 1 : 0 ) + ( countInsert
? 1 : 0 ) + 1;
4359 } else if ( pointer
!== 0 && diffs
[ pointer
- 1 ][ 0 ] === DIFF_EQUAL
) {
4361 // Merge this equality with the previous one.
4362 diffs
[ pointer
- 1 ][ 1 ] += diffs
[ pointer
][ 1 ];
4363 diffs
.splice( pointer
, 1 );
4374 if ( diffs
[ diffs
.length
- 1 ][ 1 ] === "" ) {
4375 diffs
.pop(); // Remove the dummy entry at the end.
4378 // Second pass: look for single edits surrounded on both sides by equalities
4379 // which can be shifted sideways to eliminate an equality.
4380 // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
4384 // Intentionally ignore the first and last element (don't need checking).
4385 while ( pointer
< diffs
.length
- 1 ) {
4386 if ( diffs
[ pointer
- 1 ][ 0 ] === DIFF_EQUAL
&&
4387 diffs
[ pointer
+ 1 ][ 0 ] === DIFF_EQUAL
) {
4389 diffPointer
= diffs
[ pointer
][ 1 ];
4390 position
= diffPointer
.substring(
4391 diffPointer
.length
- diffs
[ pointer
- 1 ][ 1 ].length
4394 // This is a single edit surrounded by equalities.
4395 if ( position
=== diffs
[ pointer
- 1 ][ 1 ] ) {
4397 // Shift the edit over the previous equality.
4398 diffs
[ pointer
][ 1 ] = diffs
[ pointer
- 1 ][ 1 ] +
4399 diffs
[ pointer
][ 1 ].substring( 0, diffs
[ pointer
][ 1 ].length
-
4400 diffs
[ pointer
- 1 ][ 1 ].length
);
4401 diffs
[ pointer
+ 1 ][ 1 ] =
4402 diffs
[ pointer
- 1 ][ 1 ] + diffs
[ pointer
+ 1 ][ 1 ];
4403 diffs
.splice( pointer
- 1, 1 );
4405 } else if ( diffPointer
.substring( 0, diffs
[ pointer
+ 1 ][ 1 ].length
) ===
4406 diffs
[ pointer
+ 1 ][ 1 ] ) {
4408 // Shift the edit over the next equality.
4409 diffs
[ pointer
- 1 ][ 1 ] += diffs
[ pointer
+ 1 ][ 1 ];
4410 diffs
[ pointer
][ 1 ] =
4411 diffs
[ pointer
][ 1 ].substring( diffs
[ pointer
+ 1 ][ 1 ].length
) +
4412 diffs
[ pointer
+ 1 ][ 1 ];
4413 diffs
.splice( pointer
+ 1, 1 );
4420 // If shifts were made, the diff needs reordering and another shift sweep.
4422 this.diffCleanupMerge( diffs
);
4426 return function( o
, n
) {
4427 var diff
, output
, text
;
4428 diff
= new DiffMatchPatch();
4429 output
= diff
.DiffMain( o
, n
);
4430 diff
.diffCleanupEfficiency( output
);
4431 text
= diff
.diffPrettyHtml( output
);