5 * Copyright 2013 jQuery Foundation and other contributors
6 * Released under the MIT license
7 * http://jquery.org/license
9 * Date: 2014-01-31T16:40Z
19 fileName
= (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
20 toString
= Object
.prototype.toString
,
21 hasOwn
= Object
.prototype.hasOwnProperty
,
22 // Keep a local reference to Date (GH-283)
24 setTimeout
= window
.setTimeout
,
25 clearTimeout
= window
.clearTimeout
,
27 document
: typeof window
.document
!== "undefined",
28 setTimeout
: typeof window
.setTimeout
!== "undefined",
29 sessionStorage
: (function() {
30 var x
= "qunit-test-string";
32 sessionStorage
.setItem( x
, x
);
33 sessionStorage
.removeItem( x
);
41 * Provides a normalized error string, correcting an issue
42 * with IE 7 (and prior) where Error.prototype.toString is
43 * not properly implemented
45 * Based on http://es5.github.com/#x15.11.4.4
47 * @param {String|Error} error
48 * @return {String} error message
50 errorString = function( error
) {
52 errorString
= error
.toString();
53 if ( errorString
.substring( 0, 7 ) === "[object" ) {
54 name
= error
.name
? error
.name
.toString() : "Error";
55 message
= error
.message
? error
.message
.toString() : "";
56 if ( name
&& message
) {
57 return name
+ ": " + message
;
60 } else if ( message
) {
70 * Makes a clone of an object using only Array or Object as base,
71 * and copies over the own enumerable properties.
74 * @return {Object} New object with only the own properties (recursively).
76 objectValues = function( obj
) {
77 // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392.
78 /*jshint newcap: false */
80 vals
= QUnit
.is( "array", obj
) ? [] : {};
82 if ( hasOwn
.call( obj
, key
) ) {
84 vals
[key
] = val
=== Object(val
) ? objectValues(val
) : val
;
92 // `QUnit` initialized at top of scope
95 // call on start of module test to prepend name to all tests
96 module: function( name
, testEnvironment
) {
97 config
.currentModule
= name
;
98 config
.currentModuleTestEnvironment
= testEnvironment
;
99 config
.modules
[name
] = true;
102 asyncTest: function( testName
, expected
, callback
) {
103 if ( arguments
.length
=== 2 ) {
108 QUnit
.test( testName
, expected
, callback
, true );
111 test: function( testName
, expected
, callback
, async
) {
113 nameHtml
= "<span class='test-name'>" + escapeText( testName
) + "</span>";
115 if ( arguments
.length
=== 2 ) {
120 if ( config
.currentModule
) {
121 nameHtml
= "<span class='module-name'>" + escapeText( config
.currentModule
) + "</span>: " + nameHtml
;
130 module
: config
.currentModule
,
131 moduleTestEnvironment
: config
.currentModuleTestEnvironment
,
132 stack
: sourceFromStacktrace( 2 )
135 if ( !validTest( test
) ) {
142 // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through.
143 expect: function( asserts
) {
144 if (arguments
.length
=== 1) {
145 config
.current
.expected
= asserts
;
147 return config
.current
.expected
;
151 start: function( count
) {
152 // QUnit hasn't been initialized yet.
153 // Note: RequireJS (et al) may delay onLoad
154 if ( config
.semaphore
=== undefined ) {
155 QUnit
.begin(function() {
156 // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first
157 setTimeout(function() {
158 QUnit
.start( count
);
164 config
.semaphore
-= count
|| 1;
165 // don't start until equal number of stop-calls
166 if ( config
.semaphore
> 0 ) {
169 // ignore if start is called more often then stop
170 if ( config
.semaphore
< 0 ) {
171 config
.semaphore
= 0;
172 QUnit
.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) );
175 // A slight delay, to avoid any current callbacks
176 if ( defined
.setTimeout
) {
177 setTimeout(function() {
178 if ( config
.semaphore
> 0 ) {
181 if ( config
.timeout
) {
182 clearTimeout( config
.timeout
);
185 config
.blocking
= false;
189 config
.blocking
= false;
194 stop: function( count
) {
195 config
.semaphore
+= count
|| 1;
196 config
.blocking
= true;
198 if ( config
.testTimeout
&& defined
.setTimeout
) {
199 clearTimeout( config
.timeout
);
200 config
.timeout
= setTimeout(function() {
201 QUnit
.ok( false, "Test timed out" );
202 config
.semaphore
= 1;
204 }, config
.testTimeout
);
209 // We use the prototype to distinguish between properties that should
210 // be exposed as globals (and in exports) and those that shouldn't
215 // Make F QUnit's constructor so that we can add to the prototype later
216 QUnit
.constructor = F
;
220 * Config object: Maintain internal state
221 * Later exposed as QUnit.config
222 * `config` initialized at top of scope
225 // The queue of tests to run
228 // block until document ready
231 // when enabled, show only failing tests
232 // gets persisted through sessionStorage and can be changed in UI via checkbox
235 // by default, run previously failed tests first
236 // very useful in combination with "Hide passed tests" checked
239 // by default, modify document.title when suite is done
242 // by default, scroll to top of the page when suite is done
245 // when enabled, all tests must call expect()
246 requireExpects
: false,
248 // add checkboxes that are persisted in the query-string
249 // when enabled, the id is set to `true` as a `QUnit.config` property
253 label
: "Check for Globals",
254 tooltip
: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings."
258 label
: "No try-catch",
259 tooltip
: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings."
263 // Set of all modules.
266 // logging callback queues
276 // Initialize more QUnit.config and QUnit.urlParams
279 location
= window
.location
|| { search
: "", protocol
: "file:" },
280 params
= location
.search
.slice( 1 ).split( "&" ),
281 length
= params
.length
,
285 for ( i
= 0; i
< length
; i
++ ) {
286 current
= params
[ i
].split( "=" );
287 current
[ 0 ] = decodeURIComponent( current
[ 0 ] );
289 // allow just a key to turn on a flag, e.g., test.html?noglobals
290 current
[ 1 ] = current
[ 1 ] ? decodeURIComponent( current
[ 1 ] ) : true;
291 if ( urlParams
[ current
[ 0 ] ] ) {
292 urlParams
[ current
[ 0 ] ] = [].concat( urlParams
[ current
[ 0 ] ], current
[ 1 ] );
294 urlParams
[ current
[ 0 ] ] = current
[ 1 ];
299 QUnit
.urlParams
= urlParams
;
301 // String search anywhere in moduleName+testName
302 config
.filter
= urlParams
.filter
;
304 // Exact match of the module name
305 config
.module
= urlParams
.module
;
307 config
.testNumber
= [];
308 if ( urlParams
.testNumber
) {
310 // Ensure that urlParams.testNumber is an array
311 urlParams
.testNumber
= [].concat( urlParams
.testNumber
);
312 for ( i
= 0; i
< urlParams
.testNumber
.length
; i
++ ) {
313 current
= urlParams
.testNumber
[ i
];
314 config
.testNumber
.push( parseInt( current
, 10 ) );
318 // Figure out if we're running the tests from a server or not
319 QUnit
.isLocal
= location
.protocol
=== "file:";
326 // Initialize the configuration options
329 stats
: { all
: 0, bad
: 0 },
330 moduleStats
: { all
: 0, bad
: 0 },
331 started
: +new Date(),
341 var tests
, banner
, result
,
342 qunit
= id( "qunit" );
346 "<h1 id='qunit-header'>" + escapeText( document
.title
) + "</h1>" +
347 "<h2 id='qunit-banner'></h2>" +
348 "<div id='qunit-testrunner-toolbar'></div>" +
349 "<h2 id='qunit-userAgent'></h2>" +
350 "<ol id='qunit-tests'></ol>";
353 tests
= id( "qunit-tests" );
354 banner
= id( "qunit-banner" );
355 result
= id( "qunit-testresult" );
358 tests
.innerHTML
= "";
362 banner
.className
= "";
366 result
.parentNode
.removeChild( result
);
370 result
= document
.createElement( "p" );
371 result
.id
= "qunit-testresult";
372 result
.className
= "result";
373 tests
.parentNode
.insertBefore( result
, tests
);
374 result
.innerHTML
= "Running...<br/> ";
378 // Resets the test setup. Useful for tests that modify the DOM.
380 DEPRECATED: Use multiple tests instead of resetting inside a test.
381 Use testStart or testDone for custom cleanup.
382 This method will throw an error in 2.0, and will be removed in 2.1
385 var fixture
= id( "qunit-fixture" );
387 fixture
.innerHTML
= config
.fixture
;
391 // Safe object type checking
392 is: function( type
, obj
) {
393 return QUnit
.objectType( obj
) === type
;
396 objectType: function( obj
) {
397 if ( typeof obj
=== "undefined" ) {
401 // Consider: typeof null === object
402 if ( obj
=== null ) {
406 var match
= toString
.call( obj
).match(/^\[object\s(.*)\]$/),
407 type
= match
&& match
[1] || "";
421 return type
.toLowerCase();
423 if ( typeof obj
=== "object" ) {
429 push: function( result
, actual
, expected
, message
) {
430 if ( !config
.current
) {
431 throw new Error( "assertion outside test context, was " + sourceFromStacktrace() );
436 module
: config
.current
.module
,
437 name
: config
.current
.testName
,
444 message
= escapeText( message
) || ( result
? "okay" : "failed" );
445 message
= "<span class='test-message'>" + message
+ "</span>";
449 expected
= escapeText( QUnit
.jsDump
.parse(expected
) );
450 actual
= escapeText( QUnit
.jsDump
.parse(actual
) );
451 output
+= "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected
+ "</pre></td></tr>";
453 if ( actual
!== expected
) {
454 output
+= "<tr class='test-actual'><th>Result: </th><td><pre>" + actual
+ "</pre></td></tr>";
455 output
+= "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit
.diff( expected
, actual
) + "</pre></td></tr>";
458 source
= sourceFromStacktrace();
461 details
.source
= source
;
462 output
+= "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source
) + "</pre></td></tr>";
465 output
+= "</table>";
468 runLoggingCallbacks( "log", QUnit
, details
);
470 config
.current
.assertions
.push({
476 pushFailure: function( message
, source
, actual
) {
477 if ( !config
.current
) {
478 throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
483 module
: config
.current
.module
,
484 name
: config
.current
.testName
,
489 message
= escapeText( message
) || "error";
490 message
= "<span class='test-message'>" + message
+ "</span>";
496 output
+= "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText( actual
) + "</pre></td></tr>";
500 details
.source
= source
;
501 output
+= "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source
) + "</pre></td></tr>";
504 output
+= "</table>";
506 runLoggingCallbacks( "log", QUnit
, details
);
508 config
.current
.assertions
.push({
514 url: function( params
) {
515 params
= extend( extend( {}, QUnit
.urlParams
), params
);
519 for ( key
in params
) {
520 if ( hasOwn
.call( params
, key
) ) {
521 querystring
+= encodeURIComponent( key
) + "=" +
522 encodeURIComponent( params
[ key
] ) + "&";
525 return window
.location
.protocol
+ "//" + window
.location
.host
+
526 window
.location
.pathname
+ querystring
.slice( 0, -1 );
534 removeClass
: removeClass
535 // load, equiv, jsDump, diff: Attached later
539 * @deprecated: Created for backwards compatibility with test runner that set the hook function
540 * into QUnit.{hook}, instead of invoking it and passing the hook function.
541 * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here.
542 * Doing this allows us to tell if the following methods have been overwritten on the actual
545 extend( QUnit
.constructor.prototype, {
547 // Logging callbacks; all receive a single argument with the listed properties
548 // run test/logs.html for any related changes
549 begin
: registerLoggingCallback( "begin" ),
551 // done: { failed, passed, total, runtime }
552 done
: registerLoggingCallback( "done" ),
554 // log: { result, actual, expected, message }
555 log
: registerLoggingCallback( "log" ),
557 // testStart: { name }
558 testStart
: registerLoggingCallback( "testStart" ),
560 // testDone: { name, failed, passed, total, runtime }
561 testDone
: registerLoggingCallback( "testDone" ),
563 // moduleStart: { name }
564 moduleStart
: registerLoggingCallback( "moduleStart" ),
566 // moduleDone: { name, failed, passed, total }
567 moduleDone
: registerLoggingCallback( "moduleDone" )
570 if ( !defined
.document
|| document
.readyState
=== "complete" ) {
571 config
.autorun
= true;
574 QUnit
.load = function() {
575 runLoggingCallbacks( "begin", QUnit
, {} );
577 // Initialize the config, saving the execution queue
578 var banner
, filter
, i
, j
, label
, len
, main
, ol
, toolbar
, val
, selection
,
579 urlConfigContainer
, moduleFilter
, userAgent
,
582 moduleFilterHtml
= "",
584 oldconfig
= extend( {}, config
);
587 extend(config
, oldconfig
);
589 config
.blocking
= false;
591 len
= config
.urlConfig
.length
;
593 for ( i
= 0; i
< len
; i
++ ) {
594 val
= config
.urlConfig
[i
];
595 if ( typeof val
=== "string" ) {
601 config
[ val
.id
] = QUnit
.urlParams
[ val
.id
];
602 if ( !val
.value
|| typeof val
.value
=== "string" ) {
603 urlConfigHtml
+= "<input id='qunit-urlconfig-" + escapeText( val
.id
) +
604 "' name='" + escapeText( val
.id
) +
605 "' type='checkbox'" +
606 ( val
.value
? " value='" + escapeText( val
.value
) + "'" : "" ) +
607 ( config
[ val
.id
] ? " checked='checked'" : "" ) +
608 " title='" + escapeText( val
.tooltip
) +
609 "'><label for='qunit-urlconfig-" + escapeText( val
.id
) +
610 "' title='" + escapeText( val
.tooltip
) + "'>" + val
.label
+ "</label>";
612 urlConfigHtml
+= "<label for='qunit-urlconfig-" + escapeText( val
.id
) +
613 "' title='" + escapeText( val
.tooltip
) +
615 ": </label><select id='qunit-urlconfig-" + escapeText( val
.id
) +
616 "' name='" + escapeText( val
.id
) +
617 "' title='" + escapeText( val
.tooltip
) +
618 "'><option></option>";
620 if ( QUnit
.is( "array", val
.value
) ) {
621 for ( j
= 0; j
< val
.value
.length
; j
++ ) {
622 urlConfigHtml
+= "<option value='" + escapeText( val
.value
[j
] ) + "'" +
623 ( config
[ val
.id
] === val
.value
[j
] ?
624 (selection
= true) && " selected='selected'" :
626 ">" + escapeText( val
.value
[j
] ) + "</option>";
629 for ( j
in val
.value
) {
630 if ( hasOwn
.call( val
.value
, j
) ) {
631 urlConfigHtml
+= "<option value='" + escapeText( j
) + "'" +
632 ( config
[ val
.id
] === j
?
633 (selection
= true) && " selected='selected'" :
635 ">" + escapeText( val
.value
[j
] ) + "</option>";
639 if ( config
[ val
.id
] && !selection
) {
640 urlConfigHtml
+= "<option value='" + escapeText( config
[ val
.id
] ) +
641 "' selected='selected' disabled='disabled'>" +
642 escapeText( config
[ val
.id
] ) +
645 urlConfigHtml
+= "</select>";
648 for ( i
in config
.modules
) {
649 if ( config
.modules
.hasOwnProperty( i
) ) {
653 numModules
= moduleNames
.length
;
654 moduleNames
.sort( function( a
, b
) {
655 return a
.localeCompare( b
);
657 moduleFilterHtml
+= "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " +
658 ( config
.module
=== undefined ? "selected='selected'" : "" ) +
659 ">< All Modules ></option>";
662 for ( i
= 0; i
< numModules
; i
++) {
663 moduleFilterHtml
+= "<option value='" + escapeText( encodeURIComponent(moduleNames
[i
]) ) + "' " +
664 ( config
.module
=== moduleNames
[i
] ? "selected='selected'" : "" ) +
665 ">" + escapeText(moduleNames
[i
]) + "</option>";
667 moduleFilterHtml
+= "</select>";
669 // `userAgent` initialized at top of scope
670 userAgent
= id( "qunit-userAgent" );
672 userAgent
.innerHTML
= navigator
.userAgent
;
675 // `banner` initialized at top of scope
676 banner
= id( "qunit-header" );
678 banner
.innerHTML
= "<a href='" + QUnit
.url({ filter
: undefined, module
: undefined, testNumber
: undefined }) + "'>" + banner
.innerHTML
+ "</a> ";
681 // `toolbar` initialized at top of scope
682 toolbar
= id( "qunit-testrunner-toolbar" );
684 // `filter` initialized at top of scope
685 filter
= document
.createElement( "input" );
686 filter
.type
= "checkbox";
687 filter
.id
= "qunit-filter-pass";
689 addEvent( filter
, "click", function() {
691 ol
= id( "qunit-tests" );
693 if ( filter
.checked
) {
694 ol
.className
= ol
.className
+ " hidepass";
696 tmp
= " " + ol
.className
.replace( /[\n\t\r]/g, " " ) + " ";
697 ol
.className
= tmp
.replace( / hidepass
/, " " );
699 if ( defined
.sessionStorage
) {
700 if (filter
.checked
) {
701 sessionStorage
.setItem( "qunit-filter-passed-tests", "true" );
703 sessionStorage
.removeItem( "qunit-filter-passed-tests" );
708 if ( config
.hidepassed
|| defined
.sessionStorage
&& sessionStorage
.getItem( "qunit-filter-passed-tests" ) ) {
709 filter
.checked
= true;
710 // `ol` initialized at top of scope
711 ol
= id( "qunit-tests" );
712 ol
.className
= ol
.className
+ " hidepass";
714 toolbar
.appendChild( filter
);
716 // `label` initialized at top of scope
717 label
= document
.createElement( "label" );
718 label
.setAttribute( "for", "qunit-filter-pass" );
719 label
.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." );
720 label
.innerHTML
= "Hide passed tests";
721 toolbar
.appendChild( label
);
723 urlConfigContainer
= document
.createElement("span");
724 urlConfigContainer
.innerHTML
= urlConfigHtml
;
725 // For oldIE support:
726 // * Add handlers to the individual elements instead of the container
727 // * Use "click" instead of "change" for checkboxes
728 // * Fallback from event.target to event.srcElement
729 addEvents( urlConfigContainer
.getElementsByTagName("input"), "click", function( event
) {
731 target
= event
.target
|| event
.srcElement
;
732 params
[ target
.name
] = target
.checked
?
733 target
.defaultValue
|| true :
735 window
.location
= QUnit
.url( params
);
737 addEvents( urlConfigContainer
.getElementsByTagName("select"), "change", function( event
) {
739 target
= event
.target
|| event
.srcElement
;
740 params
[ target
.name
] = target
.options
[ target
.selectedIndex
].value
|| undefined;
741 window
.location
= QUnit
.url( params
);
743 toolbar
.appendChild( urlConfigContainer
);
745 if (numModules
> 1) {
746 moduleFilter
= document
.createElement( "span" );
747 moduleFilter
.setAttribute( "id", "qunit-modulefilter-container" );
748 moduleFilter
.innerHTML
= moduleFilterHtml
;
749 addEvent( moduleFilter
.lastChild
, "change", function() {
750 var selectBox
= moduleFilter
.getElementsByTagName("select")[0],
751 selectedModule
= decodeURIComponent(selectBox
.options
[selectBox
.selectedIndex
].value
);
753 window
.location
= QUnit
.url({
754 module
: ( selectedModule
=== "" ) ? undefined : selectedModule
,
755 // Remove any existing filters
757 testNumber
: undefined
760 toolbar
.appendChild(moduleFilter
);
764 // `main` initialized at top of scope
765 main
= id( "qunit-fixture" );
767 config
.fixture
= main
.innerHTML
;
770 if ( config
.autostart
) {
775 if ( defined
.document
) {
776 addEvent( window
, "load", QUnit
.load
);
779 // `onErrorFnPrev` initialized at top of scope
780 // Preserve other handlers
781 onErrorFnPrev
= window
.onerror
;
783 // Cover uncaught exceptions
784 // Returning true will suppress the default browser handler,
785 // returning false will let it run.
786 window
.onerror = function ( error
, filePath
, linerNr
) {
788 if ( onErrorFnPrev
) {
789 ret
= onErrorFnPrev( error
, filePath
, linerNr
);
792 // Treat return value as window.onerror itself does,
793 // Only do our handling if not suppressed.
794 if ( ret
!== true ) {
795 if ( QUnit
.config
.current
) {
796 if ( QUnit
.config
.current
.ignoreGlobalErrors
) {
799 QUnit
.pushFailure( error
, filePath
+ ":" + linerNr
);
801 QUnit
.test( "global failure", extend( function() {
802 QUnit
.pushFailure( error
, filePath
+ ":" + linerNr
);
803 }, { validTest
: validTest
} ) );
812 config
.autorun
= true;
814 // Log the last module results
815 if ( config
.previousModule
) {
816 runLoggingCallbacks( "moduleDone", QUnit
, {
817 name
: config
.previousModule
,
818 failed
: config
.moduleStats
.bad
,
819 passed
: config
.moduleStats
.all
- config
.moduleStats
.bad
,
820 total
: config
.moduleStats
.all
823 delete config
.previousModule
;
826 banner
= id( "qunit-banner" ),
827 tests
= id( "qunit-tests" ),
828 runtime
= +new Date() - config
.started
,
829 passed
= config
.stats
.all
- config
.stats
.bad
,
831 "Tests completed in ",
833 " milliseconds.<br/>",
834 "<span class='passed'>",
836 "</span> assertions of <span class='total'>",
838 "</span> passed, <span class='failed'>",
844 banner
.className
= ( config
.stats
.bad
? "qunit-fail" : "qunit-pass" );
848 id( "qunit-testresult" ).innerHTML
= html
;
851 if ( config
.altertitle
&& defined
.document
&& document
.title
) {
852 // show ✖ for good, ✔ for bad suite result in title
853 // use escape sequences in case file gets loaded with non-utf-8-charset
855 ( config
.stats
.bad
? "\u2716" : "\u2714" ),
856 document
.title
.replace( /^[\u2714\u2716] /i, "" )
860 // clear own sessionStorage items if all tests passed
861 if ( config
.reorder
&& defined
.sessionStorage
&& config
.stats
.bad
=== 0 ) {
862 // `key` & `i` initialized at top of scope
863 for ( i
= 0; i
< sessionStorage
.length
; i
++ ) {
864 key
= sessionStorage
.key( i
++ );
865 if ( key
.indexOf( "qunit-test-" ) === 0 ) {
866 sessionStorage
.removeItem( key
);
871 // scroll back to top to show results
872 if ( config
.scrolltop
&& window
.scrollTo
) {
873 window
.scrollTo(0, 0);
876 runLoggingCallbacks( "done", QUnit
, {
877 failed
: config
.stats
.bad
,
879 total
: config
.stats
.all
,
884 /** @return Boolean: true if this test should be ran */
885 function validTest( test
) {
887 filter
= config
.filter
&& config
.filter
.toLowerCase(),
888 module
= config
.module
&& config
.module
.toLowerCase(),
889 fullName
= ( test
.module
+ ": " + test
.testName
).toLowerCase();
891 // Internally-generated tests are always valid
892 if ( test
.callback
&& test
.callback
.validTest
=== validTest
) {
893 delete test
.callback
.validTest
;
897 if ( config
.testNumber
.length
> 0 ) {
898 if ( inArray( test
.testNumber
, config
.testNumber
) < 0 ) {
903 if ( module
&& ( !test
.module
|| test
.module
.toLowerCase() !== module
) ) {
911 include
= filter
.charAt( 0 ) !== "!";
913 filter
= filter
.slice( 1 );
916 // If the filter matches, we need to honour include
917 if ( fullName
.indexOf( filter
) !== -1 ) {
921 // Otherwise, do the opposite
925 // so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions)
926 // Later Safari and IE10 are supposed to support error.stack as well
927 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
928 function extractStacktrace( e
, offset
) {
929 offset
= offset
=== undefined ? 3 : offset
;
931 var stack
, include
, i
;
933 if ( e
.stacktrace
) {
935 return e
.stacktrace
.split( "\n" )[ offset
+ 3 ];
936 } else if ( e
.stack
) {
938 stack
= e
.stack
.split( "\n" );
939 if (/^error$/i.test( stack
[0] ) ) {
944 for ( i
= offset
; i
< stack
.length
; i
++ ) {
945 if ( stack
[ i
].indexOf( fileName
) !== -1 ) {
948 include
.push( stack
[ i
] );
950 if ( include
.length
) {
951 return include
.join( "\n" );
954 return stack
[ offset
];
955 } else if ( e
.sourceURL
) {
957 // hopefully one day Safari provides actual stacktraces
958 // exclude useless self-reference for generated Error objects
959 if ( /qunit.js$/.test( e
.sourceURL
) ) {
962 // for actual exceptions, this is useful
963 return e
.sourceURL
+ ":" + e
.line
;
966 function sourceFromStacktrace( offset
) {
970 return extractStacktrace( e
, offset
);
975 * Escape text for attribute or text content.
977 function escapeText( s
) {
982 // Both single quotes and double quotes (for attributes)
983 return s
.replace( /['"<>&]/g, function( s
) {
999 function synchronize( callback
, last
) {
1000 config
.queue
.push( callback
);
1002 if ( config
.autorun
&& !config
.blocking
) {
1007 function process( last
) {
1011 var start
= new Date().getTime();
1012 config
.depth
= config
.depth
? config
.depth
+ 1 : 1;
1014 while ( config
.queue
.length
&& !config
.blocking
) {
1015 if ( !defined
.setTimeout
|| config
.updateRate
<= 0 || ( ( new Date().getTime() - start
) < config
.updateRate
) ) {
1016 config
.queue
.shift()();
1018 setTimeout( next
, 13 );
1023 if ( last
&& !config
.blocking
&& !config
.queue
.length
&& config
.depth
=== 0 ) {
1028 function saveGlobal() {
1029 config
.pollution
= [];
1031 if ( config
.noglobals
) {
1032 for ( var key
in window
) {
1033 if ( hasOwn
.call( window
, key
) ) {
1034 // in Opera sometimes DOM element ids show up here, ignore them
1035 if ( /^qunit-test-output/.test( key
) ) {
1038 config
.pollution
.push( key
);
1044 function checkPollution() {
1047 old
= config
.pollution
;
1051 newGlobals
= diff( config
.pollution
, old
);
1052 if ( newGlobals
.length
> 0 ) {
1053 QUnit
.pushFailure( "Introduced global variable(s): " + newGlobals
.join(", ") );
1056 deletedGlobals
= diff( old
, config
.pollution
);
1057 if ( deletedGlobals
.length
> 0 ) {
1058 QUnit
.pushFailure( "Deleted global variable(s): " + deletedGlobals
.join(", ") );
1062 // returns a new Array with the elements that are in a but not in b
1063 function diff( a
, b
) {
1067 for ( i
= 0; i
< result
.length
; i
++ ) {
1068 for ( j
= 0; j
< b
.length
; j
++ ) {
1069 if ( result
[i
] === b
[j
] ) {
1070 result
.splice( i
, 1 );
1079 function extend( a
, b
) {
1080 for ( var prop
in b
) {
1081 if ( hasOwn
.call( b
, prop
) ) {
1082 // Avoid "Member not found" error in IE8 caused by messing with window.constructor
1083 if ( !( prop
=== "constructor" && a
=== window
) ) {
1084 if ( b
[ prop
] === undefined ) {
1087 a
[ prop
] = b
[ prop
];
1097 * @param {HTMLElement} elem
1098 * @param {string} type
1099 * @param {Function} fn
1101 function addEvent( elem
, type
, fn
) {
1102 if ( elem
.addEventListener
) {
1104 // Standards-based browsers
1105 elem
.addEventListener( type
, fn
, false );
1106 } else if ( elem
.attachEvent
) {
1109 elem
.attachEvent( "on" + type
, fn
);
1112 // Caller must ensure support for event listeners is present
1113 throw new Error( "addEvent() was called in a context without event listener support" );
1118 * @param {Array|NodeList} elems
1119 * @param {string} type
1120 * @param {Function} fn
1122 function addEvents( elems
, type
, fn
) {
1123 var i
= elems
.length
;
1125 addEvent( elems
[i
], type
, fn
);
1129 function hasClass( elem
, name
) {
1130 return (" " + elem
.className
+ " ").indexOf(" " + name
+ " ") > -1;
1133 function addClass( elem
, name
) {
1134 if ( !hasClass( elem
, name
) ) {
1135 elem
.className
+= (elem
.className
? " " : "") + name
;
1139 function removeClass( elem
, name
) {
1140 var set = " " + elem
.className
+ " ";
1141 // Class name may appear multiple times
1142 while ( set.indexOf(" " + name
+ " ") > -1 ) {
1143 set = set.replace(" " + name
+ " " , " ");
1145 // If possible, trim it for prettiness, but not necessarily
1146 elem
.className
= typeof set.trim
=== "function" ? set.trim() : set.replace(/^\s+|\s+$/g, "");
1149 function id( name
) {
1150 return defined
.document
&& document
.getElementById
&& document
.getElementById( name
);
1153 function registerLoggingCallback( key
) {
1154 return function( callback
) {
1155 config
[key
].push( callback
);
1159 // Supports deprecated method of completely overwriting logging callbacks
1160 function runLoggingCallbacks( key
, scope
, args
) {
1162 if ( QUnit
.hasOwnProperty( key
) ) {
1163 QUnit
[ key
].call(scope
, args
);
1165 callbacks
= config
[ key
];
1166 for ( i
= 0; i
< callbacks
.length
; i
++ ) {
1167 callbacks
[ i
].call( scope
, args
);
1173 function inArray( elem
, array
) {
1174 if ( array
.indexOf
) {
1175 return array
.indexOf( elem
);
1178 for ( var i
= 0, length
= array
.length
; i
< length
; i
++ ) {
1179 if ( array
[ i
] === elem
) {
1187 function Test( settings
) {
1188 extend( this, settings
);
1189 this.assertions
= [];
1190 this.testNumber
= ++Test
.count
;
1198 tests
= id( "qunit-tests" );
1201 b
= document
.createElement( "strong" );
1202 b
.innerHTML
= this.nameHtml
;
1204 // `a` initialized at top of scope
1205 a
= document
.createElement( "a" );
1206 a
.innerHTML
= "Rerun";
1207 a
.href
= QUnit
.url({ testNumber
: this.testNumber
});
1209 li
= document
.createElement( "li" );
1210 li
.appendChild( b
);
1211 li
.appendChild( a
);
1212 li
.className
= "running";
1213 li
.id
= this.id
= "qunit-test-output" + testId
++;
1215 tests
.appendChild( li
);
1220 // Emit moduleStart when we're switching from one module to another
1221 this.module
!== config
.previousModule
||
1222 // They could be equal (both undefined) but if the previousModule property doesn't
1223 // yet exist it means this is the first test in a suite that isn't wrapped in a
1224 // module, in which case we'll just emit a moduleStart event for 'undefined'.
1225 // Without this, reporters can get testStart before moduleStart which is a problem.
1226 !hasOwn
.call( config
, "previousModule" )
1228 if ( hasOwn
.call( config
, "previousModule" ) ) {
1229 runLoggingCallbacks( "moduleDone", QUnit
, {
1230 name
: config
.previousModule
,
1231 failed
: config
.moduleStats
.bad
,
1232 passed
: config
.moduleStats
.all
- config
.moduleStats
.bad
,
1233 total
: config
.moduleStats
.all
1236 config
.previousModule
= this.module
;
1237 config
.moduleStats
= { all
: 0, bad
: 0 };
1238 runLoggingCallbacks( "moduleStart", QUnit
, {
1243 config
.current
= this;
1245 this.testEnvironment
= extend({
1246 setup: function() {},
1247 teardown: function() {}
1248 }, this.moduleTestEnvironment
);
1250 this.started
= +new Date();
1251 runLoggingCallbacks( "testStart", QUnit
, {
1252 name
: this.testName
,
1256 /*jshint camelcase:false */
1260 * Expose the current test environment.
1262 * @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead.
1264 QUnit
.current_testEnvironment
= this.testEnvironment
;
1266 /*jshint camelcase:true */
1268 if ( !config
.pollution
) {
1271 if ( config
.notrycatch
) {
1272 this.testEnvironment
.setup
.call( this.testEnvironment
, QUnit
.assert
);
1276 this.testEnvironment
.setup
.call( this.testEnvironment
, QUnit
.assert
);
1278 QUnit
.pushFailure( "Setup failed on " + this.testName
+ ": " + ( e
.message
|| e
), extractStacktrace( e
, 1 ) );
1282 config
.current
= this;
1284 var running
= id( "qunit-testresult" );
1287 running
.innerHTML
= "Running: <br/>" + this.nameHtml
;
1294 this.callbackStarted
= +new Date();
1296 if ( config
.notrycatch
) {
1297 this.callback
.call( this.testEnvironment
, QUnit
.assert
);
1298 this.callbackRuntime
= +new Date() - this.callbackStarted
;
1303 this.callback
.call( this.testEnvironment
, QUnit
.assert
);
1304 this.callbackRuntime
= +new Date() - this.callbackStarted
;
1306 this.callbackRuntime
= +new Date() - this.callbackStarted
;
1308 QUnit
.pushFailure( "Died on test #" + (this.assertions
.length
+ 1) + " " + this.stack
+ ": " + ( e
.message
|| e
), extractStacktrace( e
, 0 ) );
1309 // else next test will carry the responsibility
1312 // Restart the tests if they're blocking
1313 if ( config
.blocking
) {
1318 teardown: function() {
1319 config
.current
= this;
1320 if ( config
.notrycatch
) {
1321 if ( typeof this.callbackRuntime
=== "undefined" ) {
1322 this.callbackRuntime
= +new Date() - this.callbackStarted
;
1324 this.testEnvironment
.teardown
.call( this.testEnvironment
, QUnit
.assert
);
1328 this.testEnvironment
.teardown
.call( this.testEnvironment
, QUnit
.assert
);
1330 QUnit
.pushFailure( "Teardown failed on " + this.testName
+ ": " + ( e
.message
|| e
), extractStacktrace( e
, 1 ) );
1335 finish: function() {
1336 config
.current
= this;
1337 if ( config
.requireExpects
&& this.expected
=== null ) {
1338 QUnit
.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack
);
1339 } else if ( this.expected
!== null && this.expected
!== this.assertions
.length
) {
1340 QUnit
.pushFailure( "Expected " + this.expected
+ " assertions, but " + this.assertions
.length
+ " were run", this.stack
);
1341 } else if ( this.expected
=== null && !this.assertions
.length
) {
1342 QUnit
.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack
);
1345 var i
, assertion
, a
, b
, time
, li
, ol
,
1349 tests
= id( "qunit-tests" );
1351 this.runtime
= +new Date() - this.started
;
1352 config
.stats
.all
+= this.assertions
.length
;
1353 config
.moduleStats
.all
+= this.assertions
.length
;
1356 ol
= document
.createElement( "ol" );
1357 ol
.className
= "qunit-assert-list";
1359 for ( i
= 0; i
< this.assertions
.length
; i
++ ) {
1360 assertion
= this.assertions
[i
];
1362 li
= document
.createElement( "li" );
1363 li
.className
= assertion
.result
? "pass" : "fail";
1364 li
.innerHTML
= assertion
.message
|| ( assertion
.result
? "okay" : "failed" );
1365 ol
.appendChild( li
);
1367 if ( assertion
.result
) {
1372 config
.moduleStats
.bad
++;
1376 // store result when possible
1377 if ( QUnit
.config
.reorder
&& defined
.sessionStorage
) {
1379 sessionStorage
.setItem( "qunit-test-" + this.module
+ "-" + this.testName
, bad
);
1381 sessionStorage
.removeItem( "qunit-test-" + this.module
+ "-" + this.testName
);
1386 addClass( ol
, "qunit-collapsed" );
1389 // `b` initialized at top of scope
1390 b
= document
.createElement( "strong" );
1391 b
.innerHTML
= this.nameHtml
+ " <b class='counts'>(<b class='failed'>" + bad
+ "</b>, <b class='passed'>" + good
+ "</b>, " + this.assertions
.length
+ ")</b>";
1393 addEvent(b
, "click", function() {
1394 var next
= b
.parentNode
.lastChild
,
1395 collapsed
= hasClass( next
, "qunit-collapsed" );
1396 ( collapsed
? removeClass
: addClass
)( next
, "qunit-collapsed" );
1399 addEvent(b
, "dblclick", function( e
) {
1400 var target
= e
&& e
.target
? e
.target
: window
.event
.srcElement
;
1401 if ( target
.nodeName
.toLowerCase() === "span" || target
.nodeName
.toLowerCase() === "b" ) {
1402 target
= target
.parentNode
;
1404 if ( window
.location
&& target
.nodeName
.toLowerCase() === "strong" ) {
1405 window
.location
= QUnit
.url({ testNumber
: test
.testNumber
});
1409 // `time` initialized at top of scope
1410 time
= document
.createElement( "span" );
1411 time
.className
= "runtime";
1412 time
.innerHTML
= this.runtime
+ " ms";
1414 // `li` initialized at top of scope
1416 li
.className
= bad
? "fail" : "pass";
1417 li
.removeChild( li
.firstChild
);
1419 li
.appendChild( b
);
1420 li
.appendChild( a
);
1421 li
.appendChild( time
);
1422 li
.appendChild( ol
);
1425 for ( i
= 0; i
< this.assertions
.length
; i
++ ) {
1426 if ( !this.assertions
[i
].result
) {
1429 config
.moduleStats
.bad
++;
1434 runLoggingCallbacks( "testDone", QUnit
, {
1435 name
: this.testName
,
1436 module
: this.module
,
1438 passed
: this.assertions
.length
- bad
,
1439 total
: this.assertions
.length
,
1440 runtime
: this.runtime
,
1441 // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
1442 duration
: this.runtime
1447 config
.current
= undefined;
1454 synchronize(function() {
1458 // each of these can by async
1459 synchronize(function() {
1462 synchronize(function() {
1465 synchronize(function() {
1468 synchronize(function() {
1473 // `bad` initialized at top of scope
1474 // defer when previous test run passed, if storage is available
1475 bad
= QUnit
.config
.reorder
&& defined
.sessionStorage
&&
1476 +sessionStorage
.getItem( "qunit-test-" + this.module
+ "-" + this.testName
);
1481 synchronize( run
, true );
1486 // `assert` initialized at top of scope
1488 // All of these must either call QUnit.push() or manually do:
1489 // - runLoggingCallbacks( "log", .. );
1490 // - config.current.assertions.push({ .. });
1491 assert
= QUnit
.assert
= {
1493 * Asserts rough true-ish result.
1496 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
1498 ok: function( result
, msg
) {
1499 if ( !config
.current
) {
1500 throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) );
1503 msg
= msg
|| ( result
? "okay" : "failed" );
1507 module
: config
.current
.module
,
1508 name
: config
.current
.testName
,
1513 msg
= "<span class='test-message'>" + escapeText( msg
) + "</span>";
1516 source
= sourceFromStacktrace( 2 );
1518 details
.source
= source
;
1519 msg
+= "<table><tr class='test-source'><th>Source: </th><td><pre>" +
1520 escapeText( source
) +
1521 "</pre></td></tr></table>";
1524 runLoggingCallbacks( "log", QUnit
, details
);
1525 config
.current
.assertions
.push({
1532 * Assert that the first two arguments are equal, with an optional message.
1533 * Prints out both actual and expected values.
1536 * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
1538 equal: function( actual
, expected
, message
) {
1539 /*jshint eqeqeq:false */
1540 QUnit
.push( expected
== actual
, actual
, expected
, message
);
1547 notEqual: function( actual
, expected
, message
) {
1548 /*jshint eqeqeq:false */
1549 QUnit
.push( expected
!= actual
, actual
, expected
, message
);
1556 propEqual: function( actual
, expected
, message
) {
1557 actual
= objectValues(actual
);
1558 expected
= objectValues(expected
);
1559 QUnit
.push( QUnit
.equiv(actual
, expected
), actual
, expected
, message
);
1563 * @name notPropEqual
1566 notPropEqual: function( actual
, expected
, message
) {
1567 actual
= objectValues(actual
);
1568 expected
= objectValues(expected
);
1569 QUnit
.push( !QUnit
.equiv(actual
, expected
), actual
, expected
, message
);
1576 deepEqual: function( actual
, expected
, message
) {
1577 QUnit
.push( QUnit
.equiv(actual
, expected
), actual
, expected
, message
);
1581 * @name notDeepEqual
1584 notDeepEqual: function( actual
, expected
, message
) {
1585 QUnit
.push( !QUnit
.equiv(actual
, expected
), actual
, expected
, message
);
1592 strictEqual: function( actual
, expected
, message
) {
1593 QUnit
.push( expected
=== actual
, actual
, expected
, message
);
1597 * @name notStrictEqual
1600 notStrictEqual: function( actual
, expected
, message
) {
1601 QUnit
.push( expected
!== actual
, actual
, expected
, message
);
1604 "throws": function( block
, expected
, message
) {
1606 expectedOutput
= expected
,
1609 // 'expected' is optional
1610 if ( !message
&& typeof expected
=== "string" ) {
1615 config
.current
.ignoreGlobalErrors
= true;
1617 block
.call( config
.current
.testEnvironment
);
1621 config
.current
.ignoreGlobalErrors
= false;
1625 // we don't want to validate thrown error
1628 expectedOutput
= null;
1630 // expected is an Error object
1631 } else if ( expected
instanceof Error
) {
1632 ok
= actual
instanceof Error
&&
1633 actual
.name
=== expected
.name
&&
1634 actual
.message
=== expected
.message
;
1636 // expected is a regexp
1637 } else if ( QUnit
.objectType( expected
) === "regexp" ) {
1638 ok
= expected
.test( errorString( actual
) );
1640 // expected is a string
1641 } else if ( QUnit
.objectType( expected
) === "string" ) {
1642 ok
= expected
=== errorString( actual
);
1644 // expected is a constructor
1645 } else if ( actual
instanceof expected
) {
1648 // expected is a validation function which returns true is validation passed
1649 } else if ( expected
.call( {}, actual
) === true ) {
1650 expectedOutput
= null;
1654 QUnit
.push( ok
, actual
, expectedOutput
, message
);
1656 QUnit
.pushFailure( message
, null, "No exception was thrown." );
1662 * @deprecated since 1.8.0
1663 * Kept assertion helpers in root for backwards compatibility.
1665 extend( QUnit
.constructor.prototype, assert
);
1668 * @deprecated since 1.9.0
1669 * Kept to avoid TypeErrors for undefined methods.
1671 QUnit
.constructor.prototype.raises = function() {
1672 QUnit
.push( false, false, false, "QUnit.raises has been deprecated since 2012 (fad3c1ea), use QUnit.throws instead" );
1676 * @deprecated since 1.0.0, replaced with error pushes since 1.3.0
1677 * Kept to avoid TypeErrors for undefined methods.
1679 QUnit
.constructor.prototype.equals = function() {
1680 QUnit
.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
1682 QUnit
.constructor.prototype.same = function() {
1683 QUnit
.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" );
1686 // Test for equality any JavaScript type.
1687 // Author: Philippe Rathé <prathe@gmail.com>
1688 QUnit
.equiv
= (function() {
1690 // Call the o related callback with the given arguments.
1691 function bindCallbacks( o
, callbacks
, args
) {
1692 var prop
= QUnit
.objectType( o
);
1694 if ( QUnit
.objectType( callbacks
[ prop
] ) === "function" ) {
1695 return callbacks
[ prop
].apply( callbacks
, args
);
1697 return callbacks
[ prop
]; // or undefined
1702 // the real equiv function
1704 // stack to decide between skip/abort functions
1706 // stack to avoiding loops from circular referencing
1710 getProto
= Object
.getPrototypeOf
|| function ( obj
) {
1711 /*jshint camelcase:false */
1712 return obj
.__proto__
;
1714 callbacks
= (function () {
1716 // for string, boolean, number and null
1717 function useStrictEquality( b
, a
) {
1718 /*jshint eqeqeq:false */
1719 if ( b
instanceof a
.constructor || a
instanceof b
.constructor ) {
1720 // to catch short annotation VS 'new' annotation of a
1723 // var j = new Number(1);
1731 "string": useStrictEquality
,
1732 "boolean": useStrictEquality
,
1733 "number": useStrictEquality
,
1734 "null": useStrictEquality
,
1735 "undefined": useStrictEquality
,
1737 "nan": function( b
) {
1741 "date": function( b
, a
) {
1742 return QUnit
.objectType( b
) === "date" && a
.valueOf() === b
.valueOf();
1745 "regexp": function( b
, a
) {
1746 return QUnit
.objectType( b
) === "regexp" &&
1748 a
.source
=== b
.source
&&
1749 // and its modifiers
1750 a
.global
=== b
.global
&&
1752 a
.ignoreCase
=== b
.ignoreCase
&&
1753 a
.multiline
=== b
.multiline
&&
1754 a
.sticky
=== b
.sticky
;
1757 // - skip when the property is a method of an instance (OOP)
1758 // - abort otherwise,
1759 // initial === would have catch identical references anyway
1760 "function": function() {
1761 var caller
= callers
[callers
.length
- 1];
1762 return caller
!== Object
&& typeof caller
!== "undefined";
1765 "array": function( b
, a
) {
1766 var i
, j
, len
, loop
, aCircular
, bCircular
;
1768 // b could be an object literal here
1769 if ( QUnit
.objectType( b
) !== "array" ) {
1774 if ( len
!== b
.length
) {
1779 // track reference to avoid circular references
1782 for ( i
= 0; i
< len
; i
++ ) {
1784 for ( j
= 0; j
< parents
.length
; j
++ ) {
1785 aCircular
= parents
[j
] === a
[i
];
1786 bCircular
= parentsB
[j
] === b
[i
];
1787 if ( aCircular
|| bCircular
) {
1788 if ( a
[i
] === b
[i
] || aCircular
&& bCircular
) {
1797 if ( !loop
&& !innerEquiv(a
[i
], b
[i
]) ) {
1808 "object": function( b
, a
) {
1809 /*jshint forin:false */
1810 var i
, j
, loop
, aCircular
, bCircular
,
1816 // comparing constructors is more strict than using
1818 if ( a
.constructor !== b
.constructor ) {
1819 // Allow objects with no prototype to be equivalent to
1820 // objects with Object as their constructor.
1821 if ( !(( getProto(a
) === null && getProto(b
) === Object
.prototype ) ||
1822 ( getProto(b
) === null && getProto(a
) === Object
.prototype ) ) ) {
1827 // stack constructor before traversing properties
1828 callers
.push( a
.constructor );
1830 // track reference to avoid circular references
1834 // be strict: don't ensure hasOwnProperty and go deep
1837 for ( j
= 0; j
< parents
.length
; j
++ ) {
1838 aCircular
= parents
[j
] === a
[i
];
1839 bCircular
= parentsB
[j
] === b
[i
];
1840 if ( aCircular
|| bCircular
) {
1841 if ( a
[i
] === b
[i
] || aCircular
&& bCircular
) {
1849 aProperties
.push(i
);
1850 if ( !loop
&& !innerEquiv(a
[i
], b
[i
]) ) {
1858 callers
.pop(); // unstack, we are done
1861 bProperties
.push( i
); // collect b's properties
1864 // Ensures identical properties name
1865 return eq
&& innerEquiv( aProperties
.sort(), bProperties
.sort() );
1870 innerEquiv = function() { // can take multiple arguments
1871 var args
= [].slice
.apply( arguments
);
1872 if ( args
.length
< 2 ) {
1873 return true; // end transition
1876 return (function( a
, b
) {
1878 return true; // catch the most you can
1879 } else if ( a
=== null || b
=== null || typeof a
=== "undefined" ||
1880 typeof b
=== "undefined" ||
1881 QUnit
.objectType(a
) !== QUnit
.objectType(b
) ) {
1882 return false; // don't lose time with error prone cases
1884 return bindCallbacks(a
, callbacks
, [ b
, a
]);
1887 // apply transition with (1..n) arguments
1888 }( args
[0], args
[1] ) && innerEquiv
.apply( this, args
.splice(1, args
.length
- 1 )) );
1895 * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
1896 * http://flesler.blogspot.com Licensed under BSD
1897 * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
1899 * @projectDescription Advanced and extensible data dumping for Javascript.
1901 * @author Ariel Flesler
1902 * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
1904 QUnit
.jsDump
= (function() {
1905 function quote( str
) {
1906 return "\"" + str
.toString().replace( /"/g, "\\\"" ) + "\"";
1908 function literal( o ) {
1911 function join( pre, arr, post ) {
1912 var s = jsDump.separator(),
1913 base = jsDump.indent(),
1914 inner = jsDump.indent(1);
1916 arr = arr.join( "," + s + inner );
1921 return [ pre, inner + arr, base + post ].join(s);
1923 function array( arr, stack ) {
1924 var i = arr.length, ret = new Array(i);
1927 ret[i] = this.parse( arr[i] , undefined , stack);
1930 return join( "[", ret, "]" );
1933 var reName = /^function (\w+)/,
1935 // type is used mostly internally, you can fix a (custom)type in advance
1936 parse: function( obj, type, stack ) {
1937 stack = stack || [ ];
1939 parser = this.parsers[ type || this.typeOf(obj) ];
1941 type = typeof parser;
1942 inStack = inArray( obj, stack );
1944 if ( inStack !== -1 ) {
1945 return "recursion(" + (inStack - stack.length) + ")";
1947 if ( type === "function" ) {
1949 res = parser.call( this, obj, stack );
1953 return ( type === "string
" ) ? parser : this.parsers.error;
1955 typeOf: function( obj ) {
1957 if ( obj === null ) {
1959 } else if ( typeof obj === "undefined" ) {
1961 } else if ( QUnit.is( "regexp
", obj) ) {
1963 } else if ( QUnit.is( "date
", obj) ) {
1965 } else if ( QUnit.is( "function", obj) ) {
1967 } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) {
1969 } else if ( obj.nodeType === 9 ) {
1971 } else if ( obj.nodeType ) {
1975 toString.call( obj ) === "[object Array
]" ||
1977 ( typeof obj.length === "number
" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
1980 } else if ( obj.constructor === Error.prototype.constructor ) {
1987 separator: function() {
1988 return this.multiline ? this.HTML ? "<br
/>" : "\n" : this.HTML ? " 
;" : " ";
1990 // extra can be a number, shortcut for increasing-calling-decreasing
1991 indent: function( extra ) {
1992 if ( !this.multiline ) {
1995 var chr = this.indentChar;
1997 chr = chr.replace( /\t/g, " " ).replace( / /g, " 
;" );
1999 return new Array( this.depth + ( extra || 0 ) ).join(chr);
2002 this.depth += a || 1;
2004 down: function( a ) {
2005 this.depth -= a || 1;
2007 setParser: function( name, parser ) {
2008 this.parsers[name] = parser;
2010 // The next 3 are exposed so you can use them
2016 // This is the list of parsers, to modify them, use jsDump.setParser
2019 document: "[Document
]",
2020 error: function(error) {
2021 return "Error(\"" + error.message + "\")";
2023 unknown: "[Unknown
]",
2025 "undefined": "undefined",
2026 "function": function( fn ) {
2027 var ret = "function",
2028 // functions never have name in IE
2029 name = "name
" in fn ? fn.name : (reName.exec(fn) || [])[1];
2036 ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs
" ), "){" ].join( "" );
2037 return join( ret, QUnit.jsDump.parse(fn,"functionCode
" ), "}" );
2042 object: function( map, stack ) {
2043 /*jshint forin:false */
2044 var ret = [ ], keys, key, val, i;
2047 for ( key in map ) {
2051 for ( i = 0; i < keys.length; i++ ) {
2054 ret.push( QUnit.jsDump.parse( key, "key
" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) );
2056 QUnit.jsDump.down();
2057 return join( "{", ret, "}" );
2059 node: function( node ) {
2061 open = QUnit.jsDump.HTML ? "<
;" : "<",
2062 close = QUnit.jsDump.HTML ? ">
;" : ">",
2063 tag = node.nodeName.toLowerCase(),
2065 attrs = node.attributes;
2068 for ( i = 0, len = attrs.length; i < len; i++ ) {
2069 val = attrs[i].nodeValue;
2070 // IE6 includes all attributes in .attributes, even ones not explicitly set.
2071 // Those have values like undefined, null, 0, false, "" or "inherit
".
2072 if ( val && val !== "inherit
" ) {
2073 ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute
" );
2079 // Show content of TextNode or CDATASection
2080 if ( node.nodeType === 3 || node.nodeType === 4 ) {
2081 ret += node.nodeValue;
2084 return ret + open + "/" + tag + close;
2086 // function calls it internally, it's the arguments part of the function
2087 functionArgs: function( fn ) {
2095 args = new Array(l);
2098 args[l] = String.fromCharCode(97+l);
2100 return " " + args.join( ", " ) + " ";
2102 // object calls it internally, the key part of an item in a map
2104 // function calls it internally, it's the content of the function
2105 functionCode: "[code
]",
2106 // node calls it internally, it's an html attribute value
2114 // if true, entities are escaped ( <, >, \t, space and \n )
2118 // if true, items in a collection, are separated by a \n, else just a space.
2126 * Javascript Diff Algorithm
2127 * By John Resig (http://ejohn.org/)
2128 * Modified by Chu Alan "sprite
"
2130 * Released under the MIT license.
2133 * http://ejohn.org/projects/javascript-diff-algorithm/
2135 * Usage: QUnit.diff(expected, actual)
2137 * QUnit.diff( "the quick brown fox jumped over
", "the quick fox jumps over
" ) == "the quick
<del
>brown
</del> fox <del>jumped </del><ins
>jumps
</ins
> over
"
2139 QUnit.diff = (function() {
2140 /*jshint eqeqeq:false, eqnull:true */
2141 function diff( o, n ) {
2146 for ( i = 0; i < n.length; i++ ) {
2147 if ( !hasOwn.call( ns, n[i] ) ) {
2153 ns[ n[i] ].rows.push( i );
2156 for ( i = 0; i < o.length; i++ ) {
2157 if ( !hasOwn.call( os, o[i] ) ) {
2163 os[ o[i] ].rows.push( i );
2167 if ( hasOwn.call( ns, i ) ) {
2168 if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) {
2169 n[ ns[i].rows[0] ] = {
2170 text: n[ ns[i].rows[0] ],
2173 o[ os[i].rows[0] ] = {
2174 text: o[ os[i].rows[0] ],
2181 for ( i = 0; i < n.length - 1; i++ ) {
2182 if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null &&
2183 n[ i + 1 ] == o[ n[i].row + 1 ] ) {
2189 o[ n[i].row + 1 ] = {
2190 text: o[ n[i].row + 1 ],
2196 for ( i = n.length - 1; i > 0; i-- ) {
2197 if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null &&
2198 n[ i - 1 ] == o[ n[i].row - 1 ]) {
2204 o[ n[i].row - 1 ] = {
2205 text: o[ n[i].row - 1 ],
2217 return function( o, n ) {
2218 o = o.replace( /\s+$/, "" );
2219 n = n.replace( /\s+$/, "" );
2223 out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ),
2224 oSpace = o.match(/\s+/g),
2225 nSpace = n.match(/\s+/g);
2227 if ( oSpace == null ) {
2234 if ( nSpace == null ) {
2241 if ( out.n.length === 0 ) {
2242 for ( i = 0; i < out.o.length; i++ ) {
2243 str += "<del
>" + out.o[i] + oSpace[i] + "</del
>";
2247 if ( out.n[0].text == null ) {
2248 for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) {
2249 str += "<del
>" + out.o[n] + oSpace[n] + "</del
>";
2253 for ( i = 0; i < out.n.length; i++ ) {
2254 if (out.n[i].text == null) {
2255 str += "<ins
>" + out.n[i] + nSpace[i] + "</ins
>";
2258 // `pre` initialized at top of scope
2261 for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) {
2262 pre += "<del
>" + out.o[n] + oSpace[n] + "</del
>";
2264 str += " " + out.n[i].text + nSpace[i] + pre;
2273 // For browser, export only select globals
2274 if ( typeof window !== "undefined" ) {
2275 extend( window, QUnit.constructor.prototype );
2276 window.QUnit = QUnit;
2279 // For CommonJS environments, export everything
2280 if ( typeof module !== "undefined" && module.exports ) {
2281 module.exports = QUnit;
2285 // Get a reference to the global object, like window in browsers