unittests/qunit.js
branchnew-model
changeset 924 64c2eaafe5e2
parent 923 b3ee7d1b472a
child 925 28efc97b5d78
equal deleted inserted replaced
923:b3ee7d1b472a 924:64c2eaafe5e2
     1 /**
       
     2  * QUnit - A JavaScript Unit Testing Framework
       
     3  *
       
     4  * http://docs.jquery.com/QUnit
       
     5  *
       
     6  * Copyright (c) 2011 John Resig, Jörn Zaefferer
       
     7  * Dual licensed under the MIT (MIT-LICENSE.txt)
       
     8  * or GPL (GPL-LICENSE.txt) licenses.
       
     9  * Pulled Live from Git Thu Oct  6 09:15:01 UTC 2011
       
    10  * Last Commit: 3006fa77db4625c4293c368a8999ddb1abba7f48
       
    11  */
       
    12 
       
    13 (function(window) {
       
    14 
       
    15 var defined = {
       
    16 	setTimeout: typeof window.setTimeout !== "undefined",
       
    17 	sessionStorage: (function() {
       
    18 		try {
       
    19 			return !!sessionStorage.getItem;
       
    20 		} catch(e) {
       
    21 			return false;
       
    22 		}
       
    23 	})()
       
    24 };
       
    25 
       
    26 var testId = 0;
       
    27 
       
    28 var Test = function(name, testName, expected, testEnvironmentArg, async, callback) {
       
    29 	this.name = name;
       
    30 	this.testName = testName;
       
    31 	this.expected = expected;
       
    32 	this.testEnvironmentArg = testEnvironmentArg;
       
    33 	this.async = async;
       
    34 	this.callback = callback;
       
    35 	this.assertions = [];
       
    36 };
       
    37 Test.prototype = {
       
    38 	init: function() {
       
    39 		var tests = id("qunit-tests");
       
    40 		if (tests) {
       
    41 			var b = document.createElement("strong");
       
    42 				b.innerHTML = "Running " + this.name;
       
    43 			var li = document.createElement("li");
       
    44 				li.appendChild( b );
       
    45 				li.className = "running";
       
    46 				li.id = this.id = "test-output" + testId++;
       
    47 			tests.appendChild( li );
       
    48 		}
       
    49 	},
       
    50 	setup: function() {
       
    51 		if (this.module != config.previousModule) {
       
    52 			if ( config.previousModule ) {
       
    53 				runLoggingCallbacks('moduleDone', QUnit, {
       
    54 					name: config.previousModule,
       
    55 					failed: config.moduleStats.bad,
       
    56 					passed: config.moduleStats.all - config.moduleStats.bad,
       
    57 					total: config.moduleStats.all
       
    58 				} );
       
    59 			}
       
    60 			config.previousModule = this.module;
       
    61 			config.moduleStats = { all: 0, bad: 0 };
       
    62 			runLoggingCallbacks( 'moduleStart', QUnit, {
       
    63 				name: this.module
       
    64 			} );
       
    65 		}
       
    66 
       
    67 		config.current = this;
       
    68 		this.testEnvironment = extend({
       
    69 			setup: function() {},
       
    70 			teardown: function() {}
       
    71 		}, this.moduleTestEnvironment);
       
    72 		if (this.testEnvironmentArg) {
       
    73 			extend(this.testEnvironment, this.testEnvironmentArg);
       
    74 		}
       
    75 
       
    76 		runLoggingCallbacks( 'testStart', QUnit, {
       
    77 			name: this.testName,
       
    78 			module: this.module
       
    79 		});
       
    80 
       
    81 		// allow utility functions to access the current test environment
       
    82 		// TODO why??
       
    83 		QUnit.current_testEnvironment = this.testEnvironment;
       
    84 
       
    85 		try {
       
    86 			if ( !config.pollution ) {
       
    87 				saveGlobal();
       
    88 			}
       
    89 
       
    90 			this.testEnvironment.setup.call(this.testEnvironment);
       
    91 		} catch(e) {
       
    92 			QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message );
       
    93 		}
       
    94 	},
       
    95 	run: function() {
       
    96 		if ( this.async ) {
       
    97 			QUnit.stop();
       
    98 		}
       
    99 
       
   100 		if ( config.notrycatch ) {
       
   101 			this.callback.call(this.testEnvironment);
       
   102 			return;
       
   103 		}
       
   104 		try {
       
   105 			this.callback.call(this.testEnvironment);
       
   106 		} catch(e) {
       
   107 			fail("Test " + this.testName + " died, exception and test follows", e, this.callback);
       
   108 			QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) );
       
   109 			// else next test will carry the responsibility
       
   110 			saveGlobal();
       
   111 
       
   112 			// Restart the tests if they're blocking
       
   113 			if ( config.blocking ) {
       
   114 				start();
       
   115 			}
       
   116 		}
       
   117 	},
       
   118 	teardown: function() {
       
   119 		try {
       
   120 			this.testEnvironment.teardown.call(this.testEnvironment);
       
   121 			checkPollution();
       
   122 		} catch(e) {
       
   123 			QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message );
       
   124 		}
       
   125 	},
       
   126 	finish: function() {
       
   127 		if ( this.expected && this.expected != this.assertions.length ) {
       
   128 			QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
       
   129 		}
       
   130 
       
   131 		var good = 0, bad = 0,
       
   132 			tests = id("qunit-tests");
       
   133 
       
   134 		config.stats.all += this.assertions.length;
       
   135 		config.moduleStats.all += this.assertions.length;
       
   136 
       
   137 		if ( tests ) {
       
   138 			var ol = document.createElement("ol");
       
   139 
       
   140 			for ( var i = 0; i < this.assertions.length; i++ ) {
       
   141 				var assertion = this.assertions[i];
       
   142 
       
   143 				var li = document.createElement("li");
       
   144 				li.className = assertion.result ? "pass" : "fail";
       
   145 				li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
       
   146 				ol.appendChild( li );
       
   147 
       
   148 				if ( assertion.result ) {
       
   149 					good++;
       
   150 				} else {
       
   151 					bad++;
       
   152 					config.stats.bad++;
       
   153 					config.moduleStats.bad++;
       
   154 				}
       
   155 			}
       
   156 
       
   157 			// store result when possible
       
   158 			if ( QUnit.config.reorder && defined.sessionStorage ) {
       
   159 				if (bad) {
       
   160 					sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad);
       
   161 				} else {
       
   162 					sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName);
       
   163 				}
       
   164 			}
       
   165 
       
   166 			if (bad == 0) {
       
   167 				ol.style.display = "none";
       
   168 			}
       
   169 
       
   170 			var b = document.createElement("strong");
       
   171 			b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
       
   172 
       
   173 			var a = document.createElement("a");
       
   174 			a.innerHTML = "Rerun";
       
   175 			a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
       
   176 
       
   177 			addEvent(b, "click", function() {
       
   178 				var next = b.nextSibling.nextSibling,
       
   179 					display = next.style.display;
       
   180 				next.style.display = display === "none" ? "block" : "none";
       
   181 			});
       
   182 
       
   183 			addEvent(b, "dblclick", function(e) {
       
   184 				var target = e && e.target ? e.target : window.event.srcElement;
       
   185 				if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
       
   186 					target = target.parentNode;
       
   187 				}
       
   188 				if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
       
   189 					window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
       
   190 				}
       
   191 			});
       
   192 
       
   193 			var li = id(this.id);
       
   194 			li.className = bad ? "fail" : "pass";
       
   195 			li.removeChild( li.firstChild );
       
   196 			li.appendChild( b );
       
   197 			li.appendChild( a );
       
   198 			li.appendChild( ol );
       
   199 
       
   200 		} else {
       
   201 			for ( var i = 0; i < this.assertions.length; i++ ) {
       
   202 				if ( !this.assertions[i].result ) {
       
   203 					bad++;
       
   204 					config.stats.bad++;
       
   205 					config.moduleStats.bad++;
       
   206 				}
       
   207 			}
       
   208 		}
       
   209 
       
   210 		try {
       
   211 			QUnit.reset();
       
   212 		} catch(e) {
       
   213 			fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset);
       
   214 		}
       
   215 
       
   216 		runLoggingCallbacks( 'testDone', QUnit, {
       
   217 			name: this.testName,
       
   218 			module: this.module,
       
   219 			failed: bad,
       
   220 			passed: this.assertions.length - bad,
       
   221 			total: this.assertions.length
       
   222 		} );
       
   223 	},
       
   224 
       
   225 	queue: function() {
       
   226 		var test = this;
       
   227 		synchronize(function() {
       
   228 			test.init();
       
   229 		});
       
   230 		function run() {
       
   231 			// each of these can by async
       
   232 			synchronize(function() {
       
   233 				test.setup();
       
   234 			});
       
   235 			synchronize(function() {
       
   236 				test.run();
       
   237 			});
       
   238 			synchronize(function() {
       
   239 				test.teardown();
       
   240 			});
       
   241 			synchronize(function() {
       
   242 				test.finish();
       
   243 			});
       
   244 		}
       
   245 		// defer when previous test run passed, if storage is available
       
   246 		var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName);
       
   247 		if (bad) {
       
   248 			run();
       
   249 		} else {
       
   250 			synchronize(run);
       
   251 		};
       
   252 	}
       
   253 
       
   254 };
       
   255 
       
   256 var QUnit = {
       
   257 
       
   258 	// call on start of module test to prepend name to all tests
       
   259 	module: function(name, testEnvironment) {
       
   260 		config.currentModule = name;
       
   261 		config.currentModuleTestEnviroment = testEnvironment;
       
   262 	},
       
   263 
       
   264 	asyncTest: function(testName, expected, callback) {
       
   265 		if ( arguments.length === 2 ) {
       
   266 			callback = expected;
       
   267 			expected = 0;
       
   268 		}
       
   269 
       
   270 		QUnit.test(testName, expected, callback, true);
       
   271 	},
       
   272 
       
   273 	test: function(testName, expected, callback, async) {
       
   274 		var name = '<span class="test-name">' + testName + '</span>', testEnvironmentArg;
       
   275 
       
   276 		if ( arguments.length === 2 ) {
       
   277 			callback = expected;
       
   278 			expected = null;
       
   279 		}
       
   280 		// is 2nd argument a testEnvironment?
       
   281 		if ( expected && typeof expected === 'object') {
       
   282 			testEnvironmentArg = expected;
       
   283 			expected = null;
       
   284 		}
       
   285 
       
   286 		if ( config.currentModule ) {
       
   287 			name = '<span class="module-name">' + config.currentModule + "</span>: " + name;
       
   288 		}
       
   289 
       
   290 		if ( !validTest(config.currentModule + ": " + testName) ) {
       
   291 			return;
       
   292 		}
       
   293 
       
   294 		var test = new Test(name, testName, expected, testEnvironmentArg, async, callback);
       
   295 		test.module = config.currentModule;
       
   296 		test.moduleTestEnvironment = config.currentModuleTestEnviroment;
       
   297 		test.queue();
       
   298 	},
       
   299 
       
   300 	/**
       
   301 	 * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
       
   302 	 */
       
   303 	expect: function(asserts) {
       
   304 		config.current.expected = asserts;
       
   305 	},
       
   306 
       
   307 	/**
       
   308 	 * Asserts true.
       
   309 	 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
       
   310 	 */
       
   311 	ok: function(a, msg) {
       
   312 		a = !!a;
       
   313 		var details = {
       
   314 			result: a,
       
   315 			message: msg
       
   316 		};
       
   317 		msg = escapeInnerText(msg);
       
   318 		runLoggingCallbacks( 'log', QUnit, details );
       
   319 		config.current.assertions.push({
       
   320 			result: a,
       
   321 			message: msg
       
   322 		});
       
   323 	},
       
   324 
       
   325 	/**
       
   326 	 * Checks that the first two arguments are equal, with an optional message.
       
   327 	 * Prints out both actual and expected values.
       
   328 	 *
       
   329 	 * Prefered to ok( actual == expected, message )
       
   330 	 *
       
   331 	 * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
       
   332 	 *
       
   333 	 * @param Object actual
       
   334 	 * @param Object expected
       
   335 	 * @param String message (optional)
       
   336 	 */
       
   337 	equal: function(actual, expected, message) {
       
   338 		QUnit.push(expected == actual, actual, expected, message);
       
   339 	},
       
   340 
       
   341 	notEqual: function(actual, expected, message) {
       
   342 		QUnit.push(expected != actual, actual, expected, message);
       
   343 	},
       
   344 
       
   345 	deepEqual: function(actual, expected, message) {
       
   346 		QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
       
   347 	},
       
   348 
       
   349 	notDeepEqual: function(actual, expected, message) {
       
   350 		QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
       
   351 	},
       
   352 
       
   353 	strictEqual: function(actual, expected, message) {
       
   354 		QUnit.push(expected === actual, actual, expected, message);
       
   355 	},
       
   356 
       
   357 	notStrictEqual: function(actual, expected, message) {
       
   358 		QUnit.push(expected !== actual, actual, expected, message);
       
   359 	},
       
   360 
       
   361 	raises: function(block, expected, message) {
       
   362 		var actual, ok = false;
       
   363 
       
   364 		if (typeof expected === 'string') {
       
   365 			message = expected;
       
   366 			expected = null;
       
   367 		}
       
   368 
       
   369 		try {
       
   370 			block();
       
   371 		} catch (e) {
       
   372 			actual = e;
       
   373 		}
       
   374 
       
   375 		if (actual) {
       
   376 			// we don't want to validate thrown error
       
   377 			if (!expected) {
       
   378 				ok = true;
       
   379 			// expected is a regexp
       
   380 			} else if (QUnit.objectType(expected) === "regexp") {
       
   381 				ok = expected.test(actual);
       
   382 			// expected is a constructor
       
   383 			} else if (actual instanceof expected) {
       
   384 				ok = true;
       
   385 			// expected is a validation function which returns true is validation passed
       
   386 			} else if (expected.call({}, actual) === true) {
       
   387 				ok = true;
       
   388 			}
       
   389 		}
       
   390 
       
   391 		QUnit.ok(ok, message);
       
   392 	},
       
   393 
       
   394 	start: function(count) {
       
   395 		config.semaphore -= count || 1;
       
   396 		if (config.semaphore > 0) {
       
   397 			// don't start until equal number of stop-calls
       
   398 			return;
       
   399 		}
       
   400 		if (config.semaphore < 0) {
       
   401 			// ignore if start is called more often then stop
       
   402 			config.semaphore = 0;
       
   403 		}
       
   404 		// A slight delay, to avoid any current callbacks
       
   405 		if ( defined.setTimeout ) {
       
   406 			window.setTimeout(function() {
       
   407 				if (config.semaphore > 0) {
       
   408 					return;
       
   409 				}
       
   410 				if ( config.timeout ) {
       
   411 					clearTimeout(config.timeout);
       
   412 				}
       
   413 
       
   414 				config.blocking = false;
       
   415 				process();
       
   416 			}, 13);
       
   417 		} else {
       
   418 			config.blocking = false;
       
   419 			process();
       
   420 		}
       
   421 	},
       
   422 
       
   423 	stop: function(count) {
       
   424 		config.semaphore += count || 1;
       
   425 		config.blocking = true;
       
   426 
       
   427 		if ( config.testTimeout && defined.setTimeout ) {
       
   428 			clearTimeout(config.timeout);
       
   429 			config.timeout = window.setTimeout(function() {
       
   430 				QUnit.ok( false, "Test timed out" );
       
   431 				config.semaphore = 1;
       
   432 				QUnit.start();
       
   433 			}, config.testTimeout);
       
   434 		}
       
   435 	}
       
   436 };
       
   437 
       
   438 //We want access to the constructor's prototype
       
   439 (function() {
       
   440 	function F(){};
       
   441 	F.prototype = QUnit;
       
   442 	QUnit = new F();
       
   443 	//Make F QUnit's constructor so that we can add to the prototype later
       
   444 	QUnit.constructor = F;
       
   445 })();
       
   446 
       
   447 // Backwards compatibility, deprecated
       
   448 QUnit.equals = QUnit.equal;
       
   449 QUnit.same = QUnit.deepEqual;
       
   450 
       
   451 // Maintain internal state
       
   452 var config = {
       
   453 	// The queue of tests to run
       
   454 	queue: [],
       
   455 
       
   456 	// block until document ready
       
   457 	blocking: true,
       
   458 
       
   459 	// when enabled, show only failing tests
       
   460 	// gets persisted through sessionStorage and can be changed in UI via checkbox
       
   461 	hidepassed: false,
       
   462 
       
   463 	// by default, run previously failed tests first
       
   464 	// very useful in combination with "Hide passed tests" checked
       
   465 	reorder: true,
       
   466 
       
   467 	// by default, modify document.title when suite is done
       
   468 	altertitle: true,
       
   469 
       
   470 	urlConfig: ['noglobals', 'notrycatch'],
       
   471 
       
   472 	//logging callback queues
       
   473 	begin: [],
       
   474 	done: [],
       
   475 	log: [],
       
   476 	testStart: [],
       
   477 	testDone: [],
       
   478 	moduleStart: [],
       
   479 	moduleDone: []
       
   480 };
       
   481 
       
   482 // Load paramaters
       
   483 (function() {
       
   484 	var location = window.location || { search: "", protocol: "file:" },
       
   485 		params = location.search.slice( 1 ).split( "&" ),
       
   486 		length = params.length,
       
   487 		urlParams = {},
       
   488 		current;
       
   489 
       
   490 	if ( params[ 0 ] ) {
       
   491 		for ( var i = 0; i < length; i++ ) {
       
   492 			current = params[ i ].split( "=" );
       
   493 			current[ 0 ] = decodeURIComponent( current[ 0 ] );
       
   494 			// allow just a key to turn on a flag, e.g., test.html?noglobals
       
   495 			current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
       
   496 			urlParams[ current[ 0 ] ] = current[ 1 ];
       
   497 		}
       
   498 	}
       
   499 
       
   500 	QUnit.urlParams = urlParams;
       
   501 	config.filter = urlParams.filter;
       
   502 
       
   503 	// Figure out if we're running the tests from a server or not
       
   504 	QUnit.isLocal = !!(location.protocol === 'file:');
       
   505 })();
       
   506 
       
   507 // Expose the API as global variables, unless an 'exports'
       
   508 // object exists, in that case we assume we're in CommonJS
       
   509 if ( typeof exports === "undefined" || typeof require === "undefined" ) {
       
   510 	extend(window, QUnit);
       
   511 	window.QUnit = QUnit;
       
   512 } else {
       
   513 	extend(exports, QUnit);
       
   514 	exports.QUnit = QUnit;
       
   515 }
       
   516 
       
   517 // define these after exposing globals to keep them in these QUnit namespace only
       
   518 extend(QUnit, {
       
   519 	config: config,
       
   520 
       
   521 	// Initialize the configuration options
       
   522 	init: function() {
       
   523 		extend(config, {
       
   524 			stats: { all: 0, bad: 0 },
       
   525 			moduleStats: { all: 0, bad: 0 },
       
   526 			started: +new Date,
       
   527 			updateRate: 1000,
       
   528 			blocking: false,
       
   529 			autostart: true,
       
   530 			autorun: false,
       
   531 			filter: "",
       
   532 			queue: [],
       
   533 			semaphore: 0
       
   534 		});
       
   535 
       
   536 		var tests = id( "qunit-tests" ),
       
   537 			banner = id( "qunit-banner" ),
       
   538 			result = id( "qunit-testresult" );
       
   539 
       
   540 		if ( tests ) {
       
   541 			tests.innerHTML = "";
       
   542 		}
       
   543 
       
   544 		if ( banner ) {
       
   545 			banner.className = "";
       
   546 		}
       
   547 
       
   548 		if ( result ) {
       
   549 			result.parentNode.removeChild( result );
       
   550 		}
       
   551 
       
   552 		if ( tests ) {
       
   553 			result = document.createElement( "p" );
       
   554 			result.id = "qunit-testresult";
       
   555 			result.className = "result";
       
   556 			tests.parentNode.insertBefore( result, tests );
       
   557 			result.innerHTML = 'Running...<br/>&nbsp;';
       
   558 		}
       
   559 	},
       
   560 
       
   561 	/**
       
   562 	 * Resets the test setup. Useful for tests that modify the DOM.
       
   563 	 *
       
   564 	 * If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
       
   565 	 */
       
   566 	reset: function() {
       
   567 		if ( window.jQuery ) {
       
   568 			jQuery( "#qunit-fixture" ).html( config.fixture );
       
   569 		} else {
       
   570 			var main = id( 'qunit-fixture' );
       
   571 			if ( main ) {
       
   572 				main.innerHTML = config.fixture;
       
   573 			}
       
   574 		}
       
   575 	},
       
   576 
       
   577 	/**
       
   578 	 * Trigger an event on an element.
       
   579 	 *
       
   580 	 * @example triggerEvent( document.body, "click" );
       
   581 	 *
       
   582 	 * @param DOMElement elem
       
   583 	 * @param String type
       
   584 	 */
       
   585 	triggerEvent: function( elem, type, event ) {
       
   586 		if ( document.createEvent ) {
       
   587 			event = document.createEvent("MouseEvents");
       
   588 			event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
       
   589 				0, 0, 0, 0, 0, false, false, false, false, 0, null);
       
   590 			elem.dispatchEvent( event );
       
   591 
       
   592 		} else if ( elem.fireEvent ) {
       
   593 			elem.fireEvent("on"+type);
       
   594 		}
       
   595 	},
       
   596 
       
   597 	// Safe object type checking
       
   598 	is: function( type, obj ) {
       
   599 		return QUnit.objectType( obj ) == type;
       
   600 	},
       
   601 
       
   602 	objectType: function( obj ) {
       
   603 		if (typeof obj === "undefined") {
       
   604 				return "undefined";
       
   605 
       
   606 		// consider: typeof null === object
       
   607 		}
       
   608 		if (obj === null) {
       
   609 				return "null";
       
   610 		}
       
   611 
       
   612 		var type = Object.prototype.toString.call( obj )
       
   613 			.match(/^\[object\s(.*)\]$/)[1] || '';
       
   614 
       
   615 		switch (type) {
       
   616 				case 'Number':
       
   617 						if (isNaN(obj)) {
       
   618 								return "nan";
       
   619 						} else {
       
   620 								return "number";
       
   621 						}
       
   622 				case 'String':
       
   623 				case 'Boolean':
       
   624 				case 'Array':
       
   625 				case 'Date':
       
   626 				case 'RegExp':
       
   627 				case 'Function':
       
   628 						return type.toLowerCase();
       
   629 		}
       
   630 		if (typeof obj === "object") {
       
   631 				return "object";
       
   632 		}
       
   633 		return undefined;
       
   634 	},
       
   635 
       
   636 	push: function(result, actual, expected, message) {
       
   637 		var details = {
       
   638 			result: result,
       
   639 			message: message,
       
   640 			actual: actual,
       
   641 			expected: expected
       
   642 		};
       
   643 
       
   644 		message = escapeInnerText(message) || (result ? "okay" : "failed");
       
   645 		message = '<span class="test-message">' + message + "</span>";
       
   646 		expected = escapeInnerText(QUnit.jsDump.parse(expected));
       
   647 		actual = escapeInnerText(QUnit.jsDump.parse(actual));
       
   648 		var output = message + '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected + '</pre></td></tr>';
       
   649 		if (actual != expected) {
       
   650 			output += '<tr class="test-actual"><th>Result: </th><td><pre>' + actual + '</pre></td></tr>';
       
   651 			output += '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit.diff(expected, actual) +'</pre></td></tr>';
       
   652 		}
       
   653 		if (!result) {
       
   654 			var source = sourceFromStacktrace();
       
   655 			if (source) {
       
   656 				details.source = source;
       
   657 				output += '<tr class="test-source"><th>Source: </th><td><pre>' + escapeInnerText(source) + '</pre></td></tr>';
       
   658 			}
       
   659 		}
       
   660 		output += "</table>";
       
   661 
       
   662 		runLoggingCallbacks( 'log', QUnit, details );
       
   663 
       
   664 		config.current.assertions.push({
       
   665 			result: !!result,
       
   666 			message: output
       
   667 		});
       
   668 	},
       
   669 
       
   670 	url: function( params ) {
       
   671 		params = extend( extend( {}, QUnit.urlParams ), params );
       
   672 		var querystring = "?",
       
   673 			key;
       
   674 		for ( key in params ) {
       
   675 			querystring += encodeURIComponent( key ) + "=" +
       
   676 				encodeURIComponent( params[ key ] ) + "&";
       
   677 		}
       
   678 		return window.location.pathname + querystring.slice( 0, -1 );
       
   679 	},
       
   680 
       
   681 	extend: extend,
       
   682 	id: id,
       
   683 	addEvent: addEvent
       
   684 });
       
   685 
       
   686 //QUnit.constructor is set to the empty F() above so that we can add to it's prototype later
       
   687 //Doing this allows us to tell if the following methods have been overwritten on the actual
       
   688 //QUnit object, which is a deprecated way of using the callbacks.
       
   689 extend(QUnit.constructor.prototype, {
       
   690 	// Logging callbacks; all receive a single argument with the listed properties
       
   691 	// run test/logs.html for any related changes
       
   692 	begin: registerLoggingCallback('begin'),
       
   693 	// done: { failed, passed, total, runtime }
       
   694 	done: registerLoggingCallback('done'),
       
   695 	// log: { result, actual, expected, message }
       
   696 	log: registerLoggingCallback('log'),
       
   697 	// testStart: { name }
       
   698 	testStart: registerLoggingCallback('testStart'),
       
   699 	// testDone: { name, failed, passed, total }
       
   700 	testDone: registerLoggingCallback('testDone'),
       
   701 	// moduleStart: { name }
       
   702 	moduleStart: registerLoggingCallback('moduleStart'),
       
   703 	// moduleDone: { name, failed, passed, total }
       
   704 	moduleDone: registerLoggingCallback('moduleDone')
       
   705 });
       
   706 
       
   707 if ( typeof document === "undefined" || document.readyState === "complete" ) {
       
   708 	config.autorun = true;
       
   709 }
       
   710 
       
   711 QUnit.load = function() {
       
   712 	runLoggingCallbacks( 'begin', QUnit, {} );
       
   713 
       
   714 	// Initialize the config, saving the execution queue
       
   715 	var oldconfig = extend({}, config);
       
   716 	QUnit.init();
       
   717 	extend(config, oldconfig);
       
   718 
       
   719 	config.blocking = false;
       
   720 
       
   721 	var urlConfigHtml = '', len = config.urlConfig.length;
       
   722 	for ( var i = 0, val; i < len, val = config.urlConfig[i]; i++ ) {
       
   723 		config[val] = QUnit.urlParams[val];
       
   724 		urlConfigHtml += '<label><input name="' + val + '" type="checkbox"' + ( config[val] ? ' checked="checked"' : '' ) + '>' + val + '</label>';
       
   725 	}
       
   726 
       
   727 	var userAgent = id("qunit-userAgent");
       
   728 	if ( userAgent ) {
       
   729 		userAgent.innerHTML = navigator.userAgent;
       
   730 	}
       
   731 	var banner = id("qunit-header");
       
   732 	if ( banner ) {
       
   733 		banner.innerHTML = '<a href="' + QUnit.url({ filter: undefined }) + '"> ' + banner.innerHTML + '</a> ' + urlConfigHtml;
       
   734 		addEvent( banner, "change", function( event ) {
       
   735 			var params = {};
       
   736 			params[ event.target.name ] = event.target.checked ? true : undefined;
       
   737 			window.location = QUnit.url( params );
       
   738 		});
       
   739 	}
       
   740 
       
   741 	var toolbar = id("qunit-testrunner-toolbar");
       
   742 	if ( toolbar ) {
       
   743 		var filter = document.createElement("input");
       
   744 		filter.type = "checkbox";
       
   745 		filter.id = "qunit-filter-pass";
       
   746 		addEvent( filter, "click", function() {
       
   747 			var ol = document.getElementById("qunit-tests");
       
   748 			if ( filter.checked ) {
       
   749 				ol.className = ol.className + " hidepass";
       
   750 			} else {
       
   751 				var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
       
   752 				ol.className = tmp.replace(/ hidepass /, " ");
       
   753 			}
       
   754 			if ( defined.sessionStorage ) {
       
   755 				if (filter.checked) {
       
   756 					sessionStorage.setItem("qunit-filter-passed-tests", "true");
       
   757 				} else {
       
   758 					sessionStorage.removeItem("qunit-filter-passed-tests");
       
   759 				}
       
   760 			}
       
   761 		});
       
   762 		if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) {
       
   763 			filter.checked = true;
       
   764 			var ol = document.getElementById("qunit-tests");
       
   765 			ol.className = ol.className + " hidepass";
       
   766 		}
       
   767 		toolbar.appendChild( filter );
       
   768 
       
   769 		var label = document.createElement("label");
       
   770 		label.setAttribute("for", "qunit-filter-pass");
       
   771 		label.innerHTML = "Hide passed tests";
       
   772 		toolbar.appendChild( label );
       
   773 	}
       
   774 
       
   775 	var main = id('qunit-fixture');
       
   776 	if ( main ) {
       
   777 		config.fixture = main.innerHTML;
       
   778 	}
       
   779 
       
   780 	if (config.autostart) {
       
   781 		QUnit.start();
       
   782 	}
       
   783 };
       
   784 
       
   785 addEvent(window, "load", QUnit.load);
       
   786 
       
   787 function done() {
       
   788 	config.autorun = true;
       
   789 
       
   790 	// Log the last module results
       
   791 	if ( config.currentModule ) {
       
   792 		runLoggingCallbacks( 'moduleDone', QUnit, {
       
   793 			name: config.currentModule,
       
   794 			failed: config.moduleStats.bad,
       
   795 			passed: config.moduleStats.all - config.moduleStats.bad,
       
   796 			total: config.moduleStats.all
       
   797 		} );
       
   798 	}
       
   799 
       
   800 	var banner = id("qunit-banner"),
       
   801 		tests = id("qunit-tests"),
       
   802 		runtime = +new Date - config.started,
       
   803 		passed = config.stats.all - config.stats.bad,
       
   804 		html = [
       
   805 			'Tests completed in ',
       
   806 			runtime,
       
   807 			' milliseconds.<br/>',
       
   808 			'<span class="passed">',
       
   809 			passed,
       
   810 			'</span> tests of <span class="total">',
       
   811 			config.stats.all,
       
   812 			'</span> passed, <span class="failed">',
       
   813 			config.stats.bad,
       
   814 			'</span> failed.'
       
   815 		].join('');
       
   816 
       
   817 	if ( banner ) {
       
   818 		banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
       
   819 	}
       
   820 
       
   821 	if ( tests ) {
       
   822 		id( "qunit-testresult" ).innerHTML = html;
       
   823 	}
       
   824 
       
   825 	if ( config.altertitle && typeof document !== "undefined" && document.title ) {
       
   826 		// show ✖ for good, ✔ for bad suite result in title
       
   827 		// use escape sequences in case file gets loaded with non-utf-8-charset
       
   828 		document.title = [
       
   829 			(config.stats.bad ? "\u2716" : "\u2714"),
       
   830 			document.title.replace(/^[\u2714\u2716] /i, "")
       
   831 		].join(" ");
       
   832 	}
       
   833 
       
   834 	runLoggingCallbacks( 'done', QUnit, {
       
   835 		failed: config.stats.bad,
       
   836 		passed: passed,
       
   837 		total: config.stats.all,
       
   838 		runtime: runtime
       
   839 	} );
       
   840 }
       
   841 
       
   842 function validTest( name ) {
       
   843 	var filter = config.filter,
       
   844 		run = false;
       
   845 
       
   846 	if ( !filter ) {
       
   847 		return true;
       
   848 	}
       
   849 
       
   850 	var not = filter.charAt( 0 ) === "!";
       
   851 	if ( not ) {
       
   852 		filter = filter.slice( 1 );
       
   853 	}
       
   854 
       
   855 	if ( name.indexOf( filter ) !== -1 ) {
       
   856 		return !not;
       
   857 	}
       
   858 
       
   859 	if ( not ) {
       
   860 		run = true;
       
   861 	}
       
   862 
       
   863 	return run;
       
   864 }
       
   865 
       
   866 // so far supports only Firefox, Chrome and Opera (buggy)
       
   867 // could be extended in the future to use something like https://github.com/csnover/TraceKit
       
   868 function sourceFromStacktrace() {
       
   869 	try {
       
   870 		throw new Error();
       
   871 	} catch ( e ) {
       
   872 		if (e.stacktrace) {
       
   873 			// Opera
       
   874 			return e.stacktrace.split("\n")[6];
       
   875 		} else if (e.stack) {
       
   876 			// Firefox, Chrome
       
   877 			return e.stack.split("\n")[4];
       
   878 		} else if (e.sourceURL) {
       
   879 			// Safari, PhantomJS
       
   880 			// TODO sourceURL points at the 'throw new Error' line above, useless
       
   881 			//return e.sourceURL + ":" + e.line;
       
   882 		}
       
   883 	}
       
   884 }
       
   885 
       
   886 function escapeInnerText(s) {
       
   887 	if (!s) {
       
   888 		return "";
       
   889 	}
       
   890 	s = s + "";
       
   891 	return s.replace(/[\&<>]/g, function(s) {
       
   892 		switch(s) {
       
   893 			case "&": return "&amp;";
       
   894 			case "<": return "&lt;";
       
   895 			case ">": return "&gt;";
       
   896 			default: return s;
       
   897 		}
       
   898 	});
       
   899 }
       
   900 
       
   901 function synchronize( callback ) {
       
   902 	config.queue.push( callback );
       
   903 
       
   904 	if ( config.autorun && !config.blocking ) {
       
   905 		process();
       
   906 	}
       
   907 }
       
   908 
       
   909 function process() {
       
   910 	var start = (new Date()).getTime();
       
   911 
       
   912 	while ( config.queue.length && !config.blocking ) {
       
   913 		if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) {
       
   914 			config.queue.shift()();
       
   915 		} else {
       
   916 			window.setTimeout( process, 13 );
       
   917 			break;
       
   918 		}
       
   919 	}
       
   920 	if (!config.blocking && !config.queue.length) {
       
   921 		done();
       
   922 	}
       
   923 }
       
   924 
       
   925 function saveGlobal() {
       
   926 	config.pollution = [];
       
   927 
       
   928 	if ( config.noglobals ) {
       
   929 		for ( var key in window ) {
       
   930 			config.pollution.push( key );
       
   931 		}
       
   932 	}
       
   933 }
       
   934 
       
   935 function checkPollution( name ) {
       
   936 	var old = config.pollution;
       
   937 	saveGlobal();
       
   938 
       
   939 	var newGlobals = diff( config.pollution, old );
       
   940 	if ( newGlobals.length > 0 ) {
       
   941 		ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
       
   942 	}
       
   943 
       
   944 	var deletedGlobals = diff( old, config.pollution );
       
   945 	if ( deletedGlobals.length > 0 ) {
       
   946 		ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
       
   947 	}
       
   948 }
       
   949 
       
   950 // returns a new Array with the elements that are in a but not in b
       
   951 function diff( a, b ) {
       
   952 	var result = a.slice();
       
   953 	for ( var i = 0; i < result.length; i++ ) {
       
   954 		for ( var j = 0; j < b.length; j++ ) {
       
   955 			if ( result[i] === b[j] ) {
       
   956 				result.splice(i, 1);
       
   957 				i--;
       
   958 				break;
       
   959 			}
       
   960 		}
       
   961 	}
       
   962 	return result;
       
   963 }
       
   964 
       
   965 function fail(message, exception, callback) {
       
   966 	if ( typeof console !== "undefined" && console.error && console.warn ) {
       
   967 		console.error(message);
       
   968 		console.error(exception);
       
   969 		console.warn(callback.toString());
       
   970 
       
   971 	} else if ( window.opera && opera.postError ) {
       
   972 		opera.postError(message, exception, callback.toString);
       
   973 	}
       
   974 }
       
   975 
       
   976 function extend(a, b) {
       
   977 	for ( var prop in b ) {
       
   978 		if ( b[prop] === undefined ) {
       
   979 			delete a[prop];
       
   980 		} else {
       
   981 			a[prop] = b[prop];
       
   982 		}
       
   983 	}
       
   984 
       
   985 	return a;
       
   986 }
       
   987 
       
   988 function addEvent(elem, type, fn) {
       
   989 	if ( elem.addEventListener ) {
       
   990 		elem.addEventListener( type, fn, false );
       
   991 	} else if ( elem.attachEvent ) {
       
   992 		elem.attachEvent( "on" + type, fn );
       
   993 	} else {
       
   994 		fn();
       
   995 	}
       
   996 }
       
   997 
       
   998 function id(name) {
       
   999 	return !!(typeof document !== "undefined" && document && document.getElementById) &&
       
  1000 		document.getElementById( name );
       
  1001 }
       
  1002 
       
  1003 function registerLoggingCallback(key){
       
  1004 	return function(callback){
       
  1005 		config[key].push( callback );
       
  1006 	};
       
  1007 }
       
  1008 
       
  1009 // Supports deprecated method of completely overwriting logging callbacks
       
  1010 function runLoggingCallbacks(key, scope, args) {
       
  1011 	//debugger;
       
  1012 	var callbacks;
       
  1013 	if ( QUnit.hasOwnProperty(key) ) {
       
  1014 		QUnit[key].call(scope, args);
       
  1015 	} else {
       
  1016 		callbacks = config[key];
       
  1017 		for( var i = 0; i < callbacks.length; i++ ) {
       
  1018 			callbacks[i].call( scope, args );
       
  1019 		}
       
  1020 	}
       
  1021 }
       
  1022 
       
  1023 // Test for equality any JavaScript type.
       
  1024 // Author: Philippe Rathé <prathe@gmail.com>
       
  1025 QUnit.equiv = function () {
       
  1026 
       
  1027 	var innerEquiv; // the real equiv function
       
  1028 	var callers = []; // stack to decide between skip/abort functions
       
  1029 	var parents = []; // stack to avoiding loops from circular referencing
       
  1030 
       
  1031 	// Call the o related callback with the given arguments.
       
  1032 	function bindCallbacks(o, callbacks, args) {
       
  1033 		var prop = QUnit.objectType(o);
       
  1034 		if (prop) {
       
  1035 			if (QUnit.objectType(callbacks[prop]) === "function") {
       
  1036 				return callbacks[prop].apply(callbacks, args);
       
  1037 			} else {
       
  1038 				return callbacks[prop]; // or undefined
       
  1039 			}
       
  1040 		}
       
  1041 	}
       
  1042 
       
  1043 	var callbacks = function () {
       
  1044 
       
  1045 		// for string, boolean, number and null
       
  1046 		function useStrictEquality(b, a) {
       
  1047 			if (b instanceof a.constructor || a instanceof b.constructor) {
       
  1048 				// to catch short annotaion VS 'new' annotation of a
       
  1049 				// declaration
       
  1050 				// e.g. var i = 1;
       
  1051 				// var j = new Number(1);
       
  1052 				return a == b;
       
  1053 			} else {
       
  1054 				return a === b;
       
  1055 			}
       
  1056 		}
       
  1057 
       
  1058 		return {
       
  1059 			"string" : useStrictEquality,
       
  1060 			"boolean" : useStrictEquality,
       
  1061 			"number" : useStrictEquality,
       
  1062 			"null" : useStrictEquality,
       
  1063 			"undefined" : useStrictEquality,
       
  1064 
       
  1065 			"nan" : function(b) {
       
  1066 				return isNaN(b);
       
  1067 			},
       
  1068 
       
  1069 			"date" : function(b, a) {
       
  1070 				return QUnit.objectType(b) === "date"
       
  1071 						&& a.valueOf() === b.valueOf();
       
  1072 			},
       
  1073 
       
  1074 			"regexp" : function(b, a) {
       
  1075 				return QUnit.objectType(b) === "regexp"
       
  1076 						&& a.source === b.source && // the regex itself
       
  1077 						a.global === b.global && // and its modifers
       
  1078 													// (gmi) ...
       
  1079 						a.ignoreCase === b.ignoreCase
       
  1080 						&& a.multiline === b.multiline;
       
  1081 			},
       
  1082 
       
  1083 			// - skip when the property is a method of an instance (OOP)
       
  1084 			// - abort otherwise,
       
  1085 			// initial === would have catch identical references anyway
       
  1086 			"function" : function() {
       
  1087 				var caller = callers[callers.length - 1];
       
  1088 				return caller !== Object && typeof caller !== "undefined";
       
  1089 			},
       
  1090 
       
  1091 			"array" : function(b, a) {
       
  1092 				var i, j, loop;
       
  1093 				var len;
       
  1094 
       
  1095 				// b could be an object literal here
       
  1096 				if (!(QUnit.objectType(b) === "array")) {
       
  1097 					return false;
       
  1098 				}
       
  1099 
       
  1100 				len = a.length;
       
  1101 				if (len !== b.length) { // safe and faster
       
  1102 					return false;
       
  1103 				}
       
  1104 
       
  1105 				// track reference to avoid circular references
       
  1106 				parents.push(a);
       
  1107 				for (i = 0; i < len; i++) {
       
  1108 					loop = false;
       
  1109 					for (j = 0; j < parents.length; j++) {
       
  1110 						if (parents[j] === a[i]) {
       
  1111 							loop = true;// dont rewalk array
       
  1112 						}
       
  1113 					}
       
  1114 					if (!loop && !innerEquiv(a[i], b[i])) {
       
  1115 						parents.pop();
       
  1116 						return false;
       
  1117 					}
       
  1118 				}
       
  1119 				parents.pop();
       
  1120 				return true;
       
  1121 			},
       
  1122 
       
  1123 			"object" : function(b, a) {
       
  1124 				var i, j, loop;
       
  1125 				var eq = true; // unless we can proove it
       
  1126 				var aProperties = [], bProperties = []; // collection of
       
  1127 														// strings
       
  1128 
       
  1129 				// comparing constructors is more strict than using
       
  1130 				// instanceof
       
  1131 				if (a.constructor !== b.constructor) {
       
  1132 					return false;
       
  1133 				}
       
  1134 
       
  1135 				// stack constructor before traversing properties
       
  1136 				callers.push(a.constructor);
       
  1137 				// track reference to avoid circular references
       
  1138 				parents.push(a);
       
  1139 
       
  1140 				for (i in a) { // be strict: don't ensures hasOwnProperty
       
  1141 								// and go deep
       
  1142 					loop = false;
       
  1143 					for (j = 0; j < parents.length; j++) {
       
  1144 						if (parents[j] === a[i])
       
  1145 							loop = true; // don't go down the same path
       
  1146 											// twice
       
  1147 					}
       
  1148 					aProperties.push(i); // collect a's properties
       
  1149 
       
  1150 					if (!loop && !innerEquiv(a[i], b[i])) {
       
  1151 						eq = false;
       
  1152 						break;
       
  1153 					}
       
  1154 				}
       
  1155 
       
  1156 				callers.pop(); // unstack, we are done
       
  1157 				parents.pop();
       
  1158 
       
  1159 				for (i in b) {
       
  1160 					bProperties.push(i); // collect b's properties
       
  1161 				}
       
  1162 
       
  1163 				// Ensures identical properties name
       
  1164 				return eq
       
  1165 						&& innerEquiv(aProperties.sort(), bProperties
       
  1166 								.sort());
       
  1167 			}
       
  1168 		};
       
  1169 	}();
       
  1170 
       
  1171 	innerEquiv = function() { // can take multiple arguments
       
  1172 		var args = Array.prototype.slice.apply(arguments);
       
  1173 		if (args.length < 2) {
       
  1174 			return true; // end transition
       
  1175 		}
       
  1176 
       
  1177 		return (function(a, b) {
       
  1178 			if (a === b) {
       
  1179 				return true; // catch the most you can
       
  1180 			} else if (a === null || b === null || typeof a === "undefined"
       
  1181 					|| typeof b === "undefined"
       
  1182 					|| QUnit.objectType(a) !== QUnit.objectType(b)) {
       
  1183 				return false; // don't lose time with error prone cases
       
  1184 			} else {
       
  1185 				return bindCallbacks(a, callbacks, [ b, a ]);
       
  1186 			}
       
  1187 
       
  1188 			// apply transition with (1..n) arguments
       
  1189 		})(args[0], args[1])
       
  1190 				&& arguments.callee.apply(this, args.splice(1,
       
  1191 						args.length - 1));
       
  1192 	};
       
  1193 
       
  1194 	return innerEquiv;
       
  1195 
       
  1196 }();
       
  1197 
       
  1198 /**
       
  1199  * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
       
  1200  * http://flesler.blogspot.com Licensed under BSD
       
  1201  * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
       
  1202  *
       
  1203  * @projectDescription Advanced and extensible data dumping for Javascript.
       
  1204  * @version 1.0.0
       
  1205  * @author Ariel Flesler
       
  1206  * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
       
  1207  */
       
  1208 QUnit.jsDump = (function() {
       
  1209 	function quote( str ) {
       
  1210 		return '"' + str.toString().replace(/"/g, '\\"') + '"';
       
  1211 	};
       
  1212 	function literal( o ) {
       
  1213 		return o + '';
       
  1214 	};
       
  1215 	function join( pre, arr, post ) {
       
  1216 		var s = jsDump.separator(),
       
  1217 			base = jsDump.indent(),
       
  1218 			inner = jsDump.indent(1);
       
  1219 		if ( arr.join )
       
  1220 			arr = arr.join( ',' + s + inner );
       
  1221 		if ( !arr )
       
  1222 			return pre + post;
       
  1223 		return [ pre, inner + arr, base + post ].join(s);
       
  1224 	};
       
  1225 	function array( arr, stack ) {
       
  1226 		var i = arr.length, ret = Array(i);
       
  1227 		this.up();
       
  1228 		while ( i-- )
       
  1229 			ret[i] = this.parse( arr[i] , undefined , stack);
       
  1230 		this.down();
       
  1231 		return join( '[', ret, ']' );
       
  1232 	};
       
  1233 
       
  1234 	var reName = /^function (\w+)/;
       
  1235 
       
  1236 	var jsDump = {
       
  1237 		parse:function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance
       
  1238 			stack = stack || [ ];
       
  1239 			var parser = this.parsers[ type || this.typeOf(obj) ];
       
  1240 			type = typeof parser;
       
  1241 			var inStack = inArray(obj, stack);
       
  1242 			if (inStack != -1) {
       
  1243 				return 'recursion('+(inStack - stack.length)+')';
       
  1244 			}
       
  1245 			//else
       
  1246 			if (type == 'function')  {
       
  1247 					stack.push(obj);
       
  1248 					var res = parser.call( this, obj, stack );
       
  1249 					stack.pop();
       
  1250 					return res;
       
  1251 			}
       
  1252 			// else
       
  1253 			return (type == 'string') ? parser : this.parsers.error;
       
  1254 		},
       
  1255 		typeOf:function( obj ) {
       
  1256 			var type;
       
  1257 			if ( obj === null ) {
       
  1258 				type = "null";
       
  1259 			} else if (typeof obj === "undefined") {
       
  1260 				type = "undefined";
       
  1261 			} else if (QUnit.is("RegExp", obj)) {
       
  1262 				type = "regexp";
       
  1263 			} else if (QUnit.is("Date", obj)) {
       
  1264 				type = "date";
       
  1265 			} else if (QUnit.is("Function", obj)) {
       
  1266 				type = "function";
       
  1267 			} else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") {
       
  1268 				type = "window";
       
  1269 			} else if (obj.nodeType === 9) {
       
  1270 				type = "document";
       
  1271 			} else if (obj.nodeType) {
       
  1272 				type = "node";
       
  1273 			} else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) {
       
  1274 				type = "array";
       
  1275 			} else {
       
  1276 				type = typeof obj;
       
  1277 			}
       
  1278 			return type;
       
  1279 		},
       
  1280 		separator:function() {
       
  1281 			return this.multiline ?	this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
       
  1282 		},
       
  1283 		indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
       
  1284 			if ( !this.multiline )
       
  1285 				return '';
       
  1286 			var chr = this.indentChar;
       
  1287 			if ( this.HTML )
       
  1288 				chr = chr.replace(/\t/g,'   ').replace(/ /g,'&nbsp;');
       
  1289 			return Array( this._depth_ + (extra||0) ).join(chr);
       
  1290 		},
       
  1291 		up:function( a ) {
       
  1292 			this._depth_ += a || 1;
       
  1293 		},
       
  1294 		down:function( a ) {
       
  1295 			this._depth_ -= a || 1;
       
  1296 		},
       
  1297 		setParser:function( name, parser ) {
       
  1298 			this.parsers[name] = parser;
       
  1299 		},
       
  1300 		// The next 3 are exposed so you can use them
       
  1301 		quote:quote,
       
  1302 		literal:literal,
       
  1303 		join:join,
       
  1304 		//
       
  1305 		_depth_: 1,
       
  1306 		// This is the list of parsers, to modify them, use jsDump.setParser
       
  1307 		parsers:{
       
  1308 			window: '[Window]',
       
  1309 			document: '[Document]',
       
  1310 			error:'[ERROR]', //when no parser is found, shouldn't happen
       
  1311 			unknown: '[Unknown]',
       
  1312 			'null':'null',
       
  1313 			'undefined':'undefined',
       
  1314 			'function':function( fn ) {
       
  1315 				var ret = 'function',
       
  1316 					name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
       
  1317 				if ( name )
       
  1318 					ret += ' ' + name;
       
  1319 				ret += '(';
       
  1320 
       
  1321 				ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join('');
       
  1322 				return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' );
       
  1323 			},
       
  1324 			array: array,
       
  1325 			nodelist: array,
       
  1326 			arguments: array,
       
  1327 			object:function( map, stack ) {
       
  1328 				var ret = [ ];
       
  1329 				QUnit.jsDump.up();
       
  1330 				for ( var key in map ) {
       
  1331 				    var val = map[key];
       
  1332 					ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(val, undefined, stack));
       
  1333                 }
       
  1334 				QUnit.jsDump.down();
       
  1335 				return join( '{', ret, '}' );
       
  1336 			},
       
  1337 			node:function( node ) {
       
  1338 				var open = QUnit.jsDump.HTML ? '&lt;' : '<',
       
  1339 					close = QUnit.jsDump.HTML ? '&gt;' : '>';
       
  1340 
       
  1341 				var tag = node.nodeName.toLowerCase(),
       
  1342 					ret = open + tag;
       
  1343 
       
  1344 				for ( var a in QUnit.jsDump.DOMAttrs ) {
       
  1345 					var val = node[QUnit.jsDump.DOMAttrs[a]];
       
  1346 					if ( val )
       
  1347 						ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' );
       
  1348 				}
       
  1349 				return ret + close + open + '/' + tag + close;
       
  1350 			},
       
  1351 			functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
       
  1352 				var l = fn.length;
       
  1353 				if ( !l ) return '';
       
  1354 
       
  1355 				var args = Array(l);
       
  1356 				while ( l-- )
       
  1357 					args[l] = String.fromCharCode(97+l);//97 is 'a'
       
  1358 				return ' ' + args.join(', ') + ' ';
       
  1359 			},
       
  1360 			key:quote, //object calls it internally, the key part of an item in a map
       
  1361 			functionCode:'[code]', //function calls it internally, it's the content of the function
       
  1362 			attribute:quote, //node calls it internally, it's an html attribute value
       
  1363 			string:quote,
       
  1364 			date:quote,
       
  1365 			regexp:literal, //regex
       
  1366 			number:literal,
       
  1367 			'boolean':literal
       
  1368 		},
       
  1369 		DOMAttrs:{//attributes to dump from nodes, name=>realName
       
  1370 			id:'id',
       
  1371 			name:'name',
       
  1372 			'class':'className'
       
  1373 		},
       
  1374 		HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
       
  1375 		indentChar:'  ',//indentation unit
       
  1376 		multiline:true //if true, items in a collection, are separated by a \n, else just a space.
       
  1377 	};
       
  1378 
       
  1379 	return jsDump;
       
  1380 })();
       
  1381 
       
  1382 // from Sizzle.js
       
  1383 function getText( elems ) {
       
  1384 	var ret = "", elem;
       
  1385 
       
  1386 	for ( var i = 0; elems[i]; i++ ) {
       
  1387 		elem = elems[i];
       
  1388 
       
  1389 		// Get the text from text nodes and CDATA nodes
       
  1390 		if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
       
  1391 			ret += elem.nodeValue;
       
  1392 
       
  1393 		// Traverse everything else, except comment nodes
       
  1394 		} else if ( elem.nodeType !== 8 ) {
       
  1395 			ret += getText( elem.childNodes );
       
  1396 		}
       
  1397 	}
       
  1398 
       
  1399 	return ret;
       
  1400 };
       
  1401 
       
  1402 //from jquery.js
       
  1403 function inArray( elem, array ) {
       
  1404 	if ( array.indexOf ) {
       
  1405 		return array.indexOf( elem );
       
  1406 	}
       
  1407 
       
  1408 	for ( var i = 0, length = array.length; i < length; i++ ) {
       
  1409 		if ( array[ i ] === elem ) {
       
  1410 			return i;
       
  1411 		}
       
  1412 	}
       
  1413 
       
  1414 	return -1;
       
  1415 }
       
  1416 
       
  1417 /*
       
  1418  * Javascript Diff Algorithm
       
  1419  *  By John Resig (http://ejohn.org/)
       
  1420  *  Modified by Chu Alan "sprite"
       
  1421  *
       
  1422  * Released under the MIT license.
       
  1423  *
       
  1424  * More Info:
       
  1425  *  http://ejohn.org/projects/javascript-diff-algorithm/
       
  1426  *
       
  1427  * Usage: QUnit.diff(expected, actual)
       
  1428  *
       
  1429  * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the  quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
       
  1430  */
       
  1431 QUnit.diff = (function() {
       
  1432 	function diff(o, n) {
       
  1433 		var ns = {};
       
  1434 		var os = {};
       
  1435 
       
  1436 		for (var i = 0; i < n.length; i++) {
       
  1437 			if (ns[n[i]] == null)
       
  1438 				ns[n[i]] = {
       
  1439 					rows: [],
       
  1440 					o: null
       
  1441 				};
       
  1442 			ns[n[i]].rows.push(i);
       
  1443 		}
       
  1444 
       
  1445 		for (var i = 0; i < o.length; i++) {
       
  1446 			if (os[o[i]] == null)
       
  1447 				os[o[i]] = {
       
  1448 					rows: [],
       
  1449 					n: null
       
  1450 				};
       
  1451 			os[o[i]].rows.push(i);
       
  1452 		}
       
  1453 
       
  1454 		for (var i in ns) {
       
  1455 			if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
       
  1456 				n[ns[i].rows[0]] = {
       
  1457 					text: n[ns[i].rows[0]],
       
  1458 					row: os[i].rows[0]
       
  1459 				};
       
  1460 				o[os[i].rows[0]] = {
       
  1461 					text: o[os[i].rows[0]],
       
  1462 					row: ns[i].rows[0]
       
  1463 				};
       
  1464 			}
       
  1465 		}
       
  1466 
       
  1467 		for (var i = 0; i < n.length - 1; i++) {
       
  1468 			if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
       
  1469 			n[i + 1] == o[n[i].row + 1]) {
       
  1470 				n[i + 1] = {
       
  1471 					text: n[i + 1],
       
  1472 					row: n[i].row + 1
       
  1473 				};
       
  1474 				o[n[i].row + 1] = {
       
  1475 					text: o[n[i].row + 1],
       
  1476 					row: i + 1
       
  1477 				};
       
  1478 			}
       
  1479 		}
       
  1480 
       
  1481 		for (var i = n.length - 1; i > 0; i--) {
       
  1482 			if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
       
  1483 			n[i - 1] == o[n[i].row - 1]) {
       
  1484 				n[i - 1] = {
       
  1485 					text: n[i - 1],
       
  1486 					row: n[i].row - 1
       
  1487 				};
       
  1488 				o[n[i].row - 1] = {
       
  1489 					text: o[n[i].row - 1],
       
  1490 					row: i - 1
       
  1491 				};
       
  1492 			}
       
  1493 		}
       
  1494 
       
  1495 		return {
       
  1496 			o: o,
       
  1497 			n: n
       
  1498 		};
       
  1499 	}
       
  1500 
       
  1501 	return function(o, n) {
       
  1502 		o = o.replace(/\s+$/, '');
       
  1503 		n = n.replace(/\s+$/, '');
       
  1504 		var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));
       
  1505 
       
  1506 		var str = "";
       
  1507 
       
  1508 		var oSpace = o.match(/\s+/g);
       
  1509 		if (oSpace == null) {
       
  1510 			oSpace = [" "];
       
  1511 		}
       
  1512 		else {
       
  1513 			oSpace.push(" ");
       
  1514 		}
       
  1515 		var nSpace = n.match(/\s+/g);
       
  1516 		if (nSpace == null) {
       
  1517 			nSpace = [" "];
       
  1518 		}
       
  1519 		else {
       
  1520 			nSpace.push(" ");
       
  1521 		}
       
  1522 
       
  1523 		if (out.n.length == 0) {
       
  1524 			for (var i = 0; i < out.o.length; i++) {
       
  1525 				str += '<del>' + out.o[i] + oSpace[i] + "</del>";
       
  1526 			}
       
  1527 		}
       
  1528 		else {
       
  1529 			if (out.n[0].text == null) {
       
  1530 				for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
       
  1531 					str += '<del>' + out.o[n] + oSpace[n] + "</del>";
       
  1532 				}
       
  1533 			}
       
  1534 
       
  1535 			for (var i = 0; i < out.n.length; i++) {
       
  1536 				if (out.n[i].text == null) {
       
  1537 					str += '<ins>' + out.n[i] + nSpace[i] + "</ins>";
       
  1538 				}
       
  1539 				else {
       
  1540 					var pre = "";
       
  1541 
       
  1542 					for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
       
  1543 						pre += '<del>' + out.o[n] + oSpace[n] + "</del>";
       
  1544 					}
       
  1545 					str += " " + out.n[i].text + nSpace[i] + pre;
       
  1546 				}
       
  1547 			}
       
  1548 		}
       
  1549 
       
  1550 		return str;
       
  1551 	};
       
  1552 })();
       
  1553 
       
  1554 })(this);