1/**
2 * QUnit - A JavaScript Unit Testing Framework
3 *
4 * http://docs.jquery.com/QUnit
5 *
6 * Copyright (c) 2011 John Resig, Jörn Zaefferer
7 * Dual licensed under the MIT (MIT-LICENSE.txt)
8 * or GPL (GPL-LICENSE.txt) licenses.
9 */
10
11(function(window) {
12
13var 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
24var testId = 0;
25
26var 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};
35Test.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
252var 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
431QUnit.equals = QUnit.equal;
432QUnit.same = QUnit.deepEqual;
433
434// Maintain internal state
435var 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
480if ( 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
489extend(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
669if ( typeof document === "undefined" || document.readyState === "complete" ) {
670	config.autorun = true;
671}
672
673addEvent(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
743function 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
795function 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
821function 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
835function 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
852function synchronize( callback ) {
853	config.queue.push( callback );
854
855	if ( config.autorun && !config.blocking ) {
856		process();
857	}
858}
859
860function 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
876function saveGlobal() {
877	config.pollution = [];
878
879	if ( config.noglobals ) {
880		for ( var key in window ) {
881			config.pollution.push( key );
882		}
883	}
884}
885
886function 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
902function 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
916function 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
927function 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
939function 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
949function 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@gmail.com>
958QUnit.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 */
1129QUnit.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
1292function 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 */
1325QUnit.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