twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

qunit.js (29005B)


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