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