2 * QUnit 1.2.0pre - A JavaScript Unit Testing Framework
4 * http://docs.jquery.com/QUnit
6 * Copyright (c) 2011 John Resig, Jörn Zaefferer
7 * Dual licensed under the MIT (MIT-LICENSE.txt)
8 * or GPL (GPL-LICENSE.txt) licenses.
14 setTimeout
: typeof window
.setTimeout
!== "undefined",
15 sessionStorage
: (function() {
17 return !!sessionStorage
.getItem
;
25 toString
= Object
.prototype.toString
,
26 hasOwn
= Object
.prototype.hasOwnProperty
;
28 var Test = function(name
, testName
, expected
, testEnvironmentArg
, async
, callback
) {
30 this.testName
= testName
;
31 this.expected
= expected
;
32 this.testEnvironmentArg
= testEnvironmentArg
;
34 this.callback
= callback
;
39 var tests
= id("qunit-tests");
41 var b
= document
.createElement("strong");
42 b
.innerHTML
= "Running " + this.name
;
43 var li
= document
.createElement("li");
45 li
.className
= "running";
46 li
.id
= this.id
= "test-output" + testId
++;
47 tests
.appendChild( li
);
51 if (this.module
!= config
.previousModule
) {
52 if ( config
.previousModule
) {
53 runLoggingCallbacks('moduleDone', QUnit
, {
54 name
: config
.previousModule
,
55 failed
: config
.moduleStats
.bad
,
56 passed
: config
.moduleStats
.all
- config
.moduleStats
.bad
,
57 total
: config
.moduleStats
.all
60 config
.previousModule
= this.module
;
61 config
.moduleStats
= { all
: 0, bad
: 0 };
62 runLoggingCallbacks( 'moduleStart', QUnit
, {
67 config
.current
= this;
68 this.testEnvironment
= extend({
70 teardown: function() {}
71 }, this.moduleTestEnvironment
);
72 if (this.testEnvironmentArg
) {
73 extend(this.testEnvironment
, this.testEnvironmentArg
);
76 runLoggingCallbacks( 'testStart', QUnit
, {
81 // allow utility functions to access the current test environment
83 QUnit
.current_testEnvironment
= this.testEnvironment
;
86 if ( !config
.pollution
) {
90 this.testEnvironment
.setup
.call(this.testEnvironment
);
92 QUnit
.ok( false, "Setup failed on " + this.testName
+ ": " + e
.message
);
96 config
.current
= this;
101 if ( config
.notrycatch
) {
102 this.callback
.call(this.testEnvironment
);
106 this.callback
.call(this.testEnvironment
);
108 fail("Test " + this.testName
+ " died, exception and test follows", e
, this.callback
);
109 QUnit
.ok( false, "Died on test #" + (this.assertions
.length
+ 1) + ": " + e
.message
+ " - " + QUnit
.jsDump
.parse(e
) );
110 // else next test will carry the responsibility
113 // Restart the tests if they're blocking
114 if ( config
.blocking
) {
119 teardown: function() {
120 config
.current
= this;
122 this.testEnvironment
.teardown
.call(this.testEnvironment
);
125 QUnit
.ok( false, "Teardown failed on " + this.testName
+ ": " + e
.message
);
129 config
.current
= this;
130 if ( this.expected
!= null && this.expected
!= this.assertions
.length
) {
131 QUnit
.ok( false, "Expected " + this.expected
+ " assertions, but " + this.assertions
.length
+ " were run" );
134 var good
= 0, bad
= 0,
135 tests
= id("qunit-tests");
137 config
.stats
.all
+= this.assertions
.length
;
138 config
.moduleStats
.all
+= this.assertions
.length
;
141 var ol
= document
.createElement("ol");
143 for ( var i
= 0; i
< this.assertions
.length
; i
++ ) {
144 var assertion
= this.assertions
[i
];
146 var li
= document
.createElement("li");
147 li
.className
= assertion
.result
? "pass" : "fail";
148 li
.innerHTML
= assertion
.message
|| (assertion
.result
? "okay" : "failed");
149 ol
.appendChild( li
);
151 if ( assertion
.result
) {
156 config
.moduleStats
.bad
++;
160 // store result when possible
161 if ( QUnit
.config
.reorder
&& defined
.sessionStorage
) {
163 sessionStorage
.setItem("qunit-" + this.module
+ "-" + this.testName
, bad
);
165 sessionStorage
.removeItem("qunit-" + this.module
+ "-" + this.testName
);
170 ol
.style
.display
= "none";
173 var b
= document
.createElement("strong");
174 b
.innerHTML
= this.name
+ " <b class='counts'>(<b class='failed'>" + bad
+ "</b>, <b class='passed'>" + good
+ "</b>, " + this.assertions
.length
+ ")</b>";
176 var a
= document
.createElement("a");
177 a
.innerHTML
= "Rerun";
178 a
.href
= QUnit
.url({ filter
: getText([b
]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
180 addEvent(b
, "click", function() {
181 var next
= b
.nextSibling
.nextSibling
,
182 display
= next
.style
.display
;
183 next
.style
.display
= display
=== "none" ? "block" : "none";
186 addEvent(b
, "dblclick", function(e
) {
187 var target
= e
&& e
.target
? e
.target
: window
.event
.srcElement
;
188 if ( target
.nodeName
.toLowerCase() == "span" || target
.nodeName
.toLowerCase() == "b" ) {
189 target
= target
.parentNode
;
191 if ( window
.location
&& target
.nodeName
.toLowerCase() === "strong" ) {
192 window
.location
= QUnit
.url({ filter
: getText([target
]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
196 var li
= id(this.id
);
197 li
.className
= bad
? "fail" : "pass";
198 li
.removeChild( li
.firstChild
);
201 li
.appendChild( ol
);
204 for ( var i
= 0; i
< this.assertions
.length
; i
++ ) {
205 if ( !this.assertions
[i
].result
) {
208 config
.moduleStats
.bad
++;
216 fail("reset() failed, following Test " + this.testName
+ ", exception and reset fn follows", e
, QUnit
.reset
);
219 runLoggingCallbacks( 'testDone', QUnit
, {
223 passed
: this.assertions
.length
- bad
,
224 total
: this.assertions
.length
230 synchronize(function() {
234 // each of these can by async
235 synchronize(function() {
238 synchronize(function() {
241 synchronize(function() {
244 synchronize(function() {
248 // defer when previous test run passed, if storage is available
249 var bad
= QUnit
.config
.reorder
&& defined
.sessionStorage
&& +sessionStorage
.getItem("qunit-" + this.module
+ "-" + this.testName
);
253 synchronize(run
, true);
261 // call on start of module test to prepend name to all tests
262 module: function(name
, testEnvironment
) {
263 config
.currentModule
= name
;
264 config
.currentModuleTestEnviroment
= testEnvironment
;
267 asyncTest: function(testName
, expected
, callback
) {
268 if ( arguments
.length
=== 2 ) {
273 QUnit
.test(testName
, expected
, callback
, true);
276 test: function(testName
, expected
, callback
, async
) {
277 var name
= '<span class="test-name">' + testName
+ '</span>', testEnvironmentArg
;
279 if ( arguments
.length
=== 2 ) {
283 // is 2nd argument a testEnvironment?
284 if ( expected
&& typeof expected
=== 'object') {
285 testEnvironmentArg
= expected
;
289 if ( config
.currentModule
) {
290 name
= '<span class="module-name">' + config
.currentModule
+ "</span>: " + name
;
293 if ( !validTest(config
.currentModule
+ ": " + testName
) ) {
297 var test
= new Test(name
, testName
, expected
, testEnvironmentArg
, async
, callback
);
298 test
.module
= config
.currentModule
;
299 test
.moduleTestEnvironment
= config
.currentModuleTestEnviroment
;
304 * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
306 expect: function(asserts
) {
307 config
.current
.expected
= asserts
;
312 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
314 ok: function(a
, msg
) {
320 msg
= escapeInnerText(msg
);
321 runLoggingCallbacks( 'log', QUnit
, details
);
322 config
.current
.assertions
.push({
329 * Checks that the first two arguments are equal, with an optional message.
330 * Prints out both actual and expected values.
332 * Prefered to ok( actual == expected, message )
334 * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
336 * @param Object actual
337 * @param Object expected
338 * @param String message (optional)
340 equal: function(actual
, expected
, message
) {
341 QUnit
.push(expected
== actual
, actual
, expected
, message
);
344 notEqual: function(actual
, expected
, message
) {
345 QUnit
.push(expected
!= actual
, actual
, expected
, message
);
348 deepEqual: function(actual
, expected
, message
) {
349 QUnit
.push(QUnit
.equiv(actual
, expected
), actual
, expected
, message
);
352 notDeepEqual: function(actual
, expected
, message
) {
353 QUnit
.push(!QUnit
.equiv(actual
, expected
), actual
, expected
, message
);
356 strictEqual: function(actual
, expected
, message
) {
357 QUnit
.push(expected
=== actual
, actual
, expected
, message
);
360 notStrictEqual: function(actual
, expected
, message
) {
361 QUnit
.push(expected
!== actual
, actual
, expected
, message
);
364 raises: function(block
, expected
, message
) {
365 var actual
, ok
= false;
367 if (typeof expected
=== 'string') {
379 // we don't want to validate thrown error
382 // expected is a regexp
383 } else if (QUnit
.objectType(expected
) === "regexp") {
384 ok
= expected
.test(actual
);
385 // expected is a constructor
386 } else if (actual
instanceof expected
) {
388 // expected is a validation function which returns true is validation passed
389 } else if (expected
.call({}, actual
) === true) {
394 QUnit
.ok(ok
, message
);
397 start: function(count
) {
398 config
.semaphore
-= count
|| 1;
399 if (config
.semaphore
> 0) {
400 // don't start until equal number of stop-calls
403 if (config
.semaphore
< 0) {
404 // ignore if start is called more often then stop
405 config
.semaphore
= 0;
407 // A slight delay, to avoid any current callbacks
408 if ( defined
.setTimeout
) {
409 window
.setTimeout(function() {
410 if (config
.semaphore
> 0) {
413 if ( config
.timeout
) {
414 clearTimeout(config
.timeout
);
417 config
.blocking
= false;
421 config
.blocking
= false;
426 stop: function(count
) {
427 config
.semaphore
+= count
|| 1;
428 config
.blocking
= true;
430 if ( config
.testTimeout
&& defined
.setTimeout
) {
431 clearTimeout(config
.timeout
);
432 config
.timeout
= window
.setTimeout(function() {
433 QUnit
.ok( false, "Test timed out" );
434 config
.semaphore
= 1;
436 }, config
.testTimeout
);
441 //We want access to the constructor's prototype
446 //Make F QUnit's constructor so that we can add to the prototype later
447 QUnit
.constructor = F
;
450 // Backwards compatibility, deprecated
451 QUnit
.equals
= QUnit
.equal
;
452 QUnit
.same
= QUnit
.deepEqual
;
454 // Maintain internal state
456 // The queue of tests to run
459 // block until document ready
462 // when enabled, show only failing tests
463 // gets persisted through sessionStorage and can be changed in UI via checkbox
466 // by default, run previously failed tests first
467 // very useful in combination with "Hide passed tests" checked
470 // by default, modify document.title when suite is done
473 urlConfig
: ['noglobals', 'notrycatch'],
475 //logging callback queues
487 var location
= window
.location
|| { search
: "", protocol
: "file:" },
488 params
= location
.search
.slice( 1 ).split( "&" ),
489 length
= params
.length
,
494 for ( var i
= 0; i
< length
; i
++ ) {
495 current
= params
[ i
].split( "=" );
496 current
[ 0 ] = decodeURIComponent( current
[ 0 ] );
497 // allow just a key to turn on a flag, e.g., test.html?noglobals
498 current
[ 1 ] = current
[ 1 ] ? decodeURIComponent( current
[ 1 ] ) : true;
499 urlParams
[ current
[ 0 ] ] = current
[ 1 ];
503 QUnit
.urlParams
= urlParams
;
504 config
.filter
= urlParams
.filter
;
506 // Figure out if we're running the tests from a server or not
507 QUnit
.isLocal
= !!(location
.protocol
=== 'file:');
510 // Expose the API as global variables, unless an 'exports'
511 // object exists, in that case we assume we're in CommonJS
512 if ( typeof exports
=== "undefined" || typeof require
=== "undefined" ) {
513 extend(window
, QUnit
);
514 window
.QUnit
= QUnit
;
516 extend(exports
, QUnit
);
517 exports
.QUnit
= QUnit
;
520 // define these after exposing globals to keep them in these QUnit namespace only
524 // Initialize the configuration options
527 stats
: { all
: 0, bad
: 0 },
528 moduleStats
: { all
: 0, bad
: 0 },
539 var tests
= id( "qunit-tests" ),
540 banner
= id( "qunit-banner" ),
541 result
= id( "qunit-testresult" );
544 tests
.innerHTML
= "";
548 banner
.className
= "";
552 result
.parentNode
.removeChild( result
);
556 result
= document
.createElement( "p" );
557 result
.id
= "qunit-testresult";
558 result
.className
= "result";
559 tests
.parentNode
.insertBefore( result
, tests
);
560 result
.innerHTML
= 'Running...<br/> ';
565 * Resets the test setup. Useful for tests that modify the DOM.
567 * If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
570 if ( window
.jQuery
) {
571 jQuery( "#qunit-fixture" ).html( config
.fixture
);
573 var main
= id( 'qunit-fixture' );
575 main
.innerHTML
= config
.fixture
;
581 * Trigger an event on an element.
583 * @example triggerEvent( document.body, "click" );
585 * @param DOMElement elem
588 triggerEvent: function( elem
, type
, event
) {
589 if ( document
.createEvent
) {
590 event
= document
.createEvent("MouseEvents");
591 event
.initMouseEvent(type
, true, true, elem
.ownerDocument
.defaultView
,
592 0, 0, 0, 0, 0, false, false, false, false, 0, null);
593 elem
.dispatchEvent( event
);
595 } else if ( elem
.fireEvent
) {
596 elem
.fireEvent("on"+type
);
600 // Safe object type checking
601 is: function( type
, obj
) {
602 return QUnit
.objectType( obj
) == type
;
605 objectType: function( obj
) {
606 if (typeof obj
=== "undefined") {
609 // consider: typeof null === object
615 var type
= toString
.call( obj
).match(/^\[object\s(.*)\]$/)[1] || '';
630 return type
.toLowerCase();
632 if (typeof obj
=== "object") {
638 push: function(result
, actual
, expected
, message
) {
646 message
= escapeInnerText(message
) || (result
? "okay" : "failed");
647 message
= '<span class="test-message">' + message
+ "</span>";
648 expected
= escapeInnerText(QUnit
.jsDump
.parse(expected
));
649 actual
= escapeInnerText(QUnit
.jsDump
.parse(actual
));
650 var output
= message
+ '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected
+ '</pre></td></tr>';
651 if (actual
!= expected
) {
652 output
+= '<tr class="test-actual"><th>Result: </th><td><pre>' + actual
+ '</pre></td></tr>';
653 output
+= '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit
.diff(expected
, actual
) +'</pre></td></tr>';
656 var source
= sourceFromStacktrace();
658 details
.source
= source
;
659 output
+= '<tr class="test-source"><th>Source: </th><td><pre>' + escapeInnerText(source
) + '</pre></td></tr>';
662 output
+= "</table>";
664 runLoggingCallbacks( 'log', QUnit
, details
);
666 config
.current
.assertions
.push({
672 url: function( params
) {
673 params
= extend( extend( {}, QUnit
.urlParams
), params
);
674 var querystring
= "?",
676 for ( key
in params
) {
677 if ( !hasOwn
.call( params
, key
) ) {
680 querystring
+= encodeURIComponent( key
) + "=" +
681 encodeURIComponent( params
[ key
] ) + "&";
683 return window
.location
.pathname
+ querystring
.slice( 0, -1 );
691 //QUnit.constructor is set to the empty F() above so that we can add to it's prototype later
692 //Doing this allows us to tell if the following methods have been overwritten on the actual
693 //QUnit object, which is a deprecated way of using the callbacks.
694 extend(QUnit
.constructor.prototype, {
695 // Logging callbacks; all receive a single argument with the listed properties
696 // run test/logs.html for any related changes
697 begin
: registerLoggingCallback('begin'),
698 // done: { failed, passed, total, runtime }
699 done
: registerLoggingCallback('done'),
700 // log: { result, actual, expected, message }
701 log
: registerLoggingCallback('log'),
702 // testStart: { name }
703 testStart
: registerLoggingCallback('testStart'),
704 // testDone: { name, failed, passed, total }
705 testDone
: registerLoggingCallback('testDone'),
706 // moduleStart: { name }
707 moduleStart
: registerLoggingCallback('moduleStart'),
708 // moduleDone: { name, failed, passed, total }
709 moduleDone
: registerLoggingCallback('moduleDone')
712 if ( typeof document
=== "undefined" || document
.readyState
=== "complete" ) {
713 config
.autorun
= true;
716 QUnit
.load = function() {
717 runLoggingCallbacks( 'begin', QUnit
, {} );
719 // Initialize the config, saving the execution queue
720 var oldconfig
= extend({}, config
);
722 extend(config
, oldconfig
);
724 config
.blocking
= false;
726 var urlConfigHtml
= '', len
= config
.urlConfig
.length
;
727 for ( var i
= 0, val
; i
< len
, val
= config
.urlConfig
[i
]; i
++ ) {
728 config
[val
] = QUnit
.urlParams
[val
];
729 urlConfigHtml
+= '<label><input name="' + val
+ '" type="checkbox"' + ( config
[val
] ? ' checked="checked"' : '' ) + '>' + val
+ '</label>';
732 var userAgent
= id("qunit-userAgent");
734 userAgent
.innerHTML
= navigator
.userAgent
;
736 var banner
= id("qunit-header");
738 banner
.innerHTML
= '<a href="' + QUnit
.url({ filter
: undefined }) + '"> ' + banner
.innerHTML
+ '</a> ' + urlConfigHtml
;
739 addEvent( banner
, "change", function( event
) {
741 params
[ event
.target
.name
] = event
.target
.checked
? true : undefined;
742 window
.location
= QUnit
.url( params
);
746 var toolbar
= id("qunit-testrunner-toolbar");
748 var filter
= document
.createElement("input");
749 filter
.type
= "checkbox";
750 filter
.id
= "qunit-filter-pass";
751 addEvent( filter
, "click", function() {
752 var ol
= document
.getElementById("qunit-tests");
753 if ( filter
.checked
) {
754 ol
.className
= ol
.className
+ " hidepass";
756 var tmp
= " " + ol
.className
.replace( /[\n\t\r]/g, " " ) + " ";
757 ol
.className
= tmp
.replace(/ hidepass
/, " ");
759 if ( defined
.sessionStorage
) {
760 if (filter
.checked
) {
761 sessionStorage
.setItem("qunit-filter-passed-tests", "true");
763 sessionStorage
.removeItem("qunit-filter-passed-tests");
767 if ( config
.hidepassed
|| defined
.sessionStorage
&& sessionStorage
.getItem("qunit-filter-passed-tests") ) {
768 filter
.checked
= true;
769 var ol
= document
.getElementById("qunit-tests");
770 ol
.className
= ol
.className
+ " hidepass";
772 toolbar
.appendChild( filter
);
774 var label
= document
.createElement("label");
775 label
.setAttribute("for", "qunit-filter-pass");
776 label
.innerHTML
= "Hide passed tests";
777 toolbar
.appendChild( label
);
780 var main
= id('qunit-fixture');
782 config
.fixture
= main
.innerHTML
;
785 if (config
.autostart
) {
790 addEvent(window
, "load", QUnit
.load
);
792 // addEvent(window, "error") gives us a useless event object
793 window
.onerror = function( message
, file
, line
) {
794 if ( QUnit
.config
.current
) {
795 ok( false, message
+ ", " + file
+ ":" + line
);
797 test( "global failure", function() {
798 ok( false, message
+ ", " + file
+ ":" + line
);
804 config
.autorun
= true;
806 // Log the last module results
807 if ( config
.currentModule
) {
808 runLoggingCallbacks( 'moduleDone', QUnit
, {
809 name
: config
.currentModule
,
810 failed
: config
.moduleStats
.bad
,
811 passed
: config
.moduleStats
.all
- config
.moduleStats
.bad
,
812 total
: config
.moduleStats
.all
816 var banner
= id("qunit-banner"),
817 tests
= id("qunit-tests"),
818 runtime
= +new Date
- config
.started
,
819 passed
= config
.stats
.all
- config
.stats
.bad
,
821 'Tests completed in ',
823 ' milliseconds.<br/>',
824 '<span class="passed">',
826 '</span> tests of <span class="total">',
828 '</span> passed, <span class="failed">',
834 banner
.className
= (config
.stats
.bad
? "qunit-fail" : "qunit-pass");
838 id( "qunit-testresult" ).innerHTML
= html
;
841 if ( config
.altertitle
&& typeof document
!== "undefined" && document
.title
) {
842 // show ✖ for good, ✔ for bad suite result in title
843 // use escape sequences in case file gets loaded with non-utf-8-charset
845 (config
.stats
.bad
? "\u2716" : "\u2714"),
846 document
.title
.replace(/^[\u2714\u2716] /i, "")
850 runLoggingCallbacks( 'done', QUnit
, {
851 failed
: config
.stats
.bad
,
853 total
: config
.stats
.all
,
858 function validTest( name
) {
859 var filter
= config
.filter
,
866 var not
= filter
.charAt( 0 ) === "!";
868 filter
= filter
.slice( 1 );
871 if ( name
.indexOf( filter
) !== -1 ) {
882 // so far supports only Firefox, Chrome and Opera (buggy)
883 // could be extended in the future to use something like https://github.com/csnover/TraceKit
884 function sourceFromStacktrace() {
890 return e
.stacktrace
.split("\n")[6];
891 } else if (e
.stack
) {
893 return e
.stack
.split("\n")[4];
894 } else if (e
.sourceURL
) {
896 // TODO sourceURL points at the 'throw new Error' line above, useless
897 //return e.sourceURL + ":" + e.line;
902 function escapeInnerText(s
) {
907 return s
.replace(/[\&<>]/g, function(s
) {
909 case "&": return "&";
910 case "<": return "<";
911 case ">": return ">";
917 function synchronize( callback
, last
) {
918 config
.queue
.push( callback
);
920 if ( config
.autorun
&& !config
.blocking
) {
925 function process( last
) {
926 var start
= new Date().getTime();
927 config
.depth
= config
.depth
? config
.depth
+ 1 : 1;
929 while ( config
.queue
.length
&& !config
.blocking
) {
930 if ( !defined
.setTimeout
|| config
.updateRate
<= 0 || ( ( new Date().getTime() - start
) < config
.updateRate
) ) {
931 config
.queue
.shift()();
933 window
.setTimeout( function(){
940 if ( last
&& !config
.blocking
&& !config
.queue
.length
&& config
.depth
=== 0 ) {
945 function saveGlobal() {
946 config
.pollution
= [];
948 if ( config
.noglobals
) {
949 for ( var key
in window
) {
950 if ( !hasOwn
.call( window
, key
) ) {
953 config
.pollution
.push( key
);
958 function checkPollution( name
) {
959 var old
= config
.pollution
;
962 var newGlobals
= diff( config
.pollution
, old
);
963 if ( newGlobals
.length
> 0 ) {
964 ok( false, "Introduced global variable(s): " + newGlobals
.join(", ") );
967 var deletedGlobals
= diff( old
, config
.pollution
);
968 if ( deletedGlobals
.length
> 0 ) {
969 ok( false, "Deleted global variable(s): " + deletedGlobals
.join(", ") );
973 // returns a new Array with the elements that are in a but not in b
974 function diff( a
, b
) {
975 var result
= a
.slice();
976 for ( var i
= 0; i
< result
.length
; i
++ ) {
977 for ( var j
= 0; j
< b
.length
; j
++ ) {
978 if ( result
[i
] === b
[j
] ) {
988 function fail(message
, exception
, callback
) {
989 if ( typeof console
!== "undefined" && console
.error
&& console
.warn
) {
990 console
.error(message
);
991 console
.error(exception
);
992 console
.warn(callback
.toString());
994 } else if ( window
.opera
&& opera
.postError
) {
995 opera
.postError(message
, exception
, callback
.toString
);
999 function extend(a
, b
) {
1000 for ( var prop
in b
) {
1001 if ( b
[prop
] === undefined ) {
1004 // Avoid "Member not found" error in IE8 caused by setting window.constructor
1005 } else if ( prop
!== "constructor" || a
!== window
) {
1013 function addEvent(elem
, type
, fn
) {
1014 if ( elem
.addEventListener
) {
1015 elem
.addEventListener( type
, fn
, false );
1016 } else if ( elem
.attachEvent
) {
1017 elem
.attachEvent( "on" + type
, fn
);
1024 return !!(typeof document
!== "undefined" && document
&& document
.getElementById
) &&
1025 document
.getElementById( name
);
1028 function registerLoggingCallback(key
){
1029 return function(callback
){
1030 config
[key
].push( callback
);
1034 // Supports deprecated method of completely overwriting logging callbacks
1035 function runLoggingCallbacks(key
, scope
, args
) {
1038 if ( QUnit
.hasOwnProperty(key
) ) {
1039 QUnit
[key
].call(scope
, args
);
1041 callbacks
= config
[key
];
1042 for( var i
= 0; i
< callbacks
.length
; i
++ ) {
1043 callbacks
[i
].call( scope
, args
);
1048 // Test for equality any JavaScript type.
1049 // Author: Philippe Rathé <prathe@gmail.com>
1050 QUnit
.equiv = function () {
1052 var innerEquiv
; // the real equiv function
1053 var callers
= []; // stack to decide between skip/abort functions
1054 var parents
= []; // stack to avoiding loops from circular referencing
1056 // Call the o related callback with the given arguments.
1057 function bindCallbacks(o
, callbacks
, args
) {
1058 var prop
= QUnit
.objectType(o
);
1060 if (QUnit
.objectType(callbacks
[prop
]) === "function") {
1061 return callbacks
[prop
].apply(callbacks
, args
);
1063 return callbacks
[prop
]; // or undefined
1068 var callbacks = function () {
1070 // for string, boolean, number and null
1071 function useStrictEquality(b
, a
) {
1072 if (b
instanceof a
.constructor || a
instanceof b
.constructor) {
1073 // to catch short annotaion VS 'new' annotation of a
1076 // var j = new Number(1);
1084 "string" : useStrictEquality
,
1085 "boolean" : useStrictEquality
,
1086 "number" : useStrictEquality
,
1087 "null" : useStrictEquality
,
1088 "undefined" : useStrictEquality
,
1090 "nan" : function(b
) {
1094 "date" : function(b
, a
) {
1095 return QUnit
.objectType(b
) === "date"
1096 && a
.valueOf() === b
.valueOf();
1099 "regexp" : function(b
, a
) {
1100 return QUnit
.objectType(b
) === "regexp"
1101 && a
.source
=== b
.source
&& // the regex itself
1102 a
.global
=== b
.global
&& // and its modifers
1104 a
.ignoreCase
=== b
.ignoreCase
1105 && a
.multiline
=== b
.multiline
;
1108 // - skip when the property is a method of an instance (OOP)
1109 // - abort otherwise,
1110 // initial === would have catch identical references anyway
1111 "function" : function() {
1112 var caller
= callers
[callers
.length
- 1];
1113 return caller
!== Object
&& typeof caller
!== "undefined";
1116 "array" : function(b
, a
) {
1120 // b could be an object literal here
1121 if (!(QUnit
.objectType(b
) === "array")) {
1126 if (len
!== b
.length
) { // safe and faster
1130 // track reference to avoid circular references
1132 for (i
= 0; i
< len
; i
++) {
1134 for (j
= 0; j
< parents
.length
; j
++) {
1135 if (parents
[j
] === a
[i
]) {
1136 loop
= true;// dont rewalk array
1139 if (!loop
&& !innerEquiv(a
[i
], b
[i
])) {
1148 "object" : function(b
, a
) {
1150 var eq
= true; // unless we can proove it
1151 var aProperties
= [], bProperties
= []; // collection of
1154 // comparing constructors is more strict than using
1156 if (a
.constructor !== b
.constructor) {
1160 // stack constructor before traversing properties
1161 callers
.push(a
.constructor);
1162 // track reference to avoid circular references
1165 for (i
in a
) { // be strict: don't ensures hasOwnProperty
1168 for (j
= 0; j
< parents
.length
; j
++) {
1169 if (parents
[j
] === a
[i
])
1170 loop
= true; // don't go down the same path
1173 aProperties
.push(i
); // collect a's properties
1175 if (!loop
&& !innerEquiv(a
[i
], b
[i
])) {
1181 callers
.pop(); // unstack, we are done
1185 bProperties
.push(i
); // collect b's properties
1188 // Ensures identical properties name
1190 && innerEquiv(aProperties
.sort(), bProperties
1196 innerEquiv = function() { // can take multiple arguments
1197 var args
= Array
.prototype.slice
.apply(arguments
);
1198 if (args
.length
< 2) {
1199 return true; // end transition
1202 return (function(a
, b
) {
1204 return true; // catch the most you can
1205 } else if (a
=== null || b
=== null || typeof a
=== "undefined"
1206 || typeof b
=== "undefined"
1207 || QUnit
.objectType(a
) !== QUnit
.objectType(b
)) {
1208 return false; // don't lose time with error prone cases
1210 return bindCallbacks(a
, callbacks
, [ b
, a
]);
1213 // apply transition with (1..n) arguments
1214 })(args
[0], args
[1])
1215 && arguments
.callee
.apply(this, args
.splice(1,
1224 * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
1225 * http://flesler.blogspot.com Licensed under BSD
1226 * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
1228 * @projectDescription Advanced and extensible data dumping for Javascript.
1230 * @author Ariel Flesler
1231 * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
1233 QUnit
.jsDump
= (function() {
1234 function quote( str
) {
1235 return '"' + str
.toString().replace(/"/g, '\\"') + '"';
1237 function literal( o ) {
1240 function join( pre, arr, post ) {
1241 var s = jsDump.separator(),
1242 base = jsDump.indent(),
1243 inner = jsDump.indent(1);
1245 arr = arr.join( ',' + s + inner );
1248 return [ pre, inner + arr, base + post ].join(s);
1250 function array( arr, stack ) {
1251 var i = arr.length, ret = Array(i);
1254 ret[i] = this.parse( arr[i] , undefined , stack);
1256 return join( '[', ret, ']' );
1259 var reName = /^function (\w+)/;
1262 parse:function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance
1263 stack = stack || [ ];
1264 var parser = this.parsers[ type || this.typeOf(obj) ];
1265 type = typeof parser;
1266 var inStack = inArray(obj, stack);
1267 if (inStack != -1) {
1268 return 'recursion('+(inStack - stack.length)+')';
1271 if (type == 'function') {
1273 var res = parser.call( this, obj, stack );
1278 return (type == 'string') ? parser : this.parsers.error;
1280 typeOf:function( obj ) {
1282 if ( obj === null ) {
1284 } else if (typeof obj === "undefined") {
1286 } else if (QUnit.is("RegExp
", obj)) {
1288 } else if (QUnit.is("Date
", obj)) {
1290 } else if (QUnit.is("Function
", obj)) {
1292 } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") {
1294 } else if (obj.nodeType === 9) {
1296 } else if (obj.nodeType) {
1300 toString.call( obj ) === "[object Array
]" ||
1302 ( typeof obj.length === "number
" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
1310 separator:function() {
1311 return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? ' ' : ' ';
1313 indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
1314 if ( !this.multiline )
1316 var chr = this.indentChar;
1318 chr = chr.replace(/\t/g,' ').replace(/ /g,' ');
1319 return Array( this._depth_ + (extra||0) ).join(chr);
1322 this._depth_ += a || 1;
1324 down:function( a ) {
1325 this._depth_ -= a || 1;
1327 setParser:function( name, parser ) {
1328 this.parsers[name] = parser;
1330 // The next 3 are exposed so you can use them
1336 // This is the list of parsers, to modify them, use jsDump.setParser
1339 document: '[Document]',
1340 error:'[ERROR]', //when no parser is found, shouldn't happen
1341 unknown: '[Unknown]',
1343 'undefined':'undefined',
1344 'function':function( fn ) {
1345 var ret = 'function',
1346 name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
1351 ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join('');
1352 return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' );
1357 object:function( map, stack ) {
1360 for ( var key in map ) {
1362 ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(val, undefined, stack));
1364 QUnit.jsDump.down();
1365 return join( '{', ret, '}' );
1367 node:function( node ) {
1368 var open = QUnit.jsDump.HTML ? '<' : '<',
1369 close = QUnit.jsDump.HTML ? '>' : '>';
1371 var tag = node.nodeName.toLowerCase(),
1374 for ( var a in QUnit.jsDump.DOMAttrs ) {
1375 var val = node[QUnit.jsDump.DOMAttrs[a]];
1377 ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' );
1379 return ret + close + open + '/' + tag + close;
1381 functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
1383 if ( !l ) return '';
1385 var args = Array(l);
1387 args[l] = String.fromCharCode(97+l);//97 is 'a'
1388 return ' ' + args.join(', ') + ' ';
1390 key:quote, //object calls it internally, the key part of an item in a map
1391 functionCode:'[code]', //function calls it internally, it's the content of the function
1392 attribute:quote, //node calls it internally, it's an html attribute value
1395 regexp:literal, //regex
1399 DOMAttrs:{//attributes to dump from nodes, name=>realName
1404 HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
1405 indentChar:' ',//indentation unit
1406 multiline:true //if true, items in a collection, are separated by a \n, else just a space.
1413 function getText( elems ) {
1416 for ( var i = 0; elems[i]; i++ ) {
1419 // Get the text from text nodes and CDATA nodes
1420 if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
1421 ret += elem.nodeValue;
1423 // Traverse everything else, except comment nodes
1424 } else if ( elem.nodeType !== 8 ) {
1425 ret += getText( elem.childNodes );
1433 function inArray( elem, array ) {
1434 if ( array.indexOf ) {
1435 return array.indexOf( elem );
1438 for ( var i = 0, length = array.length; i < length; i++ ) {
1439 if ( array[ i ] === elem ) {
1448 * Javascript Diff Algorithm
1449 * By John Resig (http://ejohn.org/)
1450 * Modified by Chu Alan "sprite
"
1452 * Released under the MIT license.
1455 * http://ejohn.org/projects/javascript-diff-algorithm/
1457 * Usage: QUnit.diff(expected, actual)
1459 * 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
"
1461 QUnit.diff = (function() {
1462 function diff(o, n) {
1466 for (var i = 0; i < n.length; i++) {
1467 if (ns[n[i]] == null)
1472 ns[n[i]].rows.push(i);
1475 for (var i = 0; i < o.length; i++) {
1476 if (os[o[i]] == null)
1481 os[o[i]].rows.push(i);
1485 if ( !hasOwn.call( ns, i ) ) {
1488 if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
1489 n[ns[i].rows[0]] = {
1490 text: n[ns[i].rows[0]],
1493 o[os[i].rows[0]] = {
1494 text: o[os[i].rows[0]],
1500 for (var i = 0; i < n.length - 1; i++) {
1501 if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
1502 n[i + 1] == o[n[i].row + 1]) {
1508 text: o[n[i].row + 1],
1514 for (var i = n.length - 1; i > 0; i--) {
1515 if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
1516 n[i - 1] == o[n[i].row - 1]) {
1522 text: o[n[i].row - 1],
1534 return function(o, n) {
1535 o = o.replace(/\s+$/, '');
1536 n = n.replace(/\s+$/, '');
1537 var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));
1541 var oSpace = o.match(/\s+/g);
1542 if (oSpace == null) {
1548 var nSpace = n.match(/\s+/g);
1549 if (nSpace == null) {
1556 if (out.n.length == 0) {
1557 for (var i = 0; i < out.o.length; i++) {
1558 str += '<del>' + out.o[i] + oSpace[i] + "</del
>";
1562 if (out.n[0].text == null) {
1563 for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
1564 str += '<del>' + out.o[n] + oSpace[n] + "</del
>";
1568 for (var i = 0; i < out.n.length; i++) {
1569 if (out.n[i].text == null) {
1570 str += '<ins>' + out.n[i] + nSpace[i] + "</ins
>";
1575 for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
1576 pre += '<del>' + out.o[n] + oSpace[n] + "</del
>";
1578 str += " " + out.n[i].text + nSpace[i] + pre;