5 * Copyright jQuery Foundation and other contributors
6 * Released under the MIT license
7 * https://jquery.org/license
9 * Date: 2016-04-12T17:29Z
12 ( function( global
) {
16 var Date
= global
.Date
;
17 var now
= Date
.now
|| function() {
18 return new Date().getTime();
21 var setTimeout
= global
.setTimeout
;
22 var clearTimeout
= global
.clearTimeout
;
24 // Store a local window from the global to allow direct references.
25 var window
= global
.window
;
28 document
: window
&& window
.document
!== undefined,
29 setTimeout
: setTimeout
!== undefined,
30 sessionStorage
: ( function() {
31 var x
= "qunit-test-string";
33 sessionStorage
.setItem( x
, x
);
34 sessionStorage
.removeItem( x
);
42 var fileName
= ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" );
43 var globalStartCalled
= false;
44 var runStarted
= false;
46 var toString
= Object
.prototype.toString
,
47 hasOwn
= Object
.prototype.hasOwnProperty
;
49 // Returns a new Array with the elements that are in a but not in b
50 function diff( a
, b
) {
54 for ( i
= 0; i
< result
.length
; i
++ ) {
55 for ( j
= 0; j
< b
.length
; j
++ ) {
56 if ( result
[ i
] === b
[ j
] ) {
57 result
.splice( i
, 1 );
67 function inArray( elem
, array
) {
68 if ( array
.indexOf
) {
69 return array
.indexOf( elem
);
72 for ( var i
= 0, length
= array
.length
; i
< length
; i
++ ) {
73 if ( array
[ i
] === elem
) {
82 * Makes a clone of an object using only Array or Object as base,
83 * and copies over the own enumerable properties.
86 * @return {Object} New object with only the own properties (recursively).
88 function objectValues ( obj
) {
90 vals
= QUnit
.is( "array", obj
) ? [] : {};
92 if ( hasOwn
.call( obj
, key
) ) {
94 vals
[ key
] = val
=== Object( val
) ? objectValues( val
) : val
;
100 function extend( a
, b
, undefOnly
) {
101 for ( var prop
in b
) {
102 if ( hasOwn
.call( b
, prop
) ) {
104 // Avoid "Member not found" error in IE8 caused by messing with window.constructor
105 // This block runs on every environment, so `global` is being used instead of `window`
106 // to avoid errors on node.
107 if ( prop
!== "constructor" || a
!== global
) {
108 if ( b
[ prop
] === undefined ) {
110 } else if ( !( undefOnly
&& typeof a
[ prop
] !== "undefined" ) ) {
111 a
[ prop
] = b
[ prop
];
120 function objectType( obj
) {
121 if ( typeof obj
=== "undefined" ) {
125 // Consider: typeof null === object
126 if ( obj
=== null ) {
130 var match
= toString
.call( obj
).match( /^\[object\s(.*)\]$/ ),
131 type
= match
&& match
[ 1 ];
135 if ( isNaN( obj
) ) {
148 return type
.toLowerCase();
150 if ( typeof obj
=== "object" ) {
155 // Safe object type checking
156 function is( type
, obj
) {
157 return QUnit
.objectType( obj
) === type
;
160 // Doesn't support IE6 to IE9, it will return undefined on these browsers
161 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
162 function extractStacktrace( e
, offset
) {
163 offset
= offset
=== undefined ? 4 : offset
;
165 var stack
, include
, i
;
168 stack
= e
.stack
.split( "\n" );
169 if ( /^error$/i.test( stack
[ 0 ] ) ) {
174 for ( i
= offset
; i
< stack
.length
; i
++ ) {
175 if ( stack
[ i
].indexOf( fileName
) !== -1 ) {
178 include
.push( stack
[ i
] );
180 if ( include
.length
) {
181 return include
.join( "\n" );
184 return stack
[ offset
];
186 // Support: Safari <=6 only
187 } else if ( e
.sourceURL
) {
189 // Exclude useless self-reference for generated Error objects
190 if ( /qunit.js$/.test( e
.sourceURL
) ) {
194 // For actual exceptions, this is useful
195 return e
.sourceURL
+ ":" + e
.line
;
199 function sourceFromStacktrace( offset
) {
200 var error
= new Error();
202 // Support: Safari <=7 only, IE <=10 - 11 only
203 // Not all browsers generate the `stack` property for `new Error()`, see also #636
204 if ( !error
.stack
) {
212 return extractStacktrace( error
, offset
);
216 * Config object: Maintain internal state
217 * Later exposed as QUnit.config
218 * `config` initialized at top of scope
222 // The queue of tests to run
225 // Block until document ready
228 // By default, run previously failed tests first
229 // very useful in combination with "Hide passed tests" checked
232 // By default, modify document.title when suite is done
235 // HTML Reporter: collapse every test except the first failing test
236 // If false, all failing tests will be expanded
239 // By default, scroll to top of the page when suite is done
242 // Depth up-to which object will be dumped
245 // When enabled, all tests must call expect()
246 requireExpects
: false,
248 // Placeholder for user-configurable form-exposed URL parameters
251 // Set of all modules.
254 // Stack of nested modules
257 // The first unnamed module
266 // Push a loose unnamed module to the modules collection
267 config
.modules
.push( config
.currentModule
);
269 var loggingCallbacks
= {};
271 // Register logging callbacks
272 function registerLoggingCallbacks( obj
) {
274 callbackNames
= [ "begin", "done", "log", "testStart", "testDone",
275 "moduleStart", "moduleDone" ];
277 function registerLoggingCallback( key
) {
278 var loggingCallback = function( callback
) {
279 if ( objectType( callback
) !== "function" ) {
281 "QUnit logging methods require a callback function as their first parameters."
285 config
.callbacks
[ key
].push( callback
);
288 // DEPRECATED: This will be removed on QUnit 2.0.0+
289 // Stores the registered functions allowing restoring
290 // at verifyLoggingCallbacks() if modified
291 loggingCallbacks
[ key
] = loggingCallback
;
293 return loggingCallback
;
296 for ( i
= 0, l
= callbackNames
.length
; i
< l
; i
++ ) {
297 key
= callbackNames
[ i
];
299 // Initialize key collection of logging callback
300 if ( objectType( config
.callbacks
[ key
] ) === "undefined" ) {
301 config
.callbacks
[ key
] = [];
304 obj
[ key
] = registerLoggingCallback( key
);
308 function runLoggingCallbacks( key
, args
) {
311 callbacks
= config
.callbacks
[ key
];
312 for ( i
= 0, l
= callbacks
.length
; i
< l
; i
++ ) {
313 callbacks
[ i
]( args
);
317 // DEPRECATED: This will be removed on 2.0.0+
318 // This function verifies if the loggingCallbacks were modified by the user
319 // If so, it will restore it, assign the given callback and print a console warning
320 function verifyLoggingCallbacks() {
321 var loggingCallback
, userCallback
;
323 for ( loggingCallback
in loggingCallbacks
) {
324 if ( QUnit
[ loggingCallback
] !== loggingCallbacks
[ loggingCallback
] ) {
326 userCallback
= QUnit
[ loggingCallback
];
328 // Restore the callback function
329 QUnit
[ loggingCallback
] = loggingCallbacks
[ loggingCallback
];
331 // Assign the deprecated given callback
332 QUnit
[ loggingCallback
]( userCallback
);
334 if ( global
.console
&& global
.console
.warn
) {
336 "QUnit." + loggingCallback
+ " was replaced with a new value.\n" +
337 "Please, check out the documentation on how to apply logging callbacks.\n" +
338 "Reference: https://api.qunitjs.com/category/callbacks/"
346 if ( !defined
.document
) {
350 // `onErrorFnPrev` initialized at top of scope
351 // Preserve other handlers
352 var onErrorFnPrev
= window
.onerror
;
354 // Cover uncaught exceptions
355 // Returning true will suppress the default browser handler,
356 // returning false will let it run.
357 window
.onerror = function( error
, filePath
, linerNr
) {
359 if ( onErrorFnPrev
) {
360 ret
= onErrorFnPrev( error
, filePath
, linerNr
);
363 // Treat return value as window.onerror itself does,
364 // Only do our handling if not suppressed.
365 if ( ret
!== true ) {
366 if ( QUnit
.config
.current
) {
367 if ( QUnit
.config
.current
.ignoreGlobalErrors
) {
370 QUnit
.pushFailure( error
, filePath
+ ":" + linerNr
);
372 QUnit
.test( "global failure", extend( function() {
373 QUnit
.pushFailure( error
, filePath
+ ":" + linerNr
);
374 }, { validTest
: true } ) );
383 // Figure out if we're running the tests from a server or not
384 QUnit
.isLocal
= !( defined
.document
&& window
.location
.protocol
!== "file:" );
386 // Expose the current QUnit version
387 QUnit
.version
= "1.23.1";
391 // Call on start of module test to prepend name to all tests
392 module: function( name
, testEnvironment
, executeNow
) {
393 var module
, moduleFns
;
394 var currentModule
= config
.currentModule
;
396 if ( arguments
.length
=== 2 ) {
397 if ( objectType( testEnvironment
) === "function" ) {
398 executeNow
= testEnvironment
;
399 testEnvironment
= undefined;
403 // DEPRECATED: handles setup/teardown functions,
404 // beforeEach and afterEach should be used instead
405 if ( testEnvironment
&& testEnvironment
.setup
) {
406 testEnvironment
.beforeEach
= testEnvironment
.setup
;
407 delete testEnvironment
.setup
;
409 if ( testEnvironment
&& testEnvironment
.teardown
) {
410 testEnvironment
.afterEach
= testEnvironment
.teardown
;
411 delete testEnvironment
.teardown
;
414 module
= createModule();
417 beforeEach
: setHook( module
, "beforeEach" ),
418 afterEach
: setHook( module
, "afterEach" )
421 if ( objectType( executeNow
) === "function" ) {
422 config
.moduleStack
.push( module
);
423 setCurrentModule( module
);
424 executeNow
.call( module
.testEnvironment
, moduleFns
);
425 config
.moduleStack
.pop();
426 module
= module
.parentModule
|| currentModule
;
429 setCurrentModule( module
);
431 function createModule() {
432 var parentModule
= config
.moduleStack
.length
?
433 config
.moduleStack
.slice( -1 )[ 0 ] : null;
434 var moduleName
= parentModule
!== null ?
435 [ parentModule
.name
, name
].join( " > " ) : name
;
438 parentModule
: parentModule
,
440 moduleId
: generateHash( moduleName
)
444 if ( parentModule
) {
445 extend( env
, parentModule
.testEnvironment
);
446 delete env
.beforeEach
;
447 delete env
.afterEach
;
449 extend( env
, testEnvironment
);
450 module
.testEnvironment
= env
;
452 config
.modules
.push( module
);
456 function setCurrentModule( module
) {
457 config
.currentModule
= module
;
462 // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
463 asyncTest
: asyncTest
,
471 // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
472 // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
473 start: function( count
) {
474 var globalStartAlreadyCalled
= globalStartCalled
;
476 if ( !config
.current
) {
477 globalStartCalled
= true;
480 throw new Error( "Called start() outside of a test context while already started" );
481 } else if ( globalStartAlreadyCalled
|| count
> 1 ) {
482 throw new Error( "Called start() outside of a test context too many times" );
483 } else if ( config
.autostart
) {
484 throw new Error( "Called start() outside of a test context when " +
485 "QUnit.config.autostart was true" );
486 } else if ( !config
.pageLoaded
) {
488 // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
489 config
.autostart
= true;
494 // If a test is running, adjust its semaphore
495 config
.current
.semaphore
-= count
|| 1;
497 // If semaphore is non-numeric, throw error
498 if ( isNaN( config
.current
.semaphore
) ) {
499 config
.current
.semaphore
= 0;
502 "Called start() with a non-numeric decrement.",
503 sourceFromStacktrace( 2 )
508 // Don't start until equal number of stop-calls
509 if ( config
.current
.semaphore
> 0 ) {
513 // Throw an Error if start is called more often than stop
514 if ( config
.current
.semaphore
< 0 ) {
515 config
.current
.semaphore
= 0;
518 "Called start() while already started (test's semaphore was 0 already)",
519 sourceFromStacktrace( 2 )
528 // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
529 stop: function( count
) {
531 // If there isn't a test running, don't allow QUnit.stop() to be called
532 if ( !config
.current
) {
533 throw new Error( "Called stop() outside of a test context" );
536 // If a test is running, adjust its semaphore
537 config
.current
.semaphore
+= count
|| 1;
546 objectType
: objectType
,
551 config
.pageLoaded
= true;
553 // Initialize the configuration options
555 stats
: { all
: 0, bad
: 0 },
556 moduleStats
: { all
: 0, bad
: 0 },
563 config
.blocking
= false;
565 if ( config
.autostart
) {
570 stack: function( offset
) {
571 offset
= ( offset
|| 0 ) + 2;
572 return sourceFromStacktrace( offset
);
576 registerLoggingCallbacks( QUnit
);
582 // If the test run hasn't officially begun yet
583 if ( !config
.started
) {
585 // Record the time of the test run's beginning
586 config
.started
= now();
588 verifyLoggingCallbacks();
590 // Delete the loose unnamed module if unused.
591 if ( config
.modules
[ 0 ].name
=== "" && config
.modules
[ 0 ].tests
.length
=== 0 ) {
592 config
.modules
.shift();
595 // Avoid unnecessary information by not logging modules' test environments
596 for ( i
= 0, l
= config
.modules
.length
; i
< l
; i
++ ) {
598 name
: config
.modules
[ i
].name
,
599 tests
: config
.modules
[ i
].tests
603 // The test run is officially beginning now
604 runLoggingCallbacks( "begin", {
605 totalTests
: Test
.count
,
610 config
.blocking
= false;
614 function process( last
) {
619 config
.depth
= ( config
.depth
|| 0 ) + 1;
621 while ( config
.queue
.length
&& !config
.blocking
) {
622 if ( !defined
.setTimeout
|| config
.updateRate
<= 0 ||
623 ( ( now() - start
) < config
.updateRate
) ) {
624 if ( config
.current
) {
626 // Reset async tracking for each phase of the Test lifecycle
627 config
.current
.usedAsync
= false;
629 config
.queue
.shift()();
631 setTimeout( next
, 13 );
636 if ( last
&& !config
.blocking
&& !config
.queue
.length
&& config
.depth
=== 0 ) {
641 function pauseProcessing() {
642 config
.blocking
= true;
644 if ( config
.testTimeout
&& defined
.setTimeout
) {
645 clearTimeout( config
.timeout
);
646 config
.timeout
= setTimeout( function() {
647 if ( config
.current
) {
648 config
.current
.semaphore
= 0;
649 QUnit
.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
651 throw new Error( "Test timed out" );
654 }, config
.testTimeout
);
658 function resumeProcessing() {
661 // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
662 if ( defined
.setTimeout
) {
663 setTimeout( function() {
664 if ( config
.current
&& config
.current
.semaphore
> 0 ) {
667 if ( config
.timeout
) {
668 clearTimeout( config
.timeout
);
681 config
.autorun
= true;
683 // Log the last module results
684 if ( config
.previousModule
) {
685 runLoggingCallbacks( "moduleDone", {
686 name
: config
.previousModule
.name
,
687 tests
: config
.previousModule
.tests
,
688 failed
: config
.moduleStats
.bad
,
689 passed
: config
.moduleStats
.all
- config
.moduleStats
.bad
,
690 total
: config
.moduleStats
.all
,
691 runtime
: now() - config
.moduleStats
.started
694 delete config
.previousModule
;
696 runtime
= now() - config
.started
;
697 passed
= config
.stats
.all
- config
.stats
.bad
;
699 runLoggingCallbacks( "done", {
700 failed
: config
.stats
.bad
,
702 total
: config
.stats
.all
,
707 function setHook( module
, hookName
) {
708 if ( module
.testEnvironment
=== undefined ) {
709 module
.testEnvironment
= {};
712 return function( callback
) {
713 module
.testEnvironment
[ hookName
] = callback
;
718 var priorityCount
= 0;
721 function Test( settings
) {
726 extend( this, settings
);
727 this.assertions
= [];
729 this.usedAsync
= false;
730 this.module
= config
.currentModule
;
731 this.stack
= sourceFromStacktrace( 3 );
733 // Register unique strings
734 for ( i
= 0, l
= this.module
.tests
; i
< l
.length
; i
++ ) {
735 if ( this.module
.tests
[ i
].name
=== this.testName
) {
736 this.testName
+= " ";
740 this.testId
= generateHash( this.module
.name
, this.testName
);
742 this.module
.tests
.push( {
747 if ( settings
.skip
) {
749 // Skipped tests will fully ignore any sent callback
750 this.callback = function() {};
754 this.assert
= new Assert( this );
764 // Emit moduleStart when we're switching from one module to another
765 this.module
!== config
.previousModule
||
767 // They could be equal (both undefined) but if the previousModule property doesn't
768 // yet exist it means this is the first test in a suite that isn't wrapped in a
769 // module, in which case we'll just emit a moduleStart event for 'undefined'.
770 // Without this, reporters can get testStart before moduleStart which is a problem.
771 !hasOwn
.call( config
, "previousModule" )
773 if ( hasOwn
.call( config
, "previousModule" ) ) {
774 runLoggingCallbacks( "moduleDone", {
775 name
: config
.previousModule
.name
,
776 tests
: config
.previousModule
.tests
,
777 failed
: config
.moduleStats
.bad
,
778 passed
: config
.moduleStats
.all
- config
.moduleStats
.bad
,
779 total
: config
.moduleStats
.all
,
780 runtime
: now() - config
.moduleStats
.started
783 config
.previousModule
= this.module
;
784 config
.moduleStats
= { all
: 0, bad
: 0, started
: now() };
785 runLoggingCallbacks( "moduleStart", {
786 name
: this.module
.name
,
787 tests
: this.module
.tests
791 config
.current
= this;
793 if ( this.module
.testEnvironment
) {
794 delete this.module
.testEnvironment
.beforeEach
;
795 delete this.module
.testEnvironment
.afterEach
;
797 this.testEnvironment
= extend( {}, this.module
.testEnvironment
);
799 this.started
= now();
800 runLoggingCallbacks( "testStart", {
802 module
: this.module
.name
,
806 if ( !config
.pollution
) {
814 config
.current
= this;
820 this.callbackStarted
= now();
822 if ( config
.notrycatch
) {
830 this.pushFailure( "Died on test #" + ( this.assertions
.length
+ 1 ) + " " +
831 this.stack
+ ": " + ( e
.message
|| e
), extractStacktrace( e
, 0 ) );
833 // Else next test will carry the responsibility
836 // Restart the tests if they're blocking
837 if ( config
.blocking
) {
842 function runTest( test
) {
843 promise
= test
.callback
.call( test
.testEnvironment
, test
.assert
);
844 test
.resolvePromise( promise
);
852 queueHook: function( hook
, hookName
) {
855 return function runHook() {
856 config
.current
= test
;
857 if ( config
.notrycatch
) {
864 test
.pushFailure( hookName
+ " failed on " + test
.testName
+ ": " +
865 ( error
.message
|| error
), extractStacktrace( error
, 0 ) );
868 function callHook() {
869 promise
= hook
.call( test
.testEnvironment
, test
.assert
);
870 test
.resolvePromise( promise
, hookName
);
875 // Currently only used for module level hooks, can be used to add global level ones
876 hooks: function( handler
) {
879 function processHooks( test
, module
) {
880 if ( module
.parentModule
) {
881 processHooks( test
, module
.parentModule
);
883 if ( module
.testEnvironment
&&
884 QUnit
.objectType( module
.testEnvironment
[ handler
] ) === "function" ) {
885 hooks
.push( test
.queueHook( module
.testEnvironment
[ handler
], handler
) );
889 // Hooks are ignored on skipped tests
891 processHooks( this, this.module
);
897 config
.current
= this;
898 if ( config
.requireExpects
&& this.expected
=== null ) {
899 this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
900 "not called.", this.stack
);
901 } else if ( this.expected
!== null && this.expected
!== this.assertions
.length
) {
902 this.pushFailure( "Expected " + this.expected
+ " assertions, but " +
903 this.assertions
.length
+ " were run", this.stack
);
904 } else if ( this.expected
=== null && !this.assertions
.length
) {
905 this.pushFailure( "Expected at least one assertion, but none were run - call " +
906 "expect(0) to accept zero assertions.", this.stack
);
912 this.runtime
= now() - this.started
;
913 config
.stats
.all
+= this.assertions
.length
;
914 config
.moduleStats
.all
+= this.assertions
.length
;
916 for ( i
= 0; i
< this.assertions
.length
; i
++ ) {
917 if ( !this.assertions
[ i
].result
) {
920 config
.moduleStats
.bad
++;
924 runLoggingCallbacks( "testDone", {
926 module
: this.module
.name
,
927 skipped
: !!this.skip
,
929 passed
: this.assertions
.length
- bad
,
930 total
: this.assertions
.length
,
931 runtime
: this.runtime
,
934 assertions
: this.assertions
,
940 // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
941 duration
: this.runtime
944 // QUnit.reset() is deprecated and will be replaced for a new
945 // fixture reset function on QUnit 2.0/2.1.
946 // It's still called here for backwards compatibility handling
949 config
.current
= undefined;
956 if ( !this.valid() ) {
962 // Each of these can by async
968 test
.hooks( "beforeEach" ),
973 test
.hooks( "afterEach" ).reverse(),
984 // Prioritize previously failed tests, detected from sessionStorage
985 priority
= QUnit
.config
.reorder
&& defined
.sessionStorage
&&
986 +sessionStorage
.getItem( "qunit-test-" + this.module
.name
+ "-" + this.testName
);
988 return synchronize( run
, priority
, config
.seed
);
991 pushResult: function( resultInfo
) {
993 // Destructure of resultInfo = { result, actual, expected, message, negative }
996 module
: this.module
.name
,
998 result
: resultInfo
.result
,
999 message
: resultInfo
.message
,
1000 actual
: resultInfo
.actual
,
1001 expected
: resultInfo
.expected
,
1002 testId
: this.testId
,
1003 negative
: resultInfo
.negative
|| false,
1004 runtime
: now() - this.started
1007 if ( !resultInfo
.result
) {
1008 source
= sourceFromStacktrace();
1011 details
.source
= source
;
1015 runLoggingCallbacks( "log", details
);
1017 this.assertions
.push( {
1018 result
: !!resultInfo
.result
,
1019 message
: resultInfo
.message
1023 pushFailure: function( message
, source
, actual
) {
1024 if ( !( this instanceof Test
) ) {
1025 throw new Error( "pushFailure() assertion outside test context, was " +
1026 sourceFromStacktrace( 2 ) );
1030 module
: this.module
.name
,
1031 name
: this.testName
,
1033 message
: message
|| "error",
1034 actual
: actual
|| null,
1035 testId
: this.testId
,
1036 runtime
: now() - this.started
1040 details
.source
= source
;
1043 runLoggingCallbacks( "log", details
);
1045 this.assertions
.push( {
1051 resolvePromise: function( promise
, phase
) {
1054 if ( promise
!= null ) {
1055 then
= promise
.then
;
1056 if ( QUnit
.objectType( then
) === "function" ) {
1060 function() { QUnit
.start(); },
1062 message
= "Promise rejected " +
1063 ( !phase
? "during" : phase
.replace( /Each$/, "" ) ) +
1064 " " + test
.testName
+ ": " + ( error
.message
|| error
);
1065 test
.pushFailure( message
, extractStacktrace( error
, 0 ) );
1067 // Else next test will carry the responsibility
1079 var filter
= config
.filter
,
1080 regexFilter
= /^(!?)\/([\w\W]*)\/(i?$)/.exec( filter
),
1081 module
= config
.module
&& config
.module
.toLowerCase(),
1082 fullName
= ( this.module
.name
+ ": " + this.testName
);
1084 function moduleChainNameMatch( testModule
) {
1085 var testModuleName
= testModule
.name
? testModule
.name
.toLowerCase() : null;
1086 if ( testModuleName
=== module
) {
1088 } else if ( testModule
.parentModule
) {
1089 return moduleChainNameMatch( testModule
.parentModule
);
1095 function moduleChainIdMatch( testModule
) {
1096 return inArray( testModule
.moduleId
, config
.moduleId
) > -1 ||
1097 testModule
.parentModule
&& moduleChainIdMatch( testModule
.parentModule
);
1100 // Internally-generated tests are always valid
1101 if ( this.callback
&& this.callback
.validTest
) {
1105 if ( config
.moduleId
&& config
.moduleId
.length
> 0 &&
1106 !moduleChainIdMatch( this.module
) ) {
1111 if ( config
.testId
&& config
.testId
.length
> 0 &&
1112 inArray( this.testId
, config
.testId
) < 0 ) {
1117 if ( module
&& !moduleChainNameMatch( this.module
) ) {
1125 return regexFilter
?
1126 this.regexFilter( !!regexFilter
[ 1 ], regexFilter
[ 2 ], regexFilter
[ 3 ], fullName
) :
1127 this.stringFilter( filter
, fullName
);
1130 regexFilter: function( exclude
, pattern
, flags
, fullName
) {
1131 var regex
= new RegExp( pattern
, flags
);
1132 var match
= regex
.test( fullName
);
1134 return match
!== exclude
;
1137 stringFilter: function( filter
, fullName
) {
1138 filter
= filter
.toLowerCase();
1139 fullName
= fullName
.toLowerCase();
1141 var include
= filter
.charAt( 0 ) !== "!";
1143 filter
= filter
.slice( 1 );
1146 // If the filter matches, we need to honour include
1147 if ( fullName
.indexOf( filter
) !== -1 ) {
1151 // Otherwise, do the opposite
1156 // Resets the test setup. Useful for tests that modify the DOM.
1158 DEPRECATED: Use multiple tests instead of resetting inside a test.
1159 Use testStart or testDone for custom cleanup.
1160 This method will throw an error in 2.0, and will be removed in 2.1
1162 QUnit
.reset = function() {
1164 // Return on non-browser environments
1165 // This is necessary to not break on node tests
1166 if ( !defined
.document
) {
1170 var fixture
= defined
.document
&& document
.getElementById
&&
1171 document
.getElementById( "qunit-fixture" );
1174 fixture
.innerHTML
= config
.fixture
;
1178 QUnit
.pushFailure = function() {
1179 if ( !QUnit
.config
.current
) {
1180 throw new Error( "pushFailure() assertion outside test context, in " +
1181 sourceFromStacktrace( 2 ) );
1184 // Gets current test obj
1185 var currentTest
= QUnit
.config
.current
;
1187 return currentTest
.pushFailure
.apply( currentTest
, arguments
);
1190 // Based on Java's String.hashCode, a simple but not
1191 // rigorously collision resistant hashing function
1192 function generateHash( module
, testName
) {
1196 str
= module
+ "\x1C" + testName
,
1199 for ( ; i
< len
; i
++ ) {
1200 hash
= ( ( hash
<< 5 ) - hash
) + str
.charCodeAt( i
);
1204 // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
1205 // strictly necessary but increases user understanding that the id is a SHA-like hash
1206 hex
= ( 0x100000000 + hash
).toString( 16 );
1207 if ( hex
.length
< 8 ) {
1208 hex
= "0000000" + hex
;
1211 return hex
.slice( -8 );
1214 function synchronize( callback
, priority
, seed
) {
1215 var last
= !priority
,
1218 if ( QUnit
.objectType( callback
) === "array" ) {
1219 while ( callback
.length
) {
1220 synchronize( callback
.shift() );
1226 config
.queue
.splice( priorityCount
++, 0, callback
);
1227 } else if ( seed
) {
1228 if ( !unitSampler
) {
1229 unitSampler
= unitSamplerGenerator( seed
);
1232 // Insert into a random position after all priority items
1233 index
= Math
.floor( unitSampler() * ( config
.queue
.length
- priorityCount
+ 1 ) );
1234 config
.queue
.splice( priorityCount
+ index
, 0, callback
);
1236 config
.queue
.push( callback
);
1239 if ( config
.autorun
&& !config
.blocking
) {
1244 function unitSamplerGenerator( seed
) {
1246 // 32-bit xorshift, requires only a nonzero seed
1247 // http://excamera.com/sphinx/article-xorshift.html
1248 var sample
= parseInt( generateHash( seed
), 16 ) || -1;
1250 sample
^= sample
<< 13;
1251 sample
^= sample
>>> 17;
1252 sample
^= sample
<< 5;
1254 // ECMAScript has no unsigned number type
1256 sample
+= 0x100000000;
1259 return sample
/ 0x100000000;
1263 function saveGlobal() {
1264 config
.pollution
= [];
1266 if ( config
.noglobals
) {
1267 for ( var key
in global
) {
1268 if ( hasOwn
.call( global
, key
) ) {
1270 // In Opera sometimes DOM element ids show up here, ignore them
1271 if ( /^qunit-test-output/.test( key
) ) {
1274 config
.pollution
.push( key
);
1280 function checkPollution() {
1283 old
= config
.pollution
;
1287 newGlobals
= diff( config
.pollution
, old
);
1288 if ( newGlobals
.length
> 0 ) {
1289 QUnit
.pushFailure( "Introduced global variable(s): " + newGlobals
.join( ", " ) );
1292 deletedGlobals
= diff( old
, config
.pollution
);
1293 if ( deletedGlobals
.length
> 0 ) {
1294 QUnit
.pushFailure( "Deleted global variable(s): " + deletedGlobals
.join( ", " ) );
1298 // Will be exposed as QUnit.asyncTest
1299 function asyncTest( testName
, expected
, callback
) {
1300 if ( arguments
.length
=== 2 ) {
1301 callback
= expected
;
1305 QUnit
.test( testName
, expected
, callback
, true );
1308 // Will be exposed as QUnit.test
1309 function test( testName
, expected
, callback
, async
) {
1310 if ( focused
) { return; }
1314 if ( arguments
.length
=== 2 ) {
1315 callback
= expected
;
1319 newTest
= new Test( {
1329 // Will be exposed as QUnit.skip
1330 function skip( testName
) {
1331 if ( focused
) { return; }
1333 var test
= new Test( {
1341 // Will be exposed as QUnit.only
1342 function only( testName
, expected
, callback
, async
) {
1345 if ( focused
) { return; }
1347 QUnit
.config
.queue
.length
= 0;
1350 if ( arguments
.length
=== 2 ) {
1351 callback
= expected
;
1355 newTest
= new Test( {
1365 function Assert( testContext
) {
1366 this.test
= testContext
;
1370 QUnit
.assert
= Assert
.prototype = {
1372 // Specify the number of expected assertions to guarantee that failed test
1373 // (no assertions are run at all) don't slip through.
1374 expect: function( asserts
) {
1375 if ( arguments
.length
=== 1 ) {
1376 this.test
.expected
= asserts
;
1378 return this.test
.expected
;
1382 // Increment this Test's semaphore counter, then return a function that
1383 // decrements that counter a maximum of once.
1384 async: function( count
) {
1385 var test
= this.test
,
1387 acceptCallCount
= count
;
1389 if ( typeof acceptCallCount
=== "undefined" ) {
1390 acceptCallCount
= 1;
1393 test
.semaphore
+= 1;
1394 test
.usedAsync
= true;
1397 return function done() {
1400 test
.pushFailure( "Too many calls to the `assert.async` callback",
1401 sourceFromStacktrace( 2 ) );
1404 acceptCallCount
-= 1;
1405 if ( acceptCallCount
> 0 ) {
1409 test
.semaphore
-= 1;
1415 // Exports test.push() to the user API
1416 // Alias of pushResult.
1417 push: function( result
, actual
, expected
, message
, negative
) {
1418 var currentAssert
= this instanceof Assert
? this : QUnit
.config
.current
.assert
;
1419 return currentAssert
.pushResult( {
1428 pushResult: function( resultInfo
) {
1430 // Destructure of resultInfo = { result, actual, expected, message, negative }
1432 currentTest
= ( assert
instanceof Assert
&& assert
.test
) || QUnit
.config
.current
;
1434 // Backwards compatibility fix.
1435 // Allows the direct use of global exported assertions and QUnit.assert.*
1436 // Although, it's use is not recommended as it can leak assertions
1437 // to other tests from async tests, because we only get a reference to the current test,
1438 // not exactly the test where assertion were intended to be called.
1439 if ( !currentTest
) {
1440 throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
1443 if ( currentTest
.usedAsync
=== true && currentTest
.semaphore
=== 0 ) {
1444 currentTest
.pushFailure( "Assertion after the final `assert.async` was resolved",
1445 sourceFromStacktrace( 2 ) );
1447 // Allow this assertion to continue running anyway...
1450 if ( !( assert
instanceof Assert
) ) {
1451 assert
= currentTest
.assert
;
1454 return assert
.test
.pushResult( resultInfo
);
1457 ok: function( result
, message
) {
1458 message
= message
|| ( result
? "okay" : "failed, expected argument to be truthy, was: " +
1459 QUnit
.dump
.parse( result
) );
1468 notOk: function( result
, message
) {
1469 message
= message
|| ( !result
? "okay" : "failed, expected argument to be falsy, was: " +
1470 QUnit
.dump
.parse( result
) );
1479 equal: function( actual
, expected
, message
) {
1480 /*jshint eqeqeq:false */
1482 result
: expected
== actual
,
1489 notEqual: function( actual
, expected
, message
) {
1490 /*jshint eqeqeq:false */
1492 result
: expected
!= actual
,
1500 propEqual: function( actual
, expected
, message
) {
1501 actual
= objectValues( actual
);
1502 expected
= objectValues( expected
);
1504 result
: QUnit
.equiv( actual
, expected
),
1511 notPropEqual: function( actual
, expected
, message
) {
1512 actual
= objectValues( actual
);
1513 expected
= objectValues( expected
);
1515 result
: !QUnit
.equiv( actual
, expected
),
1523 deepEqual: function( actual
, expected
, message
) {
1525 result
: QUnit
.equiv( actual
, expected
),
1532 notDeepEqual: function( actual
, expected
, message
) {
1534 result
: !QUnit
.equiv( actual
, expected
),
1542 strictEqual: function( actual
, expected
, message
) {
1544 result
: expected
=== actual
,
1551 notStrictEqual: function( actual
, expected
, message
) {
1553 result
: expected
!== actual
,
1561 "throws": function( block
, expected
, message
) {
1562 var actual
, expectedType
,
1563 expectedOutput
= expected
,
1565 currentTest
= ( this instanceof Assert
&& this.test
) || QUnit
.config
.current
;
1567 // 'expected' is optional unless doing string comparison
1568 if ( message
== null && typeof expected
=== "string" ) {
1573 currentTest
.ignoreGlobalErrors
= true;
1575 block
.call( currentTest
.testEnvironment
);
1579 currentTest
.ignoreGlobalErrors
= false;
1582 expectedType
= QUnit
.objectType( expected
);
1584 // We don't want to validate thrown error
1587 expectedOutput
= null;
1589 // Expected is a regexp
1590 } else if ( expectedType
=== "regexp" ) {
1591 ok
= expected
.test( errorString( actual
) );
1593 // Expected is a string
1594 } else if ( expectedType
=== "string" ) {
1595 ok
= expected
=== errorString( actual
);
1597 // Expected is a constructor, maybe an Error constructor
1598 } else if ( expectedType
=== "function" && actual
instanceof expected
) {
1601 // Expected is an Error object
1602 } else if ( expectedType
=== "object" ) {
1603 ok
= actual
instanceof expected
.constructor &&
1604 actual
.name
=== expected
.name
&&
1605 actual
.message
=== expected
.message
;
1607 // Expected is a validation function which returns true if validation passed
1608 } else if ( expectedType
=== "function" && expected
.call( {}, actual
) === true ) {
1609 expectedOutput
= null;
1614 currentTest
.assert
.pushResult( {
1617 expected
: expectedOutput
,
1623 // Provide an alternative to assert.throws(), for environments that consider throws a reserved word
1624 // Known to us are: Closure Compiler, Narwhal
1626 /*jshint sub:true */
1627 Assert
.prototype.raises
= Assert
.prototype [ "throws" ]; //jscs:ignore requireDotNotation
1630 function errorString( error
) {
1632 resultErrorString
= error
.toString();
1633 if ( resultErrorString
.substring( 0, 7 ) === "[object" ) {
1634 name
= error
.name
? error
.name
.toString() : "Error";
1635 message
= error
.message
? error
.message
.toString() : "";
1636 if ( name
&& message
) {
1637 return name
+ ": " + message
;
1638 } else if ( name
) {
1640 } else if ( message
) {
1646 return resultErrorString
;
1650 // Test for equality any JavaScript type.
1651 // Author: Philippe Rathé <prathe@gmail.com>
1652 QUnit
.equiv
= ( function() {
1654 // Stack to decide between skip/abort functions
1657 // Stack to avoiding loops from circular referencing
1661 var getProto
= Object
.getPrototypeOf
|| function( obj
) {
1663 /*jshint proto: true */
1664 return obj
.__proto__
;
1667 function useStrictEquality( b
, a
) {
1669 // To catch short annotation VS 'new' annotation of a declaration. e.g.:
1671 // `var j = new Number(1);`
1672 if ( typeof a
=== "object" ) {
1675 if ( typeof b
=== "object" ) {
1682 function compareConstructors( a
, b
) {
1683 var protoA
= getProto( a
);
1684 var protoB
= getProto( b
);
1686 // Comparing constructors is more strict than using `instanceof`
1687 if ( a
.constructor === b
.constructor ) {
1692 // If the obj prototype descends from a null constructor, treat it
1693 // as a null prototype.
1694 if ( protoA
&& protoA
.constructor === null ) {
1697 if ( protoB
&& protoB
.constructor === null ) {
1701 // Allow objects with no prototype to be equivalent to
1702 // objects with Object as their constructor.
1703 if ( ( protoA
=== null && protoB
=== Object
.prototype ) ||
1704 ( protoB
=== null && protoA
=== Object
.prototype ) ) {
1711 function getRegExpFlags( regexp
) {
1712 return "flags" in regexp
? regexp
.flags
: regexp
.toString().match( /[gimuy]*$/ )[ 0 ];
1716 "string": useStrictEquality
,
1717 "boolean": useStrictEquality
,
1718 "number": useStrictEquality
,
1719 "null": useStrictEquality
,
1720 "undefined": useStrictEquality
,
1721 "symbol": useStrictEquality
,
1722 "date": useStrictEquality
,
1728 "regexp": function( b
, a
) {
1729 return a
.source
=== b
.source
&&
1731 // Include flags in the comparison
1732 getRegExpFlags( a
) === getRegExpFlags( b
);
1735 // - skip when the property is a method of an instance (OOP)
1736 // - abort otherwise,
1737 // initial === would have catch identical references anyway
1738 "function": function() {
1739 var caller
= callers
[ callers
.length
- 1 ];
1740 return caller
!== Object
&& typeof caller
!== "undefined";
1743 "array": function( b
, a
) {
1744 var i
, j
, len
, loop
, aCircular
, bCircular
;
1747 if ( len
!== b
.length
) {
1753 // Track reference to avoid circular references
1756 for ( i
= 0; i
< len
; i
++ ) {
1758 for ( j
= 0; j
< parents
.length
; j
++ ) {
1759 aCircular
= parents
[ j
] === a
[ i
];
1760 bCircular
= parentsB
[ j
] === b
[ i
];
1761 if ( aCircular
|| bCircular
) {
1762 if ( a
[ i
] === b
[ i
] || aCircular
&& bCircular
) {
1771 if ( !loop
&& !innerEquiv( a
[ i
], b
[ i
] ) ) {
1782 "set": function( b
, a
) {
1786 if ( a
.size
!== b
.size
) {
1790 a
.forEach( function( aVal
) {
1793 b
.forEach( function( bVal
) {
1794 if ( innerEquiv( bVal
, aVal
) ) {
1807 "map": function( b
, a
) {
1811 if ( a
.size
!== b
.size
) {
1815 a
.forEach( function( aVal
, aKey
) {
1818 b
.forEach( function( bVal
, bKey
) {
1819 if ( innerEquiv( [ bVal
, bKey
], [ aVal
, aKey
] ) ) {
1832 "object": function( b
, a
) {
1833 var i
, j
, loop
, aCircular
, bCircular
;
1837 var aProperties
= [];
1838 var bProperties
= [];
1840 if ( compareConstructors( a
, b
) === false ) {
1844 // Stack constructor before traversing properties
1845 callers
.push( a
.constructor );
1847 // Track reference to avoid circular references
1851 // Be strict: don't ensure hasOwnProperty and go deep
1854 for ( j
= 0; j
< parents
.length
; j
++ ) {
1855 aCircular
= parents
[ j
] === a
[ i
];
1856 bCircular
= parentsB
[ j
] === b
[ i
];
1857 if ( aCircular
|| bCircular
) {
1858 if ( a
[ i
] === b
[ i
] || aCircular
&& bCircular
) {
1866 aProperties
.push( i
);
1867 if ( !loop
&& !innerEquiv( a
[ i
], b
[ i
] ) ) {
1876 // Unstack, we are done
1881 // Collect b's properties
1882 bProperties
.push( i
);
1885 // Ensures identical properties name
1886 return eq
&& innerEquiv( aProperties
.sort(), bProperties
.sort() );
1890 function typeEquiv( a
, b
) {
1891 var type
= QUnit
.objectType( a
);
1892 return QUnit
.objectType( b
) === type
&& callbacks
[ type
]( b
, a
);
1895 // The real equiv function
1896 function innerEquiv( a
, b
) {
1898 // We're done when there's nothing more to compare
1899 if ( arguments
.length
< 2 ) {
1903 // Require type-specific equality
1904 return ( a
=== b
|| typeEquiv( a
, b
) ) &&
1906 // ...across all consecutive argument pairs
1907 ( arguments
.length
=== 2 || innerEquiv
.apply( this, [].slice
.call( arguments
, 1 ) ) );
1913 // Based on jsDump by Ariel Flesler
1914 // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
1915 QUnit
.dump
= ( function() {
1916 function quote( str
) {
1917 return "\"" + str
.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\"";
1919 function literal( o ) {
1922 function join( pre, arr, post ) {
1923 var s = dump.separator(),
1924 base = dump.indent(),
1925 inner = dump.indent( 1 );
1927 arr = arr.join( "," + s + inner );
1932 return [ pre, inner + arr, base + post ].join( s );
1934 function array( arr, stack ) {
1936 ret = new Array( i );
1938 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1939 return "[object Array
]";
1944 ret[ i ] = this.parse( arr[ i ], undefined, stack );
1947 return join( "[", ret, "]" );
1950 var reName = /^function (\w+)/,
1953 // The objType is used mostly internally, you can fix a (custom) type in advance
1954 parse: function( obj, objType, stack ) {
1955 stack = stack || [];
1956 var res, parser, parserType,
1957 inStack = inArray( obj, stack );
1959 if ( inStack !== -1 ) {
1960 return "recursion(" + ( inStack - stack.length ) + ")";
1963 objType = objType || this.typeOf( obj );
1964 parser = this.parsers[ objType ];
1965 parserType = typeof parser;
1967 if ( parserType === "function" ) {
1969 res = parser.call( this, obj, stack );
1973 return ( parserType === "string
" ) ? parser : this.parsers.error;
1975 typeOf: function( obj ) {
1977 if ( obj === null ) {
1979 } else if ( typeof obj === "undefined" ) {
1981 } else if ( QUnit.is( "regexp
", obj ) ) {
1983 } else if ( QUnit.is( "date
", obj ) ) {
1985 } else if ( QUnit.is( "function", obj ) ) {
1987 } else if ( obj.setInterval !== undefined &&
1988 obj.document !== undefined &&
1989 obj.nodeType === undefined ) {
1991 } else if ( obj.nodeType === 9 ) {
1993 } else if ( obj.nodeType ) {
1998 toString.call( obj ) === "[object Array
]" ||
2001 ( typeof obj.length === "number
" && obj.item !== undefined &&
2002 ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
2003 obj[ 0 ] === undefined ) ) )
2006 } else if ( obj.constructor === Error.prototype.constructor ) {
2014 separator: function() {
2015 return this.multiline ? this.HTML ? "<br
/>" : "\n" : this.HTML ? " " : " ";
2018 // Extra can be a number, shortcut for increasing-calling-decreasing
2019 indent: function( extra ) {
2020 if ( !this.multiline ) {
2023 var chr = this.indentChar;
2025 chr = chr.replace( /\t/g, " " ).replace( / /g, " " );
2027 return new Array( this.depth + ( extra || 0 ) ).join( chr );
2030 this.depth += a || 1;
2032 down: function( a ) {
2033 this.depth -= a || 1;
2035 setParser: function( name, parser ) {
2036 this.parsers[ name ] = parser;
2039 // The next 3 are exposed so you can use them
2044 maxDepth: QUnit.config.maxDepth,
2046 // This is the list of parsers, to modify them, use dump.setParser
2049 document: "[Document
]",
2050 error: function( error ) {
2051 return "Error(\"" + error.message + "\")";
2053 unknown: "[Unknown
]",
2055 "undefined": "undefined",
2056 "function": function( fn ) {
2057 var ret = "function",
2059 // Functions never have name in IE
2060 name = "name
" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
2067 ret = [ ret, dump.parse( fn, "functionArgs
" ), "){" ].join( "" );
2068 return join( ret, dump.parse( fn, "functionCode
" ), "}" );
2073 object: function( map, stack ) {
2074 var keys, key, val, i, nonEnumerableProperties,
2077 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
2078 return "[object Object
]";
2083 for ( key in map ) {
2087 // Some properties are not always enumerable on Error objects.
2088 nonEnumerableProperties = [ "message
", "name
" ];
2089 for ( i in nonEnumerableProperties ) {
2090 key = nonEnumerableProperties[ i ];
2091 if ( key in map && inArray( key, keys ) < 0 ) {
2096 for ( i = 0; i < keys.length; i++ ) {
2099 ret.push( dump.parse( key, "key
" ) + ": " +
2100 dump.parse( val, undefined, stack ) );
2103 return join( "{", ret, "}" );
2105 node: function( node ) {
2107 open = dump.HTML ? "<
;" : "<",
2108 close = dump.HTML ? ">
;" : ">",
2109 tag = node.nodeName.toLowerCase(),
2111 attrs = node.attributes;
2114 for ( i = 0, len = attrs.length; i < len; i++ ) {
2115 val = attrs[ i ].nodeValue;
2117 // IE6 includes all attributes in .attributes, even ones not explicitly
2118 // set. Those have values like undefined, null, 0, false, "" or
2120 if ( val && val !== "inherit
" ) {
2121 ret += " " + attrs[ i ].nodeName + "=" +
2122 dump.parse( val, "attribute
" );
2128 // Show content of TextNode or CDATASection
2129 if ( node.nodeType === 3 || node.nodeType === 4 ) {
2130 ret += node.nodeValue;
2133 return ret + open + "/" + tag + close;
2136 // Function calls it internally, it's the arguments part of the function
2137 functionArgs: function( fn ) {
2145 args = new Array( l );
2149 args[ l ] = String.fromCharCode( 97 + l );
2151 return " " + args.join( ", " ) + " ";
2154 // Object calls it internally, the key part of an item in a map
2157 // Function calls it internally, it's the content of the function
2158 functionCode: "[code
]",
2160 // Node calls it internally, it's a html attribute value
2169 // If true, entities are escaped ( <, >, \t, space and \n )
2175 // If true, items in a collection, are separated by a \n, else just a space.
2183 QUnit.jsDump = QUnit.dump;
2186 // Extend assert methods to QUnit for Backwards compatibility
2189 assertions = Assert.prototype;
2191 function applyCurrent( current ) {
2193 var assert = new Assert( QUnit.config.current );
2194 current.apply( assert, arguments );
2198 for ( i in assertions ) {
2199 QUnit[ i ] = applyCurrent( assertions[ i ] );
2203 // For browser, export only select globals
2204 if ( defined.document ) {
2229 for ( i = 0, l = keys.length; i < l; i++ ) {
2230 window[ keys[ i ] ] = QUnit[ keys[ i ] ];
2234 window.QUnit = QUnit;
2238 if ( typeof module !== "undefined" && module && module.exports ) {
2239 module.exports = QUnit;
2241 // For consistency with CommonJS environments' exports
2242 module.exports.QUnit = QUnit;
2245 // For CommonJS with exports, but without module.exports, like Rhino
2246 if ( typeof exports !== "undefined" && exports ) {
2247 exports.QUnit = QUnit;
2250 if ( typeof define === "function" && define.amd ) {
2251 define( function() {
2254 QUnit.config.autostart = false;
2257 // Get a reference to the global object, like window in browsers
2264 // Only interact with URLs via window.location
2265 var location = typeof window !== "undefined" && window.location;
2270 var urlParams = getUrlParams();
2272 QUnit.urlParams = urlParams;
2274 // Match module/test by inclusion in an array
2275 QUnit.config.moduleId = [].concat( urlParams.moduleId || [] );
2276 QUnit.config.testId = [].concat( urlParams.testId || [] );
2278 // Exact case-insensitive match of the module name
2279 QUnit.config.module = urlParams.module;
2281 // Regular expression or case-insenstive substring match against "moduleName
: testName
"
2282 QUnit.config.filter = urlParams.filter;
2284 // Test order randomization
2285 if ( urlParams.seed === true ) {
2287 // Generate a random seed if the option is specified without a value
2288 QUnit.config.seed = Math.random().toString( 36 ).slice( 2 );
2289 } else if ( urlParams.seed ) {
2290 QUnit.config.seed = urlParams.seed;
2293 // Add URL-parameter-mapped config values with UI form rendering data
2294 QUnit.config.urlConfig.push(
2297 label: "Hide passed tests
",
2298 tooltip: "Only show tests and assertions that fail
. Stored as query
-strings
."
2302 label: "Check
for Globals
",
2303 tooltip: "Enabling
this will test
if any test introduces
new properties on the
" +
2304 "global
object (`window` in Browsers
). Stored as query
-strings
."
2308 label: "No
try-catch",
2309 tooltip: "Enabling
this will run tests outside
of a
try-catch block
. Makes debugging
" +
2310 "exceptions
in IE reasonable
. Stored as query
-strings
."
2314 QUnit.begin( function() {
2316 urlConfig = QUnit.config.urlConfig;
2318 for ( i = 0; i < urlConfig.length; i++ ) {
2320 // Options can be either strings or objects with nonempty "id
" properties
2321 option = QUnit.config.urlConfig[ i ];
2322 if ( typeof option !== "string
" ) {
2326 if ( QUnit.config[ option ] === undefined ) {
2327 QUnit.config[ option ] = urlParams[ option ];
2332 function getUrlParams() {
2333 var i, param, name, value;
2335 var params = location.search.slice( 1 ).split( "&" );
2336 var length = params.length;
2338 for ( i = 0; i < length; i++ ) {
2339 if ( params[ i ] ) {
2340 param = params[ i ].split( "=" );
2341 name = decodeURIComponent( param[ 0 ] );
2343 // Allow just a key to turn on a flag, e.g., test.html?noglobals
2344 value = param.length === 1 ||
2345 decodeURIComponent( param.slice( 1 ).join( "=" ) ) ;
2346 if ( urlParams[ name ] ) {
2347 urlParams[ name ] = [].concat( urlParams[ name ], value );
2349 urlParams[ name ] = value;
2357 // Don't load the HTML Reporter on non-browser environments
2358 if ( typeof window === "undefined" || !window.document ) {
2362 // Deprecated QUnit.init - Ref #530
2363 // Re-initialize the configuration options
2364 QUnit.init = function() {
2365 var config = QUnit.config;
2367 config.stats = { all: 0, bad: 0 };
2368 config.moduleStats = { all: 0, bad: 0 };
2370 config.updateRate = 1000;
2371 config.blocking = false;
2372 config.autostart = true;
2373 config.autorun = false;
2380 var config = QUnit.config,
2381 document = window.document,
2382 collapseNext = false,
2383 hasOwn = Object.prototype.hasOwnProperty,
2384 unfilteredUrl = setUrl( { filter: undefined, module: undefined,
2385 moduleId: undefined, testId: undefined } ),
2387 sessionStorage: ( function() {
2388 var x = "qunit
-test
-string
";
2390 sessionStorage.setItem( x, x );
2391 sessionStorage.removeItem( x );
2401 * Escape text for attribute or text content.
2403 function escapeText( s ) {
2409 // Both single quotes and double quotes (for attributes)
2410 return s.replace( /['"<>&]/g
, function( s
) {
2427 * @param {HTMLElement} elem
2428 * @param {string} type
2429 * @param {Function} fn
2431 function addEvent( elem
, type
, fn
) {
2432 if ( elem
.addEventListener
) {
2434 // Standards-based browsers
2435 elem
.addEventListener( type
, fn
, false );
2436 } else if ( elem
.attachEvent
) {
2439 elem
.attachEvent( "on" + type
, function() {
2440 var event
= window
.event
;
2441 if ( !event
.target
) {
2442 event
.target
= event
.srcElement
|| document
;
2445 fn
.call( elem
, event
);
2451 * @param {Array|NodeList} elems
2452 * @param {string} type
2453 * @param {Function} fn
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 getUrlConfigHtml() {
2498 escaped
, escapedTooltip
,
2500 urlConfig
= config
.urlConfig
,
2503 for ( i
= 0; i
< urlConfig
.length
; i
++ ) {
2505 // Options can be either strings or objects with nonempty "id" properties
2506 val
= config
.urlConfig
[ i
];
2507 if ( typeof val
=== "string" ) {
2514 escaped
= escapeText( val
.id
);
2515 escapedTooltip
= escapeText( val
.tooltip
);
2517 if ( !val
.value
|| typeof val
.value
=== "string" ) {
2518 urlConfigHtml
+= "<input id='qunit-urlconfig-" + escaped
+
2519 "' name='" + escaped
+ "' type='checkbox'" +
2520 ( val
.value
? " value='" + escapeText( val
.value
) + "'" : "" ) +
2521 ( config
[ val
.id
] ? " checked='checked'" : "" ) +
2522 " title='" + escapedTooltip
+ "' /><label for='qunit-urlconfig-" + escaped
+
2523 "' title='" + escapedTooltip
+ "'>" + val
.label
+ "</label>";
2525 urlConfigHtml
+= "<label for='qunit-urlconfig-" + escaped
+
2526 "' title='" + escapedTooltip
+ "'>" + val
.label
+
2527 ": </label><select id='qunit-urlconfig-" + escaped
+
2528 "' name='" + escaped
+ "' title='" + escapedTooltip
+ "'><option></option>";
2530 if ( QUnit
.is( "array", val
.value
) ) {
2531 for ( j
= 0; j
< val
.value
.length
; j
++ ) {
2532 escaped
= escapeText( val
.value
[ j
] );
2533 urlConfigHtml
+= "<option value='" + escaped
+ "'" +
2534 ( config
[ val
.id
] === val
.value
[ j
] ?
2535 ( selection
= true ) && " selected='selected'" : "" ) +
2536 ">" + escaped
+ "</option>";
2539 for ( j
in val
.value
) {
2540 if ( hasOwn
.call( val
.value
, j
) ) {
2541 urlConfigHtml
+= "<option value='" + escapeText( j
) + "'" +
2542 ( config
[ val
.id
] === j
?
2543 ( selection
= true ) && " selected='selected'" : "" ) +
2544 ">" + escapeText( val
.value
[ j
] ) + "</option>";
2548 if ( config
[ val
.id
] && !selection
) {
2549 escaped
= escapeText( config
[ val
.id
] );
2550 urlConfigHtml
+= "<option value='" + escaped
+
2551 "' selected='selected' disabled='disabled'>" + escaped
+ "</option>";
2553 urlConfigHtml
+= "</select>";
2557 return urlConfigHtml
;
2560 // Handle "click" events on toolbar checkboxes and "change" for select menus.
2561 // Updates the URL with the new state of `config.urlConfig` values.
2562 function toolbarChanged() {
2563 var updatedUrl
, value
, tests
,
2567 // Detect if field is a select menu or a checkbox
2568 if ( "selectedIndex" in field
) {
2569 value
= field
.options
[ field
.selectedIndex
].value
|| undefined;
2571 value
= field
.checked
? ( field
.defaultValue
|| true ) : undefined;
2574 params
[ field
.name
] = value
;
2575 updatedUrl
= setUrl( params
);
2577 // Check if we can apply the change without a page refresh
2578 if ( "hidepassed" === field
.name
&& "replaceState" in window
.history
) {
2579 QUnit
.urlParams
[ field
.name
] = value
;
2580 config
[ field
.name
] = value
|| false;
2581 tests
= id( "qunit-tests" );
2583 toggleClass( tests
, "hidepass", value
|| false );
2585 window
.history
.replaceState( null, "", updatedUrl
);
2587 window
.location
= updatedUrl
;
2591 function setUrl( params
) {
2592 var key
, arrValue
, i
,
2594 location
= window
.location
;
2596 params
= QUnit
.extend( QUnit
.extend( {}, QUnit
.urlParams
), params
);
2598 for ( key
in params
) {
2600 // Skip inherited or undefined properties
2601 if ( hasOwn
.call( params
, key
) && params
[ key
] !== undefined ) {
2603 // Output a parameter for each value of this key (but usually just one)
2604 arrValue
= [].concat( params
[ key
] );
2605 for ( i
= 0; i
< arrValue
.length
; i
++ ) {
2606 querystring
+= encodeURIComponent( key
);
2607 if ( arrValue
[ i
] !== true ) {
2608 querystring
+= "=" + encodeURIComponent( arrValue
[ i
] );
2614 return location
.protocol
+ "//" + location
.host
+
2615 location
.pathname
+ querystring
.slice( 0, -1 );
2618 function applyUrlParams() {
2620 modulesList
= id( "qunit-modulefilter" ),
2621 filter
= id( "qunit-filter-input" ).value
;
2623 selectedModule
= modulesList
?
2624 decodeURIComponent( modulesList
.options
[ modulesList
.selectedIndex
].value
) :
2627 window
.location
= setUrl( {
2628 module
: ( selectedModule
=== "" ) ? undefined : selectedModule
,
2629 filter
: ( filter
=== "" ) ? undefined : filter
,
2631 // Remove moduleId and testId filters
2632 moduleId
: undefined,
2637 function toolbarUrlConfigContainer() {
2638 var urlConfigContainer
= document
.createElement( "span" );
2640 urlConfigContainer
.innerHTML
= getUrlConfigHtml();
2641 addClass( urlConfigContainer
, "qunit-url-config" );
2643 // For oldIE support:
2644 // * Add handlers to the individual elements instead of the container
2645 // * Use "click" instead of "change" for checkboxes
2646 addEvents( urlConfigContainer
.getElementsByTagName( "input" ), "click", toolbarChanged
);
2647 addEvents( urlConfigContainer
.getElementsByTagName( "select" ), "change", toolbarChanged
);
2649 return urlConfigContainer
;
2652 function toolbarLooseFilter() {
2653 var filter
= document
.createElement( "form" ),
2654 label
= document
.createElement( "label" ),
2655 input
= document
.createElement( "input" ),
2656 button
= document
.createElement( "button" );
2658 addClass( filter
, "qunit-filter" );
2660 label
.innerHTML
= "Filter: ";
2662 input
.type
= "text";
2663 input
.value
= config
.filter
|| "";
2664 input
.name
= "filter";
2665 input
.id
= "qunit-filter-input";
2667 button
.innerHTML
= "Go";
2669 label
.appendChild( input
);
2671 filter
.appendChild( label
);
2672 filter
.appendChild( button
);
2673 addEvent( filter
, "submit", function( ev
) {
2676 if ( ev
&& ev
.preventDefault
) {
2677 ev
.preventDefault();
2686 function toolbarModuleFilterHtml() {
2688 moduleFilterHtml
= "";
2690 if ( !modulesList
.length
) {
2694 moduleFilterHtml
+= "<label for='qunit-modulefilter'>Module: </label>" +
2695 "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
2696 ( QUnit
.urlParams
.module
=== undefined ? "selected='selected'" : "" ) +
2697 ">< All Modules ></option>";
2699 for ( i
= 0; i
< modulesList
.length
; i
++ ) {
2700 moduleFilterHtml
+= "<option value='" +
2701 escapeText( encodeURIComponent( modulesList
[ i
] ) ) + "' " +
2702 ( QUnit
.urlParams
.module
=== modulesList
[ i
] ? "selected='selected'" : "" ) +
2703 ">" + escapeText( modulesList
[ i
] ) + "</option>";
2705 moduleFilterHtml
+= "</select>";
2707 return moduleFilterHtml
;
2710 function toolbarModuleFilter() {
2711 var toolbar
= id( "qunit-testrunner-toolbar" ),
2712 moduleFilter
= document
.createElement( "span" ),
2713 moduleFilterHtml
= toolbarModuleFilterHtml();
2715 if ( !toolbar
|| !moduleFilterHtml
) {
2719 moduleFilter
.setAttribute( "id", "qunit-modulefilter-container" );
2720 moduleFilter
.innerHTML
= moduleFilterHtml
;
2722 addEvent( moduleFilter
.lastChild
, "change", applyUrlParams
);
2724 toolbar
.appendChild( moduleFilter
);
2727 function appendToolbar() {
2728 var toolbar
= id( "qunit-testrunner-toolbar" );
2731 toolbar
.appendChild( toolbarUrlConfigContainer() );
2732 toolbar
.appendChild( toolbarLooseFilter() );
2733 toolbarModuleFilter();
2737 function appendHeader() {
2738 var header
= id( "qunit-header" );
2741 header
.innerHTML
= "<a href='" + escapeText( unfilteredUrl
) + "'>" + header
.innerHTML
+
2746 function appendBanner() {
2747 var banner
= id( "qunit-banner" );
2750 banner
.className
= "";
2754 function appendTestResults() {
2755 var tests
= id( "qunit-tests" ),
2756 result
= id( "qunit-testresult" );
2759 result
.parentNode
.removeChild( result
);
2763 tests
.innerHTML
= "";
2764 result
= document
.createElement( "p" );
2765 result
.id
= "qunit-testresult";
2766 result
.className
= "result";
2767 tests
.parentNode
.insertBefore( result
, tests
);
2768 result
.innerHTML
= "Running...<br /> ";
2772 function storeFixture() {
2773 var fixture
= id( "qunit-fixture" );
2775 config
.fixture
= fixture
.innerHTML
;
2779 function appendFilteredTest() {
2780 var testId
= QUnit
.config
.testId
;
2781 if ( !testId
|| testId
.length
<= 0 ) {
2784 return "<div id='qunit-filteredTest'>Rerunning selected tests: " +
2785 escapeText( testId
.join( ", " ) ) +
2786 " <a id='qunit-clearFilter' href='" +
2787 escapeText( unfilteredUrl
) +
2788 "'>Run all tests</a></div>";
2791 function appendUserAgent() {
2792 var userAgent
= id( "qunit-userAgent" );
2795 userAgent
.innerHTML
= "";
2796 userAgent
.appendChild(
2797 document
.createTextNode(
2798 "QUnit " + QUnit
.version
+ "; " + navigator
.userAgent
2804 function appendInterface() {
2805 var qunit
= id( "qunit" );
2809 "<h1 id='qunit-header'>" + escapeText( document
.title
) + "</h1>" +
2810 "<h2 id='qunit-banner'></h2>" +
2811 "<div id='qunit-testrunner-toolbar'></div>" +
2812 appendFilteredTest() +
2813 "<h2 id='qunit-userAgent'></h2>" +
2814 "<ol id='qunit-tests'></ol>";
2819 appendTestResults();
2824 function appendTestsList( modules
) {
2825 var i
, l
, x
, z
, test
, moduleObj
;
2827 for ( i
= 0, l
= modules
.length
; i
< l
; i
++ ) {
2828 moduleObj
= modules
[ i
];
2830 for ( x
= 0, z
= moduleObj
.tests
.length
; x
< z
; x
++ ) {
2831 test
= moduleObj
.tests
[ x
];
2833 appendTest( test
.name
, test
.testId
, moduleObj
.name
);
2838 function appendTest( name
, testId
, moduleName
) {
2839 var title
, rerunTrigger
, testBlock
, assertList
,
2840 tests
= id( "qunit-tests" );
2846 title
= document
.createElement( "strong" );
2847 title
.innerHTML
= getNameHtml( name
, moduleName
);
2849 rerunTrigger
= document
.createElement( "a" );
2850 rerunTrigger
.innerHTML
= "Rerun";
2851 rerunTrigger
.href
= setUrl( { testId
: testId
} );
2853 testBlock
= document
.createElement( "li" );
2854 testBlock
.appendChild( title
);
2855 testBlock
.appendChild( rerunTrigger
);
2856 testBlock
.id
= "qunit-test-output-" + testId
;
2858 assertList
= document
.createElement( "ol" );
2859 assertList
.className
= "qunit-assert-list";
2861 testBlock
.appendChild( assertList
);
2863 tests
.appendChild( testBlock
);
2866 // HTML Reporter initialization and load
2867 QUnit
.begin( function( details
) {
2868 var i
, moduleObj
, tests
;
2870 // Sort modules by name for the picker
2871 for ( i
= 0; i
< details
.modules
.length
; i
++ ) {
2872 moduleObj
= details
.modules
[ i
];
2873 if ( moduleObj
.name
) {
2874 modulesList
.push( moduleObj
.name
);
2877 modulesList
.sort( function( a
, b
) {
2878 return a
.localeCompare( b
);
2881 // Capture fixture HTML from the page
2884 // Initialize QUnit elements
2886 appendTestsList( details
.modules
);
2887 tests
= id( "qunit-tests" );
2888 if ( tests
&& config
.hidepassed
) {
2889 addClass( tests
, "hidepass" );
2893 QUnit
.done( function( details
) {
2895 banner
= id( "qunit-banner" ),
2896 tests
= id( "qunit-tests" ),
2898 "Tests completed in ",
2900 " milliseconds.<br />",
2901 "<span class='passed'>",
2903 "</span> assertions of <span class='total'>",
2905 "</span> passed, <span class='failed'>",
2911 banner
.className
= details
.failed
? "qunit-fail" : "qunit-pass";
2915 id( "qunit-testresult" ).innerHTML
= html
;
2918 if ( config
.altertitle
&& document
.title
) {
2920 // Show ✖ for good, ✔ for bad suite result in title
2921 // use escape sequences in case file gets loaded with non-utf-8-charset
2923 ( details
.failed
? "\u2716" : "\u2714" ),
2924 document
.title
.replace( /^[\u2714\u2716] /i, "" )
2928 // Clear own sessionStorage items if all tests passed
2929 if ( config
.reorder
&& defined
.sessionStorage
&& details
.failed
=== 0 ) {
2930 for ( i
= 0; i
< sessionStorage
.length
; i
++ ) {
2931 key
= sessionStorage
.key( i
++ );
2932 if ( key
.indexOf( "qunit-test-" ) === 0 ) {
2933 sessionStorage
.removeItem( key
);
2938 // Scroll back to top to show results
2939 if ( config
.scrolltop
&& window
.scrollTo
) {
2940 window
.scrollTo( 0, 0 );
2944 function getNameHtml( name
, module
) {
2948 nameHtml
= "<span class='module-name'>" + escapeText( module
) + "</span>: ";
2951 nameHtml
+= "<span class='test-name'>" + escapeText( name
) + "</span>";
2956 QUnit
.testStart( function( details
) {
2957 var running
, testBlock
, bad
;
2959 testBlock
= id( "qunit-test-output-" + details
.testId
);
2961 testBlock
.className
= "running";
2964 // Report later registered tests
2965 appendTest( details
.name
, details
.testId
, details
.module
);
2968 running
= id( "qunit-testresult" );
2970 bad
= QUnit
.config
.reorder
&& defined
.sessionStorage
&&
2971 +sessionStorage
.getItem( "qunit-test-" + details
.module
+ "-" + details
.name
);
2973 running
.innerHTML
= ( bad
?
2974 "Rerunning previously failed test: <br />" :
2975 "Running: <br />" ) +
2976 getNameHtml( details
.name
, details
.module
);
2981 function stripHtml( string
) {
2983 // Strip tags, html entity and whitespaces
2984 return string
.replace( /<\/?[^>]+(>|$)/g, "" ).replace( /\"/g, "" ).replace( /\s+/g, "" );
2987 QUnit
.log( function( details
) {
2988 var assertList
, assertLi
,
2989 message
, expected
, actual
, diff
,
2991 testItem
= id( "qunit-test-output-" + details
.testId
);
2997 message
= escapeText( details
.message
) || ( details
.result
? "okay" : "failed" );
2998 message
= "<span class='test-message'>" + message
+ "</span>";
2999 message
+= "<span class='runtime'>@ " + details
.runtime
+ " ms</span>";
3001 // The pushFailure doesn't provide details.expected
3002 // when it calls, it's implicit to also not show expected and diff stuff
3003 // Also, we need to check details.expected existence, as it can exist and be undefined
3004 if ( !details
.result
&& hasOwn
.call( details
, "expected" ) ) {
3005 if ( details
.negative
) {
3006 expected
= "NOT " + QUnit
.dump
.parse( details
.expected
);
3008 expected
= QUnit
.dump
.parse( details
.expected
);
3011 actual
= QUnit
.dump
.parse( details
.actual
);
3012 message
+= "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
3013 escapeText( expected
) +
3016 if ( actual
!== expected
) {
3018 message
+= "<tr class='test-actual'><th>Result: </th><td><pre>" +
3019 escapeText( actual
) + "</pre></td></tr>";
3021 // Don't show diff if actual or expected are booleans
3022 if ( !( /^(true|false)$/.test( actual
) ) &&
3023 !( /^(true|false)$/.test( expected
) ) ) {
3024 diff
= QUnit
.diff( expected
, actual
);
3025 showDiff
= stripHtml( diff
).length
!==
3026 stripHtml( expected
).length
+
3027 stripHtml( actual
).length
;
3030 // Don't show diff if expected and actual are totally different
3032 message
+= "<tr class='test-diff'><th>Diff: </th><td><pre>" +
3033 diff
+ "</pre></td></tr>";
3035 } else if ( expected
.indexOf( "[object Array]" ) !== -1 ||
3036 expected
.indexOf( "[object Object]" ) !== -1 ) {
3037 message
+= "<tr class='test-message'><th>Message: </th><td>" +
3038 "Diff suppressed as the depth of object is more than current max depth (" +
3039 QUnit
.config
.maxDepth
+ ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " +
3040 " run with a higher max depth or <a href='" +
3041 escapeText( setUrl( { maxDepth
: -1 } ) ) + "'>" +
3042 "Rerun</a> without max depth.</p></td></tr>";
3044 message
+= "<tr class='test-message'><th>Message: </th><td>" +
3045 "Diff suppressed as the expected and actual results have an equivalent" +
3046 " serialization</td></tr>";
3049 if ( details
.source
) {
3050 message
+= "<tr class='test-source'><th>Source: </th><td><pre>" +
3051 escapeText( details
.source
) + "</pre></td></tr>";
3054 message
+= "</table>";
3056 // This occurs when pushFailure is set and we have an extracted stack trace
3057 } else if ( !details
.result
&& details
.source
) {
3058 message
+= "<table>" +
3059 "<tr class='test-source'><th>Source: </th><td><pre>" +
3060 escapeText( details
.source
) + "</pre></td></tr>" +
3064 assertList
= testItem
.getElementsByTagName( "ol" )[ 0 ];
3066 assertLi
= document
.createElement( "li" );
3067 assertLi
.className
= details
.result
? "pass" : "fail";
3068 assertLi
.innerHTML
= message
;
3069 assertList
.appendChild( assertLi
);
3072 QUnit
.testDone( function( details
) {
3073 var testTitle
, time
, testItem
, assertList
,
3074 good
, bad
, testCounts
, skipped
, sourceName
,
3075 tests
= id( "qunit-tests" );
3081 testItem
= id( "qunit-test-output-" + details
.testId
);
3083 assertList
= testItem
.getElementsByTagName( "ol" )[ 0 ];
3085 good
= details
.passed
;
3086 bad
= details
.failed
;
3088 // Store result when possible
3089 if ( config
.reorder
&& defined
.sessionStorage
) {
3091 sessionStorage
.setItem( "qunit-test-" + details
.module
+ "-" + details
.name
, bad
);
3093 sessionStorage
.removeItem( "qunit-test-" + details
.module
+ "-" + details
.name
);
3099 // Collapse the passing tests
3100 addClass( assertList
, "qunit-collapsed" );
3101 } else if ( bad
&& config
.collapse
&& !collapseNext
) {
3103 // Skip collapsing the first failing test
3104 collapseNext
= true;
3107 // Collapse remaining tests
3108 addClass( assertList
, "qunit-collapsed" );
3111 // The testItem.firstChild is the test name
3112 testTitle
= testItem
.firstChild
;
3115 "<b class='failed'>" + bad
+ "</b>, " + "<b class='passed'>" + good
+ "</b>, " :
3118 testTitle
.innerHTML
+= " <b class='counts'>(" + testCounts
+
3119 details
.assertions
.length
+ ")</b>";
3121 if ( details
.skipped
) {
3122 testItem
.className
= "skipped";
3123 skipped
= document
.createElement( "em" );
3124 skipped
.className
= "qunit-skipped-label";
3125 skipped
.innerHTML
= "skipped";
3126 testItem
.insertBefore( skipped
, testTitle
);
3128 addEvent( testTitle
, "click", function() {
3129 toggleClass( assertList
, "qunit-collapsed" );
3132 testItem
.className
= bad
? "fail" : "pass";
3134 time
= document
.createElement( "span" );
3135 time
.className
= "runtime";
3136 time
.innerHTML
= details
.runtime
+ " ms";
3137 testItem
.insertBefore( time
, assertList
);
3140 // Show the source of the test when showing assertions
3141 if ( details
.source
) {
3142 sourceName
= document
.createElement( "p" );
3143 sourceName
.innerHTML
= "<strong>Source: </strong>" + details
.source
;
3144 addClass( sourceName
, "qunit-source" );
3146 addClass( sourceName
, "qunit-collapsed" );
3148 addEvent( testTitle
, "click", function() {
3149 toggleClass( sourceName
, "qunit-collapsed" );
3151 testItem
.appendChild( sourceName
);
3155 // Avoid readyState issue with phantomjs
3157 var notPhantom
= ( function( p
) {
3158 return !( p
&& p
.version
&& p
.version
.major
> 0 );
3159 } )( window
.phantom
);
3161 if ( notPhantom
&& document
.readyState
=== "complete" ) {
3164 addEvent( window
, "load", QUnit
.load
);
3168 * This file is a modified version of google-diff-match-patch's JavaScript implementation
3169 * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
3170 * modifications are licensed as more fully set forth in LICENSE.txt.
3172 * The original source of google-diff-match-patch is attributable and licensed as follows:
3174 * Copyright 2006 Google Inc.
3175 * https://code.google.com/p/google-diff-match-patch/
3177 * Licensed under the Apache License, Version 2.0 (the "License");
3178 * you may not use this file except in compliance with the License.
3179 * You may obtain a copy of the License at
3181 * https://www.apache.org/licenses/LICENSE-2.0
3183 * Unless required by applicable law or agreed to in writing, software
3184 * distributed under the License is distributed on an "AS IS" BASIS,
3185 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3186 * See the License for the specific language governing permissions and
3187 * limitations under the License.
3190 * https://code.google.com/p/google-diff-match-patch/
3192 * Usage: QUnit.diff(expected, actual)
3195 QUnit
.diff
= ( function() {
3196 function DiffMatchPatch() {
3202 * The data structure representing a diff is an array of tuples:
3203 * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
3204 * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
3206 var DIFF_DELETE
= -1,
3211 * Find the differences between two texts. Simplifies the problem by stripping
3212 * any common prefix or suffix off the texts before diffing.
3213 * @param {string} text1 Old string to be diffed.
3214 * @param {string} text2 New string to be diffed.
3215 * @param {boolean=} optChecklines Optional speedup flag. If present and false,
3216 * then don't run a line-level diff first to identify the changed areas.
3217 * Defaults to true, which does a faster, slightly less optimal diff.
3218 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
3220 DiffMatchPatch
.prototype.DiffMain = function( text1
, text2
, optChecklines
) {
3221 var deadline
, checklines
, commonlength
,
3222 commonprefix
, commonsuffix
, diffs
;
3224 // The diff must be complete in up to 1 second.
3225 deadline
= ( new Date() ).getTime() + 1000;
3227 // Check for null inputs.
3228 if ( text1
=== null || text2
=== null ) {
3229 throw new Error( "Null input. (DiffMain)" );
3232 // Check for equality (speedup).
3233 if ( text1
=== text2
) {
3236 [ DIFF_EQUAL
, text1
]
3242 if ( typeof optChecklines
=== "undefined" ) {
3243 optChecklines
= true;
3246 checklines
= optChecklines
;
3248 // Trim off common prefix (speedup).
3249 commonlength
= this.diffCommonPrefix( text1
, text2
);
3250 commonprefix
= text1
.substring( 0, commonlength
);
3251 text1
= text1
.substring( commonlength
);
3252 text2
= text2
.substring( commonlength
);
3254 // Trim off common suffix (speedup).
3255 commonlength
= this.diffCommonSuffix( text1
, text2
);
3256 commonsuffix
= text1
.substring( text1
.length
- commonlength
);
3257 text1
= text1
.substring( 0, text1
.length
- commonlength
);
3258 text2
= text2
.substring( 0, text2
.length
- commonlength
);
3260 // Compute the diff on the middle block.
3261 diffs
= this.diffCompute( text1
, text2
, checklines
, deadline
);
3263 // Restore the prefix and suffix.
3264 if ( commonprefix
) {
3265 diffs
.unshift( [ DIFF_EQUAL
, commonprefix
] );
3267 if ( commonsuffix
) {
3268 diffs
.push( [ DIFF_EQUAL
, commonsuffix
] );
3270 this.diffCleanupMerge( diffs
);
3275 * Reduce the number of edits by eliminating operationally trivial equalities.
3276 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
3278 DiffMatchPatch
.prototype.diffCleanupEfficiency = function( diffs
) {
3279 var changes
, equalities
, equalitiesLength
, lastequality
,
3280 pointer
, preIns
, preDel
, postIns
, postDel
;
3282 equalities
= []; // Stack of indices where equalities are found.
3283 equalitiesLength
= 0; // Keeping our own length var is faster in JS.
3284 /** @type {?string} */
3285 lastequality
= null;
3287 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
3288 pointer
= 0; // Index of current position.
3290 // Is there an insertion operation before the last equality.
3293 // Is there a deletion operation before the last equality.
3296 // Is there an insertion operation after the last equality.
3299 // Is there a deletion operation after the last equality.
3301 while ( pointer
< diffs
.length
) {
3304 if ( diffs
[ pointer
][ 0 ] === DIFF_EQUAL
) {
3305 if ( diffs
[ pointer
][ 1 ].length
< 4 && ( postIns
|| postDel
) ) {
3308 equalities
[ equalitiesLength
++ ] = pointer
;
3311 lastequality
= diffs
[ pointer
][ 1 ];
3314 // Not a candidate, and can never become one.
3315 equalitiesLength
= 0;
3316 lastequality
= null;
3318 postIns
= postDel
= false;
3320 // An insertion or deletion.
3323 if ( diffs
[ pointer
][ 0 ] === DIFF_DELETE
) {
3330 * Five types to be split:
3331 * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
3332 * <ins>A</ins>X<ins>C</ins><del>D</del>
3333 * <ins>A</ins><del>B</del>X<ins>C</ins>
3334 * <ins>A</del>X<ins>C</ins><del>D</del>
3335 * <ins>A</ins><del>B</del>X<del>C</del>
3337 if ( lastequality
&& ( ( preIns
&& preDel
&& postIns
&& postDel
) ||
3338 ( ( lastequality
.length
< 2 ) &&
3339 ( preIns
+ preDel
+ postIns
+ postDel
) === 3 ) ) ) {
3341 // Duplicate record.
3343 equalities
[ equalitiesLength
- 1 ],
3345 [ DIFF_DELETE
, lastequality
]
3348 // Change second copy to insert.
3349 diffs
[ equalities
[ equalitiesLength
- 1 ] + 1 ][ 0 ] = DIFF_INSERT
;
3350 equalitiesLength
--; // Throw away the equality we just deleted;
3351 lastequality
= null;
3352 if ( preIns
&& preDel
) {
3354 // No changes made which could affect previous entry, keep going.
3355 postIns
= postDel
= true;
3356 equalitiesLength
= 0;
3358 equalitiesLength
--; // Throw away the previous equality.
3359 pointer
= equalitiesLength
> 0 ? equalities
[ equalitiesLength
- 1 ] : -1;
3360 postIns
= postDel
= false;
3369 this.diffCleanupMerge( diffs
);
3374 * Convert a diff array into a pretty HTML report.
3375 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
3376 * @param {integer} string to be beautified.
3377 * @return {string} HTML representation.
3379 DiffMatchPatch
.prototype.diffPrettyHtml = function( diffs
) {
3382 for ( x
= 0; x
< diffs
.length
; x
++ ) {
3383 op
= diffs
[ x
][ 0 ]; // Operation (insert, delete, equal)
3384 data
= diffs
[ x
][ 1 ]; // Text of change.
3387 html
[ x
] = "<ins>" + escapeText( data
) + "</ins>";
3390 html
[ x
] = "<del>" + escapeText( data
) + "</del>";
3393 html
[ x
] = "<span>" + escapeText( data
) + "</span>";
3397 return html
.join( "" );
3401 * Determine the common prefix of two strings.
3402 * @param {string} text1 First string.
3403 * @param {string} text2 Second string.
3404 * @return {number} The number of characters common to the start of each
3407 DiffMatchPatch
.prototype.diffCommonPrefix = function( text1
, text2
) {
3408 var pointermid
, pointermax
, pointermin
, pointerstart
;
3410 // Quick check for common null cases.
3411 if ( !text1
|| !text2
|| text1
.charAt( 0 ) !== text2
.charAt( 0 ) ) {
3416 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
3418 pointermax
= Math
.min( text1
.length
, text2
.length
);
3419 pointermid
= pointermax
;
3421 while ( pointermin
< pointermid
) {
3422 if ( text1
.substring( pointerstart
, pointermid
) ===
3423 text2
.substring( pointerstart
, pointermid
) ) {
3424 pointermin
= pointermid
;
3425 pointerstart
= pointermin
;
3427 pointermax
= pointermid
;
3429 pointermid
= Math
.floor( ( pointermax
- pointermin
) / 2 + pointermin
);
3435 * Determine the common suffix of two strings.
3436 * @param {string} text1 First string.
3437 * @param {string} text2 Second string.
3438 * @return {number} The number of characters common to the end of each string.
3440 DiffMatchPatch
.prototype.diffCommonSuffix = function( text1
, text2
) {
3441 var pointermid
, pointermax
, pointermin
, pointerend
;
3443 // Quick check for common null cases.
3446 text1
.charAt( text1
.length
- 1 ) !== text2
.charAt( text2
.length
- 1 ) ) {
3451 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
3453 pointermax
= Math
.min( text1
.length
, text2
.length
);
3454 pointermid
= pointermax
;
3456 while ( pointermin
< pointermid
) {
3457 if ( text1
.substring( text1
.length
- pointermid
, text1
.length
- pointerend
) ===
3458 text2
.substring( text2
.length
- pointermid
, text2
.length
- pointerend
) ) {
3459 pointermin
= pointermid
;
3460 pointerend
= pointermin
;
3462 pointermax
= pointermid
;
3464 pointermid
= Math
.floor( ( pointermax
- pointermin
) / 2 + pointermin
);
3470 * Find the differences between two texts. Assumes that the texts do not
3471 * have any common prefix or suffix.
3472 * @param {string} text1 Old string to be diffed.
3473 * @param {string} text2 New string to be diffed.
3474 * @param {boolean} checklines Speedup flag. If false, then don't run a
3475 * line-level diff first to identify the changed areas.
3476 * If true, then run a faster, slightly less optimal diff.
3477 * @param {number} deadline Time when the diff should be complete by.
3478 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
3481 DiffMatchPatch
.prototype.diffCompute = function( text1
, text2
, checklines
, deadline
) {
3482 var diffs
, longtext
, shorttext
, i
, hm
,
3483 text1A
, text2A
, text1B
, text2B
,
3484 midCommon
, diffsA
, diffsB
;
3488 // Just add some text (speedup).
3490 [ DIFF_INSERT
, text2
]
3496 // Just delete some text (speedup).
3498 [ DIFF_DELETE
, text1
]
3502 longtext
= text1
.length
> text2
.length
? text1
: text2
;
3503 shorttext
= text1
.length
> text2
.length
? text2
: text1
;
3504 i
= longtext
.indexOf( shorttext
);
3507 // Shorter text is inside the longer text (speedup).
3509 [ DIFF_INSERT
, longtext
.substring( 0, i
) ],
3510 [ DIFF_EQUAL
, shorttext
],
3511 [ DIFF_INSERT
, longtext
.substring( i
+ shorttext
.length
) ]
3514 // Swap insertions for deletions if diff is reversed.
3515 if ( text1
.length
> text2
.length
) {
3516 diffs
[ 0 ][ 0 ] = diffs
[ 2 ][ 0 ] = DIFF_DELETE
;
3521 if ( shorttext
.length
=== 1 ) {
3523 // Single character string.
3524 // After the previous speedup, the character can't be an equality.
3526 [ DIFF_DELETE
, text1
],
3527 [ DIFF_INSERT
, text2
]
3531 // Check to see if the problem can be split in two.
3532 hm
= this.diffHalfMatch( text1
, text2
);
3535 // A half-match was found, sort out the return data.
3540 midCommon
= hm
[ 4 ];
3542 // Send both pairs off for separate processing.
3543 diffsA
= this.DiffMain( text1A
, text2A
, checklines
, deadline
);
3544 diffsB
= this.DiffMain( text1B
, text2B
, checklines
, deadline
);
3546 // Merge the results.
3547 return diffsA
.concat( [
3548 [ DIFF_EQUAL
, midCommon
]
3552 if ( checklines
&& text1
.length
> 100 && text2
.length
> 100 ) {
3553 return this.diffLineMode( text1
, text2
, deadline
);
3556 return this.diffBisect( text1
, text2
, deadline
);
3560 * Do the two texts share a substring which is at least half the length of the
3562 * This speedup can produce non-minimal diffs.
3563 * @param {string} text1 First string.
3564 * @param {string} text2 Second string.
3565 * @return {Array.<string>} Five element Array, containing the prefix of
3566 * text1, the suffix of text1, the prefix of text2, the suffix of
3567 * text2 and the common middle. Or null if there was no match.
3570 DiffMatchPatch
.prototype.diffHalfMatch = function( text1
, text2
) {
3571 var longtext
, shorttext
, dmp
,
3572 text1A
, text2B
, text2A
, text1B
, midCommon
,
3575 longtext
= text1
.length
> text2
.length
? text1
: text2
;
3576 shorttext
= text1
.length
> text2
.length
? text2
: text1
;
3577 if ( longtext
.length
< 4 || shorttext
.length
* 2 < longtext
.length
) {
3578 return null; // Pointless.
3580 dmp
= this; // 'this' becomes 'window' in a closure.
3583 * Does a substring of shorttext exist within longtext such that the substring
3584 * is at least half the length of longtext?
3585 * Closure, but does not reference any external variables.
3586 * @param {string} longtext Longer string.
3587 * @param {string} shorttext Shorter string.
3588 * @param {number} i Start index of quarter length substring within longtext.
3589 * @return {Array.<string>} Five element Array, containing the prefix of
3590 * longtext, the suffix of longtext, the prefix of shorttext, the suffix
3591 * of shorttext and the common middle. Or null if there was no match.
3594 function diffHalfMatchI( longtext
, shorttext
, i
) {
3595 var seed
, j
, bestCommon
, prefixLength
, suffixLength
,
3596 bestLongtextA
, bestLongtextB
, bestShorttextA
, bestShorttextB
;
3598 // Start with a 1/4 length substring at position i as a seed.
3599 seed
= longtext
.substring( i
, i
+ Math
.floor( longtext
.length
/ 4 ) );
3602 while ( ( j
= shorttext
.indexOf( seed
, j
+ 1 ) ) !== -1 ) {
3603 prefixLength
= dmp
.diffCommonPrefix( longtext
.substring( i
),
3604 shorttext
.substring( j
) );
3605 suffixLength
= dmp
.diffCommonSuffix( longtext
.substring( 0, i
),
3606 shorttext
.substring( 0, j
) );
3607 if ( bestCommon
.length
< suffixLength
+ prefixLength
) {
3608 bestCommon
= shorttext
.substring( j
- suffixLength
, j
) +
3609 shorttext
.substring( j
, j
+ prefixLength
);
3610 bestLongtextA
= longtext
.substring( 0, i
- suffixLength
);
3611 bestLongtextB
= longtext
.substring( i
+ prefixLength
);
3612 bestShorttextA
= shorttext
.substring( 0, j
- suffixLength
);
3613 bestShorttextB
= shorttext
.substring( j
+ prefixLength
);
3616 if ( bestCommon
.length
* 2 >= longtext
.length
) {
3617 return [ bestLongtextA
, bestLongtextB
,
3618 bestShorttextA
, bestShorttextB
, bestCommon
3625 // First check if the second quarter is the seed for a half-match.
3626 hm1
= diffHalfMatchI( longtext
, shorttext
,
3627 Math
.ceil( longtext
.length
/ 4 ) );
3629 // Check again based on the third quarter.
3630 hm2
= diffHalfMatchI( longtext
, shorttext
,
3631 Math
.ceil( longtext
.length
/ 2 ) );
3632 if ( !hm1
&& !hm2
) {
3634 } else if ( !hm2
) {
3636 } else if ( !hm1
) {
3640 // Both matched. Select the longest.
3641 hm
= hm1
[ 4 ].length
> hm2
[ 4 ].length
? hm1
: hm2
;
3644 // A half-match was found, sort out the return data.
3645 text1A
, text1B
, text2A
, text2B
;
3646 if ( text1
.length
> text2
.length
) {
3657 midCommon
= hm
[ 4 ];
3658 return [ text1A
, text1B
, text2A
, text2B
, midCommon
];
3662 * Do a quick line-level diff on both strings, then rediff the parts for
3664 * This speedup can produce non-minimal diffs.
3665 * @param {string} text1 Old string to be diffed.
3666 * @param {string} text2 New string to be diffed.
3667 * @param {number} deadline Time when the diff should be complete by.
3668 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
3671 DiffMatchPatch
.prototype.diffLineMode = function( text1
, text2
, deadline
) {
3672 var a
, diffs
, linearray
, pointer
, countInsert
,
3673 countDelete
, textInsert
, textDelete
, j
;
3675 // Scan the text on a line-by-line basis first.
3676 a
= this.diffLinesToChars( text1
, text2
);
3679 linearray
= a
.lineArray
;
3681 diffs
= this.DiffMain( text1
, text2
, false, deadline
);
3683 // Convert the diff back to original text.
3684 this.diffCharsToLines( diffs
, linearray
);
3686 // Eliminate freak matches (e.g. blank lines)
3687 this.diffCleanupSemantic( diffs
);
3689 // Rediff any replacement blocks, this time character-by-character.
3690 // Add a dummy entry at the end.
3691 diffs
.push( [ DIFF_EQUAL
, "" ] );
3697 while ( pointer
< diffs
.length
) {
3698 switch ( diffs
[ pointer
][ 0 ] ) {
3701 textInsert
+= diffs
[ pointer
][ 1 ];
3705 textDelete
+= diffs
[ pointer
][ 1 ];
3709 // Upon reaching an equality, check for prior redundancies.
3710 if ( countDelete
>= 1 && countInsert
>= 1 ) {
3712 // Delete the offending records and add the merged ones.
3713 diffs
.splice( pointer
- countDelete
- countInsert
,
3714 countDelete
+ countInsert
);
3715 pointer
= pointer
- countDelete
- countInsert
;
3716 a
= this.DiffMain( textDelete
, textInsert
, false, deadline
);
3717 for ( j
= a
.length
- 1; j
>= 0; j
-- ) {
3718 diffs
.splice( pointer
, 0, a
[ j
] );
3720 pointer
= pointer
+ a
.length
;
3730 diffs
.pop(); // Remove the dummy entry at the end.
3736 * Find the 'middle snake' of a diff, split the problem in two
3737 * and return the recursively constructed diff.
3738 * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
3739 * @param {string} text1 Old string to be diffed.
3740 * @param {string} text2 New string to be diffed.
3741 * @param {number} deadline Time at which to bail if not yet complete.
3742 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
3745 DiffMatchPatch
.prototype.diffBisect = function( text1
, text2
, deadline
) {
3746 var text1Length
, text2Length
, maxD
, vOffset
, vLength
,
3747 v1
, v2
, x
, delta
, front
, k1start
, k1end
, k2start
,
3748 k2end
, k2Offset
, k1Offset
, x1
, x2
, y1
, y2
, d
, k1
, k2
;
3750 // Cache the text lengths to prevent multiple calls.
3751 text1Length
= text1
.length
;
3752 text2Length
= text2
.length
;
3753 maxD
= Math
.ceil( ( text1Length
+ text2Length
) / 2 );
3756 v1
= new Array( vLength
);
3757 v2
= new Array( vLength
);
3759 // Setting all elements to -1 is faster in Chrome & Firefox than mixing
3760 // integers and undefined.
3761 for ( x
= 0; x
< vLength
; x
++ ) {
3765 v1
[ vOffset
+ 1 ] = 0;
3766 v2
[ vOffset
+ 1 ] = 0;
3767 delta
= text1Length
- text2Length
;
3769 // If the total number of characters is odd, then the front path will collide
3770 // with the reverse path.
3771 front
= ( delta
% 2 !== 0 );
3773 // Offsets for start and end of k loop.
3774 // Prevents mapping of space beyond the grid.
3779 for ( d
= 0; d
< maxD
; d
++ ) {
3781 // Bail out if deadline is reached.
3782 if ( ( new Date() ).getTime() > deadline
) {
3786 // Walk the front path one step.
3787 for ( k1
= -d
+ k1start
; k1
<= d
- k1end
; k1
+= 2 ) {
3788 k1Offset
= vOffset
+ k1
;
3789 if ( k1
=== -d
|| ( k1
!== d
&& v1
[ k1Offset
- 1 ] < v1
[ k1Offset
+ 1 ] ) ) {
3790 x1
= v1
[ k1Offset
+ 1 ];
3792 x1
= v1
[ k1Offset
- 1 ] + 1;
3795 while ( x1
< text1Length
&& y1
< text2Length
&&
3796 text1
.charAt( x1
) === text2
.charAt( y1
) ) {
3800 v1
[ k1Offset
] = x1
;
3801 if ( x1
> text1Length
) {
3803 // Ran off the right of the graph.
3805 } else if ( y1
> text2Length
) {
3807 // Ran off the bottom of the graph.
3809 } else if ( front
) {
3810 k2Offset
= vOffset
+ delta
- k1
;
3811 if ( k2Offset
>= 0 && k2Offset
< vLength
&& v2
[ k2Offset
] !== -1 ) {
3813 // Mirror x2 onto top-left coordinate system.
3814 x2
= text1Length
- v2
[ k2Offset
];
3817 // Overlap detected.
3818 return this.diffBisectSplit( text1
, text2
, x1
, y1
, deadline
);
3824 // Walk the reverse path one step.
3825 for ( k2
= -d
+ k2start
; k2
<= d
- k2end
; k2
+= 2 ) {
3826 k2Offset
= vOffset
+ k2
;
3827 if ( k2
=== -d
|| ( k2
!== d
&& v2
[ k2Offset
- 1 ] < v2
[ k2Offset
+ 1 ] ) ) {
3828 x2
= v2
[ k2Offset
+ 1 ];
3830 x2
= v2
[ k2Offset
- 1 ] + 1;
3833 while ( x2
< text1Length
&& y2
< text2Length
&&
3834 text1
.charAt( text1Length
- x2
- 1 ) ===
3835 text2
.charAt( text2Length
- y2
- 1 ) ) {
3839 v2
[ k2Offset
] = x2
;
3840 if ( x2
> text1Length
) {
3842 // Ran off the left of the graph.
3844 } else if ( y2
> text2Length
) {
3846 // Ran off the top of the graph.
3848 } else if ( !front
) {
3849 k1Offset
= vOffset
+ delta
- k2
;
3850 if ( k1Offset
>= 0 && k1Offset
< vLength
&& v1
[ k1Offset
] !== -1 ) {
3851 x1
= v1
[ k1Offset
];
3852 y1
= vOffset
+ x1
- k1Offset
;
3854 // Mirror x2 onto top-left coordinate system.
3855 x2
= text1Length
- x2
;
3858 // Overlap detected.
3859 return this.diffBisectSplit( text1
, text2
, x1
, y1
, deadline
);
3866 // Diff took too long and hit the deadline or
3867 // number of diffs equals number of characters, no commonality at all.
3869 [ DIFF_DELETE
, text1
],
3870 [ DIFF_INSERT
, text2
]
3875 * Given the location of the 'middle snake', split the diff in two parts
3877 * @param {string} text1 Old string to be diffed.
3878 * @param {string} text2 New string to be diffed.
3879 * @param {number} x Index of split point in text1.
3880 * @param {number} y Index of split point in text2.
3881 * @param {number} deadline Time at which to bail if not yet complete.
3882 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
3885 DiffMatchPatch
.prototype.diffBisectSplit = function( text1
, text2
, x
, y
, deadline
) {
3886 var text1a
, text1b
, text2a
, text2b
, diffs
, diffsb
;
3887 text1a
= text1
.substring( 0, x
);
3888 text2a
= text2
.substring( 0, y
);
3889 text1b
= text1
.substring( x
);
3890 text2b
= text2
.substring( y
);
3892 // Compute both diffs serially.
3893 diffs
= this.DiffMain( text1a
, text2a
, false, deadline
);
3894 diffsb
= this.DiffMain( text1b
, text2b
, false, deadline
);
3896 return diffs
.concat( diffsb
);
3900 * Reduce the number of edits by eliminating semantically trivial equalities.
3901 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
3903 DiffMatchPatch
.prototype.diffCleanupSemantic = function( diffs
) {
3904 var changes
, equalities
, equalitiesLength
, lastequality
,
3905 pointer
, lengthInsertions2
, lengthDeletions2
, lengthInsertions1
,
3906 lengthDeletions1
, deletion
, insertion
, overlapLength1
, overlapLength2
;
3908 equalities
= []; // Stack of indices where equalities are found.
3909 equalitiesLength
= 0; // Keeping our own length var is faster in JS.
3910 /** @type {?string} */
3911 lastequality
= null;
3913 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
3914 pointer
= 0; // Index of current position.
3916 // Number of characters that changed prior to the equality.
3917 lengthInsertions1
= 0;
3918 lengthDeletions1
= 0;
3920 // Number of characters that changed after the equality.
3921 lengthInsertions2
= 0;
3922 lengthDeletions2
= 0;
3923 while ( pointer
< diffs
.length
) {
3924 if ( diffs
[ pointer
][ 0 ] === DIFF_EQUAL
) { // Equality found.
3925 equalities
[ equalitiesLength
++ ] = pointer
;
3926 lengthInsertions1
= lengthInsertions2
;
3927 lengthDeletions1
= lengthDeletions2
;
3928 lengthInsertions2
= 0;
3929 lengthDeletions2
= 0;
3930 lastequality
= diffs
[ pointer
][ 1 ];
3931 } else { // An insertion or deletion.
3932 if ( diffs
[ pointer
][ 0 ] === DIFF_INSERT
) {
3933 lengthInsertions2
+= diffs
[ pointer
][ 1 ].length
;
3935 lengthDeletions2
+= diffs
[ pointer
][ 1 ].length
;
3938 // Eliminate an equality that is smaller or equal to the edits on both
3940 if ( lastequality
&& ( lastequality
.length
<=
3941 Math
.max( lengthInsertions1
, lengthDeletions1
) ) &&
3942 ( lastequality
.length
<= Math
.max( lengthInsertions2
,
3943 lengthDeletions2
) ) ) {
3945 // Duplicate record.
3947 equalities
[ equalitiesLength
- 1 ],
3949 [ DIFF_DELETE
, lastequality
]
3952 // Change second copy to insert.
3953 diffs
[ equalities
[ equalitiesLength
- 1 ] + 1 ][ 0 ] = DIFF_INSERT
;
3955 // Throw away the equality we just deleted.
3958 // Throw away the previous equality (it needs to be reevaluated).
3960 pointer
= equalitiesLength
> 0 ? equalities
[ equalitiesLength
- 1 ] : -1;
3962 // Reset the counters.
3963 lengthInsertions1
= 0;
3964 lengthDeletions1
= 0;
3965 lengthInsertions2
= 0;
3966 lengthDeletions2
= 0;
3967 lastequality
= null;
3974 // Normalize the diff.
3976 this.diffCleanupMerge( diffs
);
3979 // Find any overlaps between deletions and insertions.
3980 // e.g: <del>abcxxx</del><ins>xxxdef</ins>
3981 // -> <del>abc</del>xxx<ins>def</ins>
3982 // e.g: <del>xxxabc</del><ins>defxxx</ins>
3983 // -> <ins>def</ins>xxx<del>abc</del>
3984 // Only extract an overlap if it is as big as the edit ahead or behind it.
3986 while ( pointer
< diffs
.length
) {
3987 if ( diffs
[ pointer
- 1 ][ 0 ] === DIFF_DELETE
&&
3988 diffs
[ pointer
][ 0 ] === DIFF_INSERT
) {
3989 deletion
= diffs
[ pointer
- 1 ][ 1 ];
3990 insertion
= diffs
[ pointer
][ 1 ];
3991 overlapLength1
= this.diffCommonOverlap( deletion
, insertion
);
3992 overlapLength2
= this.diffCommonOverlap( insertion
, deletion
);
3993 if ( overlapLength1
>= overlapLength2
) {
3994 if ( overlapLength1
>= deletion
.length
/ 2 ||
3995 overlapLength1
>= insertion
.length
/ 2 ) {
3997 // Overlap found. Insert an equality and trim the surrounding edits.
4001 [ DIFF_EQUAL
, insertion
.substring( 0, overlapLength1
) ]
4003 diffs
[ pointer
- 1 ][ 1 ] =
4004 deletion
.substring( 0, deletion
.length
- overlapLength1
);
4005 diffs
[ pointer
+ 1 ][ 1 ] = insertion
.substring( overlapLength1
);
4009 if ( overlapLength2
>= deletion
.length
/ 2 ||
4010 overlapLength2
>= insertion
.length
/ 2 ) {
4012 // Reverse overlap found.
4013 // Insert an equality and swap and trim the surrounding edits.
4017 [ DIFF_EQUAL
, deletion
.substring( 0, overlapLength2
) ]
4020 diffs
[ pointer
- 1 ][ 0 ] = DIFF_INSERT
;
4021 diffs
[ pointer
- 1 ][ 1 ] =
4022 insertion
.substring( 0, insertion
.length
- overlapLength2
);
4023 diffs
[ pointer
+ 1 ][ 0 ] = DIFF_DELETE
;
4024 diffs
[ pointer
+ 1 ][ 1 ] =
4025 deletion
.substring( overlapLength2
);
4036 * Determine if the suffix of one string is the prefix of another.
4037 * @param {string} text1 First string.
4038 * @param {string} text2 Second string.
4039 * @return {number} The number of characters common to the end of the first
4040 * string and the start of the second string.
4043 DiffMatchPatch
.prototype.diffCommonOverlap = function( text1
, text2
) {
4044 var text1Length
, text2Length
, textLength
,
4045 best
, length
, pattern
, found
;
4047 // Cache the text lengths to prevent multiple calls.
4048 text1Length
= text1
.length
;
4049 text2Length
= text2
.length
;
4051 // Eliminate the null case.
4052 if ( text1Length
=== 0 || text2Length
=== 0 ) {
4056 // Truncate the longer string.
4057 if ( text1Length
> text2Length
) {
4058 text1
= text1
.substring( text1Length
- text2Length
);
4059 } else if ( text1Length
< text2Length
) {
4060 text2
= text2
.substring( 0, text1Length
);
4062 textLength
= Math
.min( text1Length
, text2Length
);
4064 // Quick check for the worst case.
4065 if ( text1
=== text2
) {
4069 // Start by looking for a single character match
4070 // and increase length until no match is found.
4071 // Performance analysis: https://neil.fraser.name/news/2010/11/04/
4075 pattern
= text1
.substring( textLength
- length
);
4076 found
= text2
.indexOf( pattern
);
4077 if ( found
=== -1 ) {
4081 if ( found
=== 0 || text1
.substring( textLength
- length
) ===
4082 text2
.substring( 0, length
) ) {
4090 * Split two texts into an array of strings. Reduce the texts to a string of
4091 * hashes where each Unicode character represents one line.
4092 * @param {string} text1 First string.
4093 * @param {string} text2 Second string.
4094 * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
4095 * An object containing the encoded text1, the encoded text2 and
4096 * the array of unique strings.
4097 * The zeroth element of the array of unique strings is intentionally blank.
4100 DiffMatchPatch
.prototype.diffLinesToChars = function( text1
, text2
) {
4101 var lineArray
, lineHash
, chars1
, chars2
;
4102 lineArray
= []; // E.g. lineArray[4] === 'Hello\n'
4103 lineHash
= {}; // E.g. lineHash['Hello\n'] === 4
4105 // '\x00' is a valid character, but various debuggers don't like it.
4106 // So we'll insert a junk entry to avoid generating a null character.
4107 lineArray
[ 0 ] = "";
4110 * Split a text into an array of strings. Reduce the texts to a string of
4111 * hashes where each Unicode character represents one line.
4112 * Modifies linearray and linehash through being a closure.
4113 * @param {string} text String to encode.
4114 * @return {string} Encoded string.
4117 function diffLinesToCharsMunge( text
) {
4118 var chars
, lineStart
, lineEnd
, lineArrayLength
, line
;
4121 // Walk the text, pulling out a substring for each line.
4122 // text.split('\n') would would temporarily double our memory footprint.
4123 // Modifying text would create many large strings to garbage collect.
4127 // Keeping our own length variable is faster than looking it up.
4128 lineArrayLength
= lineArray
.length
;
4129 while ( lineEnd
< text
.length
- 1 ) {
4130 lineEnd
= text
.indexOf( "\n", lineStart
);
4131 if ( lineEnd
=== -1 ) {
4132 lineEnd
= text
.length
- 1;
4134 line
= text
.substring( lineStart
, lineEnd
+ 1 );
4135 lineStart
= lineEnd
+ 1;
4137 if ( lineHash
.hasOwnProperty
? lineHash
.hasOwnProperty( line
) :
4138 ( lineHash
[ line
] !== undefined ) ) {
4139 chars
+= String
.fromCharCode( lineHash
[ line
] );
4141 chars
+= String
.fromCharCode( lineArrayLength
);
4142 lineHash
[ line
] = lineArrayLength
;
4143 lineArray
[ lineArrayLength
++ ] = line
;
4149 chars1
= diffLinesToCharsMunge( text1
);
4150 chars2
= diffLinesToCharsMunge( text2
);
4154 lineArray
: lineArray
4159 * Rehydrate the text in a diff from a string of line hashes to real lines of
4161 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4162 * @param {!Array.<string>} lineArray Array of unique strings.
4165 DiffMatchPatch
.prototype.diffCharsToLines = function( diffs
, lineArray
) {
4166 var x
, chars
, text
, y
;
4167 for ( x
= 0; x
< diffs
.length
; x
++ ) {
4168 chars
= diffs
[ x
][ 1 ];
4170 for ( y
= 0; y
< chars
.length
; y
++ ) {
4171 text
[ y
] = lineArray
[ chars
.charCodeAt( y
) ];
4173 diffs
[ x
][ 1 ] = text
.join( "" );
4178 * Reorder and merge like edit sections. Merge equalities.
4179 * Any edit section can move as long as it doesn't cross an equality.
4180 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4182 DiffMatchPatch
.prototype.diffCleanupMerge = function( diffs
) {
4183 var pointer
, countDelete
, countInsert
, textInsert
, textDelete
,
4184 commonlength
, changes
, diffPointer
, position
;
4185 diffs
.push( [ DIFF_EQUAL
, "" ] ); // Add a dummy entry at the end.
4192 while ( pointer
< diffs
.length
) {
4193 switch ( diffs
[ pointer
][ 0 ] ) {
4196 textInsert
+= diffs
[ pointer
][ 1 ];
4201 textDelete
+= diffs
[ pointer
][ 1 ];
4206 // Upon reaching an equality, check for prior redundancies.
4207 if ( countDelete
+ countInsert
> 1 ) {
4208 if ( countDelete
!== 0 && countInsert
!== 0 ) {
4210 // Factor out any common prefixes.
4211 commonlength
= this.diffCommonPrefix( textInsert
, textDelete
);
4212 if ( commonlength
!== 0 ) {
4213 if ( ( pointer
- countDelete
- countInsert
) > 0 &&
4214 diffs
[ pointer
- countDelete
- countInsert
- 1 ][ 0 ] ===
4216 diffs
[ pointer
- countDelete
- countInsert
- 1 ][ 1 ] +=
4217 textInsert
.substring( 0, commonlength
);
4219 diffs
.splice( 0, 0, [ DIFF_EQUAL
,
4220 textInsert
.substring( 0, commonlength
)
4224 textInsert
= textInsert
.substring( commonlength
);
4225 textDelete
= textDelete
.substring( commonlength
);
4228 // Factor out any common suffixies.
4229 commonlength
= this.diffCommonSuffix( textInsert
, textDelete
);
4230 if ( commonlength
!== 0 ) {
4231 diffs
[ pointer
][ 1 ] = textInsert
.substring( textInsert
.length
-
4232 commonlength
) + diffs
[ pointer
][ 1 ];
4233 textInsert
= textInsert
.substring( 0, textInsert
.length
-
4235 textDelete
= textDelete
.substring( 0, textDelete
.length
-
4240 // Delete the offending records and add the merged ones.
4241 if ( countDelete
=== 0 ) {
4242 diffs
.splice( pointer
- countInsert
,
4243 countDelete
+ countInsert
, [ DIFF_INSERT
, textInsert
] );
4244 } else if ( countInsert
=== 0 ) {
4245 diffs
.splice( pointer
- countDelete
,
4246 countDelete
+ countInsert
, [ DIFF_DELETE
, textDelete
] );
4249 pointer
- countDelete
- countInsert
,
4250 countDelete
+ countInsert
,
4251 [ DIFF_DELETE
, textDelete
], [ DIFF_INSERT
, textInsert
]
4254 pointer
= pointer
- countDelete
- countInsert
+
4255 ( countDelete
? 1 : 0 ) + ( countInsert
? 1 : 0 ) + 1;
4256 } else if ( pointer
!== 0 && diffs
[ pointer
- 1 ][ 0 ] === DIFF_EQUAL
) {
4258 // Merge this equality with the previous one.
4259 diffs
[ pointer
- 1 ][ 1 ] += diffs
[ pointer
][ 1 ];
4260 diffs
.splice( pointer
, 1 );
4271 if ( diffs
[ diffs
.length
- 1 ][ 1 ] === "" ) {
4272 diffs
.pop(); // Remove the dummy entry at the end.
4275 // Second pass: look for single edits surrounded on both sides by equalities
4276 // which can be shifted sideways to eliminate an equality.
4277 // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
4281 // Intentionally ignore the first and last element (don't need checking).
4282 while ( pointer
< diffs
.length
- 1 ) {
4283 if ( diffs
[ pointer
- 1 ][ 0 ] === DIFF_EQUAL
&&
4284 diffs
[ pointer
+ 1 ][ 0 ] === DIFF_EQUAL
) {
4286 diffPointer
= diffs
[ pointer
][ 1 ];
4287 position
= diffPointer
.substring(
4288 diffPointer
.length
- diffs
[ pointer
- 1 ][ 1 ].length
4291 // This is a single edit surrounded by equalities.
4292 if ( position
=== diffs
[ pointer
- 1 ][ 1 ] ) {
4294 // Shift the edit over the previous equality.
4295 diffs
[ pointer
][ 1 ] = diffs
[ pointer
- 1 ][ 1 ] +
4296 diffs
[ pointer
][ 1 ].substring( 0, diffs
[ pointer
][ 1 ].length
-
4297 diffs
[ pointer
- 1 ][ 1 ].length
);
4298 diffs
[ pointer
+ 1 ][ 1 ] =
4299 diffs
[ pointer
- 1 ][ 1 ] + diffs
[ pointer
+ 1 ][ 1 ];
4300 diffs
.splice( pointer
- 1, 1 );
4302 } else if ( diffPointer
.substring( 0, diffs
[ pointer
+ 1 ][ 1 ].length
) ===
4303 diffs
[ pointer
+ 1 ][ 1 ] ) {
4305 // Shift the edit over the next equality.
4306 diffs
[ pointer
- 1 ][ 1 ] += diffs
[ pointer
+ 1 ][ 1 ];
4307 diffs
[ pointer
][ 1 ] =
4308 diffs
[ pointer
][ 1 ].substring( diffs
[ pointer
+ 1 ][ 1 ].length
) +
4309 diffs
[ pointer
+ 1 ][ 1 ];
4310 diffs
.splice( pointer
+ 1, 1 );
4317 // If shifts were made, the diff needs reordering and another shift sweep.
4319 this.diffCleanupMerge( diffs
);
4323 return function( o
, n
) {
4324 var diff
, output
, text
;
4325 diff
= new DiffMatchPatch();
4326 output
= diff
.DiffMain( o
, n
);
4327 diff
.diffCleanupEfficiency( output
);
4328 text
= diff
.diffPrettyHtml( output
);