Subversion-Projekte lars-tiefland.cienc

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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