spec/qunit.js
changeset 0 54063d8b0412
equal deleted inserted replaced
-1:000000000000 0:54063d8b0412
       
     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.innerHTML = 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);