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