2 * QUnit - 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
;
26 var Test = function(name
, testName
, expected
, testEnvironmentArg
, async
, callback
) {
28 this.testName
= testName
;
29 this.expected
= expected
;
30 this.testEnvironmentArg
= testEnvironmentArg
;
32 this.callback
= callback
;
37 var tests
= id("qunit-tests");
39 var b
= document
.createElement("strong");
40 b
.innerHTML
= "Running " + this.name
;
41 var li
= document
.createElement("li");
43 li
.className
= "running";
44 li
.id
= this.id
= "test-output" + testId
++;
45 tests
.appendChild( li
);
49 if (this.module
!= config
.previousModule
) {
50 if ( config
.previousModule
) {
52 name
: config
.previousModule
,
53 failed
: config
.moduleStats
.bad
,
54 passed
: config
.moduleStats
.all
- config
.moduleStats
.bad
,
55 total
: config
.moduleStats
.all
58 config
.previousModule
= this.module
;
59 config
.moduleStats
= { all
: 0, bad
: 0 };
65 config
.current
= this;
66 this.testEnvironment
= extend({
68 teardown: function() {}
69 }, this.moduleTestEnvironment
);
70 if (this.testEnvironmentArg
) {
71 extend(this.testEnvironment
, this.testEnvironmentArg
);
78 // allow utility functions to access the current test environment
80 QUnit
.current_testEnvironment
= this.testEnvironment
;
83 if ( !config
.pollution
) {
87 this.testEnvironment
.setup
.call(this.testEnvironment
);
89 QUnit
.ok( false, "Setup failed on " + this.testName
+ ": " + e
.message
);
97 if ( config
.notrycatch
) {
98 this.callback
.call(this.testEnvironment
);
102 this.callback
.call(this.testEnvironment
);
104 fail("Test " + this.testName
+ " died, exception and test follows", e
, this.callback
);
105 QUnit
.ok( false, "Died on test #" + (this.assertions
.length
+ 1) + ": " + e
.message
+ " - " + QUnit
.jsDump
.parse(e
) );
106 // else next test will carry the responsibility
109 // Restart the tests if they're blocking
110 if ( config
.blocking
) {
115 teardown: function() {
118 this.testEnvironment
.teardown
.call(this.testEnvironment
);
120 QUnit
.ok( false, "Teardown failed on " + this.testName
+ ": " + e
.message
);
124 if ( this.expected
&& this.expected
!= this.assertions
.length
) {
125 QUnit
.ok( false, "Expected " + this.expected
+ " assertions, but " + this.assertions
.length
+ " were run" );
128 var good
= 0, bad
= 0,
129 tests
= id("qunit-tests");
131 config
.stats
.all
+= this.assertions
.length
;
132 config
.moduleStats
.all
+= this.assertions
.length
;
135 var ol
= document
.createElement("ol");
137 for ( var i
= 0; i
< this.assertions
.length
; i
++ ) {
138 var assertion
= this.assertions
[i
];
140 var li
= document
.createElement("li");
141 li
.className
= assertion
.result
? "pass" : "fail";
142 li
.innerHTML
= assertion
.message
|| (assertion
.result
? "okay" : "failed");
143 ol
.appendChild( li
);
145 if ( assertion
.result
) {
150 config
.moduleStats
.bad
++;
154 // store result when possible
155 if ( QUnit
.config
.reorder
&& defined
.sessionStorage
) {
157 sessionStorage
.setItem("qunit-" + this.module
+ "-" + this.testName
, bad
);
159 sessionStorage
.removeItem("qunit-" + this.module
+ "-" + this.testName
);
164 ol
.style
.display
= "none";
167 var b
= document
.createElement("strong");
168 b
.innerHTML
= this.name
+ " <b class='counts'>(<b class='failed'>" + bad
+ "</b>, <b class='passed'>" + good
+ "</b>, " + this.assertions
.length
+ ")</b>";
170 var a
= document
.createElement("a");
171 a
.innerHTML
= "Rerun";
172 a
.href
= QUnit
.url({ filter
: getText([b
]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
174 addEvent(b
, "click", function() {
175 var next
= b
.nextSibling
.nextSibling
,
176 display
= next
.style
.display
;
177 next
.style
.display
= display
=== "none" ? "block" : "none";
180 addEvent(b
, "dblclick", function(e
) {
181 var target
= e
&& e
.target
? e
.target
: window
.event
.srcElement
;
182 if ( target
.nodeName
.toLowerCase() == "span" || target
.nodeName
.toLowerCase() == "b" ) {
183 target
= target
.parentNode
;
185 if ( window
.location
&& target
.nodeName
.toLowerCase() === "strong" ) {
186 window
.location
= QUnit
.url({ filter
: getText([target
]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
190 var li
= id(this.id
);
191 li
.className
= bad
? "fail" : "pass";
192 li
.removeChild( li
.firstChild
);
195 li
.appendChild( ol
);
198 for ( var i
= 0; i
< this.assertions
.length
; i
++ ) {
199 if ( !this.assertions
[i
].result
) {
202 config
.moduleStats
.bad
++;
210 fail("reset() failed, following Test " + this.testName
+ ", exception and reset fn follows", e
, QUnit
.reset
);
216 passed
: this.assertions
.length
- bad
,
217 total
: this.assertions
.length
223 synchronize(function() {
227 // each of these can by async
228 synchronize(function() {
231 synchronize(function() {
234 synchronize(function() {
237 synchronize(function() {
241 // defer when previous test run passed, if storage is available
242 var bad
= QUnit
.config
.reorder
&& defined
.sessionStorage
&& +sessionStorage
.getItem("qunit-" + this.module
+ "-" + this.testName
);
254 // call on start of module test to prepend name to all tests
255 module: function(name
, testEnvironment
) {
256 config
.currentModule
= name
;
257 config
.currentModuleTestEnviroment
= testEnvironment
;
260 asyncTest: function(testName
, expected
, callback
) {
261 if ( arguments
.length
=== 2 ) {
266 QUnit
.test(testName
, expected
, callback
, true);
269 test: function(testName
, expected
, callback
, async
) {
270 var name
= '<span class="test-name">' + testName
+ '</span>', testEnvironmentArg
;
272 if ( arguments
.length
=== 2 ) {
276 // is 2nd argument a testEnvironment?
277 if ( expected
&& typeof expected
=== 'object') {
278 testEnvironmentArg
= expected
;
282 if ( config
.currentModule
) {
283 name
= '<span class="module-name">' + config
.currentModule
+ "</span>: " + name
;
286 if ( !validTest(config
.currentModule
+ ": " + testName
) ) {
290 var test
= new Test(name
, testName
, expected
, testEnvironmentArg
, async
, callback
);
291 test
.module
= config
.currentModule
;
292 test
.moduleTestEnvironment
= config
.currentModuleTestEnviroment
;
297 * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
299 expect: function(asserts
) {
300 config
.current
.expected
= asserts
;
305 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
307 ok: function(a
, msg
) {
313 msg
= escapeHtml(msg
);
315 config
.current
.assertions
.push({
322 * Checks that the first two arguments are equal, with an optional message.
323 * Prints out both actual and expected values.
325 * Prefered to ok( actual == expected, message )
327 * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
329 * @param Object actual
330 * @param Object expected
331 * @param String message (optional)
333 equal: function(actual
, expected
, message
) {
334 QUnit
.push(expected
== actual
, actual
, expected
, message
);
337 notEqual: function(actual
, expected
, message
) {
338 QUnit
.push(expected
!= actual
, actual
, expected
, message
);
341 deepEqual: function(actual
, expected
, message
) {
342 QUnit
.push(QUnit
.equiv(actual
, expected
), actual
, expected
, message
);
345 notDeepEqual: function(actual
, expected
, message
) {
346 QUnit
.push(!QUnit
.equiv(actual
, expected
), actual
, expected
, message
);
349 strictEqual: function(actual
, expected
, message
) {
350 QUnit
.push(expected
=== actual
, actual
, expected
, message
);
353 notStrictEqual: function(actual
, expected
, message
) {
354 QUnit
.push(expected
!== actual
, actual
, expected
, message
);
357 raises: function(block
, expected
, message
) {
358 var actual
, ok
= false;
360 if (typeof expected
=== 'string') {
372 // we don't want to validate thrown error
375 // expected is a regexp
376 } else if (QUnit
.objectType(expected
) === "regexp") {
377 ok
= expected
.test(actual
);
378 // expected is a constructor
379 } else if (actual
instanceof expected
) {
381 // expected is a validation function which returns true is validation passed
382 } else if (expected
.call({}, actual
) === true) {
387 QUnit
.ok(ok
, message
);
392 if (config
.semaphore
> 0) {
393 // don't start until equal number of stop-calls
396 if (config
.semaphore
< 0) {
397 // ignore if start is called more often then stop
398 config
.semaphore
= 0;
400 // A slight delay, to avoid any current callbacks
401 if ( defined
.setTimeout
) {
402 window
.setTimeout(function() {
403 if ( config
.timeout
) {
404 clearTimeout(config
.timeout
);
407 config
.blocking
= false;
411 config
.blocking
= false;
416 stop: function(timeout
) {
418 config
.blocking
= true;
420 if ( timeout
&& defined
.setTimeout
) {
421 clearTimeout(config
.timeout
);
422 config
.timeout
= window
.setTimeout(function() {
423 QUnit
.ok( false, "Test timed out" );
430 // Backwards compatibility, deprecated
431 QUnit
.equals
= QUnit
.equal
;
432 QUnit
.same
= QUnit
.deepEqual
;
434 // Maintain internal state
436 // The queue of tests to run
439 // block until document ready
442 // by default, run previously failed tests first
443 // very useful in combination with "Hide passed tests" checked
452 var location
= window
.location
|| { search
: "", protocol
: "file:" },
453 params
= location
.search
.slice( 1 ).split( "&" ),
454 length
= params
.length
,
459 for ( var i
= 0; i
< length
; i
++ ) {
460 current
= params
[ i
].split( "=" );
461 current
[ 0 ] = decodeURIComponent( current
[ 0 ] );
462 // allow just a key to turn on a flag, e.g., test.html?noglobals
463 current
[ 1 ] = current
[ 1 ] ? decodeURIComponent( current
[ 1 ] ) : true;
464 urlParams
[ current
[ 0 ] ] = current
[ 1 ];
465 if ( current
[ 0 ] in config
) {
466 config
[ current
[ 0 ] ] = current
[ 1 ];
471 QUnit
.urlParams
= urlParams
;
472 config
.filter
= urlParams
.filter
;
474 // Figure out if we're running the tests from a server or not
475 QUnit
.isLocal
= !!(location
.protocol
=== 'file:');
478 // Expose the API as global variables, unless an 'exports'
479 // object exists, in that case we assume we're in CommonJS
480 if ( typeof exports
=== "undefined" || typeof require
=== "undefined" ) {
481 extend(window
, QUnit
);
482 window
.QUnit
= QUnit
;
484 extend(exports
, QUnit
);
485 exports
.QUnit
= QUnit
;
488 // define these after exposing globals to keep them in these QUnit namespace only
492 // Initialize the configuration options
495 stats
: { all
: 0, bad
: 0 },
496 moduleStats
: { all
: 0, bad
: 0 },
507 var tests
= id( "qunit-tests" ),
508 banner
= id( "qunit-banner" ),
509 result
= id( "qunit-testresult" );
512 tests
.innerHTML
= "";
516 banner
.className
= "";
520 result
.parentNode
.removeChild( result
);
524 result
= document
.createElement( "p" );
525 result
.id
= "qunit-testresult";
526 result
.className
= "result";
527 tests
.parentNode
.insertBefore( result
, tests
);
528 result
.innerHTML
= 'Running...<br/> ';
533 * Resets the test setup. Useful for tests that modify the DOM.
535 * If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
538 if ( window
.jQuery
) {
539 jQuery( "#qunit-fixture" ).html( config
.fixture
);
541 var main
= id( 'qunit-fixture' );
543 main
.innerHTML
= config
.fixture
;
549 * Trigger an event on an element.
551 * @example triggerEvent( document.body, "click" );
553 * @param DOMElement elem
556 triggerEvent: function( elem
, type
, event
) {
557 if ( document
.createEvent
) {
558 event
= document
.createEvent("MouseEvents");
559 event
.initMouseEvent(type
, true, true, elem
.ownerDocument
.defaultView
,
560 0, 0, 0, 0, 0, false, false, false, false, 0, null);
561 elem
.dispatchEvent( event
);
563 } else if ( elem
.fireEvent
) {
564 elem
.fireEvent("on"+type
);
568 // Safe object type checking
569 is: function( type
, obj
) {
570 return QUnit
.objectType( obj
) == type
;
573 objectType: function( obj
) {
574 if (typeof obj
=== "undefined") {
577 // consider: typeof null === object
583 var type
= Object
.prototype.toString
.call( obj
)
584 .match(/^\[object\s(.*)\]$/)[1] || '';
599 return type
.toLowerCase();
601 if (typeof obj
=== "object") {
607 push: function(result
, actual
, expected
, message
) {
615 message
= escapeHtml(message
) || (result
? "okay" : "failed");
616 message
= '<span class="test-message">' + message
+ "</span>";
617 expected
= escapeHtml(QUnit
.jsDump
.parse(expected
));
618 actual
= escapeHtml(QUnit
.jsDump
.parse(actual
));
619 var output
= message
+ '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected
+ '</pre></td></tr>';
620 if (actual
!= expected
) {
621 output
+= '<tr class="test-actual"><th>Result: </th><td><pre>' + actual
+ '</pre></td></tr>';
622 output
+= '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit
.diff(expected
, actual
) +'</pre></td></tr>';
625 var source
= sourceFromStacktrace();
627 details
.source
= source
;
628 output
+= '<tr class="test-source"><th>Source: </th><td><pre>' + source
+'</pre></td></tr>';
631 output
+= "</table>";
635 config
.current
.assertions
.push({
641 url: function( params
) {
642 params
= extend( extend( {}, QUnit
.urlParams
), params
);
643 var querystring
= "?",
645 for ( key
in params
) {
646 querystring
+= encodeURIComponent( key
) + "=" +
647 encodeURIComponent( params
[ key
] ) + "&";
649 return window
.location
.pathname
+ querystring
.slice( 0, -1 );
652 // Logging callbacks; all receive a single argument with the listed properties
653 // run test/logs.html for any related changes
654 begin: function() {},
655 // done: { failed, passed, total, runtime }
657 // log: { result, actual, expected, message }
659 // testStart: { name }
660 testStart: function() {},
661 // testDone: { name, failed, passed, total }
662 testDone: function() {},
663 // moduleStart: { name }
664 moduleStart: function() {},
665 // moduleDone: { name, failed, passed, total }
666 moduleDone: function() {}
669 if ( typeof document
=== "undefined" || document
.readyState
=== "complete" ) {
670 config
.autorun
= true;
673 addEvent(window
, "load", function() {
676 // Initialize the config, saving the execution queue
677 var oldconfig
= extend({}, config
);
679 extend(config
, oldconfig
);
681 config
.blocking
= false;
683 var userAgent
= id("qunit-userAgent");
685 userAgent
.innerHTML
= navigator
.userAgent
;
687 var banner
= id("qunit-header");
689 banner
.innerHTML
= '<a href="' + QUnit
.url({ filter
: undefined }) + '"> ' + banner
.innerHTML
+ '</a> ' +
690 '<label><input name="noglobals" type="checkbox"' + ( config
.noglobals
? ' checked="checked"' : '' ) + '>noglobals</label>' +
691 '<label><input name="notrycatch" type="checkbox"' + ( config
.notrycatch
? ' checked="checked"' : '' ) + '>notrycatch</label>';
692 addEvent( banner
, "change", function( event
) {
694 params
[ event
.target
.name
] = event
.target
.checked
? true : undefined;
695 window
.location
= QUnit
.url( params
);
699 var toolbar
= id("qunit-testrunner-toolbar");
701 var filter
= document
.createElement("input");
702 filter
.type
= "checkbox";
703 filter
.id
= "qunit-filter-pass";
704 addEvent( filter
, "click", function() {
705 var ol
= document
.getElementById("qunit-tests");
706 if ( filter
.checked
) {
707 ol
.className
= ol
.className
+ " hidepass";
709 var tmp
= " " + ol
.className
.replace( /[\n\t\r]/g, " " ) + " ";
710 ol
.className
= tmp
.replace(/ hidepass
/, " ");
712 if ( defined
.sessionStorage
) {
713 if (filter
.checked
) {
714 sessionStorage
.setItem("qunit-filter-passed-tests", "true");
716 sessionStorage
.removeItem("qunit-filter-passed-tests");
720 if ( defined
.sessionStorage
&& sessionStorage
.getItem("qunit-filter-passed-tests") ) {
721 filter
.checked
= true;
722 var ol
= document
.getElementById("qunit-tests");
723 ol
.className
= ol
.className
+ " hidepass";
725 toolbar
.appendChild( filter
);
727 var label
= document
.createElement("label");
728 label
.setAttribute("for", "qunit-filter-pass");
729 label
.innerHTML
= "Hide passed tests";
730 toolbar
.appendChild( label
);
733 var main
= id('qunit-fixture');
735 config
.fixture
= main
.innerHTML
;
738 if (config
.autostart
) {
744 config
.autorun
= true;
746 // Log the last module results
747 if ( config
.currentModule
) {
749 name
: config
.currentModule
,
750 failed
: config
.moduleStats
.bad
,
751 passed
: config
.moduleStats
.all
- config
.moduleStats
.bad
,
752 total
: config
.moduleStats
.all
756 var banner
= id("qunit-banner"),
757 tests
= id("qunit-tests"),
758 runtime
= +new Date
- config
.started
,
759 passed
= config
.stats
.all
- config
.stats
.bad
,
761 'Tests completed in ',
763 ' milliseconds.<br/>',
764 '<span class="passed">',
766 '</span> tests of <span class="total">',
768 '</span> passed, <span class="failed">',
774 banner
.className
= (config
.stats
.bad
? "qunit-fail" : "qunit-pass");
778 id( "qunit-testresult" ).innerHTML
= html
;
782 failed
: config
.stats
.bad
,
784 total
: config
.stats
.all
,
789 function validTest( name
) {
790 var filter
= config
.filter
,
797 not
= filter
.charAt( 0 ) === "!";
799 filter
= filter
.slice( 1 );
802 if ( name
.indexOf( filter
) !== -1 ) {
813 // so far supports only Firefox, Chrome and Opera (buggy)
814 // could be extended in the future to use something like https://github.com/csnover/TraceKit
815 function sourceFromStacktrace() {
821 return e
.stacktrace
.split("\n")[6];
822 } else if (e
.stack
) {
824 return e
.stack
.split("\n")[4];
829 function escapeHtml(s
) {
834 return s
.replace(/[\&"<>\\]/g, function(s
) {
836 case "&": return "&";
837 case "\\": return "\\\\";
838 case '"': return '\"';
839 case "<": return "<";
840 case ">": return ">";
846 function synchronize( callback
) {
847 config
.queue
.push( callback
);
849 if ( config
.autorun
&& !config
.blocking
) {
855 var start
= (new Date()).getTime();
857 while ( config
.queue
.length
&& !config
.blocking
) {
858 if ( config
.updateRate
<= 0 || (((new Date()).getTime() - start
) < config
.updateRate
) ) {
859 config
.queue
.shift()();
861 window
.setTimeout( process
, 13 );
865 if (!config
.blocking
&& !config
.queue
.length
) {
870 function saveGlobal() {
871 config
.pollution
= [];
873 if ( config
.noglobals
) {
874 for ( var key
in window
) {
875 config
.pollution
.push( key
);
880 function checkPollution( name
) {
881 var old
= config
.pollution
;
884 var newGlobals
= diff( config
.pollution
, old
);
885 if ( newGlobals
.length
> 0 ) {
886 ok( false, "Introduced global variable(s): " + newGlobals
.join(", ") );
889 var deletedGlobals
= diff( old
, config
.pollution
);
890 if ( deletedGlobals
.length
> 0 ) {
891 ok( false, "Deleted global variable(s): " + deletedGlobals
.join(", ") );
895 // returns a new Array with the elements that are in a but not in b
896 function diff( a
, b
) {
897 var result
= a
.slice();
898 for ( var i
= 0; i
< result
.length
; i
++ ) {
899 for ( var j
= 0; j
< b
.length
; j
++ ) {
900 if ( result
[i
] === b
[j
] ) {
910 function fail(message
, exception
, callback
) {
911 if ( typeof console
!== "undefined" && console
.error
&& console
.warn
) {
912 console
.error(message
);
913 console
.error(exception
);
914 console
.warn(callback
.toString());
916 } else if ( window
.opera
&& opera
.postError
) {
917 opera
.postError(message
, exception
, callback
.toString
);
921 function extend(a
, b
) {
922 for ( var prop
in b
) {
923 if ( b
[prop
] === undefined ) {
933 function addEvent(elem
, type
, fn
) {
934 if ( elem
.addEventListener
) {
935 elem
.addEventListener( type
, fn
, false );
936 } else if ( elem
.attachEvent
) {
937 elem
.attachEvent( "on" + type
, fn
);
944 return !!(typeof document
!== "undefined" && document
&& document
.getElementById
) &&
945 document
.getElementById( name
);
948 // Test for equality any JavaScript type.
949 // Discussions and reference: http://philrathe.com/articles/equiv
950 // Test suites: http://philrathe.com/tests/equiv
951 // Author: Philippe Rathé <prathe@gmail.com>
952 QUnit
.equiv = function () {
954 var innerEquiv
; // the real equiv function
955 var callers
= []; // stack to decide between skip/abort functions
956 var parents
= []; // stack to avoiding loops from circular referencing
958 // Call the o related callback with the given arguments.
959 function bindCallbacks(o
, callbacks
, args
) {
960 var prop
= QUnit
.objectType(o
);
962 if (QUnit
.objectType(callbacks
[prop
]) === "function") {
963 return callbacks
[prop
].apply(callbacks
, args
);
965 return callbacks
[prop
]; // or undefined
970 var callbacks = function () {
972 // for string, boolean, number and null
973 function useStrictEquality(b
, a
) {
974 if (b
instanceof a
.constructor || a
instanceof b
.constructor) {
975 // to catch short annotaion VS 'new' annotation of a declaration
977 // var j = new Number(1);
985 "string": useStrictEquality
,
986 "boolean": useStrictEquality
,
987 "number": useStrictEquality
,
988 "null": useStrictEquality
,
989 "undefined": useStrictEquality
,
991 "nan": function (b
) {
995 "date": function (b
, a
) {
996 return QUnit
.objectType(b
) === "date" && a
.valueOf() === b
.valueOf();
999 "regexp": function (b
, a
) {
1000 return QUnit
.objectType(b
) === "regexp" &&
1001 a
.source
=== b
.source
&& // the regex itself
1002 a
.global
=== b
.global
&& // and its modifers (gmi) ...
1003 a
.ignoreCase
=== b
.ignoreCase
&&
1004 a
.multiline
=== b
.multiline
;
1007 // - skip when the property is a method of an instance (OOP)
1008 // - abort otherwise,
1009 // initial === would have catch identical references anyway
1010 "function": function () {
1011 var caller
= callers
[callers
.length
- 1];
1012 return caller
!== Object
&&
1013 typeof caller
!== "undefined";
1016 "array": function (b
, a
) {
1020 // b could be an object literal here
1021 if ( ! (QUnit
.objectType(b
) === "array")) {
1026 if (len
!== b
.length
) { // safe and faster
1030 //track reference to avoid circular references
1032 for (i
= 0; i
< len
; i
++) {
1034 for(j
=0;j
<parents
.length
;j
++){
1035 if(parents
[j
] === a
[i
]){
1036 loop
= true;//dont rewalk array
1039 if (!loop
&& ! innerEquiv(a
[i
], b
[i
])) {
1048 "object": function (b
, a
) {
1050 var eq
= true; // unless we can proove it
1051 var aProperties
= [], bProperties
= []; // collection of strings
1053 // comparing constructors is more strict than using instanceof
1054 if ( a
.constructor !== b
.constructor) {
1058 // stack constructor before traversing properties
1059 callers
.push(a
.constructor);
1060 //track reference to avoid circular references
1063 for (i
in a
) { // be strict: don't ensures hasOwnProperty and go deep
1065 for(j
=0;j
<parents
.length
;j
++){
1066 if(parents
[j
] === a
[i
])
1067 loop
= true; //don't go down the same path twice
1069 aProperties
.push(i
); // collect a's properties
1071 if (!loop
&& ! innerEquiv(a
[i
], b
[i
])) {
1077 callers
.pop(); // unstack, we are done
1081 bProperties
.push(i
); // collect b's properties
1084 // Ensures identical properties name
1085 return eq
&& innerEquiv(aProperties
.sort(), bProperties
.sort());
1090 innerEquiv = function () { // can take multiple arguments
1091 var args
= Array
.prototype.slice
.apply(arguments
);
1092 if (args
.length
< 2) {
1093 return true; // end transition
1096 return (function (a
, b
) {
1098 return true; // catch the most you can
1099 } else if (a
=== null || b
=== null || typeof a
=== "undefined" || typeof b
=== "undefined" || QUnit
.objectType(a
) !== QUnit
.objectType(b
)) {
1100 return false; // don't lose time with error prone cases
1102 return bindCallbacks(a
, callbacks
, [b
, a
]);
1105 // apply transition with (1..n) arguments
1106 })(args
[0], args
[1]) && arguments
.callee
.apply(this, args
.splice(1, args
.length
-1));
1115 * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
1116 * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
1118 * @projectDescription Advanced and extensible data dumping for Javascript.
1120 * @author Ariel Flesler
1121 * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
1123 QUnit
.jsDump
= (function() {
1124 function quote( str
) {
1125 return '"' + str
.toString().replace(/"/g, '\\"') + '"';
1127 function literal( o ) {
1130 function join( pre, arr, post ) {
1131 var s = jsDump.separator(),
1132 base = jsDump.indent(),
1133 inner = jsDump.indent(1);
1135 arr = arr.join( ',' + s + inner );
1138 return [ pre, inner + arr, base + post ].join(s);
1140 function array( arr ) {
1141 var i = arr.length, ret = Array(i);
1144 ret[i] = this.parse( arr[i] );
1146 return join( '[', ret, ']' );
1149 var reName = /^function (\w+)/;
1152 parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance
1153 var parser = this.parsers[ type || this.typeOf(obj) ];
1154 type = typeof parser;
1156 return type == 'function' ? parser.call( this, obj ) :
1157 type == 'string' ? parser :
1160 typeOf:function( obj ) {
1162 if ( obj === null ) {
1164 } else if (typeof obj === "undefined") {
1166 } else if (QUnit.is("RegExp
", obj)) {
1168 } else if (QUnit.is("Date
", obj)) {
1170 } else if (QUnit.is("Function
", obj)) {
1172 } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") {
1174 } else if (obj.nodeType === 9) {
1176 } else if (obj.nodeType) {
1178 } else if (typeof obj === "object
" && typeof obj.length === "number
" && obj.length >= 0) {
1185 separator:function() {
1186 return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? ' ' : ' ';
1188 indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
1189 if ( !this.multiline )
1191 var chr = this.indentChar;
1193 chr = chr.replace(/\t/g,' ').replace(/ /g,' ');
1194 return Array( this._depth_ + (extra||0) ).join(chr);
1197 this._depth_ += a || 1;
1199 down:function( a ) {
1200 this._depth_ -= a || 1;
1202 setParser:function( name, parser ) {
1203 this.parsers[name] = parser;
1205 // The next 3 are exposed so you can use them
1211 // This is the list of parsers, to modify them, use jsDump.setParser
1214 document: '[Document]',
1215 error:'[ERROR]', //when no parser is found, shouldn't happen
1216 unknown: '[Unknown]',
1218 'undefined':'undefined',
1219 'function':function( fn ) {
1220 var ret = 'function',
1221 name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
1226 ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join('');
1227 return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' );
1232 object:function( map ) {
1235 for ( var key in map )
1236 ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) );
1237 QUnit.jsDump.down();
1238 return join( '{', ret, '}' );
1240 node:function( node ) {
1241 var open = QUnit.jsDump.HTML ? '<' : '<',
1242 close = QUnit.jsDump.HTML ? '>' : '>';
1244 var tag = node.nodeName.toLowerCase(),
1247 for ( var a in QUnit.jsDump.DOMAttrs ) {
1248 var val = node[QUnit.jsDump.DOMAttrs[a]];
1250 ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' );
1252 return ret + close + open + '/' + tag + close;
1254 functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
1256 if ( !l ) return '';
1258 var args = Array(l);
1260 args[l] = String.fromCharCode(97+l);//97 is 'a'
1261 return ' ' + args.join(', ') + ' ';
1263 key:quote, //object calls it internally, the key part of an item in a map
1264 functionCode:'[code]', //function calls it internally, it's the content of the function
1265 attribute:quote, //node calls it internally, it's an html attribute value
1268 regexp:literal, //regex
1272 DOMAttrs:{//attributes to dump from nodes, name=>realName
1277 HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
1278 indentChar:' ',//indentation unit
1279 multiline:true //if true, items in a collection, are separated by a \n, else just a space.
1286 function getText( elems ) {
1289 for ( var i = 0; elems[i]; i++ ) {
1292 // Get the text from text nodes and CDATA nodes
1293 if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
1294 ret += elem.nodeValue;
1296 // Traverse everything else, except comment nodes
1297 } else if ( elem.nodeType !== 8 ) {
1298 ret += getText( elem.childNodes );
1306 * Javascript Diff Algorithm
1307 * By John Resig (http://ejohn.org/)
1308 * Modified by Chu Alan "sprite
"
1310 * Released under the MIT license.
1313 * http://ejohn.org/projects/javascript-diff-algorithm/
1315 * Usage: QUnit.diff(expected, actual)
1317 * 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
"
1319 QUnit.diff = (function() {
1320 function diff(o, n){
1321 var ns = new Object();
1322 var os = new Object();
1324 for (var i = 0; i < n.length; i++) {
1325 if (ns[n[i]] == null)
1330 ns[n[i]].rows.push(i);
1333 for (var i = 0; i < o.length; i++) {
1334 if (os[o[i]] == null)
1339 os[o[i]].rows.push(i);
1343 if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
1344 n[ns[i].rows[0]] = {
1345 text: n[ns[i].rows[0]],
1348 o[os[i].rows[0]] = {
1349 text: o[os[i].rows[0]],
1355 for (var i = 0; i < n.length - 1; i++) {
1356 if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
1357 n[i + 1] == o[n[i].row + 1]) {
1363 text: o[n[i].row + 1],
1369 for (var i = n.length - 1; i > 0; i--) {
1370 if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
1371 n[i - 1] == o[n[i].row - 1]) {
1377 text: o[n[i].row - 1],
1389 return function(o, n){
1390 o = o.replace(/\s+$/, '');
1391 n = n.replace(/\s+$/, '');
1392 var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));
1396 var oSpace = o.match(/\s+/g);
1397 if (oSpace == null) {
1403 var nSpace = n.match(/\s+/g);
1404 if (nSpace == null) {
1411 if (out.n.length == 0) {
1412 for (var i = 0; i < out.o.length; i++) {
1413 str += '<del>' + out.o[i] + oSpace[i] + "</del
>";
1417 if (out.n[0].text == null) {
1418 for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
1419 str += '<del>' + out.o[n] + oSpace[n] + "</del
>";
1423 for (var i = 0; i < out.n.length; i++) {
1424 if (out.n[i].text == null) {
1425 str += '<ins>' + out.n[i] + nSpace[i] + "</ins
>";
1430 for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
1431 pre += '<del>' + out.o[n] + oSpace[n] + "</del
>";
1433 str += " " + out.n[i].text + nSpace[i] + pre;