5 * Copyright jQuery Foundation and other contributors
6 * Released under the MIT license
7 * https://jquery.org/license
9 * Date: 2016-02-23T15:57Z
16 var Date
= global
.Date
;
17 var now
= Date
.now
|| function() {
18 return new Date().getTime();
21 var setTimeout
= global
.setTimeout
;
22 var clearTimeout
= global
.clearTimeout
;
24 // Store a local window from the global to allow direct references.
25 var window
= global
.window
;
28 document
: window
&& window
.document
!== undefined,
29 setTimeout
: setTimeout
!== undefined,
30 sessionStorage
: (function() {
31 var x
= "qunit-test-string";
33 sessionStorage
.setItem( x
, x
);
34 sessionStorage
.removeItem( x
);
42 var fileName
= ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" );
43 var globalStartCalled
= false;
44 var runStarted
= false;
46 var toString
= Object
.prototype.toString
,
47 hasOwn
= Object
.prototype.hasOwnProperty
;
49 // returns a new Array with the elements that are in a but not in b
50 function diff( a
, b
) {
54 for ( i
= 0; i
< result
.length
; i
++ ) {
55 for ( j
= 0; j
< b
.length
; j
++ ) {
56 if ( result
[ i
] === b
[ j
] ) {
57 result
.splice( i
, 1 );
67 function inArray( elem
, array
) {
68 if ( array
.indexOf
) {
69 return array
.indexOf( elem
);
72 for ( var i
= 0, length
= array
.length
; i
< length
; i
++ ) {
73 if ( array
[ i
] === elem
) {
82 * Makes a clone of an object using only Array or Object as base,
83 * and copies over the own enumerable properties.
86 * @return {Object} New object with only the own properties (recursively).
88 function objectValues ( obj
) {
90 vals
= QUnit
.is( "array", obj
) ? [] : {};
92 if ( hasOwn
.call( obj
, key
) ) {
94 vals
[ key
] = val
=== Object( val
) ? objectValues( val
) : val
;
100 function extend( a
, b
, undefOnly
) {
101 for ( var prop
in b
) {
102 if ( hasOwn
.call( b
, prop
) ) {
104 // Avoid "Member not found" error in IE8 caused by messing with window.constructor
105 // This block runs on every environment, so `global` is being used instead of `window`
106 // to avoid errors on node.
107 if ( prop
!== "constructor" || a
!== global
) {
108 if ( b
[ prop
] === undefined ) {
110 } else if ( !( undefOnly
&& typeof a
[ prop
] !== "undefined" ) ) {
111 a
[ prop
] = b
[ prop
];
120 function objectType( obj
) {
121 if ( typeof obj
=== "undefined" ) {
125 // Consider: typeof null === object
126 if ( obj
=== null ) {
130 var match
= toString
.call( obj
).match( /^\[object\s(.*)\]$/ ),
131 type
= match
&& match
[ 1 ];
135 if ( isNaN( obj
) ) {
148 return type
.toLowerCase();
150 if ( typeof obj
=== "object" ) {
155 // Safe object type checking
156 function is( type
, obj
) {
157 return QUnit
.objectType( obj
) === type
;
160 var getUrlParams = function() {
161 var i
, param
, name
, value
;
163 var location
= window
.location
;
164 var params
= location
.search
.slice( 1 ).split( "&" );
165 var length
= params
.length
;
167 for ( i
= 0; i
< length
; i
++ ) {
169 param
= params
[ i
].split( "=" );
170 name
= decodeURIComponent( param
[ 0 ] );
172 // allow just a key to turn on a flag, e.g., test.html?noglobals
173 value
= param
.length
=== 1 ||
174 decodeURIComponent( param
.slice( 1 ).join( "=" ) ) ;
175 if ( urlParams
[ name
] ) {
176 urlParams
[ name
] = [].concat( urlParams
[ name
], value
);
178 urlParams
[ name
] = value
;
186 // Doesn't support IE6 to IE9, it will return undefined on these browsers
187 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
188 function extractStacktrace( e
, offset
) {
189 offset
= offset
=== undefined ? 4 : offset
;
191 var stack
, include
, i
;
194 stack
= e
.stack
.split( "\n" );
195 if ( /^error$/i.test( stack
[ 0 ] ) ) {
200 for ( i
= offset
; i
< stack
.length
; i
++ ) {
201 if ( stack
[ i
].indexOf( fileName
) !== -1 ) {
204 include
.push( stack
[ i
] );
206 if ( include
.length
) {
207 return include
.join( "\n" );
210 return stack
[ offset
];
212 // Support: Safari <=6 only
213 } else if ( e
.sourceURL
) {
215 // exclude useless self-reference for generated Error objects
216 if ( /qunit.js$/.test( e
.sourceURL
) ) {
220 // for actual exceptions, this is useful
221 return e
.sourceURL
+ ":" + e
.line
;
225 function sourceFromStacktrace( offset
) {
226 var error
= new Error();
228 // Support: Safari <=7 only, IE <=10 - 11 only
229 // Not all browsers generate the `stack` property for `new Error()`, see also #636
230 if ( !error
.stack
) {
238 return extractStacktrace( error
, offset
);
242 * Config object: Maintain internal state
243 * Later exposed as QUnit.config
244 * `config` initialized at top of scope
247 // The queue of tests to run
250 // block until document ready
253 // by default, run previously failed tests first
254 // very useful in combination with "Hide passed tests" checked
257 // by default, modify document.title when suite is done
260 // HTML Reporter: collapse every test except the first failing test
261 // If false, all failing tests will be expanded
264 // by default, scroll to top of the page when suite is done
267 // depth up-to which object will be dumped
270 // when enabled, all tests must call expect()
271 requireExpects
: false,
273 // add checkboxes that are persisted in the query-string
274 // when enabled, the id is set to `true` as a `QUnit.config` property
278 label
: "Hide passed tests",
279 tooltip
: "Only show tests and assertions that fail. Stored as query-strings."
283 label
: "Check for Globals",
284 tooltip
: "Enabling this will test if any test introduces new properties on the " +
285 "global object (`window` in Browsers). Stored as query-strings."
289 label
: "No try-catch",
290 tooltip
: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
291 "exceptions in IE reasonable. Stored as query-strings."
295 // Set of all modules.
298 // Stack of nested modules
301 // The first unnamed module
310 var urlParams
= defined
.document
? getUrlParams() : {};
312 // Push a loose unnamed module to the modules collection
313 config
.modules
.push( config
.currentModule
);
315 if ( urlParams
.filter
=== true ) {
316 delete urlParams
.filter
;
319 // String search anywhere in moduleName+testName
320 config
.filter
= urlParams
.filter
;
323 if ( urlParams
.testId
) {
324 // Ensure that urlParams.testId is an array
325 urlParams
.testId
= decodeURIComponent( urlParams
.testId
).split( "," );
326 for (var i
= 0; i
< urlParams
.testId
.length
; i
++ ) {
327 config
.testId
.push( urlParams
.testId
[ i
] );
331 var loggingCallbacks
= {};
333 // Register logging callbacks
334 function registerLoggingCallbacks( obj
) {
336 callbackNames
= [ "begin", "done", "log", "testStart", "testDone",
337 "moduleStart", "moduleDone" ];
339 function registerLoggingCallback( key
) {
340 var loggingCallback = function( callback
) {
341 if ( objectType( callback
) !== "function" ) {
343 "QUnit logging methods require a callback function as their first parameters."
347 config
.callbacks
[ key
].push( callback
);
350 // DEPRECATED: This will be removed on QUnit 2.0.0+
351 // Stores the registered functions allowing restoring
352 // at verifyLoggingCallbacks() if modified
353 loggingCallbacks
[ key
] = loggingCallback
;
355 return loggingCallback
;
358 for ( i
= 0, l
= callbackNames
.length
; i
< l
; i
++ ) {
359 key
= callbackNames
[ i
];
361 // Initialize key collection of logging callback
362 if ( objectType( config
.callbacks
[ key
] ) === "undefined" ) {
363 config
.callbacks
[ key
] = [];
366 obj
[ key
] = registerLoggingCallback( key
);
370 function runLoggingCallbacks( key
, args
) {
373 callbacks
= config
.callbacks
[ key
];
374 for ( i
= 0, l
= callbacks
.length
; i
< l
; i
++ ) {
375 callbacks
[ i
]( args
);
379 // DEPRECATED: This will be removed on 2.0.0+
380 // This function verifies if the loggingCallbacks were modified by the user
381 // If so, it will restore it, assign the given callback and print a console warning
382 function verifyLoggingCallbacks() {
383 var loggingCallback
, userCallback
;
385 for ( loggingCallback
in loggingCallbacks
) {
386 if ( QUnit
[ loggingCallback
] !== loggingCallbacks
[ loggingCallback
] ) {
388 userCallback
= QUnit
[ loggingCallback
];
390 // Restore the callback function
391 QUnit
[ loggingCallback
] = loggingCallbacks
[ loggingCallback
];
393 // Assign the deprecated given callback
394 QUnit
[ loggingCallback
]( userCallback
);
396 if ( global
.console
&& global
.console
.warn
) {
398 "QUnit." + loggingCallback
+ " was replaced with a new value.\n" +
399 "Please, check out the documentation on how to apply logging callbacks.\n" +
400 "Reference: https://api.qunitjs.com/category/callbacks/"
408 if ( !defined
.document
) {
412 // `onErrorFnPrev` initialized at top of scope
413 // Preserve other handlers
414 var onErrorFnPrev
= window
.onerror
;
416 // Cover uncaught exceptions
417 // Returning true will suppress the default browser handler,
418 // returning false will let it run.
419 window
.onerror = function( error
, filePath
, linerNr
) {
421 if ( onErrorFnPrev
) {
422 ret
= onErrorFnPrev( error
, filePath
, linerNr
);
425 // Treat return value as window.onerror itself does,
426 // Only do our handling if not suppressed.
427 if ( ret
!== true ) {
428 if ( QUnit
.config
.current
) {
429 if ( QUnit
.config
.current
.ignoreGlobalErrors
) {
432 QUnit
.pushFailure( error
, filePath
+ ":" + linerNr
);
434 QUnit
.test( "global failure", extend(function() {
435 QUnit
.pushFailure( error
, filePath
+ ":" + linerNr
);
436 }, { validTest
: true } ) );
445 QUnit
.urlParams
= urlParams
;
447 // Figure out if we're running the tests from a server or not
448 QUnit
.isLocal
= !( defined
.document
&& window
.location
.protocol
!== "file:" );
450 // Expose the current QUnit version
451 QUnit
.version
= "1.22.0";
455 // call on start of module test to prepend name to all tests
456 module: function( name
, testEnvironment
, executeNow
) {
457 var module
, moduleFns
;
458 var currentModule
= config
.currentModule
;
460 if ( arguments
.length
=== 2 ) {
461 if ( testEnvironment
instanceof Function
) {
462 executeNow
= testEnvironment
;
463 testEnvironment
= undefined;
467 // DEPRECATED: handles setup/teardown functions,
468 // beforeEach and afterEach should be used instead
469 if ( testEnvironment
&& testEnvironment
.setup
) {
470 testEnvironment
.beforeEach
= testEnvironment
.setup
;
471 delete testEnvironment
.setup
;
473 if ( testEnvironment
&& testEnvironment
.teardown
) {
474 testEnvironment
.afterEach
= testEnvironment
.teardown
;
475 delete testEnvironment
.teardown
;
478 module
= createModule();
481 beforeEach
: setHook( module
, "beforeEach" ),
482 afterEach
: setHook( module
, "afterEach" )
485 if ( executeNow
instanceof Function
) {
486 config
.moduleStack
.push( module
);
487 setCurrentModule( module
);
488 executeNow
.call( module
.testEnvironment
, moduleFns
);
489 config
.moduleStack
.pop();
490 module
= module
.parentModule
|| currentModule
;
493 setCurrentModule( module
);
495 function createModule() {
496 var parentModule
= config
.moduleStack
.length
?
497 config
.moduleStack
.slice( -1 )[ 0 ] : null;
498 var moduleName
= parentModule
!== null ?
499 [ parentModule
.name
, name
].join( " > " ) : name
;
502 parentModule
: parentModule
,
507 if ( parentModule
) {
508 extend( env
, parentModule
.testEnvironment
);
509 delete env
.beforeEach
;
510 delete env
.afterEach
;
512 extend( env
, testEnvironment
);
513 module
.testEnvironment
= env
;
515 config
.modules
.push( module
);
519 function setCurrentModule( module
) {
520 config
.currentModule
= module
;
525 // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
526 asyncTest
: asyncTest
,
534 // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
535 // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
536 start: function( count
) {
537 var globalStartAlreadyCalled
= globalStartCalled
;
539 if ( !config
.current
) {
540 globalStartCalled
= true;
543 throw new Error( "Called start() outside of a test context while already started" );
544 } else if ( globalStartAlreadyCalled
|| count
> 1 ) {
545 throw new Error( "Called start() outside of a test context too many times" );
546 } else if ( config
.autostart
) {
547 throw new Error( "Called start() outside of a test context when " +
548 "QUnit.config.autostart was true" );
549 } else if ( !config
.pageLoaded
) {
551 // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
552 config
.autostart
= true;
557 // If a test is running, adjust its semaphore
558 config
.current
.semaphore
-= count
|| 1;
560 // If semaphore is non-numeric, throw error
561 if ( isNaN( config
.current
.semaphore
) ) {
562 config
.current
.semaphore
= 0;
565 "Called start() with a non-numeric decrement.",
566 sourceFromStacktrace( 2 )
571 // Don't start until equal number of stop-calls
572 if ( config
.current
.semaphore
> 0 ) {
576 // throw an Error if start is called more often than stop
577 if ( config
.current
.semaphore
< 0 ) {
578 config
.current
.semaphore
= 0;
581 "Called start() while already started (test's semaphore was 0 already)",
582 sourceFromStacktrace( 2 )
591 // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
592 stop: function( count
) {
594 // If there isn't a test running, don't allow QUnit.stop() to be called
595 if ( !config
.current
) {
596 throw new Error( "Called stop() outside of a test context" );
599 // If a test is running, adjust its semaphore
600 config
.current
.semaphore
+= count
|| 1;
609 objectType
: objectType
,
614 config
.pageLoaded
= true;
616 // Initialize the configuration options
618 stats
: { all
: 0, bad
: 0 },
619 moduleStats
: { all
: 0, bad
: 0 },
626 config
.blocking
= false;
628 if ( config
.autostart
) {
633 stack: function( offset
) {
634 offset
= ( offset
|| 0 ) + 2;
635 return sourceFromStacktrace( offset
);
639 registerLoggingCallbacks( QUnit
);
645 // If the test run hasn't officially begun yet
646 if ( !config
.started
) {
648 // Record the time of the test run's beginning
649 config
.started
= now();
651 verifyLoggingCallbacks();
653 // Delete the loose unnamed module if unused.
654 if ( config
.modules
[ 0 ].name
=== "" && config
.modules
[ 0 ].tests
.length
=== 0 ) {
655 config
.modules
.shift();
658 // Avoid unnecessary information by not logging modules' test environments
659 for ( i
= 0, l
= config
.modules
.length
; i
< l
; i
++ ) {
661 name
: config
.modules
[ i
].name
,
662 tests
: config
.modules
[ i
].tests
666 // The test run is officially beginning now
667 runLoggingCallbacks( "begin", {
668 totalTests
: Test
.count
,
673 config
.blocking
= false;
677 function process( last
) {
682 config
.depth
= ( config
.depth
|| 0 ) + 1;
684 while ( config
.queue
.length
&& !config
.blocking
) {
685 if ( !defined
.setTimeout
|| config
.updateRate
<= 0 ||
686 ( ( now() - start
) < config
.updateRate
) ) {
687 if ( config
.current
) {
689 // Reset async tracking for each phase of the Test lifecycle
690 config
.current
.usedAsync
= false;
692 config
.queue
.shift()();
694 setTimeout( next
, 13 );
699 if ( last
&& !config
.blocking
&& !config
.queue
.length
&& config
.depth
=== 0 ) {
704 function pauseProcessing() {
705 config
.blocking
= true;
707 if ( config
.testTimeout
&& defined
.setTimeout
) {
708 clearTimeout( config
.timeout
);
709 config
.timeout
= setTimeout(function() {
710 if ( config
.current
) {
711 config
.current
.semaphore
= 0;
712 QUnit
.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
714 throw new Error( "Test timed out" );
717 }, config
.testTimeout
);
721 function resumeProcessing() {
724 // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
725 if ( defined
.setTimeout
) {
726 setTimeout(function() {
727 if ( config
.current
&& config
.current
.semaphore
> 0 ) {
730 if ( config
.timeout
) {
731 clearTimeout( config
.timeout
);
744 config
.autorun
= true;
746 // Log the last module results
747 if ( config
.previousModule
) {
748 runLoggingCallbacks( "moduleDone", {
749 name
: config
.previousModule
.name
,
750 tests
: config
.previousModule
.tests
,
751 failed
: config
.moduleStats
.bad
,
752 passed
: config
.moduleStats
.all
- config
.moduleStats
.bad
,
753 total
: config
.moduleStats
.all
,
754 runtime
: now() - config
.moduleStats
.started
757 delete config
.previousModule
;
759 runtime
= now() - config
.started
;
760 passed
= config
.stats
.all
- config
.stats
.bad
;
762 runLoggingCallbacks( "done", {
763 failed
: config
.stats
.bad
,
765 total
: config
.stats
.all
,
770 function setHook( module
, hookName
) {
771 if ( module
.testEnvironment
=== undefined ) {
772 module
.testEnvironment
= {};
775 return function( callback
) {
776 module
.testEnvironment
[ hookName
] = callback
;
781 var priorityCount
= 0;
783 function Test( settings
) {
788 extend( this, settings
);
789 this.assertions
= [];
791 this.usedAsync
= false;
792 this.module
= config
.currentModule
;
793 this.stack
= sourceFromStacktrace( 3 );
795 // Register unique strings
796 for ( i
= 0, l
= this.module
.tests
; i
< l
.length
; i
++ ) {
797 if ( this.module
.tests
[ i
].name
=== this.testName
) {
798 this.testName
+= " ";
802 this.testId
= generateHash( this.module
.name
, this.testName
);
804 this.module
.tests
.push({
809 if ( settings
.skip
) {
811 // Skipped tests will fully ignore any sent callback
812 this.callback = function() {};
816 this.assert
= new Assert( this );
826 // Emit moduleStart when we're switching from one module to another
827 this.module
!== config
.previousModule
||
829 // They could be equal (both undefined) but if the previousModule property doesn't
830 // yet exist it means this is the first test in a suite that isn't wrapped in a
831 // module, in which case we'll just emit a moduleStart event for 'undefined'.
832 // Without this, reporters can get testStart before moduleStart which is a problem.
833 !hasOwn
.call( config
, "previousModule" )
835 if ( hasOwn
.call( config
, "previousModule" ) ) {
836 runLoggingCallbacks( "moduleDone", {
837 name
: config
.previousModule
.name
,
838 tests
: config
.previousModule
.tests
,
839 failed
: config
.moduleStats
.bad
,
840 passed
: config
.moduleStats
.all
- config
.moduleStats
.bad
,
841 total
: config
.moduleStats
.all
,
842 runtime
: now() - config
.moduleStats
.started
845 config
.previousModule
= this.module
;
846 config
.moduleStats
= { all
: 0, bad
: 0, started
: now() };
847 runLoggingCallbacks( "moduleStart", {
848 name
: this.module
.name
,
849 tests
: this.module
.tests
853 config
.current
= this;
855 if ( this.module
.testEnvironment
) {
856 delete this.module
.testEnvironment
.beforeEach
;
857 delete this.module
.testEnvironment
.afterEach
;
859 this.testEnvironment
= extend( {}, this.module
.testEnvironment
);
861 this.started
= now();
862 runLoggingCallbacks( "testStart", {
864 module
: this.module
.name
,
868 if ( !config
.pollution
) {
876 config
.current
= this;
882 this.callbackStarted
= now();
884 if ( config
.notrycatch
) {
892 this.pushFailure( "Died on test #" + ( this.assertions
.length
+ 1 ) + " " +
893 this.stack
+ ": " + ( e
.message
|| e
), extractStacktrace( e
, 0 ) );
895 // else next test will carry the responsibility
898 // Restart the tests if they're blocking
899 if ( config
.blocking
) {
904 function runTest( test
) {
905 promise
= test
.callback
.call( test
.testEnvironment
, test
.assert
);
906 test
.resolvePromise( promise
);
914 queueHook: function( hook
, hookName
) {
917 return function runHook() {
918 config
.current
= test
;
919 if ( config
.notrycatch
) {
926 test
.pushFailure( hookName
+ " failed on " + test
.testName
+ ": " +
927 ( error
.message
|| error
), extractStacktrace( error
, 0 ) );
930 function callHook() {
931 promise
= hook
.call( test
.testEnvironment
, test
.assert
);
932 test
.resolvePromise( promise
, hookName
);
937 // Currently only used for module level hooks, can be used to add global level ones
938 hooks: function( handler
) {
941 function processHooks( test
, module
) {
942 if ( module
.parentModule
) {
943 processHooks( test
, module
.parentModule
);
945 if ( module
.testEnvironment
&&
946 QUnit
.objectType( module
.testEnvironment
[ handler
] ) === "function" ) {
947 hooks
.push( test
.queueHook( module
.testEnvironment
[ handler
], handler
) );
951 // Hooks are ignored on skipped tests
953 processHooks( this, this.module
);
959 config
.current
= this;
960 if ( config
.requireExpects
&& this.expected
=== null ) {
961 this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
962 "not called.", this.stack
);
963 } else if ( this.expected
!== null && this.expected
!== this.assertions
.length
) {
964 this.pushFailure( "Expected " + this.expected
+ " assertions, but " +
965 this.assertions
.length
+ " were run", this.stack
);
966 } else if ( this.expected
=== null && !this.assertions
.length
) {
967 this.pushFailure( "Expected at least one assertion, but none were run - call " +
968 "expect(0) to accept zero assertions.", this.stack
);
974 this.runtime
= now() - this.started
;
975 config
.stats
.all
+= this.assertions
.length
;
976 config
.moduleStats
.all
+= this.assertions
.length
;
978 for ( i
= 0; i
< this.assertions
.length
; i
++ ) {
979 if ( !this.assertions
[ i
].result
) {
982 config
.moduleStats
.bad
++;
986 runLoggingCallbacks( "testDone", {
988 module
: this.module
.name
,
989 skipped
: !!this.skip
,
991 passed
: this.assertions
.length
- bad
,
992 total
: this.assertions
.length
,
993 runtime
: this.runtime
,
996 assertions
: this.assertions
,
1002 // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
1003 duration
: this.runtime
1006 // QUnit.reset() is deprecated and will be replaced for a new
1007 // fixture reset function on QUnit 2.0/2.1.
1008 // It's still called here for backwards compatibility handling
1011 config
.current
= undefined;
1018 if ( !this.valid() ) {
1024 // each of these can by async
1030 test
.hooks( "beforeEach" ),
1035 test
.hooks( "afterEach" ).reverse(),
1046 // Prioritize previously failed tests, detected from sessionStorage
1047 priority
= QUnit
.config
.reorder
&& defined
.sessionStorage
&&
1048 +sessionStorage
.getItem( "qunit-test-" + this.module
.name
+ "-" + this.testName
);
1050 return synchronize( run
, priority
);
1053 pushResult: function( resultInfo
) {
1055 // resultInfo = { result, actual, expected, message, negative }
1058 module
: this.module
.name
,
1059 name
: this.testName
,
1060 result
: resultInfo
.result
,
1061 message
: resultInfo
.message
,
1062 actual
: resultInfo
.actual
,
1063 expected
: resultInfo
.expected
,
1064 testId
: this.testId
,
1065 negative
: resultInfo
.negative
|| false,
1066 runtime
: now() - this.started
1069 if ( !resultInfo
.result
) {
1070 source
= sourceFromStacktrace();
1073 details
.source
= source
;
1077 runLoggingCallbacks( "log", details
);
1079 this.assertions
.push({
1080 result
: !!resultInfo
.result
,
1081 message
: resultInfo
.message
1085 pushFailure: function( message
, source
, actual
) {
1086 if ( !( this instanceof Test
) ) {
1087 throw new Error( "pushFailure() assertion outside test context, was " +
1088 sourceFromStacktrace( 2 ) );
1092 module
: this.module
.name
,
1093 name
: this.testName
,
1095 message
: message
|| "error",
1096 actual
: actual
|| null,
1097 testId
: this.testId
,
1098 runtime
: now() - this.started
1102 details
.source
= source
;
1105 runLoggingCallbacks( "log", details
);
1107 this.assertions
.push({
1113 resolvePromise: function( promise
, phase
) {
1116 if ( promise
!= null ) {
1117 then
= promise
.then
;
1118 if ( QUnit
.objectType( then
) === "function" ) {
1122 function() { QUnit
.start(); },
1124 message
= "Promise rejected " +
1125 ( !phase
? "during" : phase
.replace( /Each$/, "" ) ) +
1126 " " + test
.testName
+ ": " + ( error
.message
|| error
);
1127 test
.pushFailure( message
, extractStacktrace( error
, 0 ) );
1129 // else next test will carry the responsibility
1141 var filter
= config
.filter
,
1142 regexFilter
= /^(!?)\/([\w\W]*)\/(i?$)/.exec( filter
),
1143 module
= QUnit
.urlParams
.module
&& QUnit
.urlParams
.module
.toLowerCase(),
1144 fullName
= ( this.module
.name
+ ": " + this.testName
);
1146 function testInModuleChain( testModule
) {
1147 var testModuleName
= testModule
.name
? testModule
.name
.toLowerCase() : null;
1148 if ( testModuleName
=== module
) {
1150 } else if ( testModule
.parentModule
) {
1151 return testInModuleChain( testModule
.parentModule
);
1157 // Internally-generated tests are always valid
1158 if ( this.callback
&& this.callback
.validTest
) {
1162 if ( config
.testId
.length
> 0 && inArray( this.testId
, config
.testId
) < 0 ) {
1166 if ( module
&& !testInModuleChain( this.module
) ) {
1174 return regexFilter
?
1175 this.regexFilter( !!regexFilter
[1], regexFilter
[2], regexFilter
[3], fullName
) :
1176 this.stringFilter( filter
, fullName
);
1179 regexFilter: function( exclude
, pattern
, flags
, fullName
) {
1180 var regex
= new RegExp( pattern
, flags
);
1181 var match
= regex
.test( fullName
);
1183 return match
!== exclude
;
1186 stringFilter: function( filter
, fullName
) {
1187 filter
= filter
.toLowerCase();
1188 fullName
= fullName
.toLowerCase();
1190 var include
= filter
.charAt( 0 ) !== "!";
1192 filter
= filter
.slice( 1 );
1195 // If the filter matches, we need to honour include
1196 if ( fullName
.indexOf( filter
) !== -1 ) {
1200 // Otherwise, do the opposite
1205 // Resets the test setup. Useful for tests that modify the DOM.
1207 DEPRECATED: Use multiple tests instead of resetting inside a test.
1208 Use testStart or testDone for custom cleanup.
1209 This method will throw an error in 2.0, and will be removed in 2.1
1211 QUnit
.reset = function() {
1213 // Return on non-browser environments
1214 // This is necessary to not break on node tests
1215 if ( !defined
.document
) {
1219 var fixture
= defined
.document
&& document
.getElementById
&&
1220 document
.getElementById( "qunit-fixture" );
1223 fixture
.innerHTML
= config
.fixture
;
1227 QUnit
.pushFailure = function() {
1228 if ( !QUnit
.config
.current
) {
1229 throw new Error( "pushFailure() assertion outside test context, in " +
1230 sourceFromStacktrace( 2 ) );
1233 // Gets current test obj
1234 var currentTest
= QUnit
.config
.current
;
1236 return currentTest
.pushFailure
.apply( currentTest
, arguments
);
1239 // Based on Java's String.hashCode, a simple but not
1240 // rigorously collision resistant hashing function
1241 function generateHash( module
, testName
) {
1245 str
= module
+ "\x1C" + testName
,
1248 for ( ; i
< len
; i
++ ) {
1249 hash
= ( ( hash
<< 5 ) - hash
) + str
.charCodeAt( i
);
1253 // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
1254 // strictly necessary but increases user understanding that the id is a SHA-like hash
1255 hex
= ( 0x100000000 + hash
).toString( 16 );
1256 if ( hex
.length
< 8 ) {
1257 hex
= "0000000" + hex
;
1260 return hex
.slice( -8 );
1263 function synchronize( callback
, priority
) {
1264 var last
= !priority
;
1266 if ( QUnit
.objectType( callback
) === "array" ) {
1267 while ( callback
.length
) {
1268 synchronize( callback
.shift() );
1274 config
.queue
.splice( priorityCount
++, 0, callback
);
1276 config
.queue
.push( callback
);
1279 if ( config
.autorun
&& !config
.blocking
) {
1284 function saveGlobal() {
1285 config
.pollution
= [];
1287 if ( config
.noglobals
) {
1288 for ( var key
in global
) {
1289 if ( hasOwn
.call( global
, key
) ) {
1291 // in Opera sometimes DOM element ids show up here, ignore them
1292 if ( /^qunit-test-output/.test( key
) ) {
1295 config
.pollution
.push( key
);
1301 function checkPollution() {
1304 old
= config
.pollution
;
1308 newGlobals
= diff( config
.pollution
, old
);
1309 if ( newGlobals
.length
> 0 ) {
1310 QUnit
.pushFailure( "Introduced global variable(s): " + newGlobals
.join( ", " ) );
1313 deletedGlobals
= diff( old
, config
.pollution
);
1314 if ( deletedGlobals
.length
> 0 ) {
1315 QUnit
.pushFailure( "Deleted global variable(s): " + deletedGlobals
.join( ", " ) );
1319 // Will be exposed as QUnit.asyncTest
1320 function asyncTest( testName
, expected
, callback
) {
1321 if ( arguments
.length
=== 2 ) {
1322 callback
= expected
;
1326 QUnit
.test( testName
, expected
, callback
, true );
1329 // Will be exposed as QUnit.test
1330 function test( testName
, expected
, callback
, async
) {
1331 if ( focused
) { return; }
1335 if ( arguments
.length
=== 2 ) {
1336 callback
= expected
;
1340 newTest
= new Test({
1350 // Will be exposed as QUnit.skip
1351 function skip( testName
) {
1352 if ( focused
) { return; }
1354 var test
= new Test({
1362 // Will be exposed as QUnit.only
1363 function only( testName
, expected
, callback
, async
) {
1366 if ( focused
) { return; }
1368 QUnit
.config
.queue
.length
= 0;
1371 if ( arguments
.length
=== 2 ) {
1372 callback
= expected
;
1376 newTest
= new Test({
1386 function Assert( testContext
) {
1387 this.test
= testContext
;
1391 QUnit
.assert
= Assert
.prototype = {
1393 // Specify the number of expected assertions to guarantee that failed test
1394 // (no assertions are run at all) don't slip through.
1395 expect: function( asserts
) {
1396 if ( arguments
.length
=== 1 ) {
1397 this.test
.expected
= asserts
;
1399 return this.test
.expected
;
1403 // Increment this Test's semaphore counter, then return a function that
1404 // decrements that counter a maximum of once.
1405 async: function( count
) {
1406 var test
= this.test
,
1408 acceptCallCount
= count
;
1410 if ( typeof acceptCallCount
=== "undefined" ) {
1411 acceptCallCount
= 1;
1414 test
.semaphore
+= 1;
1415 test
.usedAsync
= true;
1418 return function done() {
1421 test
.pushFailure( "Too many calls to the `assert.async` callback",
1422 sourceFromStacktrace( 2 ) );
1425 acceptCallCount
-= 1;
1426 if ( acceptCallCount
> 0 ) {
1430 test
.semaphore
-= 1;
1436 // Exports test.push() to the user API
1437 // Alias of pushResult.
1438 push: function( result
, actual
, expected
, message
, negative
) {
1439 var currentAssert
= this instanceof Assert
? this : QUnit
.config
.current
.assert
;
1440 return currentAssert
.pushResult( {
1449 pushResult: function( resultInfo
) {
1451 // resultInfo = { result, actual, expected, message, negative }
1453 currentTest
= ( assert
instanceof Assert
&& assert
.test
) || QUnit
.config
.current
;
1455 // Backwards compatibility fix.
1456 // Allows the direct use of global exported assertions and QUnit.assert.*
1457 // Although, it's use is not recommended as it can leak assertions
1458 // to other tests from async tests, because we only get a reference to the current test,
1459 // not exactly the test where assertion were intended to be called.
1460 if ( !currentTest
) {
1461 throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
1464 if ( currentTest
.usedAsync
=== true && currentTest
.semaphore
=== 0 ) {
1465 currentTest
.pushFailure( "Assertion after the final `assert.async` was resolved",
1466 sourceFromStacktrace( 2 ) );
1468 // Allow this assertion to continue running anyway...
1471 if ( !( assert
instanceof Assert
) ) {
1472 assert
= currentTest
.assert
;
1475 return assert
.test
.pushResult( resultInfo
);
1478 ok: function( result
, message
) {
1479 message
= message
|| ( result
? "okay" : "failed, expected argument to be truthy, was: " +
1480 QUnit
.dump
.parse( result
) );
1489 notOk: function( result
, message
) {
1490 message
= message
|| ( !result
? "okay" : "failed, expected argument to be falsy, was: " +
1491 QUnit
.dump
.parse( result
) );
1500 equal: function( actual
, expected
, message
) {
1501 /*jshint eqeqeq:false */
1503 result
: expected
== actual
,
1510 notEqual: function( actual
, expected
, message
) {
1511 /*jshint eqeqeq:false */
1513 result
: expected
!= actual
,
1521 propEqual: function( actual
, expected
, message
) {
1522 actual
= objectValues( actual
);
1523 expected
= objectValues( expected
);
1525 result
: QUnit
.equiv( actual
, expected
),
1532 notPropEqual: function( actual
, expected
, message
) {
1533 actual
= objectValues( actual
);
1534 expected
= objectValues( expected
);
1536 result
: !QUnit
.equiv( actual
, expected
),
1544 deepEqual: function( actual
, expected
, message
) {
1546 result
: QUnit
.equiv( actual
, expected
),
1553 notDeepEqual: function( actual
, expected
, message
) {
1555 result
: !QUnit
.equiv( actual
, expected
),
1563 strictEqual: function( actual
, expected
, message
) {
1565 result
: expected
=== actual
,
1572 notStrictEqual: function( actual
, expected
, message
) {
1574 result
: expected
!== actual
,
1582 "throws": function( block
, expected
, message
) {
1583 var actual
, expectedType
,
1584 expectedOutput
= expected
,
1586 currentTest
= ( this instanceof Assert
&& this.test
) || QUnit
.config
.current
;
1588 // 'expected' is optional unless doing string comparison
1589 if ( message
== null && typeof expected
=== "string" ) {
1594 currentTest
.ignoreGlobalErrors
= true;
1596 block
.call( currentTest
.testEnvironment
);
1600 currentTest
.ignoreGlobalErrors
= false;
1603 expectedType
= QUnit
.objectType( expected
);
1605 // we don't want to validate thrown error
1608 expectedOutput
= null;
1610 // expected is a regexp
1611 } else if ( expectedType
=== "regexp" ) {
1612 ok
= expected
.test( errorString( actual
) );
1614 // expected is a string
1615 } else if ( expectedType
=== "string" ) {
1616 ok
= expected
=== errorString( actual
);
1618 // expected is a constructor, maybe an Error constructor
1619 } else if ( expectedType
=== "function" && actual
instanceof expected
) {
1622 // expected is an Error object
1623 } else if ( expectedType
=== "object" ) {
1624 ok
= actual
instanceof expected
.constructor &&
1625 actual
.name
=== expected
.name
&&
1626 actual
.message
=== expected
.message
;
1628 // expected is a validation function which returns true if validation passed
1629 } else if ( expectedType
=== "function" && expected
.call( {}, actual
) === true ) {
1630 expectedOutput
= null;
1635 currentTest
.assert
.pushResult( {
1638 expected
: expectedOutput
,
1644 // Provide an alternative to assert.throws(), for environments that consider throws a reserved word
1645 // Known to us are: Closure Compiler, Narwhal
1647 /*jshint sub:true */
1648 Assert
.prototype.raises
= Assert
.prototype[ "throws" ];
1651 function errorString( error
) {
1653 resultErrorString
= error
.toString();
1654 if ( resultErrorString
.substring( 0, 7 ) === "[object" ) {
1655 name
= error
.name
? error
.name
.toString() : "Error";
1656 message
= error
.message
? error
.message
.toString() : "";
1657 if ( name
&& message
) {
1658 return name
+ ": " + message
;
1659 } else if ( name
) {
1661 } else if ( message
) {
1667 return resultErrorString
;
1671 // Test for equality any JavaScript type.
1672 // Author: Philippe Rathé <prathe@gmail.com>
1673 QUnit
.equiv
= (function() {
1675 // Stack to decide between skip/abort functions
1678 // Stack to avoiding loops from circular referencing
1682 var getProto
= Object
.getPrototypeOf
|| function( obj
) {
1684 /*jshint proto: true */
1685 return obj
.__proto__
;
1688 function useStrictEquality( b
, a
) {
1690 // To catch short annotation VS 'new' annotation of a declaration. e.g.:
1692 // `var j = new Number(1);`
1693 if ( typeof a
=== "object" ) {
1696 if ( typeof b
=== "object" ) {
1703 function compareConstructors( a
, b
) {
1704 var protoA
= getProto( a
);
1705 var protoB
= getProto( b
);
1707 // Comparing constructors is more strict than using `instanceof`
1708 if ( a
.constructor === b
.constructor ) {
1713 // If the obj prototype descends from a null constructor, treat it
1714 // as a null prototype.
1715 if ( protoA
&& protoA
.constructor === null ) {
1718 if ( protoB
&& protoB
.constructor === null ) {
1722 // Allow objects with no prototype to be equivalent to
1723 // objects with Object as their constructor.
1724 if ( ( protoA
=== null && protoB
=== Object
.prototype ) ||
1725 ( protoB
=== null && protoA
=== Object
.prototype ) ) {
1732 function getRegExpFlags( regexp
) {
1733 return "flags" in regexp
? regexp
.flags
: regexp
.toString().match( /[gimuy]*$/ )[ 0 ];
1737 "string": useStrictEquality
,
1738 "boolean": useStrictEquality
,
1739 "number": useStrictEquality
,
1740 "null": useStrictEquality
,
1741 "undefined": useStrictEquality
,
1742 "symbol": useStrictEquality
,
1743 "date": useStrictEquality
,
1749 "regexp": function( b
, a
) {
1750 return a
.source
=== b
.source
&&
1752 // Include flags in the comparison
1753 getRegExpFlags( a
) === getRegExpFlags( b
);
1756 // - skip when the property is a method of an instance (OOP)
1757 // - abort otherwise,
1758 // initial === would have catch identical references anyway
1759 "function": function() {
1760 var caller
= callers
[ callers
.length
- 1 ];
1761 return caller
!== Object
&& typeof caller
!== "undefined";
1764 "array": function( b
, a
) {
1765 var i
, j
, len
, loop
, aCircular
, bCircular
;
1768 if ( len
!== b
.length
) {
1773 // Track reference to avoid circular references
1776 for ( i
= 0; i
< len
; i
++ ) {
1778 for ( j
= 0; j
< parents
.length
; j
++ ) {
1779 aCircular
= parents
[ j
] === a
[ i
];
1780 bCircular
= parentsB
[ j
] === b
[ i
];
1781 if ( aCircular
|| bCircular
) {
1782 if ( a
[ i
] === b
[ i
] || aCircular
&& bCircular
) {
1791 if ( !loop
&& !innerEquiv( a
[ i
], b
[ i
] ) ) {
1802 "set": function( b
, a
) {
1806 a
.forEach( function( v
) {
1810 b
.forEach( function( v
) {
1814 return innerEquiv( bArray
, aArray
);
1817 "map": function( b
, a
) {
1821 a
.forEach( function( v
, k
) {
1822 aArray
.push( [ k
, v
] );
1825 b
.forEach( function( v
, k
) {
1826 bArray
.push( [ k
, v
] );
1829 return innerEquiv( bArray
, aArray
);
1832 "object": function( b
, a
) {
1833 var i
, j
, loop
, aCircular
, bCircular
;
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 // 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 ) {
2013 separator: function() {
2014 return this.multiline ? this.HTML ? "<br
/>" : "\n" : this.HTML ? " " : " ";
2016 // extra can be a number, shortcut for increasing-calling-decreasing
2017 indent: function( extra ) {
2018 if ( !this.multiline ) {
2021 var chr = this.indentChar;
2023 chr = chr.replace( /\t/g, " " ).replace( / /g, " " );
2025 return new Array( this.depth + ( extra || 0 ) ).join( chr );
2028 this.depth += a || 1;
2030 down: function( a ) {
2031 this.depth -= a || 1;
2033 setParser: function( name, parser ) {
2034 this.parsers[ name ] = parser;
2036 // The next 3 are exposed so you can use them
2042 maxDepth: QUnit.config.maxDepth,
2044 // This is the list of parsers, to modify them, use dump.setParser
2047 document: "[Document
]",
2048 error: function( error ) {
2049 return "Error(\"" + error.message + "\")";
2051 unknown: "[Unknown
]",
2053 "undefined": "undefined",
2054 "function": function( fn ) {
2055 var ret = "function",
2057 // functions never have name in IE
2058 name = "name
" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
2065 ret = [ ret, dump.parse( fn, "functionArgs
" ), "){" ].join( "" );
2066 return join( ret, dump.parse( fn, "functionCode
" ), "}" );
2071 object: function( map, stack ) {
2072 var keys, key, val, i, nonEnumerableProperties,
2075 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
2076 return "[object Object
]";
2081 for ( key in map ) {
2085 // Some properties are not always enumerable on Error objects.
2086 nonEnumerableProperties = [ "message
", "name
" ];
2087 for ( i in nonEnumerableProperties ) {
2088 key = nonEnumerableProperties[ i ];
2089 if ( key in map && inArray( key, keys ) < 0 ) {
2094 for ( i = 0; i < keys.length; i++ ) {
2097 ret.push( dump.parse( key, "key
" ) + ": " +
2098 dump.parse( val, undefined, stack ) );
2101 return join( "{", ret, "}" );
2103 node: function( node ) {
2105 open = dump.HTML ? "<
;" : "<",
2106 close = dump.HTML ? ">
;" : ">",
2107 tag = node.nodeName.toLowerCase(),
2109 attrs = node.attributes;
2112 for ( i = 0, len = attrs.length; i < len; i++ ) {
2113 val = attrs[ i ].nodeValue;
2115 // IE6 includes all attributes in .attributes, even ones not explicitly
2116 // set. Those have values like undefined, null, 0, false, "" or
2118 if ( val && val !== "inherit
" ) {
2119 ret += " " + attrs[ i ].nodeName + "=" +
2120 dump.parse( val, "attribute
" );
2126 // Show content of TextNode or CDATASection
2127 if ( node.nodeType === 3 || node.nodeType === 4 ) {
2128 ret += node.nodeValue;
2131 return ret + open + "/" + tag + close;
2134 // function calls it internally, it's the arguments part of the function
2135 functionArgs: function( fn ) {
2143 args = new Array( l );
2147 args[ l ] = String.fromCharCode( 97 + l );
2149 return " " + args.join( ", " ) + " ";
2151 // object calls it internally, the key part of an item in a map
2153 // function calls it internally, it's the content of the function
2154 functionCode: "[code
]",
2155 // node calls it internally, it's a html attribute value
2163 // if true, entities are escaped ( <, >, \t, space and \n )
2167 // if true, items in a collection, are separated by a \n, else just a space.
2175 QUnit.jsDump = QUnit.dump;
2178 // Extend assert methods to QUnit for Backwards compatibility
2181 assertions = Assert.prototype;
2183 function applyCurrent( current ) {
2185 var assert = new Assert( QUnit.config.current );
2186 current.apply( assert, arguments );
2190 for ( i in assertions ) {
2191 QUnit[ i ] = applyCurrent( assertions[ i ] );
2195 // For browser, export only select globals
2196 if ( defined.document ) {
2221 for ( i = 0, l = keys.length; i < l; i++ ) {
2222 window[ keys[ i ] ] = QUnit[ keys[ i ] ];
2226 window.QUnit = QUnit;
2230 if ( typeof module !== "undefined" && module && module.exports ) {
2231 module.exports = QUnit;
2233 // For consistency with CommonJS environments' exports
2234 module.exports.QUnit = QUnit;
2237 // For CommonJS with exports, but without module.exports, like Rhino
2238 if ( typeof exports !== "undefined" && exports ) {
2239 exports.QUnit = QUnit;
2242 if ( typeof define === "function" && define.amd ) {
2243 define( function() {
2246 QUnit.config.autostart = false;
2250 * This file is a modified version of google-diff-match-patch's JavaScript implementation
2251 * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
2252 * modifications are licensed as more fully set forth in LICENSE.txt.
2254 * The original source of google-diff-match-patch is attributable and licensed as follows:
2256 * Copyright 2006 Google Inc.
2257 * https://code.google.com/p/google-diff-match-patch/
2259 * Licensed under the Apache License, Version 2.0 (the "License
");
2260 * you may not use this file except in compliance with the License.
2261 * You may obtain a copy of the License at
2263 * https://www.apache.org/licenses/LICENSE-2.0
2265 * Unless required by applicable law or agreed to in writing, software
2266 * distributed under the License is distributed on an "AS IS
" BASIS,
2267 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2268 * See the License for the specific language governing permissions and
2269 * limitations under the License.
2272 * https://code.google.com/p/google-diff-match-patch/
2274 * Usage: QUnit.diff(expected, actual)
2277 QUnit.diff = ( function() {
2278 function DiffMatchPatch() {
2284 * The data structure representing a diff is an array of tuples:
2285 * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
2286 * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
2288 var DIFF_DELETE = -1,
2293 * Find the differences between two texts. Simplifies the problem by stripping
2294 * any common prefix or suffix off the texts before diffing.
2295 * @param {string} text1 Old string to be diffed.
2296 * @param {string} text2 New string to be diffed.
2297 * @param {boolean=} optChecklines Optional speedup flag. If present and false,
2298 * then don't run a line-level diff first to identify the changed areas.
2299 * Defaults to true, which does a faster, slightly less optimal diff.
2300 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2302 DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) {
2303 var deadline, checklines, commonlength,
2304 commonprefix, commonsuffix, diffs;
2306 // The diff must be complete in up to 1 second.
2307 deadline = ( new Date() ).getTime() + 1000;
2309 // Check for null inputs.
2310 if ( text1 === null || text2 === null ) {
2311 throw new Error( "Null input
. (DiffMain
)" );
2314 // Check for equality (speedup).
2315 if ( text1 === text2 ) {
2318 [ DIFF_EQUAL, text1 ]
2324 if ( typeof optChecklines === "undefined" ) {
2325 optChecklines = true;
2328 checklines = optChecklines;
2330 // Trim off common prefix (speedup).
2331 commonlength = this.diffCommonPrefix( text1, text2 );
2332 commonprefix = text1.substring( 0, commonlength );
2333 text1 = text1.substring( commonlength );
2334 text2 = text2.substring( commonlength );
2336 // Trim off common suffix (speedup).
2337 commonlength = this.diffCommonSuffix( text1, text2 );
2338 commonsuffix = text1.substring( text1.length - commonlength );
2339 text1 = text1.substring( 0, text1.length - commonlength );
2340 text2 = text2.substring( 0, text2.length - commonlength );
2342 // Compute the diff on the middle block.
2343 diffs = this.diffCompute( text1, text2, checklines, deadline );
2345 // Restore the prefix and suffix.
2346 if ( commonprefix ) {
2347 diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
2349 if ( commonsuffix ) {
2350 diffs.push( [ DIFF_EQUAL, commonsuffix ] );
2352 this.diffCleanupMerge( diffs );
2357 * Reduce the number of edits by eliminating operationally trivial equalities.
2358 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2360 DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
2361 var changes, equalities, equalitiesLength, lastequality,
2362 pointer, preIns, preDel, postIns, postDel;
2364 equalities = []; // Stack of indices where equalities are found.
2365 equalitiesLength = 0; // Keeping our own length var is faster in JS.
2366 /** @type {?string} */
2367 lastequality = null;
2368 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
2369 pointer = 0; // Index of current position.
2370 // Is there an insertion operation before the last equality.
2372 // Is there a deletion operation before the last equality.
2374 // Is there an insertion operation after the last equality.
2376 // Is there a deletion operation after the last equality.
2378 while ( pointer < diffs.length ) {
2381 if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) {
2382 if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) {
2385 equalities[ equalitiesLength++ ] = pointer;
2388 lastequality = diffs[ pointer ][ 1 ];
2391 // Not a candidate, and can never become one.
2392 equalitiesLength = 0;
2393 lastequality = null;
2395 postIns = postDel = false;
2397 // An insertion or deletion.
2400 if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
2407 * Five types to be split:
2408 * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
2409 * <ins>A</ins>X<ins>C</ins><del>D</del>
2410 * <ins>A</ins><del>B</del>X<ins>C</ins>
2411 * <ins>A</del>X<ins>C</ins><del>D</del>
2412 * <ins>A</ins><del>B</del>X<del>C</del>
2414 if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
2415 ( ( lastequality.length < 2 ) &&
2416 ( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
2418 // Duplicate record.
2420 equalities[ equalitiesLength - 1 ],
2422 [ DIFF_DELETE, lastequality ]
2425 // Change second copy to insert.
2426 diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
2427 equalitiesLength--; // Throw away the equality we just deleted;
2428 lastequality = null;
2429 if ( preIns && preDel ) {
2430 // No changes made which could affect previous entry, keep going.
2431 postIns = postDel = true;
2432 equalitiesLength = 0;
2434 equalitiesLength--; // Throw away the previous equality.
2435 pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
2436 postIns = postDel = false;
2445 this.diffCleanupMerge( diffs );
2450 * Convert a diff array into a pretty HTML report.
2451 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2452 * @param {integer} string to be beautified.
2453 * @return {string} HTML representation.
2455 DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
2458 for ( x = 0; x < diffs.length; x++ ) {
2459 op = diffs[ x ][ 0 ]; // Operation (insert, delete, equal)
2460 data = diffs[ x ][ 1 ]; // Text of change.
2463 html[ x ] = "<ins
>" + data + "</ins
>";
2466 html[ x ] = "<del
>" + data + "</del
>";
2469 html[ x ] = "<span
>" + data + "</span
>";
2473 return html.join( "" );
2477 * Determine the common prefix of two strings.
2478 * @param {string} text1 First string.
2479 * @param {string} text2 Second string.
2480 * @return {number} The number of characters common to the start of each
2483 DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
2484 var pointermid, pointermax, pointermin, pointerstart;
2485 // Quick check for common null cases.
2486 if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) {
2490 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
2492 pointermax = Math.min( text1.length, text2.length );
2493 pointermid = pointermax;
2495 while ( pointermin < pointermid ) {
2496 if ( text1.substring( pointerstart, pointermid ) ===
2497 text2.substring( pointerstart, pointermid ) ) {
2498 pointermin = pointermid;
2499 pointerstart = pointermin;
2501 pointermax = pointermid;
2503 pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
2509 * Determine the common suffix of two strings.
2510 * @param {string} text1 First string.
2511 * @param {string} text2 Second string.
2512 * @return {number} The number of characters common to the end of each string.
2514 DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
2515 var pointermid, pointermax, pointermin, pointerend;
2516 // Quick check for common null cases.
2519 text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) {
2523 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
2525 pointermax = Math.min( text1.length, text2.length );
2526 pointermid = pointermax;
2528 while ( pointermin < pointermid ) {
2529 if ( text1.substring( text1.length - pointermid, text1.length - pointerend ) ===
2530 text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {
2531 pointermin = pointermid;
2532 pointerend = pointermin;
2534 pointermax = pointermid;
2536 pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
2542 * Find the differences between two texts. Assumes that the texts do not
2543 * have any common prefix or suffix.
2544 * @param {string} text1 Old string to be diffed.
2545 * @param {string} text2 New string to be diffed.
2546 * @param {boolean} checklines Speedup flag. If false, then don't run a
2547 * line-level diff first to identify the changed areas.
2548 * If true, then run a faster, slightly less optimal diff.
2549 * @param {number} deadline Time when the diff should be complete by.
2550 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2553 DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
2554 var diffs, longtext, shorttext, i, hm,
2555 text1A, text2A, text1B, text2B,
2556 midCommon, diffsA, diffsB;
2559 // Just add some text (speedup).
2561 [ DIFF_INSERT, text2 ]
2566 // Just delete some text (speedup).
2568 [ DIFF_DELETE, text1 ]
2572 longtext = text1.length > text2.length ? text1 : text2;
2573 shorttext = text1.length > text2.length ? text2 : text1;
2574 i = longtext.indexOf( shorttext );
2576 // Shorter text is inside the longer text (speedup).
2578 [ DIFF_INSERT, longtext.substring( 0, i ) ],
2579 [ DIFF_EQUAL, shorttext ],
2580 [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
2582 // Swap insertions for deletions if diff is reversed.
2583 if ( text1.length > text2.length ) {
2584 diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE;
2589 if ( shorttext.length === 1 ) {
2590 // Single character string.
2591 // After the previous speedup, the character can't be an equality.
2593 [ DIFF_DELETE, text1 ],
2594 [ DIFF_INSERT, text2 ]
2598 // Check to see if the problem can be split in two.
2599 hm = this.diffHalfMatch( text1, text2 );
2601 // A half-match was found, sort out the return data.
2606 midCommon = hm[ 4 ];
2607 // Send both pairs off for separate processing.
2608 diffsA = this.DiffMain( text1A, text2A, checklines, deadline );
2609 diffsB = this.DiffMain( text1B, text2B, checklines, deadline );
2610 // Merge the results.
2611 return diffsA.concat( [
2612 [ DIFF_EQUAL, midCommon ]
2616 if ( checklines && text1.length > 100 && text2.length > 100 ) {
2617 return this.diffLineMode( text1, text2, deadline );
2620 return this.diffBisect( text1, text2, deadline );
2624 * Do the two texts share a substring which is at least half the length of the
2626 * This speedup can produce non-minimal diffs.
2627 * @param {string} text1 First string.
2628 * @param {string} text2 Second string.
2629 * @return {Array.<string>} Five element Array, containing the prefix of
2630 * text1, the suffix of text1, the prefix of text2, the suffix of
2631 * text2 and the common middle. Or null if there was no match.
2634 DiffMatchPatch.prototype.diffHalfMatch = function( text1, text2 ) {
2635 var longtext, shorttext, dmp,
2636 text1A, text2B, text2A, text1B, midCommon,
2639 longtext = text1.length > text2.length ? text1 : text2;
2640 shorttext = text1.length > text2.length ? text2 : text1;
2641 if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) {
2642 return null; // Pointless.
2644 dmp = this; // 'this' becomes 'window' in a closure.
2647 * Does a substring of shorttext exist within longtext such that the substring
2648 * is at least half the length of longtext?
2649 * Closure, but does not reference any external variables.
2650 * @param {string} longtext Longer string.
2651 * @param {string} shorttext Shorter string.
2652 * @param {number} i Start index of quarter length substring within longtext.
2653 * @return {Array.<string>} Five element Array, containing the prefix of
2654 * longtext, the suffix of longtext, the prefix of shorttext, the suffix
2655 * of shorttext and the common middle. Or null if there was no match.
2658 function diffHalfMatchI( longtext, shorttext, i ) {
2659 var seed, j, bestCommon, prefixLength, suffixLength,
2660 bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
2661 // Start with a 1/4 length substring at position i as a seed.
2662 seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) );
2665 while ( ( j = shorttext.indexOf( seed, j + 1 ) ) !== -1 ) {
2666 prefixLength = dmp.diffCommonPrefix( longtext.substring( i ),
2667 shorttext.substring( j ) );
2668 suffixLength = dmp.diffCommonSuffix( longtext.substring( 0, i ),
2669 shorttext.substring( 0, j ) );
2670 if ( bestCommon.length < suffixLength + prefixLength ) {
2671 bestCommon = shorttext.substring( j - suffixLength, j ) +
2672 shorttext.substring( j, j + prefixLength );
2673 bestLongtextA = longtext.substring( 0, i - suffixLength );
2674 bestLongtextB = longtext.substring( i + prefixLength );
2675 bestShorttextA = shorttext.substring( 0, j - suffixLength );
2676 bestShorttextB = shorttext.substring( j + prefixLength );
2679 if ( bestCommon.length * 2 >= longtext.length ) {
2680 return [ bestLongtextA, bestLongtextB,
2681 bestShorttextA, bestShorttextB, bestCommon
2688 // First check if the second quarter is the seed for a half-match.
2689 hm1 = diffHalfMatchI( longtext, shorttext,
2690 Math.ceil( longtext.length / 4 ) );
2691 // Check again based on the third quarter.
2692 hm2 = diffHalfMatchI( longtext, shorttext,
2693 Math.ceil( longtext.length / 2 ) );
2694 if ( !hm1 && !hm2 ) {
2696 } else if ( !hm2 ) {
2698 } else if ( !hm1 ) {
2701 // Both matched. Select the longest.
2702 hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2;
2705 // A half-match was found, sort out the return data.
2706 text1A, text1B, text2A, text2B;
2707 if ( text1.length > text2.length ) {
2718 midCommon = hm[ 4 ];
2719 return [ text1A, text1B, text2A, text2B, midCommon ];
2723 * Do a quick line-level diff on both strings, then rediff the parts for
2725 * This speedup can produce non-minimal diffs.
2726 * @param {string} text1 Old string to be diffed.
2727 * @param {string} text2 New string to be diffed.
2728 * @param {number} deadline Time when the diff should be complete by.
2729 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2732 DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) {
2733 var a, diffs, linearray, pointer, countInsert,
2734 countDelete, textInsert, textDelete, j;
2735 // Scan the text on a line-by-line basis first.
2736 a = this.diffLinesToChars( text1, text2 );
2739 linearray = a.lineArray;
2741 diffs = this.DiffMain( text1, text2, false, deadline );
2743 // Convert the diff back to original text.
2744 this.diffCharsToLines( diffs, linearray );
2745 // Eliminate freak matches (e.g. blank lines)
2746 this.diffCleanupSemantic( diffs );
2748 // Rediff any replacement blocks, this time character-by-character.
2749 // Add a dummy entry at the end.
2750 diffs.push( [ DIFF_EQUAL, "" ] );
2756 while ( pointer < diffs.length ) {
2757 switch ( diffs[ pointer ][ 0 ] ) {
2760 textInsert += diffs[ pointer ][ 1 ];
2764 textDelete += diffs[ pointer ][ 1 ];
2767 // Upon reaching an equality, check for prior redundancies.
2768 if ( countDelete >= 1 && countInsert >= 1 ) {
2769 // Delete the offending records and add the merged ones.
2770 diffs.splice( pointer - countDelete - countInsert,
2771 countDelete + countInsert );
2772 pointer = pointer - countDelete - countInsert;
2773 a = this.DiffMain( textDelete, textInsert, false, deadline );
2774 for ( j = a.length - 1; j >= 0; j-- ) {
2775 diffs.splice( pointer, 0, a[ j ] );
2777 pointer = pointer + a.length;
2787 diffs.pop(); // Remove the dummy entry at the end.
2793 * Find the 'middle snake' of a diff, split the problem in two
2794 * and return the recursively constructed diff.
2795 * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
2796 * @param {string} text1 Old string to be diffed.
2797 * @param {string} text2 New string to be diffed.
2798 * @param {number} deadline Time at which to bail if not yet complete.
2799 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2802 DiffMatchPatch.prototype.diffBisect = function( text1, text2, deadline ) {
2803 var text1Length, text2Length, maxD, vOffset, vLength,
2804 v1, v2, x, delta, front, k1start, k1end, k2start,
2805 k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
2806 // Cache the text lengths to prevent multiple calls.
2807 text1Length = text1.length;
2808 text2Length = text2.length;
2809 maxD = Math.ceil( ( text1Length + text2Length ) / 2 );
2812 v1 = new Array( vLength );
2813 v2 = new Array( vLength );
2814 // Setting all elements to -1 is faster in Chrome & Firefox than mixing
2815 // integers and undefined.
2816 for ( x = 0; x < vLength; x++ ) {
2820 v1[ vOffset + 1 ] = 0;
2821 v2[ vOffset + 1 ] = 0;
2822 delta = text1Length - text2Length;
2823 // If the total number of characters is odd, then the front path will collide
2824 // with the reverse path.
2825 front = ( delta % 2 !== 0 );
2826 // Offsets for start and end of k loop.
2827 // Prevents mapping of space beyond the grid.
2832 for ( d = 0; d < maxD; d++ ) {
2833 // Bail out if deadline is reached.
2834 if ( ( new Date() ).getTime() > deadline ) {
2838 // Walk the front path one step.
2839 for ( k1 = -d + k1start; k1 <= d - k1end; k1 += 2 ) {
2840 k1Offset = vOffset + k1;
2841 if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {
2842 x1 = v1[ k1Offset + 1 ];
2844 x1 = v1[ k1Offset - 1 ] + 1;
2847 while ( x1 < text1Length && y1 < text2Length &&
2848 text1.charAt( x1 ) === text2.charAt( y1 ) ) {
2852 v1[ k1Offset ] = x1;
2853 if ( x1 > text1Length ) {
2854 // Ran off the right of the graph.
2856 } else if ( y1 > text2Length ) {
2857 // Ran off the bottom of the graph.
2859 } else if ( front ) {
2860 k2Offset = vOffset + delta - k1;
2861 if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) {
2862 // Mirror x2 onto top-left coordinate system.
2863 x2 = text1Length - v2[ k2Offset ];
2865 // Overlap detected.
2866 return this.diffBisectSplit( text1, text2, x1, y1, deadline );
2872 // Walk the reverse path one step.
2873 for ( k2 = -d + k2start; k2 <= d - k2end; k2 += 2 ) {
2874 k2Offset = vOffset + k2;
2875 if ( k2 === -d || ( k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {
2876 x2 = v2[ k2Offset + 1 ];
2878 x2 = v2[ k2Offset - 1 ] + 1;
2881 while ( x2 < text1Length && y2 < text2Length &&
2882 text1.charAt( text1Length - x2 - 1 ) ===
2883 text2.charAt( text2Length - y2 - 1 ) ) {
2887 v2[ k2Offset ] = x2;
2888 if ( x2 > text1Length ) {
2889 // Ran off the left of the graph.
2891 } else if ( y2 > text2Length ) {
2892 // Ran off the top of the graph.
2894 } else if ( !front ) {
2895 k1Offset = vOffset + delta - k2;
2896 if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) {
2897 x1 = v1[ k1Offset ];
2898 y1 = vOffset + x1 - k1Offset;
2899 // Mirror x2 onto top-left coordinate system.
2900 x2 = text1Length - x2;
2902 // Overlap detected.
2903 return this.diffBisectSplit( text1, text2, x1, y1, deadline );
2909 // Diff took too long and hit the deadline or
2910 // number of diffs equals number of characters, no commonality at all.
2912 [ DIFF_DELETE, text1 ],
2913 [ DIFF_INSERT, text2 ]
2918 * Given the location of the 'middle snake', split the diff in two parts
2920 * @param {string} text1 Old string to be diffed.
2921 * @param {string} text2 New string to be diffed.
2922 * @param {number} x Index of split point in text1.
2923 * @param {number} y Index of split point in text2.
2924 * @param {number} deadline Time at which to bail if not yet complete.
2925 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2928 DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
2929 var text1a, text1b, text2a, text2b, diffs, diffsb;
2930 text1a = text1.substring( 0, x );
2931 text2a = text2.substring( 0, y );
2932 text1b = text1.substring( x );
2933 text2b = text2.substring( y );
2935 // Compute both diffs serially.
2936 diffs = this.DiffMain( text1a, text2a, false, deadline );
2937 diffsb = this.DiffMain( text1b, text2b, false, deadline );
2939 return diffs.concat( diffsb );
2943 * Reduce the number of edits by eliminating semantically trivial equalities.
2944 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2946 DiffMatchPatch.prototype.diffCleanupSemantic = function( diffs ) {
2947 var changes, equalities, equalitiesLength, lastequality,
2948 pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
2949 lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
2951 equalities = []; // Stack of indices where equalities are found.
2952 equalitiesLength = 0; // Keeping our own length var is faster in JS.
2953 /** @type {?string} */
2954 lastequality = null;
2955 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
2956 pointer = 0; // Index of current position.
2957 // Number of characters that changed prior to the equality.
2958 lengthInsertions1 = 0;
2959 lengthDeletions1 = 0;
2960 // Number of characters that changed after the equality.
2961 lengthInsertions2 = 0;
2962 lengthDeletions2 = 0;
2963 while ( pointer < diffs.length ) {
2964 if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
2965 equalities[ equalitiesLength++ ] = pointer;
2966 lengthInsertions1 = lengthInsertions2;
2967 lengthDeletions1 = lengthDeletions2;
2968 lengthInsertions2 = 0;
2969 lengthDeletions2 = 0;
2970 lastequality = diffs[ pointer ][ 1 ];
2971 } else { // An insertion or deletion.
2972 if ( diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
2973 lengthInsertions2 += diffs[ pointer ][ 1 ].length;
2975 lengthDeletions2 += diffs[ pointer ][ 1 ].length;
2977 // Eliminate an equality that is smaller or equal to the edits on both
2979 if ( lastequality && ( lastequality.length <=
2980 Math.max( lengthInsertions1, lengthDeletions1 ) ) &&
2981 ( lastequality.length <= Math.max( lengthInsertions2,
2982 lengthDeletions2 ) ) ) {
2984 // Duplicate record.
2986 equalities[ equalitiesLength - 1 ],
2988 [ DIFF_DELETE, lastequality ]
2991 // Change second copy to insert.
2992 diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
2994 // Throw away the equality we just deleted.
2997 // Throw away the previous equality (it needs to be reevaluated).
2999 pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
3001 // Reset the counters.
3002 lengthInsertions1 = 0;
3003 lengthDeletions1 = 0;
3004 lengthInsertions2 = 0;
3005 lengthDeletions2 = 0;
3006 lastequality = null;
3013 // Normalize the diff.
3015 this.diffCleanupMerge( diffs );
3018 // Find any overlaps between deletions and insertions.
3019 // e.g: <del>abcxxx</del><ins>xxxdef</ins>
3020 // -> <del>abc</del>xxx<ins>def</ins>
3021 // e.g: <del>xxxabc</del><ins>defxxx</ins>
3022 // -> <ins>def</ins>xxx<del>abc</del>
3023 // Only extract an overlap if it is as big as the edit ahead or behind it.
3025 while ( pointer < diffs.length ) {
3026 if ( diffs[ pointer - 1 ][ 0 ] === DIFF_DELETE &&
3027 diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
3028 deletion = diffs[ pointer - 1 ][ 1 ];
3029 insertion = diffs[ pointer ][ 1 ];
3030 overlapLength1 = this.diffCommonOverlap( deletion, insertion );
3031 overlapLength2 = this.diffCommonOverlap( insertion, deletion );
3032 if ( overlapLength1 >= overlapLength2 ) {
3033 if ( overlapLength1 >= deletion.length / 2 ||
3034 overlapLength1 >= insertion.length / 2 ) {
3035 // Overlap found. Insert an equality and trim the surrounding edits.
3039 [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ]
3041 diffs[ pointer - 1 ][ 1 ] =
3042 deletion.substring( 0, deletion.length - overlapLength1 );
3043 diffs[ pointer + 1 ][ 1 ] = insertion.substring( overlapLength1 );
3047 if ( overlapLength2 >= deletion.length / 2 ||
3048 overlapLength2 >= insertion.length / 2 ) {
3050 // Reverse overlap found.
3051 // Insert an equality and swap and trim the surrounding edits.
3055 [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ]
3058 diffs[ pointer - 1 ][ 0 ] = DIFF_INSERT;
3059 diffs[ pointer - 1 ][ 1 ] =
3060 insertion.substring( 0, insertion.length - overlapLength2 );
3061 diffs[ pointer + 1 ][ 0 ] = DIFF_DELETE;
3062 diffs[ pointer + 1 ][ 1 ] =
3063 deletion.substring( overlapLength2 );
3074 * Determine if the suffix of one string is the prefix of another.
3075 * @param {string} text1 First string.
3076 * @param {string} text2 Second string.
3077 * @return {number} The number of characters common to the end of the first
3078 * string and the start of the second string.
3081 DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) {
3082 var text1Length, text2Length, textLength,
3083 best, length, pattern, found;
3084 // Cache the text lengths to prevent multiple calls.
3085 text1Length = text1.length;
3086 text2Length = text2.length;
3087 // Eliminate the null case.
3088 if ( text1Length === 0 || text2Length === 0 ) {
3091 // Truncate the longer string.
3092 if ( text1Length > text2Length ) {
3093 text1 = text1.substring( text1Length - text2Length );
3094 } else if ( text1Length < text2Length ) {
3095 text2 = text2.substring( 0, text1Length );
3097 textLength = Math.min( text1Length, text2Length );
3098 // Quick check for the worst case.
3099 if ( text1 === text2 ) {
3103 // Start by looking for a single character match
3104 // and increase length until no match is found.
3105 // Performance analysis: https://neil.fraser.name/news/2010/11/04/
3109 pattern = text1.substring( textLength - length );
3110 found = text2.indexOf( pattern );
3111 if ( found === -1 ) {
3115 if ( found === 0 || text1.substring( textLength - length ) ===
3116 text2.substring( 0, length ) ) {
3124 * Split two texts into an array of strings. Reduce the texts to a string of
3125 * hashes where each Unicode character represents one line.
3126 * @param {string} text1 First string.
3127 * @param {string} text2 Second string.
3128 * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
3129 * An object containing the encoded text1, the encoded text2 and
3130 * the array of unique strings.
3131 * The zeroth element of the array of unique strings is intentionally blank.
3134 DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) {
3135 var lineArray, lineHash, chars1, chars2;
3136 lineArray = []; // e.g. lineArray[4] === 'Hello\n'
3137 lineHash = {}; // e.g. lineHash['Hello\n'] === 4
3139 // '\x00' is a valid character, but various debuggers don't like it.
3140 // So we'll insert a junk entry to avoid generating a null character.
3141 lineArray[ 0 ] = "";
3144 * Split a text into an array of strings. Reduce the texts to a string of
3145 * hashes where each Unicode character represents one line.
3146 * Modifies linearray and linehash through being a closure.
3147 * @param {string} text String to encode.
3148 * @return {string} Encoded string.
3151 function diffLinesToCharsMunge( text ) {
3152 var chars, lineStart, lineEnd, lineArrayLength, line;
3154 // Walk the text, pulling out a substring for each line.
3155 // text.split('\n') would would temporarily double our memory footprint.
3156 // Modifying text would create many large strings to garbage collect.
3159 // Keeping our own length variable is faster than looking it up.
3160 lineArrayLength = lineArray.length;
3161 while ( lineEnd < text.length - 1 ) {
3162 lineEnd = text.indexOf( "\n", lineStart );
3163 if ( lineEnd === -1 ) {
3164 lineEnd = text.length - 1;
3166 line = text.substring( lineStart, lineEnd + 1 );
3167 lineStart = lineEnd + 1;
3169 if ( lineHash.hasOwnProperty ? lineHash.hasOwnProperty( line ) :
3170 ( lineHash[ line ] !== undefined ) ) {
3171 chars += String.fromCharCode( lineHash[ line ] );
3173 chars += String.fromCharCode( lineArrayLength );
3174 lineHash[ line ] = lineArrayLength;
3175 lineArray[ lineArrayLength++ ] = line;
3181 chars1 = diffLinesToCharsMunge( text1 );
3182 chars2 = diffLinesToCharsMunge( text2 );
3186 lineArray: lineArray
3191 * Rehydrate the text in a diff from a string of line hashes to real lines of
3193 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
3194 * @param {!Array.<string>} lineArray Array of unique strings.
3197 DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {
3198 var x, chars, text, y;
3199 for ( x = 0; x < diffs.length; x++ ) {
3200 chars = diffs[ x ][ 1 ];
3202 for ( y = 0; y < chars.length; y++ ) {
3203 text[ y ] = lineArray[ chars.charCodeAt( y ) ];
3205 diffs[ x ][ 1 ] = text.join( "" );
3210 * Reorder and merge like edit sections. Merge equalities.
3211 * Any edit section can move as long as it doesn't cross an equality.
3212 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
3214 DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) {
3215 var pointer, countDelete, countInsert, textInsert, textDelete,
3216 commonlength, changes, diffPointer, position;
3217 diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
3224 while ( pointer < diffs.length ) {
3225 switch ( diffs[ pointer ][ 0 ] ) {
3228 textInsert += diffs[ pointer ][ 1 ];
3233 textDelete += diffs[ pointer ][ 1 ];
3237 // Upon reaching an equality, check for prior redundancies.
3238 if ( countDelete + countInsert > 1 ) {
3239 if ( countDelete !== 0 && countInsert !== 0 ) {
3240 // Factor out any common prefixes.
3241 commonlength = this.diffCommonPrefix( textInsert, textDelete );
3242 if ( commonlength !== 0 ) {
3243 if ( ( pointer - countDelete - countInsert ) > 0 &&
3244 diffs[ pointer - countDelete - countInsert - 1 ][ 0 ] ===
3246 diffs[ pointer - countDelete - countInsert - 1 ][ 1 ] +=
3247 textInsert.substring( 0, commonlength );
3249 diffs.splice( 0, 0, [ DIFF_EQUAL,
3250 textInsert.substring( 0, commonlength )
3254 textInsert = textInsert.substring( commonlength );
3255 textDelete = textDelete.substring( commonlength );
3257 // Factor out any common suffixies.
3258 commonlength = this.diffCommonSuffix( textInsert, textDelete );
3259 if ( commonlength !== 0 ) {
3260 diffs[ pointer ][ 1 ] = textInsert.substring( textInsert.length -
3261 commonlength ) + diffs[ pointer ][ 1 ];
3262 textInsert = textInsert.substring( 0, textInsert.length -
3264 textDelete = textDelete.substring( 0, textDelete.length -
3268 // Delete the offending records and add the merged ones.
3269 if ( countDelete === 0 ) {
3270 diffs.splice( pointer - countInsert,
3271 countDelete + countInsert, [ DIFF_INSERT, textInsert ] );
3272 } else if ( countInsert === 0 ) {
3273 diffs.splice( pointer - countDelete,
3274 countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
3277 pointer - countDelete - countInsert,
3278 countDelete + countInsert,
3279 [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ]
3282 pointer = pointer - countDelete - countInsert +
3283 ( countDelete ? 1 : 0 ) + ( countInsert ? 1 : 0 ) + 1;
3284 } else if ( pointer !== 0 && diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL ) {
3286 // Merge this equality with the previous one.
3287 diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ];
3288 diffs.splice( pointer, 1 );
3299 if ( diffs[ diffs.length - 1 ][ 1 ] === "" ) {
3300 diffs.pop(); // Remove the dummy entry at the end.
3303 // Second pass: look for single edits surrounded on both sides by equalities
3304 // which can be shifted sideways to eliminate an equality.
3305 // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
3309 // Intentionally ignore the first and last element (don't need checking).
3310 while ( pointer < diffs.length - 1 ) {
3311 if ( diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL &&
3312 diffs[ pointer + 1 ][ 0 ] === DIFF_EQUAL ) {
3314 diffPointer = diffs[ pointer ][ 1 ];
3315 position = diffPointer.substring(
3316 diffPointer.length - diffs[ pointer - 1 ][ 1 ].length
3319 // This is a single edit surrounded by equalities.
3320 if ( position === diffs[ pointer - 1 ][ 1 ] ) {
3322 // Shift the edit over the previous equality.
3323 diffs[ pointer ][ 1 ] = diffs[ pointer - 1 ][ 1 ] +
3324 diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer ][ 1 ].length -
3325 diffs[ pointer - 1 ][ 1 ].length );
3326 diffs[ pointer + 1 ][ 1 ] =
3327 diffs[ pointer - 1 ][ 1 ] + diffs[ pointer + 1 ][ 1 ];
3328 diffs.splice( pointer - 1, 1 );
3330 } else if ( diffPointer.substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
3331 diffs[ pointer + 1 ][ 1 ] ) {
3333 // Shift the edit over the next equality.
3334 diffs[ pointer - 1 ][ 1 ] += diffs[ pointer + 1 ][ 1 ];
3335 diffs[ pointer ][ 1 ] =
3336 diffs[ pointer ][ 1 ].substring( diffs[ pointer + 1 ][ 1 ].length ) +
3337 diffs[ pointer + 1 ][ 1 ];
3338 diffs.splice( pointer + 1, 1 );
3344 // If shifts were made, the diff needs reordering and another shift sweep.
3346 this.diffCleanupMerge( diffs );
3350 return function( o, n ) {
3351 var diff, output, text;
3352 diff = new DiffMatchPatch();
3353 output = diff.DiffMain( o, n );
3354 diff.diffCleanupEfficiency( output );
3355 text = diff.diffPrettyHtml( output );
3361 // Get a reference to the global object, like window in browsers
3368 // Don't load the HTML Reporter on non-Browser environments
3369 if ( typeof window === "undefined" || !window.document ) {
3373 // Deprecated QUnit.init - Ref #530
3374 // Re-initialize the configuration options
3375 QUnit.init = function() {
3376 var tests, banner, result, qunit,
3377 config = QUnit.config;
3379 config.stats = { all: 0, bad: 0 };
3380 config.moduleStats = { all: 0, bad: 0 };
3382 config.updateRate = 1000;
3383 config.blocking = false;
3384 config.autostart = true;
3385 config.autorun = false;
3389 // Return on non-browser environments
3390 // This is necessary to not break on node tests
3391 if ( typeof window === "undefined" ) {
3395 qunit = id( "qunit
" );
3398 "<h1 id
='qunit-header'>" + escapeText( document.title ) + "</h1
>" +
3399 "<h2 id
='qunit-banner'></h2
>" +
3400 "<div id
='qunit-testrunner-toolbar'></div
>" +
3401 "<h2 id
='qunit-userAgent'></h2
>" +
3402 "<ol id
='qunit-tests'></ol
>";
3405 tests = id( "qunit
-tests
" );
3406 banner = id( "qunit
-banner
" );
3407 result = id( "qunit
-testresult
" );
3410 tests.innerHTML = "";
3414 banner.className = "";
3418 result.parentNode.removeChild( result );
3422 result = document.createElement( "p
" );
3423 result.id = "qunit
-testresult
";
3424 result.className = "result
";
3425 tests.parentNode.insertBefore( result, tests );
3426 result.innerHTML = "Running
...<br
/> ";
3430 var config = QUnit.config,
3431 collapseNext = false,
3432 hasOwn = Object.prototype.hasOwnProperty,
3434 document: window.document !== undefined,
3435 sessionStorage: (function() {
3436 var x = "qunit
-test
-string
";
3438 sessionStorage.setItem( x, x );
3439 sessionStorage.removeItem( x );
3449 * Escape text for attribute or text content.
3451 function escapeText( s ) {
3457 // Both single quotes and double quotes (for attributes)
3458 return s.replace( /['"<>&]/g
, function( s
) {
3475 * @param {HTMLElement} elem
3476 * @param {string} type
3477 * @param {Function} fn
3479 function addEvent( elem
, type
, fn
) {
3480 if ( elem
.addEventListener
) {
3482 // Standards-based browsers
3483 elem
.addEventListener( type
, fn
, false );
3484 } else if ( elem
.attachEvent
) {
3487 elem
.attachEvent( "on" + type
, function() {
3488 var event
= window
.event
;
3489 if ( !event
.target
) {
3490 event
.target
= event
.srcElement
|| document
;
3493 fn
.call( elem
, event
);
3499 * @param {Array|NodeList} elems
3500 * @param {string} type
3501 * @param {Function} fn
3503 function addEvents( elems
, type
, fn
) {
3504 var i
= elems
.length
;
3506 addEvent( elems
[ i
], type
, fn
);
3510 function hasClass( elem
, name
) {
3511 return ( " " + elem
.className
+ " " ).indexOf( " " + name
+ " " ) >= 0;
3514 function addClass( elem
, name
) {
3515 if ( !hasClass( elem
, name
) ) {
3516 elem
.className
+= ( elem
.className
? " " : "" ) + name
;
3520 function toggleClass( elem
, name
) {
3521 if ( hasClass( elem
, name
) ) {
3522 removeClass( elem
, name
);
3524 addClass( elem
, name
);
3528 function removeClass( elem
, name
) {
3529 var set = " " + elem
.className
+ " ";
3531 // Class name may appear multiple times
3532 while ( set.indexOf( " " + name
+ " " ) >= 0 ) {
3533 set = set.replace( " " + name
+ " ", " " );
3536 // trim for prettiness
3537 elem
.className
= typeof set.trim
=== "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
3540 function id( name
) {
3541 return defined
.document
&& document
.getElementById
&& document
.getElementById( name
);
3544 function getUrlConfigHtml() {
3546 escaped
, escapedTooltip
,
3548 len
= config
.urlConfig
.length
,
3551 for ( i
= 0; i
< len
; i
++ ) {
3552 val
= config
.urlConfig
[ i
];
3553 if ( typeof val
=== "string" ) {
3560 escaped
= escapeText( val
.id
);
3561 escapedTooltip
= escapeText( val
.tooltip
);
3563 if ( config
[ val
.id
] === undefined ) {
3564 config
[ val
.id
] = QUnit
.urlParams
[ val
.id
];
3567 if ( !val
.value
|| typeof val
.value
=== "string" ) {
3568 urlConfigHtml
+= "<input id='qunit-urlconfig-" + escaped
+
3569 "' name='" + escaped
+ "' type='checkbox'" +
3570 ( val
.value
? " value='" + escapeText( val
.value
) + "'" : "" ) +
3571 ( config
[ val
.id
] ? " checked='checked'" : "" ) +
3572 " title='" + escapedTooltip
+ "' /><label for='qunit-urlconfig-" + escaped
+
3573 "' title='" + escapedTooltip
+ "'>" + val
.label
+ "</label>";
3575 urlConfigHtml
+= "<label for='qunit-urlconfig-" + escaped
+
3576 "' title='" + escapedTooltip
+ "'>" + val
.label
+
3577 ": </label><select id='qunit-urlconfig-" + escaped
+
3578 "' name='" + escaped
+ "' title='" + escapedTooltip
+ "'><option></option>";
3580 if ( QUnit
.is( "array", val
.value
) ) {
3581 for ( j
= 0; j
< val
.value
.length
; j
++ ) {
3582 escaped
= escapeText( val
.value
[ j
] );
3583 urlConfigHtml
+= "<option value='" + escaped
+ "'" +
3584 ( config
[ val
.id
] === val
.value
[ j
] ?
3585 ( selection
= true ) && " selected='selected'" : "" ) +
3586 ">" + escaped
+ "</option>";
3589 for ( j
in val
.value
) {
3590 if ( hasOwn
.call( val
.value
, j
) ) {
3591 urlConfigHtml
+= "<option value='" + escapeText( j
) + "'" +
3592 ( config
[ val
.id
] === j
?
3593 ( selection
= true ) && " selected='selected'" : "" ) +
3594 ">" + escapeText( val
.value
[ j
] ) + "</option>";
3598 if ( config
[ val
.id
] && !selection
) {
3599 escaped
= escapeText( config
[ val
.id
] );
3600 urlConfigHtml
+= "<option value='" + escaped
+
3601 "' selected='selected' disabled='disabled'>" + escaped
+ "</option>";
3603 urlConfigHtml
+= "</select>";
3607 return urlConfigHtml
;
3610 // Handle "click" events on toolbar checkboxes and "change" for select menus.
3611 // Updates the URL with the new state of `config.urlConfig` values.
3612 function toolbarChanged() {
3613 var updatedUrl
, value
,
3617 // Detect if field is a select menu or a checkbox
3618 if ( "selectedIndex" in field
) {
3619 value
= field
.options
[ field
.selectedIndex
].value
|| undefined;
3621 value
= field
.checked
? ( field
.defaultValue
|| true ) : undefined;
3624 params
[ field
.name
] = value
;
3625 updatedUrl
= setUrl( params
);
3627 if ( "hidepassed" === field
.name
&& "replaceState" in window
.history
) {
3628 config
[ field
.name
] = value
|| false;
3630 addClass( id( "qunit-tests" ), "hidepass" );
3632 removeClass( id( "qunit-tests" ), "hidepass" );
3635 // It is not necessary to refresh the whole page
3636 window
.history
.replaceState( null, "", updatedUrl
);
3638 window
.location
= updatedUrl
;
3642 function setUrl( params
) {
3646 params
= QUnit
.extend( QUnit
.extend( {}, QUnit
.urlParams
), params
);
3648 for ( key
in params
) {
3649 if ( hasOwn
.call( params
, key
) ) {
3650 if ( params
[ key
] === undefined ) {
3653 querystring
+= encodeURIComponent( key
);
3654 if ( params
[ key
] !== true ) {
3655 querystring
+= "=" + encodeURIComponent( params
[ key
] );
3660 return location
.protocol
+ "//" + location
.host
+
3661 location
.pathname
+ querystring
.slice( 0, -1 );
3664 function applyUrlParams() {
3666 modulesList
= id( "qunit-modulefilter" ),
3667 filter
= id( "qunit-filter-input" ).value
;
3669 selectedModule
= modulesList
?
3670 decodeURIComponent( modulesList
.options
[ modulesList
.selectedIndex
].value
) :
3673 window
.location
= setUrl({
3674 module
: ( selectedModule
=== "" ) ? undefined : selectedModule
,
3675 filter
: ( filter
=== "" ) ? undefined : filter
,
3677 // Remove testId filter
3682 function toolbarUrlConfigContainer() {
3683 var urlConfigContainer
= document
.createElement( "span" );
3685 urlConfigContainer
.innerHTML
= getUrlConfigHtml();
3686 addClass( urlConfigContainer
, "qunit-url-config" );
3688 // For oldIE support:
3689 // * Add handlers to the individual elements instead of the container
3690 // * Use "click" instead of "change" for checkboxes
3691 addEvents( urlConfigContainer
.getElementsByTagName( "input" ), "click", toolbarChanged
);
3692 addEvents( urlConfigContainer
.getElementsByTagName( "select" ), "change", toolbarChanged
);
3694 return urlConfigContainer
;
3697 function toolbarLooseFilter() {
3698 var filter
= document
.createElement( "form" ),
3699 label
= document
.createElement( "label" ),
3700 input
= document
.createElement( "input" ),
3701 button
= document
.createElement( "button" );
3703 addClass( filter
, "qunit-filter" );
3705 label
.innerHTML
= "Filter: ";
3707 input
.type
= "text";
3708 input
.value
= config
.filter
|| "";
3709 input
.name
= "filter";
3710 input
.id
= "qunit-filter-input";
3712 button
.innerHTML
= "Go";
3714 label
.appendChild( input
);
3716 filter
.appendChild( label
);
3717 filter
.appendChild( button
);
3718 addEvent( filter
, "submit", function( ev
) {
3721 if ( ev
&& ev
.preventDefault
) {
3722 ev
.preventDefault();
3731 function toolbarModuleFilterHtml() {
3733 moduleFilterHtml
= "";
3735 if ( !modulesList
.length
) {
3739 modulesList
.sort(function( a
, b
) {
3740 return a
.localeCompare( b
);
3743 moduleFilterHtml
+= "<label for='qunit-modulefilter'>Module: </label>" +
3744 "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
3745 ( QUnit
.urlParams
.module
=== undefined ? "selected='selected'" : "" ) +
3746 ">< All Modules ></option>";
3748 for ( i
= 0; i
< modulesList
.length
; i
++ ) {
3749 moduleFilterHtml
+= "<option value='" +
3750 escapeText( encodeURIComponent( modulesList
[ i
] ) ) + "' " +
3751 ( QUnit
.urlParams
.module
=== modulesList
[ i
] ? "selected='selected'" : "" ) +
3752 ">" + escapeText( modulesList
[ i
] ) + "</option>";
3754 moduleFilterHtml
+= "</select>";
3756 return moduleFilterHtml
;
3759 function toolbarModuleFilter() {
3760 var toolbar
= id( "qunit-testrunner-toolbar" ),
3761 moduleFilter
= document
.createElement( "span" ),
3762 moduleFilterHtml
= toolbarModuleFilterHtml();
3764 if ( !toolbar
|| !moduleFilterHtml
) {
3768 moduleFilter
.setAttribute( "id", "qunit-modulefilter-container" );
3769 moduleFilter
.innerHTML
= moduleFilterHtml
;
3771 addEvent( moduleFilter
.lastChild
, "change", applyUrlParams
);
3773 toolbar
.appendChild( moduleFilter
);
3776 function appendToolbar() {
3777 var toolbar
= id( "qunit-testrunner-toolbar" );
3780 toolbar
.appendChild( toolbarUrlConfigContainer() );
3781 toolbar
.appendChild( toolbarLooseFilter() );
3785 function appendHeader() {
3786 var header
= id( "qunit-header" );
3789 header
.innerHTML
= "<a href='" +
3790 escapeText( setUrl( { filter
: undefined, module
: undefined, testId
: undefined } ) ) +
3791 "'>" + header
.innerHTML
+ "</a> ";
3795 function appendBanner() {
3796 var banner
= id( "qunit-banner" );
3799 banner
.className
= "";
3803 function appendTestResults() {
3804 var tests
= id( "qunit-tests" ),
3805 result
= id( "qunit-testresult" );
3808 result
.parentNode
.removeChild( result
);
3812 tests
.innerHTML
= "";
3813 result
= document
.createElement( "p" );
3814 result
.id
= "qunit-testresult";
3815 result
.className
= "result";
3816 tests
.parentNode
.insertBefore( result
, tests
);
3817 result
.innerHTML
= "Running...<br /> ";
3821 function storeFixture() {
3822 var fixture
= id( "qunit-fixture" );
3824 config
.fixture
= fixture
.innerHTML
;
3828 function appendFilteredTest() {
3829 var testId
= QUnit
.config
.testId
;
3830 if ( !testId
|| testId
.length
<= 0 ) {
3833 return "<div id='qunit-filteredTest'>Rerunning selected tests: " +
3834 escapeText( testId
.join(", ") ) +
3835 " <a id='qunit-clearFilter' href='" +
3836 escapeText( setUrl( { filter
: undefined, module
: undefined, testId
: undefined } ) ) +
3837 "'>" + "Run all tests" + "</a></div>";
3840 function appendUserAgent() {
3841 var userAgent
= id( "qunit-userAgent" );
3844 userAgent
.innerHTML
= "";
3845 userAgent
.appendChild(
3846 document
.createTextNode(
3847 "QUnit " + QUnit
.version
+ "; " + navigator
.userAgent
3853 function appendTestsList( modules
) {
3854 var i
, l
, x
, z
, test
, moduleObj
;
3856 for ( i
= 0, l
= modules
.length
; i
< l
; i
++ ) {
3857 moduleObj
= modules
[ i
];
3859 if ( moduleObj
.name
) {
3860 modulesList
.push( moduleObj
.name
);
3863 for ( x
= 0, z
= moduleObj
.tests
.length
; x
< z
; x
++ ) {
3864 test
= moduleObj
.tests
[ x
];
3866 appendTest( test
.name
, test
.testId
, moduleObj
.name
);
3871 function appendTest( name
, testId
, moduleName
) {
3872 var title
, rerunTrigger
, testBlock
, assertList
,
3873 tests
= id( "qunit-tests" );
3879 title
= document
.createElement( "strong" );
3880 title
.innerHTML
= getNameHtml( name
, moduleName
);
3882 rerunTrigger
= document
.createElement( "a" );
3883 rerunTrigger
.innerHTML
= "Rerun";
3884 rerunTrigger
.href
= setUrl({ testId
: testId
});
3886 testBlock
= document
.createElement( "li" );
3887 testBlock
.appendChild( title
);
3888 testBlock
.appendChild( rerunTrigger
);
3889 testBlock
.id
= "qunit-test-output-" + testId
;
3891 assertList
= document
.createElement( "ol" );
3892 assertList
.className
= "qunit-assert-list";
3894 testBlock
.appendChild( assertList
);
3896 tests
.appendChild( testBlock
);
3899 // HTML Reporter initialization and load
3900 QUnit
.begin(function( details
) {
3901 var qunit
= id( "qunit" );
3903 // Fixture is the only one necessary to run without the #qunit element
3908 "<h1 id='qunit-header'>" + escapeText( document
.title
) + "</h1>" +
3909 "<h2 id='qunit-banner'></h2>" +
3910 "<div id='qunit-testrunner-toolbar'></div>" +
3911 appendFilteredTest() +
3912 "<h2 id='qunit-userAgent'></h2>" +
3913 "<ol id='qunit-tests'></ol>";
3918 appendTestResults();
3921 appendTestsList( details
.modules
);
3922 toolbarModuleFilter();
3924 if ( qunit
&& config
.hidepassed
) {
3925 addClass( qunit
.lastChild
, "hidepass" );
3929 QUnit
.done(function( details
) {
3931 banner
= id( "qunit-banner" ),
3932 tests
= id( "qunit-tests" ),
3934 "Tests completed in ",
3936 " milliseconds.<br />",
3937 "<span class='passed'>",
3939 "</span> assertions of <span class='total'>",
3941 "</span> passed, <span class='failed'>",
3947 banner
.className
= details
.failed
? "qunit-fail" : "qunit-pass";
3951 id( "qunit-testresult" ).innerHTML
= html
;
3954 if ( config
.altertitle
&& defined
.document
&& document
.title
) {
3956 // show ✖ for good, ✔ for bad suite result in title
3957 // use escape sequences in case file gets loaded with non-utf-8-charset
3959 ( details
.failed
? "\u2716" : "\u2714" ),
3960 document
.title
.replace( /^[\u2714\u2716] /i, "" )
3964 // clear own sessionStorage items if all tests passed
3965 if ( config
.reorder
&& defined
.sessionStorage
&& details
.failed
=== 0 ) {
3966 for ( i
= 0; i
< sessionStorage
.length
; i
++ ) {
3967 key
= sessionStorage
.key( i
++ );
3968 if ( key
.indexOf( "qunit-test-" ) === 0 ) {
3969 sessionStorage
.removeItem( key
);
3974 // scroll back to top to show results
3975 if ( config
.scrolltop
&& window
.scrollTo
) {
3976 window
.scrollTo( 0, 0 );
3980 function getNameHtml( name
, module
) {
3984 nameHtml
= "<span class='module-name'>" + escapeText( module
) + "</span>: ";
3987 nameHtml
+= "<span class='test-name'>" + escapeText( name
) + "</span>";
3992 QUnit
.testStart(function( details
) {
3993 var running
, testBlock
, bad
;
3995 testBlock
= id( "qunit-test-output-" + details
.testId
);
3997 testBlock
.className
= "running";
4000 // Report later registered tests
4001 appendTest( details
.name
, details
.testId
, details
.module
);
4004 running
= id( "qunit-testresult" );
4006 bad
= QUnit
.config
.reorder
&& defined
.sessionStorage
&&
4007 +sessionStorage
.getItem( "qunit-test-" + details
.module
+ "-" + details
.name
);
4009 running
.innerHTML
= ( bad
?
4010 "Rerunning previously failed test: <br />" :
4011 "Running: <br />" ) +
4012 getNameHtml( details
.name
, details
.module
);
4017 function stripHtml( string
) {
4018 // strip tags, html entity and whitespaces
4019 return string
.replace(/<\/?[^>]+(>|$)/g, "").replace(/\"/g, "").replace(/\s+/g, "");
4022 QUnit
.log(function( details
) {
4023 var assertList
, assertLi
,
4024 message
, expected
, actual
, diff
,
4026 testItem
= id( "qunit-test-output-" + details
.testId
);
4032 message
= escapeText( details
.message
) || ( details
.result
? "okay" : "failed" );
4033 message
= "<span class='test-message'>" + message
+ "</span>";
4034 message
+= "<span class='runtime'>@ " + details
.runtime
+ " ms</span>";
4036 // pushFailure doesn't provide details.expected
4037 // when it calls, it's implicit to also not show expected and diff stuff
4038 // Also, we need to check details.expected existence, as it can exist and be undefined
4039 if ( !details
.result
&& hasOwn
.call( details
, "expected" ) ) {
4040 if ( details
.negative
) {
4041 expected
= escapeText( "NOT " + QUnit
.dump
.parse( details
.expected
) );
4043 expected
= escapeText( QUnit
.dump
.parse( details
.expected
) );
4046 actual
= escapeText( QUnit
.dump
.parse( details
.actual
) );
4047 message
+= "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
4051 if ( actual
!== expected
) {
4053 message
+= "<tr class='test-actual'><th>Result: </th><td><pre>" +
4054 actual
+ "</pre></td></tr>";
4056 // Don't show diff if actual or expected are booleans
4057 if ( !( /^(true|false)$/.test( actual
) ) &&
4058 !( /^(true|false)$/.test( expected
) ) ) {
4059 diff
= QUnit
.diff( expected
, actual
);
4060 showDiff
= stripHtml( diff
).length
!==
4061 stripHtml( expected
).length
+
4062 stripHtml( actual
).length
;
4065 // Don't show diff if expected and actual are totally different
4067 message
+= "<tr class='test-diff'><th>Diff: </th><td><pre>" +
4068 diff
+ "</pre></td></tr>";
4070 } else if ( expected
.indexOf( "[object Array]" ) !== -1 ||
4071 expected
.indexOf( "[object Object]" ) !== -1 ) {
4072 message
+= "<tr class='test-message'><th>Message: </th><td>" +
4073 "Diff suppressed as the depth of object is more than current max depth (" +
4074 QUnit
.config
.maxDepth
+ ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " +
4075 " run with a higher max depth or <a href='" +
4076 escapeText( setUrl( { maxDepth
: -1 } ) ) + "'>" +
4077 "Rerun</a> without max depth.</p></td></tr>";
4080 if ( details
.source
) {
4081 message
+= "<tr class='test-source'><th>Source: </th><td><pre>" +
4082 escapeText( details
.source
) + "</pre></td></tr>";
4085 message
+= "</table>";
4087 // this occurs when pushFailure is set and we have an extracted stack trace
4088 } else if ( !details
.result
&& details
.source
) {
4089 message
+= "<table>" +
4090 "<tr class='test-source'><th>Source: </th><td><pre>" +
4091 escapeText( details
.source
) + "</pre></td></tr>" +
4095 assertList
= testItem
.getElementsByTagName( "ol" )[ 0 ];
4097 assertLi
= document
.createElement( "li" );
4098 assertLi
.className
= details
.result
? "pass" : "fail";
4099 assertLi
.innerHTML
= message
;
4100 assertList
.appendChild( assertLi
);
4103 QUnit
.testDone(function( details
) {
4104 var testTitle
, time
, testItem
, assertList
,
4105 good
, bad
, testCounts
, skipped
, sourceName
,
4106 tests
= id( "qunit-tests" );
4112 testItem
= id( "qunit-test-output-" + details
.testId
);
4114 assertList
= testItem
.getElementsByTagName( "ol" )[ 0 ];
4116 good
= details
.passed
;
4117 bad
= details
.failed
;
4119 // store result when possible
4120 if ( config
.reorder
&& defined
.sessionStorage
) {
4122 sessionStorage
.setItem( "qunit-test-" + details
.module
+ "-" + details
.name
, bad
);
4124 sessionStorage
.removeItem( "qunit-test-" + details
.module
+ "-" + details
.name
);
4130 // Collapse the passing tests
4131 addClass( assertList
, "qunit-collapsed" );
4132 } else if ( bad
&& config
.collapse
&& !collapseNext
) {
4134 // Skip collapsing the first failing test
4135 collapseNext
= true;
4138 // Collapse remaining tests
4139 addClass( assertList
, "qunit-collapsed" );
4142 // testItem.firstChild is the test name
4143 testTitle
= testItem
.firstChild
;
4146 "<b class='failed'>" + bad
+ "</b>, " + "<b class='passed'>" + good
+ "</b>, " :
4149 testTitle
.innerHTML
+= " <b class='counts'>(" + testCounts
+
4150 details
.assertions
.length
+ ")</b>";
4152 if ( details
.skipped
) {
4153 testItem
.className
= "skipped";
4154 skipped
= document
.createElement( "em" );
4155 skipped
.className
= "qunit-skipped-label";
4156 skipped
.innerHTML
= "skipped";
4157 testItem
.insertBefore( skipped
, testTitle
);
4159 addEvent( testTitle
, "click", function() {
4160 toggleClass( assertList
, "qunit-collapsed" );
4163 testItem
.className
= bad
? "fail" : "pass";
4165 time
= document
.createElement( "span" );
4166 time
.className
= "runtime";
4167 time
.innerHTML
= details
.runtime
+ " ms";
4168 testItem
.insertBefore( time
, assertList
);
4171 // Show the source of the test when showing assertions
4172 if ( details
.source
) {
4173 sourceName
= document
.createElement( "p" );
4174 sourceName
.innerHTML
= "<strong>Source: </strong>" + details
.source
;
4175 addClass( sourceName
, "qunit-source" );
4177 addClass( sourceName
, "qunit-collapsed" );
4179 addEvent( testTitle
, "click", function() {
4180 toggleClass( sourceName
, "qunit-collapsed" );
4182 testItem
.appendChild( sourceName
);
4186 if ( defined
.document
) {
4188 // Avoid readyState issue with phantomjs
4190 var notPhantom
= ( function( p
) {
4191 return !( p
&& p
.version
&& p
.version
.major
> 0 );
4192 } )( window
.phantom
);
4194 if ( notPhantom
&& document
.readyState
=== "complete" ) {
4197 addEvent( window
, "load", QUnit
.load
);
4200 config
.pageLoaded
= true;
4201 config
.autorun
= true;