comparison jquery-ui/development-bundle/external/qunit.js @ 0:b2e4605f20b2

beta version
author dwinter
date Thu, 30 Jun 2011 09:07:49 +0200
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:b2e4605f20b2
1 /*
2 * QUnit - A JavaScript Unit Testing Framework
3 *
4 * http://docs.jquery.com/QUnit
5 *
6 * Copyright (c) 2009 John Resig, Jörn Zaefferer
7 * Dual licensed under the MIT (MIT-LICENSE.txt)
8 * and GPL (GPL-LICENSE.txt) licenses.
9 */
10
11 (function(window) {
12
13 var QUnit = {
14
15 // call on start of module test to prepend name to all tests
16 module: function(name, testEnvironment) {
17 config.currentModule = name;
18
19 synchronize(function() {
20 if ( config.currentModule ) {
21 QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
22 }
23
24 config.currentModule = name;
25 config.moduleTestEnvironment = testEnvironment;
26 config.moduleStats = { all: 0, bad: 0 };
27
28 QUnit.moduleStart( name, testEnvironment );
29 });
30 },
31
32 asyncTest: function(testName, expected, callback) {
33 if ( arguments.length === 2 ) {
34 callback = expected;
35 expected = 0;
36 }
37
38 QUnit.test(testName, expected, callback, true);
39 },
40
41 test: function(testName, expected, callback, async) {
42 var name = '<span class="test-name">' + testName + '</span>', testEnvironment, testEnvironmentArg;
43
44 if ( arguments.length === 2 ) {
45 callback = expected;
46 expected = null;
47 }
48 // is 2nd argument a testEnvironment?
49 if ( expected && typeof expected === 'object') {
50 testEnvironmentArg = expected;
51 expected = null;
52 }
53
54 if ( config.currentModule ) {
55 name = '<span class="module-name">' + config.currentModule + "</span>: " + name;
56 }
57
58 if ( !validTest(config.currentModule + ": " + testName) ) {
59 return;
60 }
61
62 synchronize(function() {
63
64 testEnvironment = extend({
65 setup: function() {},
66 teardown: function() {}
67 }, config.moduleTestEnvironment);
68 if (testEnvironmentArg) {
69 extend(testEnvironment,testEnvironmentArg);
70 }
71
72 QUnit.testStart( testName, testEnvironment );
73
74 // allow utility functions to access the current test environment
75 QUnit.current_testEnvironment = testEnvironment;
76
77 config.assertions = [];
78 config.expected = expected;
79
80 var tests = id("qunit-tests");
81 if (tests) {
82 var b = document.createElement("strong");
83 b.innerHTML = "Running " + name;
84 var li = document.createElement("li");
85 li.appendChild( b );
86 li.id = "current-test-output";
87 tests.appendChild( li )
88 }
89
90 try {
91 if ( !config.pollution ) {
92 saveGlobal();
93 }
94
95 testEnvironment.setup.call(testEnvironment);
96 } catch(e) {
97 QUnit.ok( false, "Setup failed on " + name + ": " + e.message );
98 }
99 });
100
101 synchronize(function() {
102 if ( async ) {
103 QUnit.stop();
104 }
105
106 try {
107 callback.call(testEnvironment);
108 } catch(e) {
109 fail("Test " + name + " died, exception and test follows", e, callback);
110 QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message );
111 // else next test will carry the responsibility
112 saveGlobal();
113
114 // Restart the tests if they're blocking
115 if ( config.blocking ) {
116 start();
117 }
118 }
119 });
120
121 synchronize(function() {
122 try {
123 checkPollution();
124 testEnvironment.teardown.call(testEnvironment);
125 } catch(e) {
126 QUnit.ok( false, "Teardown failed on " + name + ": " + e.message );
127 }
128 });
129
130 synchronize(function() {
131 try {
132 QUnit.reset();
133 } catch(e) {
134 fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset);
135 }
136
137 if ( config.expected && config.expected != config.assertions.length ) {
138 QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" );
139 }
140
141 var good = 0, bad = 0,
142 tests = id("qunit-tests");
143
144 config.stats.all += config.assertions.length;
145 config.moduleStats.all += config.assertions.length;
146
147 if ( tests ) {
148 var ol = document.createElement("ol");
149
150 for ( var i = 0; i < config.assertions.length; i++ ) {
151 var assertion = config.assertions[i];
152
153 var li = document.createElement("li");
154 li.className = assertion.result ? "pass" : "fail";
155 li.innerHTML = assertion.message || "(no message)";
156 ol.appendChild( li );
157
158 if ( assertion.result ) {
159 good++;
160 } else {
161 bad++;
162 config.stats.bad++;
163 config.moduleStats.bad++;
164 }
165 }
166 if (bad == 0) {
167 ol.style.display = "none";
168 }
169
170 var b = document.createElement("strong");
171 b.innerHTML = name + " <b style='color:black;'>(<b class='fail'>" + bad + "</b>, <b class='pass'>" + good + "</b>, " + config.assertions.length + ")</b>";
172
173 addEvent(b, "click", function() {
174 var next = b.nextSibling, display = next.style.display;
175 next.style.display = display === "none" ? "block" : "none";
176 });
177
178 addEvent(b, "dblclick", function(e) {
179 var target = e && e.target ? e.target : window.event.srcElement;
180 if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
181 target = target.parentNode;
182 }
183 if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
184 window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, ""));
185 }
186 });
187
188 var li = id("current-test-output");
189 li.id = "";
190 li.className = bad ? "fail" : "pass";
191 li.removeChild( li.firstChild );
192 li.appendChild( b );
193 li.appendChild( ol );
194
195 if ( bad ) {
196 var toolbar = id("qunit-testrunner-toolbar");
197 if ( toolbar ) {
198 toolbar.style.display = "block";
199 id("qunit-filter-pass").disabled = null;
200 id("qunit-filter-missing").disabled = null;
201 }
202 }
203
204 } else {
205 for ( var i = 0; i < config.assertions.length; i++ ) {
206 if ( !config.assertions[i].result ) {
207 bad++;
208 config.stats.bad++;
209 config.moduleStats.bad++;
210 }
211 }
212 }
213
214 QUnit.testDone( testName, bad, config.assertions.length );
215
216 if ( !window.setTimeout && !config.queue.length ) {
217 done();
218 }
219 });
220
221 synchronize( done );
222 },
223
224 /**
225 * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
226 */
227 expect: function(asserts) {
228 config.expected = asserts;
229 },
230
231 /**
232 * Asserts true.
233 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
234 */
235 ok: function(a, msg) {
236 msg = escapeHtml(msg);
237 QUnit.log(a, msg);
238
239 config.assertions.push({
240 result: !!a,
241 message: msg
242 });
243 },
244
245 /**
246 * Checks that the first two arguments are equal, with an optional message.
247 * Prints out both actual and expected values.
248 *
249 * Prefered to ok( actual == expected, message )
250 *
251 * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
252 *
253 * @param Object actual
254 * @param Object expected
255 * @param String message (optional)
256 */
257 equal: function(actual, expected, message) {
258 push(expected == actual, actual, expected, message);
259 },
260
261 notEqual: function(actual, expected, message) {
262 push(expected != actual, actual, expected, message);
263 },
264
265 deepEqual: function(actual, expected, message) {
266 push(QUnit.equiv(actual, expected), actual, expected, message);
267 },
268
269 notDeepEqual: function(actual, expected, message) {
270 push(!QUnit.equiv(actual, expected), actual, expected, message);
271 },
272
273 strictEqual: function(actual, expected, message) {
274 push(expected === actual, actual, expected, message);
275 },
276
277 notStrictEqual: function(actual, expected, message) {
278 push(expected !== actual, actual, expected, message);
279 },
280
281 raises: function(fn, message) {
282 try {
283 fn();
284 ok( false, message );
285 }
286 catch (e) {
287 ok( true, message );
288 }
289 },
290
291 start: function() {
292 // A slight delay, to avoid any current callbacks
293 if ( window.setTimeout ) {
294 window.setTimeout(function() {
295 if ( config.timeout ) {
296 clearTimeout(config.timeout);
297 }
298
299 config.blocking = false;
300 process();
301 }, 13);
302 } else {
303 config.blocking = false;
304 process();
305 }
306 },
307
308 stop: function(timeout) {
309 config.blocking = true;
310
311 if ( timeout && window.setTimeout ) {
312 config.timeout = window.setTimeout(function() {
313 QUnit.ok( false, "Test timed out" );
314 QUnit.start();
315 }, timeout);
316 }
317 }
318
319 };
320
321 // Backwards compatibility, deprecated
322 QUnit.equals = QUnit.equal;
323 QUnit.same = QUnit.deepEqual;
324
325 // Maintain internal state
326 var config = {
327 // The queue of tests to run
328 queue: [],
329
330 // block until document ready
331 blocking: true
332 };
333
334 // Load paramaters
335 (function() {
336 var location = window.location || { search: "", protocol: "file:" },
337 GETParams = location.search.slice(1).split('&');
338
339 for ( var i = 0; i < GETParams.length; i++ ) {
340 GETParams[i] = decodeURIComponent( GETParams[i] );
341 if ( GETParams[i] === "noglobals" ) {
342 GETParams.splice( i, 1 );
343 i--;
344 config.noglobals = true;
345 } else if ( GETParams[i].search('=') > -1 ) {
346 GETParams.splice( i, 1 );
347 i--;
348 }
349 }
350
351 // restrict modules/tests by get parameters
352 config.filters = GETParams;
353
354 // Figure out if we're running the tests from a server or not
355 QUnit.isLocal = !!(location.protocol === 'file:');
356 })();
357
358 // Expose the API as global variables, unless an 'exports'
359 // object exists, in that case we assume we're in CommonJS
360 if ( typeof exports === "undefined" || typeof require === "undefined" ) {
361 extend(window, QUnit);
362 window.QUnit = QUnit;
363 } else {
364 extend(exports, QUnit);
365 exports.QUnit = QUnit;
366 }
367
368 // define these after exposing globals to keep them in these QUnit namespace only
369 extend(QUnit, {
370 config: config,
371
372 // Initialize the configuration options
373 init: function() {
374 extend(config, {
375 stats: { all: 0, bad: 0 },
376 moduleStats: { all: 0, bad: 0 },
377 started: +new Date,
378 updateRate: 1000,
379 blocking: false,
380 autostart: true,
381 autorun: false,
382 assertions: [],
383 filters: [],
384 queue: []
385 });
386
387 var tests = id("qunit-tests"),
388 banner = id("qunit-banner"),
389 result = id("qunit-testresult");
390
391 if ( tests ) {
392 tests.innerHTML = "";
393 }
394
395 if ( banner ) {
396 banner.className = "";
397 }
398
399 if ( result ) {
400 result.parentNode.removeChild( result );
401 }
402 },
403
404 /**
405 * Resets the test setup. Useful for tests that modify the DOM.
406 */
407 reset: function() {
408 if ( window.jQuery ) {
409 jQuery("#main, #qunit-fixture").html( config.fixture );
410 }
411 },
412
413 /**
414 * Trigger an event on an element.
415 *
416 * @example triggerEvent( document.body, "click" );
417 *
418 * @param DOMElement elem
419 * @param String type
420 */
421 triggerEvent: function( elem, type, event ) {
422 if ( document.createEvent ) {
423 event = document.createEvent("MouseEvents");
424 event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
425 0, 0, 0, 0, 0, false, false, false, false, 0, null);
426 elem.dispatchEvent( event );
427
428 } else if ( elem.fireEvent ) {
429 elem.fireEvent("on"+type);
430 }
431 },
432
433 // Safe object type checking
434 is: function( type, obj ) {
435 return QUnit.objectType( obj ) == type;
436 },
437
438 objectType: function( obj ) {
439 if (typeof obj === "undefined") {
440 return "undefined";
441
442 // consider: typeof null === object
443 }
444 if (obj === null) {
445 return "null";
446 }
447
448 var type = Object.prototype.toString.call( obj )
449 .match(/^\[object\s(.*)\]$/)[1] || '';
450
451 switch (type) {
452 case 'Number':
453 if (isNaN(obj)) {
454 return "nan";
455 } else {
456 return "number";
457 }
458 case 'String':
459 case 'Boolean':
460 case 'Array':
461 case 'Date':
462 case 'RegExp':
463 case 'Function':
464 return type.toLowerCase();
465 }
466 if (typeof obj === "object") {
467 return "object";
468 }
469 return undefined;
470 },
471
472 // Logging callbacks
473 begin: function() {},
474 done: function(failures, total) {},
475 log: function(result, message) {},
476 testStart: function(name, testEnvironment) {},
477 testDone: function(name, failures, total) {},
478 moduleStart: function(name, testEnvironment) {},
479 moduleDone: function(name, failures, total) {}
480 });
481
482 if ( typeof document === "undefined" || document.readyState === "complete" ) {
483 config.autorun = true;
484 }
485
486 addEvent(window, "load", function() {
487 QUnit.begin();
488
489 // Initialize the config, saving the execution queue
490 var oldconfig = extend({}, config);
491 QUnit.init();
492 extend(config, oldconfig);
493
494 config.blocking = false;
495
496 var userAgent = id("qunit-userAgent");
497 if ( userAgent ) {
498 userAgent.innerHTML = navigator.userAgent;
499 }
500 var banner = id("qunit-header");
501 if ( banner ) {
502 banner.innerHTML = '<a href="' + location.href + '">' + banner.innerHTML + '</a>';
503 }
504
505 var toolbar = id("qunit-testrunner-toolbar");
506 if ( toolbar ) {
507 toolbar.style.display = "none";
508
509 var filter = document.createElement("input");
510 filter.type = "checkbox";
511 filter.id = "qunit-filter-pass";
512 filter.disabled = true;
513 addEvent( filter, "click", function() {
514 var li = document.getElementsByTagName("li");
515 for ( var i = 0; i < li.length; i++ ) {
516 if ( li[i].className.indexOf("pass") > -1 ) {
517 li[i].style.display = filter.checked ? "none" : "";
518 }
519 }
520 });
521 toolbar.appendChild( filter );
522
523 var label = document.createElement("label");
524 label.setAttribute("for", "qunit-filter-pass");
525 label.innerHTML = "Hide passed tests";
526 toolbar.appendChild( label );
527
528 var missing = document.createElement("input");
529 missing.type = "checkbox";
530 missing.id = "qunit-filter-missing";
531 missing.disabled = true;
532 addEvent( missing, "click", function() {
533 var li = document.getElementsByTagName("li");
534 for ( var i = 0; i < li.length; i++ ) {
535 if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) {
536 li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block";
537 }
538 }
539 });
540 toolbar.appendChild( missing );
541
542 label = document.createElement("label");
543 label.setAttribute("for", "qunit-filter-missing");
544 label.innerHTML = "Hide missing tests (untested code is broken code)";
545 toolbar.appendChild( label );
546 }
547
548 var main = id('main') || id('qunit-fixture');
549 if ( main ) {
550 config.fixture = main.innerHTML;
551 }
552
553 if (config.autostart) {
554 QUnit.start();
555 }
556 });
557
558 function done() {
559 if ( config.doneTimer && window.clearTimeout ) {
560 window.clearTimeout( config.doneTimer );
561 config.doneTimer = null;
562 }
563
564 if ( config.queue.length ) {
565 config.doneTimer = window.setTimeout(function(){
566 if ( !config.queue.length ) {
567 done();
568 } else {
569 synchronize( done );
570 }
571 }, 13);
572
573 return;
574 }
575
576 config.autorun = true;
577
578 // Log the last module results
579 if ( config.currentModule ) {
580 QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
581 }
582
583 var banner = id("qunit-banner"),
584 tests = id("qunit-tests"),
585 html = ['Tests completed in ',
586 +new Date - config.started, ' milliseconds.<br/>',
587 '<span class="passed">', config.stats.all - config.stats.bad, '</span> tests of <span class="total">', config.stats.all, '</span> passed, <span class="failed">', config.stats.bad,'</span> failed.'].join('');
588
589 if ( banner ) {
590 banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
591 }
592
593 if ( tests ) {
594 var result = id("qunit-testresult");
595
596 if ( !result ) {
597 result = document.createElement("p");
598 result.id = "qunit-testresult";
599 result.className = "result";
600 tests.parentNode.insertBefore( result, tests.nextSibling );
601 }
602
603 result.innerHTML = html;
604 }
605
606 QUnit.done( config.stats.bad, config.stats.all );
607 }
608
609 function validTest( name ) {
610 var i = config.filters.length,
611 run = false;
612
613 if ( !i ) {
614 return true;
615 }
616
617 while ( i-- ) {
618 var filter = config.filters[i],
619 not = filter.charAt(0) == '!';
620
621 if ( not ) {
622 filter = filter.slice(1);
623 }
624
625 if ( name.indexOf(filter) !== -1 ) {
626 return !not;
627 }
628
629 if ( not ) {
630 run = true;
631 }
632 }
633
634 return run;
635 }
636
637 function escapeHtml(s) {
638 s = s === null ? "" : s + "";
639 return s.replace(/[\&"<>\\]/g, function(s) {
640 switch(s) {
641 case "&": return "&amp;";
642 case "\\": return "\\\\";
643 case '"': return '\"';
644 case "<": return "&lt;";
645 case ">": return "&gt;";
646 default: return s;
647 }
648 });
649 }
650
651 function push(result, actual, expected, message) {
652 message = escapeHtml(message) || (result ? "okay" : "failed");
653 message = '<span class="test-message">' + message + "</span>";
654 expected = escapeHtml(QUnit.jsDump.parse(expected));
655 actual = escapeHtml(QUnit.jsDump.parse(actual));
656 var output = message + ', expected: <span class="test-expected">' + expected + '</span>';
657 if (actual != expected) {
658 output += ' result: <span class="test-actual">' + actual + '</span>, diff: ' + QUnit.diff(expected, actual);
659 }
660
661 // can't use ok, as that would double-escape messages
662 QUnit.log(result, output);
663 config.assertions.push({
664 result: !!result,
665 message: output
666 });
667 }
668
669 function synchronize( callback ) {
670 config.queue.push( callback );
671
672 if ( config.autorun && !config.blocking ) {
673 process();
674 }
675 }
676
677 function process() {
678 var start = (new Date()).getTime();
679
680 while ( config.queue.length && !config.blocking ) {
681 if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) {
682 config.queue.shift()();
683
684 } else {
685 setTimeout( process, 13 );
686 break;
687 }
688 }
689 }
690
691 function saveGlobal() {
692 config.pollution = [];
693
694 if ( config.noglobals ) {
695 for ( var key in window ) {
696 config.pollution.push( key );
697 }
698 }
699 }
700
701 function checkPollution( name ) {
702 var old = config.pollution;
703 saveGlobal();
704
705 var newGlobals = diff( old, config.pollution );
706 if ( newGlobals.length > 0 ) {
707 ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
708 config.expected++;
709 }
710
711 var deletedGlobals = diff( config.pollution, old );
712 if ( deletedGlobals.length > 0 ) {
713 ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
714 config.expected++;
715 }
716 }
717
718 // returns a new Array with the elements that are in a but not in b
719 function diff( a, b ) {
720 var result = a.slice();
721 for ( var i = 0; i < result.length; i++ ) {
722 for ( var j = 0; j < b.length; j++ ) {
723 if ( result[i] === b[j] ) {
724 result.splice(i, 1);
725 i--;
726 break;
727 }
728 }
729 }
730 return result;
731 }
732
733 function fail(message, exception, callback) {
734 if ( typeof console !== "undefined" && console.error && console.warn ) {
735 console.error(message);
736 console.error(exception);
737 console.warn(callback.toString());
738
739 } else if ( window.opera && opera.postError ) {
740 opera.postError(message, exception, callback.toString);
741 }
742 }
743
744 function extend(a, b) {
745 for ( var prop in b ) {
746 a[prop] = b[prop];
747 }
748
749 return a;
750 }
751
752 function addEvent(elem, type, fn) {
753 if ( elem.addEventListener ) {
754 elem.addEventListener( type, fn, false );
755 } else if ( elem.attachEvent ) {
756 elem.attachEvent( "on" + type, fn );
757 } else {
758 fn();
759 }
760 }
761
762 function id(name) {
763 return !!(typeof document !== "undefined" && document && document.getElementById) &&
764 document.getElementById( name );
765 }
766
767 // Test for equality any JavaScript type.
768 // Discussions and reference: http://philrathe.com/articles/equiv
769 // Test suites: http://philrathe.com/tests/equiv
770 // Author: Philippe Rathé <prathe@gmail.com>
771 QUnit.equiv = function () {
772
773 var innerEquiv; // the real equiv function
774 var callers = []; // stack to decide between skip/abort functions
775 var parents = []; // stack to avoiding loops from circular referencing
776
777 // Call the o related callback with the given arguments.
778 function bindCallbacks(o, callbacks, args) {
779 var prop = QUnit.objectType(o);
780 if (prop) {
781 if (QUnit.objectType(callbacks[prop]) === "function") {
782 return callbacks[prop].apply(callbacks, args);
783 } else {
784 return callbacks[prop]; // or undefined
785 }
786 }
787 }
788
789 var callbacks = function () {
790
791 // for string, boolean, number and null
792 function useStrictEquality(b, a) {
793 if (b instanceof a.constructor || a instanceof b.constructor) {
794 // to catch short annotaion VS 'new' annotation of a declaration
795 // e.g. var i = 1;
796 // var j = new Number(1);
797 return a == b;
798 } else {
799 return a === b;
800 }
801 }
802
803 return {
804 "string": useStrictEquality,
805 "boolean": useStrictEquality,
806 "number": useStrictEquality,
807 "null": useStrictEquality,
808 "undefined": useStrictEquality,
809
810 "nan": function (b) {
811 return isNaN(b);
812 },
813
814 "date": function (b, a) {
815 return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf();
816 },
817
818 "regexp": function (b, a) {
819 return QUnit.objectType(b) === "regexp" &&
820 a.source === b.source && // the regex itself
821 a.global === b.global && // and its modifers (gmi) ...
822 a.ignoreCase === b.ignoreCase &&
823 a.multiline === b.multiline;
824 },
825
826 // - skip when the property is a method of an instance (OOP)
827 // - abort otherwise,
828 // initial === would have catch identical references anyway
829 "function": function () {
830 var caller = callers[callers.length - 1];
831 return caller !== Object &&
832 typeof caller !== "undefined";
833 },
834
835 "array": function (b, a) {
836 var i, j, loop;
837 var len;
838
839 // b could be an object literal here
840 if ( ! (QUnit.objectType(b) === "array")) {
841 return false;
842 }
843
844 len = a.length;
845 if (len !== b.length) { // safe and faster
846 return false;
847 }
848
849 //track reference to avoid circular references
850 parents.push(a);
851 for (i = 0; i < len; i++) {
852 loop = false;
853 for(j=0;j<parents.length;j++){
854 if(parents[j] === a[i]){
855 loop = true;//dont rewalk array
856 }
857 }
858 if (!loop && ! innerEquiv(a[i], b[i])) {
859 parents.pop();
860 return false;
861 }
862 }
863 parents.pop();
864 return true;
865 },
866
867 "object": function (b, a) {
868 var i, j, loop;
869 var eq = true; // unless we can proove it
870 var aProperties = [], bProperties = []; // collection of strings
871
872 // comparing constructors is more strict than using instanceof
873 if ( a.constructor !== b.constructor) {
874 return false;
875 }
876
877 // stack constructor before traversing properties
878 callers.push(a.constructor);
879 //track reference to avoid circular references
880 parents.push(a);
881
882 for (i in a) { // be strict: don't ensures hasOwnProperty and go deep
883 loop = false;
884 for(j=0;j<parents.length;j++){
885 if(parents[j] === a[i])
886 loop = true; //don't go down the same path twice
887 }
888 aProperties.push(i); // collect a's properties
889
890 if (!loop && ! innerEquiv(a[i], b[i])) {
891 eq = false;
892 break;
893 }
894 }
895
896 callers.pop(); // unstack, we are done
897 parents.pop();
898
899 for (i in b) {
900 bProperties.push(i); // collect b's properties
901 }
902
903 // Ensures identical properties name
904 return eq && innerEquiv(aProperties.sort(), bProperties.sort());
905 }
906 };
907 }();
908
909 innerEquiv = function () { // can take multiple arguments
910 var args = Array.prototype.slice.apply(arguments);
911 if (args.length < 2) {
912 return true; // end transition
913 }
914
915 return (function (a, b) {
916 if (a === b) {
917 return true; // catch the most you can
918 } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || QUnit.objectType(a) !== QUnit.objectType(b)) {
919 return false; // don't lose time with error prone cases
920 } else {
921 return bindCallbacks(a, callbacks, [b, a]);
922 }
923
924 // apply transition with (1..n) arguments
925 })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1));
926 };
927
928 return innerEquiv;
929
930 }();
931
932 /**
933 * jsDump
934 * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
935 * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
936 * Date: 5/15/2008
937 * @projectDescription Advanced and extensible data dumping for Javascript.
938 * @version 1.0.0
939 * @author Ariel Flesler
940 * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
941 */
942 QUnit.jsDump = (function() {
943 function quote( str ) {
944 return '"' + str.toString().replace(/"/g, '\\"') + '"';
945 };
946 function literal( o ) {
947 return o + '';
948 };
949 function join( pre, arr, post ) {
950 var s = jsDump.separator(),
951 base = jsDump.indent(),
952 inner = jsDump.indent(1);
953 if ( arr.join )
954 arr = arr.join( ',' + s + inner );
955 if ( !arr )
956 return pre + post;
957 return [ pre, inner + arr, base + post ].join(s);
958 };
959 function array( arr ) {
960 var i = arr.length, ret = Array(i);
961 this.up();
962 while ( i-- )
963 ret[i] = this.parse( arr[i] );
964 this.down();
965 return join( '[', ret, ']' );
966 };
967
968 var reName = /^function (\w+)/;
969
970 var jsDump = {
971 parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance
972 var parser = this.parsers[ type || this.typeOf(obj) ];
973 type = typeof parser;
974
975 return type == 'function' ? parser.call( this, obj ) :
976 type == 'string' ? parser :
977 this.parsers.error;
978 },
979 typeOf:function( obj ) {
980 var type;
981 if ( obj === null ) {
982 type = "null";
983 } else if (typeof obj === "undefined") {
984 type = "undefined";
985 } else if (QUnit.is("RegExp", obj)) {
986 type = "regexp";
987 } else if (QUnit.is("Date", obj)) {
988 type = "date";
989 } else if (QUnit.is("Function", obj)) {
990 type = "function";
991 } else if (obj.setInterval && obj.document && !obj.nodeType) {
992 type = "window";
993 } else if (obj.nodeType === 9) {
994 type = "document";
995 } else if (obj.nodeType) {
996 type = "node";
997 } else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) {
998 type = "array";
999 } else {
1000 type = typeof obj;
1001 }
1002 return type;
1003 },
1004 separator:function() {
1005 return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
1006 },
1007 indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
1008 if ( !this.multiline )
1009 return '';
1010 var chr = this.indentChar;
1011 if ( this.HTML )
1012 chr = chr.replace(/\t/g,' ').replace(/ /g,'&nbsp;');
1013 return Array( this._depth_ + (extra||0) ).join(chr);
1014 },
1015 up:function( a ) {
1016 this._depth_ += a || 1;
1017 },
1018 down:function( a ) {
1019 this._depth_ -= a || 1;
1020 },
1021 setParser:function( name, parser ) {
1022 this.parsers[name] = parser;
1023 },
1024 // The next 3 are exposed so you can use them
1025 quote:quote,
1026 literal:literal,
1027 join:join,
1028 //
1029 _depth_: 1,
1030 // This is the list of parsers, to modify them, use jsDump.setParser
1031 parsers:{
1032 window: '[Window]',
1033 document: '[Document]',
1034 error:'[ERROR]', //when no parser is found, shouldn't happen
1035 unknown: '[Unknown]',
1036 'null':'null',
1037 undefined:'undefined',
1038 'function':function( fn ) {
1039 var ret = 'function',
1040 name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
1041 if ( name )
1042 ret += ' ' + name;
1043 ret += '(';
1044
1045 ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join('');
1046 return join( ret, this.parse(fn,'functionCode'), '}' );
1047 },
1048 array: array,
1049 nodelist: array,
1050 arguments: array,
1051 object:function( map ) {
1052 var ret = [ ];
1053 this.up();
1054 for ( var key in map )
1055 ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) );
1056 this.down();
1057 return join( '{', ret, '}' );
1058 },
1059 node:function( node ) {
1060 var open = this.HTML ? '&lt;' : '<',
1061 close = this.HTML ? '&gt;' : '>';
1062
1063 var tag = node.nodeName.toLowerCase(),
1064 ret = open + tag;
1065
1066 for ( var a in this.DOMAttrs ) {
1067 var val = node[this.DOMAttrs[a]];
1068 if ( val )
1069 ret += ' ' + a + '=' + this.parse( val, 'attribute' );
1070 }
1071 return ret + close + open + '/' + tag + close;
1072 },
1073 functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
1074 var l = fn.length;
1075 if ( !l ) return '';
1076
1077 var args = Array(l);
1078 while ( l-- )
1079 args[l] = String.fromCharCode(97+l);//97 is 'a'
1080 return ' ' + args.join(', ') + ' ';
1081 },
1082 key:quote, //object calls it internally, the key part of an item in a map
1083 functionCode:'[code]', //function calls it internally, it's the content of the function
1084 attribute:quote, //node calls it internally, it's an html attribute value
1085 string:quote,
1086 date:quote,
1087 regexp:literal, //regex
1088 number:literal,
1089 'boolean':literal
1090 },
1091 DOMAttrs:{//attributes to dump from nodes, name=>realName
1092 id:'id',
1093 name:'name',
1094 'class':'className'
1095 },
1096 HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
1097 indentChar:' ',//indentation unit
1098 multiline:false //if true, items in a collection, are separated by a \n, else just a space.
1099 };
1100
1101 return jsDump;
1102 })();
1103
1104 // from Sizzle.js
1105 function getText( elems ) {
1106 var ret = "", elem;
1107
1108 for ( var i = 0; elems[i]; i++ ) {
1109 elem = elems[i];
1110
1111 // Get the text from text nodes and CDATA nodes
1112 if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
1113 ret += elem.nodeValue;
1114
1115 // Traverse everything else, except comment nodes
1116 } else if ( elem.nodeType !== 8 ) {
1117 ret += getText( elem.childNodes );
1118 }
1119 }
1120
1121 return ret;
1122 };
1123
1124 /*
1125 * Javascript Diff Algorithm
1126 * By John Resig (http://ejohn.org/)
1127 * Modified by Chu Alan "sprite"
1128 *
1129 * Released under the MIT license.
1130 *
1131 * More Info:
1132 * http://ejohn.org/projects/javascript-diff-algorithm/
1133 *
1134 * Usage: QUnit.diff(expected, actual)
1135 *
1136 * 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"
1137 */
1138 QUnit.diff = (function() {
1139 function diff(o, n){
1140 var ns = new Object();
1141 var os = new Object();
1142
1143 for (var i = 0; i < n.length; i++) {
1144 if (ns[n[i]] == null)
1145 ns[n[i]] = {
1146 rows: new Array(),
1147 o: null
1148 };
1149 ns[n[i]].rows.push(i);
1150 }
1151
1152 for (var i = 0; i < o.length; i++) {
1153 if (os[o[i]] == null)
1154 os[o[i]] = {
1155 rows: new Array(),
1156 n: null
1157 };
1158 os[o[i]].rows.push(i);
1159 }
1160
1161 for (var i in ns) {
1162 if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
1163 n[ns[i].rows[0]] = {
1164 text: n[ns[i].rows[0]],
1165 row: os[i].rows[0]
1166 };
1167 o[os[i].rows[0]] = {
1168 text: o[os[i].rows[0]],
1169 row: ns[i].rows[0]
1170 };
1171 }
1172 }
1173
1174 for (var i = 0; i < n.length - 1; i++) {
1175 if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
1176 n[i + 1] == o[n[i].row + 1]) {
1177 n[i + 1] = {
1178 text: n[i + 1],
1179 row: n[i].row + 1
1180 };
1181 o[n[i].row + 1] = {
1182 text: o[n[i].row + 1],
1183 row: i + 1
1184 };
1185 }
1186 }
1187
1188 for (var i = n.length - 1; i > 0; i--) {
1189 if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
1190 n[i - 1] == o[n[i].row - 1]) {
1191 n[i - 1] = {
1192 text: n[i - 1],
1193 row: n[i].row - 1
1194 };
1195 o[n[i].row - 1] = {
1196 text: o[n[i].row - 1],
1197 row: i - 1
1198 };
1199 }
1200 }
1201
1202 return {
1203 o: o,
1204 n: n
1205 };
1206 }
1207
1208 return function(o, n){
1209 o = o.replace(/\s+$/, '');
1210 n = n.replace(/\s+$/, '');
1211 var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));
1212
1213 var str = "";
1214
1215 var oSpace = o.match(/\s+/g);
1216 if (oSpace == null) {
1217 oSpace = [" "];
1218 }
1219 else {
1220 oSpace.push(" ");
1221 }
1222 var nSpace = n.match(/\s+/g);
1223 if (nSpace == null) {
1224 nSpace = [" "];
1225 }
1226 else {
1227 nSpace.push(" ");
1228 }
1229
1230 if (out.n.length == 0) {
1231 for (var i = 0; i < out.o.length; i++) {
1232 str += '<del>' + out.o[i] + oSpace[i] + "</del>";
1233 }
1234 }
1235 else {
1236 if (out.n[0].text == null) {
1237 for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
1238 str += '<del>' + out.o[n] + oSpace[n] + "</del>";
1239 }
1240 }
1241
1242 for (var i = 0; i < out.n.length; i++) {
1243 if (out.n[i].text == null) {
1244 str += '<ins>' + out.n[i] + nSpace[i] + "</ins>";
1245 }
1246 else {
1247 var pre = "";
1248
1249 for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
1250 pre += '<del>' + out.o[n] + oSpace[n] + "</del>";
1251 }
1252 str += " " + out.n[i].text + nSpace[i] + pre;
1253 }
1254 }
1255 }
1256
1257 return str;
1258 }
1259 })();
1260
1261 })(this);