Project

General

Profile

1
/*
2
 * QUnit - A JavaScript Unit Testing Framework
3
 * 
4
 * http://docs.jquery.com/QUnit
5
 *
6
 * Copyright (c) 2009 John Resig, Jörn Zaefferer
7
 * Dual licensed under the MIT (MIT-LICENSE.txt)
8
 * and GPL (GPL-LICENSE.txt) licenses.
9
 */
10

    
11
(function(window) {
12

    
13
var QUnit = {
14

    
15
	// call on start of module test to prepend name to all tests
16
	module: function(name, testEnvironment) {
17
		config.currentModule = name;
18

    
19
		synchronize(function() {
20
			if ( config.currentModule ) {
21
				QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
22
			}
23

    
24
			config.currentModule = name;
25
			config.moduleTestEnvironment = testEnvironment;
26
			config.moduleStats = { all: 0, bad: 0 };
27

    
28
			QUnit.moduleStart( name, testEnvironment );
29
		});
30
	},
31

    
32
	asyncTest: function(testName, expected, callback) {
33
		if ( arguments.length === 2 ) {
34
			callback = expected;
35
			expected = 0;
36
		}
37

    
38
		QUnit.test(testName, expected, callback, true);
39
	},
40
	
41
	test: function(testName, expected, callback, async) {
42
		var name = '<span class="test-name">' + testName + '</span>', testEnvironment, testEnvironmentArg;
43

    
44
		if ( arguments.length === 2 ) {
45
			callback = expected;
46
			expected = null;
47
		}
48
		// is 2nd argument a testEnvironment?
49
		if ( expected && typeof expected === 'object') {
50
			testEnvironmentArg =  expected;
51
			expected = null;
52
		}
53

    
54
		if ( config.currentModule ) {
55
			name = '<span class="module-name">' + config.currentModule + "</span>: " + name;
56
		}
57

    
58
		if ( !validTest(config.currentModule + ": " + testName) ) {
59
			return;
60
		}
61

    
62
		synchronize(function() {
63

    
64
			testEnvironment = extend({
65
				setup: function() {},
66
				teardown: function() {}
67
			}, config.moduleTestEnvironment);
68
			if (testEnvironmentArg) {
69
				extend(testEnvironment,testEnvironmentArg);
70
			}
71

    
72
			QUnit.testStart( testName, testEnvironment );
73

    
74
			// allow utility functions to access the current test environment
75
			QUnit.current_testEnvironment = testEnvironment;
76
			
77
			config.assertions = [];
78
			config.expected = expected;
79
			
80
			var tests = id("qunit-tests");
81
			if (tests) {
82
				var b = document.createElement("strong");
83
					b.innerHTML = "Running " + name;
84
				var li = document.createElement("li");
85
					li.appendChild( b );
86
					li.id = "current-test-output";
87
				tests.appendChild( li )
88
			}
89

    
90
			try {
91
				if ( !config.pollution ) {
92
					saveGlobal();
93
				}
94

    
95
				testEnvironment.setup.call(testEnvironment);
96
			} catch(e) {
97
				QUnit.ok( false, "Setup failed on " + name + ": " + e.message );
98
			}
99
	    });
100
	
101
	    synchronize(function() {
102
			if ( async ) {
103
				QUnit.stop();
104
			}
105

    
106
			try {
107
				callback.call(testEnvironment);
108
			} catch(e) {
109
				fail("Test " + name + " died, exception and test follows", e, callback);
110
				QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message );
111
				// else next test will carry the responsibility
112
				saveGlobal();
113

    
114
				// Restart the tests if they're blocking
115
				if ( config.blocking ) {
116
					start();
117
				}
118
			}
119
		});
120

    
121
		synchronize(function() {
122
			try {
123
				checkPollution();
124
				testEnvironment.teardown.call(testEnvironment);
125
			} catch(e) {
126
				QUnit.ok( false, "Teardown failed on " + name + ": " + e.message );
127
			}
128
	    });
129
	
130
	    synchronize(function() {
131
			try {
132
				QUnit.reset();
133
			} catch(e) {
134
				fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset);
135
			}
136

    
137
			if ( config.expected && config.expected != config.assertions.length ) {
138
				QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" );
139
			}
140

    
141
			var good = 0, bad = 0,
142
				tests = id("qunit-tests");
143

    
144
			config.stats.all += config.assertions.length;
145
			config.moduleStats.all += config.assertions.length;
146

    
147
			if ( tests ) {
148
				var ol  = document.createElement("ol");
149

    
150
				for ( var i = 0; i < config.assertions.length; i++ ) {
151
					var assertion = config.assertions[i];
152

    
153
					var li = document.createElement("li");
154
					li.className = assertion.result ? "pass" : "fail";
155
					li.innerHTML = assertion.message || "(no message)";
156
					ol.appendChild( li );
157

    
158
					if ( assertion.result ) {
159
						good++;
160
					} else {
161
						bad++;
162
						config.stats.bad++;
163
						config.moduleStats.bad++;
164
					}
165
				}
166
				if (bad == 0) {
167
					ol.style.display = "none";
168
				}
169

    
170
				var b = document.createElement("strong");
171
				b.innerHTML = name + " <b style='color:black;'>(<b class='fail'>" + bad + "</b>, <b class='pass'>" + good + "</b>, " + config.assertions.length + ")</b>";
172
				
173
				addEvent(b, "click", function() {
174
					var next = b.nextSibling, display = next.style.display;
175
					next.style.display = display === "none" ? "block" : "none";
176
				});
177
				
178
				addEvent(b, "dblclick", function(e) {
179
					var target = e && e.target ? e.target : window.event.srcElement;
180
					if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
181
						target = target.parentNode;
182
					}
183
					if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
184
						window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, ""));
185
					}
186
				});
187

    
188
				var li = id("current-test-output");
189
				li.id = "";
190
				li.className = bad ? "fail" : "pass";
191
				li.removeChild( li.firstChild );
192
				li.appendChild( b );
193
				li.appendChild( ol );
194

    
195
				if ( bad ) {
196
					var toolbar = id("qunit-testrunner-toolbar");
197
					if ( toolbar ) {
198
						toolbar.style.display = "block";
199
						id("qunit-filter-pass").disabled = null;
200
						id("qunit-filter-missing").disabled = null;
201
					}
202
				}
203

    
204
			} else {
205
				for ( var i = 0; i < config.assertions.length; i++ ) {
206
					if ( !config.assertions[i].result ) {
207
						bad++;
208
						config.stats.bad++;
209
						config.moduleStats.bad++;
210
					}
211
				}
212
			}
213

    
214
			QUnit.testDone( testName, bad, config.assertions.length );
215

    
216
			if ( !window.setTimeout && !config.queue.length ) {
217
				done();
218
			}
219
		});
220

    
221
		synchronize( done );
222
	},
223
	
224
	/**
225
	 * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
226
	 */
227
	expect: function(asserts) {
228
		config.expected = asserts;
229
	},
230

    
231
	/**
232
	 * Asserts true.
233
	 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
234
	 */
235
	ok: function(a, msg) {
236
		msg = escapeHtml(msg);
237
		QUnit.log(a, msg);
238

    
239
		config.assertions.push({
240
			result: !!a,
241
			message: msg
242
		});
243
	},
244

    
245
	/**
246
	 * Checks that the first two arguments are equal, with an optional message.
247
	 * Prints out both actual and expected values.
248
	 *
249
	 * Prefered to ok( actual == expected, message )
250
	 *
251
	 * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
252
	 *
253
	 * @param Object actual
254
	 * @param Object expected
255
	 * @param String message (optional)
256
	 */
257
	equal: function(actual, expected, message) {
258
		push(expected == actual, actual, expected, message);
259
	},
260

    
261
	notEqual: function(actual, expected, message) {
262
		push(expected != actual, actual, expected, message);
263
	},
264
	
265
	deepEqual: function(actual, expected, message) {
266
		push(QUnit.equiv(actual, expected), actual, expected, message);
267
	},
268

    
269
	notDeepEqual: function(actual, expected, message) {
270
		push(!QUnit.equiv(actual, expected), actual, expected, message);
271
	},
272

    
273
	strictEqual: function(actual, expected, message) {
274
		push(expected === actual, actual, expected, message);
275
	},
276

    
277
	notStrictEqual: function(actual, expected, message) {
278
		push(expected !== actual, actual, expected, message);
279
	},
280

    
281
	raises: function(fn,  message) {
282
		try {
283
			fn();
284
			ok( false, message );
285
		}
286
		catch (e) {
287
			ok( true, message );
288
		}
289
	},
290

    
291
	start: function() {
292
		// A slight delay, to avoid any current callbacks
293
		if ( window.setTimeout ) {
294
			window.setTimeout(function() {
295
				if ( config.timeout ) {
296
					clearTimeout(config.timeout);
297
				}
298

    
299
				config.blocking = false;
300
				process();
301
			}, 13);
302
		} else {
303
			config.blocking = false;
304
			process();
305
		}
306
	},
307
	
308
	stop: function(timeout) {
309
		config.blocking = true;
310

    
311
		if ( timeout && window.setTimeout ) {
312
			config.timeout = window.setTimeout(function() {
313
				QUnit.ok( false, "Test timed out" );
314
				QUnit.start();
315
			}, timeout);
316
		}
317
	}
318

    
319
};
320

    
321
// Backwards compatibility, deprecated
322
QUnit.equals = QUnit.equal;
323
QUnit.same = QUnit.deepEqual;
324

    
325
// Maintain internal state
326
var config = {
327
	// The queue of tests to run
328
	queue: [],
329

    
330
	// block until document ready
331
	blocking: true
332
};
333

    
334
// Load paramaters
335
(function() {
336
	var location = window.location || { search: "", protocol: "file:" },
337
		GETParams = location.search.slice(1).split('&');
338

    
339
	for ( var i = 0; i < GETParams.length; i++ ) {
340
		GETParams[i] = decodeURIComponent( GETParams[i] );
341
		if ( GETParams[i] === "noglobals" ) {
342
			GETParams.splice( i, 1 );
343
			i--;
344
			config.noglobals = true;
345
		} else if ( GETParams[i].search('=') > -1 ) {
346
			GETParams.splice( i, 1 );
347
			i--;
348
		}
349
	}
350
	
351
	// restrict modules/tests by get parameters
352
	config.filters = GETParams;
353
	
354
	// Figure out if we're running the tests from a server or not
355
	QUnit.isLocal = !!(location.protocol === 'file:');
356
})();
357

    
358
// Expose the API as global variables, unless an 'exports'
359
// object exists, in that case we assume we're in CommonJS
360
if ( typeof exports === "undefined" || typeof require === "undefined" ) {
361
	extend(window, QUnit);
362
	window.QUnit = QUnit;
363
} else {
364
	extend(exports, QUnit);
365
	exports.QUnit = QUnit;
366
}
367

    
368
// define these after exposing globals to keep them in these QUnit namespace only
369
extend(QUnit, {
370
	config: config,
371

    
372
	// Initialize the configuration options
373
	init: function() {
374
		extend(config, {
375
			stats: { all: 0, bad: 0 },
376
			moduleStats: { all: 0, bad: 0 },
377
			started: +new Date,
378
			updateRate: 1000,
379
			blocking: false,
380
			autostart: true,
381
			autorun: false,
382
			assertions: [],
383
			filters: [],
384
			queue: []
385
		});
386

    
387
		var tests = id("qunit-tests"),
388
			banner = id("qunit-banner"),
389
			result = id("qunit-testresult");
390

    
391
		if ( tests ) {
392
			tests.innerHTML = "";
393
		}
394

    
395
		if ( banner ) {
396
			banner.className = "";
397
		}
398

    
399
		if ( result ) {
400
			result.parentNode.removeChild( result );
401
		}
402
	},
403
	
404
	/**
405
	 * Resets the test setup. Useful for tests that modify the DOM.
406
	 */
407
	reset: function() {
408
		if ( window.jQuery ) {
409
			jQuery("#main, #qunit-fixture").html( config.fixture );
410
		}
411
	},
412
	
413
	/**
414
	 * Trigger an event on an element.
415
	 *
416
	 * @example triggerEvent( document.body, "click" );
417
	 *
418
	 * @param DOMElement elem
419
	 * @param String type
420
	 */
421
	triggerEvent: function( elem, type, event ) {
422
		if ( document.createEvent ) {
423
			event = document.createEvent("MouseEvents");
424
			event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
425
				0, 0, 0, 0, 0, false, false, false, false, 0, null);
426
			elem.dispatchEvent( event );
427

    
428
		} else if ( elem.fireEvent ) {
429
			elem.fireEvent("on"+type);
430
		}
431
	},
432
	
433
	// Safe object type checking
434
	is: function( type, obj ) {
435
		return QUnit.objectType( obj ) == type;
436
	},
437
	
438
	objectType: function( obj ) {
439
		if (typeof obj === "undefined") {
440
				return "undefined";
441

    
442
		// consider: typeof null === object
443
		}
444
		if (obj === null) {
445
				return "null";
446
		}
447

    
448
		var type = Object.prototype.toString.call( obj )
449
			.match(/^\[object\s(.*)\]$/)[1] || '';
450

    
451
		switch (type) {
452
				case 'Number':
453
						if (isNaN(obj)) {
454
								return "nan";
455
						} else {
456
								return "number";
457
						}
458
				case 'String':
459
				case 'Boolean':
460
				case 'Array':
461
				case 'Date':
462
				case 'RegExp':
463
				case 'Function':
464
						return type.toLowerCase();
465
		}
466
		if (typeof obj === "object") {
467
				return "object";
468
		}
469
		return undefined;
470
	},
471
	
472
	// Logging callbacks
473
	begin: function() {},
474
	done: function(failures, total) {},
475
	log: function(result, message) {},
476
	testStart: function(name, testEnvironment) {},
477
	testDone: function(name, failures, total) {},
478
	moduleStart: function(name, testEnvironment) {},
479
	moduleDone: function(name, failures, total) {}
480
});
481

    
482
if ( typeof document === "undefined" || document.readyState === "complete" ) {
483
	config.autorun = true;
484
}
485

    
486
addEvent(window, "load", function() {
487
	QUnit.begin();
488
	
489
	// Initialize the config, saving the execution queue
490
	var oldconfig = extend({}, config);
491
	QUnit.init();
492
	extend(config, oldconfig);
493

    
494
	config.blocking = false;
495

    
496
	var userAgent = id("qunit-userAgent");
497
	if ( userAgent ) {
498
		userAgent.innerHTML = navigator.userAgent;
499
	}
500
	var banner = id("qunit-header");
501
	if ( banner ) {
502
		banner.innerHTML = '<a href="' + location.href + '">' + banner.innerHTML + '</a>'; 
503
	}
504
	
505
	var toolbar = id("qunit-testrunner-toolbar");
506
	if ( toolbar ) {
507
		toolbar.style.display = "none";
508
		
509
		var filter = document.createElement("input");
510
		filter.type = "checkbox";
511
		filter.id = "qunit-filter-pass";
512
		filter.disabled = true;
513
		addEvent( filter, "click", function() {
514
			var li = document.getElementsByTagName("li");
515
			for ( var i = 0; i < li.length; i++ ) {
516
				if ( li[i].className.indexOf("pass") > -1 ) {
517
					li[i].style.display = filter.checked ? "none" : "";
518
				}
519
			}
520
		});
521
		toolbar.appendChild( filter );
522

    
523
		var label = document.createElement("label");
524
		label.setAttribute("for", "qunit-filter-pass");
525
		label.innerHTML = "Hide passed tests";
526
		toolbar.appendChild( label );
527

    
528
		var missing = document.createElement("input");
529
		missing.type = "checkbox";
530
		missing.id = "qunit-filter-missing";
531
		missing.disabled = true;
532
		addEvent( missing, "click", function() {
533
			var li = document.getElementsByTagName("li");
534
			for ( var i = 0; i < li.length; i++ ) {
535
				if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) {
536
					li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block";
537
				}
538
			}
539
		});
540
		toolbar.appendChild( missing );
541

    
542
		label = document.createElement("label");
543
		label.setAttribute("for", "qunit-filter-missing");
544
		label.innerHTML = "Hide missing tests (untested code is broken code)";
545
		toolbar.appendChild( label );
546
	}
547

    
548
	var main = id('main') || id('qunit-fixture');
549
	if ( main ) {
550
		config.fixture = main.innerHTML;
551
	}
552

    
553
	if (config.autostart) {
554
		QUnit.start();
555
	}
556
});
557

    
558
function done() {
559
	if ( config.doneTimer && window.clearTimeout ) {
560
		window.clearTimeout( config.doneTimer );
561
		config.doneTimer = null;
562
	}
563

    
564
	if ( config.queue.length ) {
565
		config.doneTimer = window.setTimeout(function(){
566
			if ( !config.queue.length ) {
567
				done();
568
			} else {
569
				synchronize( done );
570
			}
571
		}, 13);
572

    
573
		return;
574
	}
575

    
576
	config.autorun = true;
577

    
578
	// Log the last module results
579
	if ( config.currentModule ) {
580
		QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
581
	}
582

    
583
	var banner = id("qunit-banner"),
584
		tests = id("qunit-tests"),
585
		html = ['Tests completed in ',
586
		+new Date - config.started, ' milliseconds.<br/>',
587
		'<span class="passed">', config.stats.all - config.stats.bad, '</span> tests of <span class="total">', config.stats.all, '</span> passed, <span class="failed">', config.stats.bad,'</span> failed.'].join('');
588

    
589
	if ( banner ) {
590
		banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
591
	}
592

    
593
	if ( tests ) {	
594
		var result = id("qunit-testresult");
595

    
596
		if ( !result ) {
597
			result = document.createElement("p");
598
			result.id = "qunit-testresult";
599
			result.className = "result";
600
			tests.parentNode.insertBefore( result, tests.nextSibling );
601
		}
602

    
603
		result.innerHTML = html;
604
	}
605

    
606
	QUnit.done( config.stats.bad, config.stats.all );
607
}
608

    
609
function validTest( name ) {
610
	var i = config.filters.length,
611
		run = false;
612

    
613
	if ( !i ) {
614
		return true;
615
	}
616
	
617
	while ( i-- ) {
618
		var filter = config.filters[i],
619
			not = filter.charAt(0) == '!';
620

    
621
		if ( not ) {
622
			filter = filter.slice(1);
623
		}
624

    
625
		if ( name.indexOf(filter) !== -1 ) {
626
			return !not;
627
		}
628

    
629
		if ( not ) {
630
			run = true;
631
		}
632
	}
633

    
634
	return run;
635
}
636

    
637
function escapeHtml(s) {
638
	s = s === null ? "" : s + "";
639
	return s.replace(/[\&"<>\\]/g, function(s) {
640
		switch(s) {
641
			case "&": return "&amp;";
642
			case "\\": return "\\\\";
643
			case '"': return '\"';
644
			case "<": return "&lt;";
645
			case ">": return "&gt;";
646
			default: return s;
647
		}
648
	});
649
}
650

    
651
function push(result, actual, expected, message) {
652
	message = escapeHtml(message) || (result ? "okay" : "failed");
653
	message = '<span class="test-message">' + message + "</span>";
654
	expected = escapeHtml(QUnit.jsDump.parse(expected));
655
	actual = escapeHtml(QUnit.jsDump.parse(actual));
656
	var output = message + ', expected: <span class="test-expected">' + expected + '</span>';
657
	if (actual != expected) {
658
		output += ' result: <span class="test-actual">' + actual + '</span>, diff: ' + QUnit.diff(expected, actual);
659
	}
660
	
661
	// can't use ok, as that would double-escape messages
662
	QUnit.log(result, output);
663
	config.assertions.push({
664
		result: !!result,
665
		message: output
666
	});
667
}
668

    
669
function synchronize( callback ) {
670
	config.queue.push( callback );
671

    
672
	if ( config.autorun && !config.blocking ) {
673
		process();
674
	}
675
}
676

    
677
function process() {
678
	var start = (new Date()).getTime();
679

    
680
	while ( config.queue.length && !config.blocking ) {
681
		if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) {
682
			config.queue.shift()();
683

    
684
		} else {
685
			setTimeout( process, 13 );
686
			break;
687
		}
688
	}
689
}
690

    
691
function saveGlobal() {
692
	config.pollution = [];
693
	
694
	if ( config.noglobals ) {
695
		for ( var key in window ) {
696
			config.pollution.push( key );
697
		}
698
	}
699
}
700

    
701
function checkPollution( name ) {
702
	var old = config.pollution;
703
	saveGlobal();
704
	
705
	var newGlobals = diff( old, config.pollution );
706
	if ( newGlobals.length > 0 ) {
707
		ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
708
		config.expected++;
709
	}
710

    
711
	var deletedGlobals = diff( config.pollution, old );
712
	if ( deletedGlobals.length > 0 ) {
713
		ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
714
		config.expected++;
715
	}
716
}
717

    
718
// returns a new Array with the elements that are in a but not in b
719
function diff( a, b ) {
720
	var result = a.slice();
721
	for ( var i = 0; i < result.length; i++ ) {
722
		for ( var j = 0; j < b.length; j++ ) {
723
			if ( result[i] === b[j] ) {
724
				result.splice(i, 1);
725
				i--;
726
				break;
727
			}
728
		}
729
	}
730
	return result;
731
}
732

    
733
function fail(message, exception, callback) {
734
	if ( typeof console !== "undefined" && console.error && console.warn ) {
735
		console.error(message);
736
		console.error(exception);
737
		console.warn(callback.toString());
738

    
739
	} else if ( window.opera && opera.postError ) {
740
		opera.postError(message, exception, callback.toString);
741
	}
742
}
743

    
744
function extend(a, b) {
745
	for ( var prop in b ) {
746
		a[prop] = b[prop];
747
	}
748

    
749
	return a;
750
}
751

    
752
function addEvent(elem, type, fn) {
753
	if ( elem.addEventListener ) {
754
		elem.addEventListener( type, fn, false );
755
	} else if ( elem.attachEvent ) {
756
		elem.attachEvent( "on" + type, fn );
757
	} else {
758
		fn();
759
	}
760
}
761

    
762
function id(name) {
763
	return !!(typeof document !== "undefined" && document && document.getElementById) &&
764
		document.getElementById( name );
765
}
766

    
767
// Test for equality any JavaScript type.
768
// Discussions and reference: http://philrathe.com/articles/equiv
769
// Test suites: http://philrathe.com/tests/equiv
770
// Author: Philippe Rathé <prathe@gmail.com>
771
QUnit.equiv = function () {
772

    
773
    var innerEquiv; // the real equiv function
774
    var callers = []; // stack to decide between skip/abort functions
775
    var parents = []; // stack to avoiding loops from circular referencing
776

    
777
    // Call the o related callback with the given arguments.
778
    function bindCallbacks(o, callbacks, args) {
779
        var prop = QUnit.objectType(o);
780
        if (prop) {
781
            if (QUnit.objectType(callbacks[prop]) === "function") {
782
                return callbacks[prop].apply(callbacks, args);
783
            } else {
784
                return callbacks[prop]; // or undefined
785
            }
786
        }
787
    }
788
    
789
    var callbacks = function () {
790

    
791
        // for string, boolean, number and null
792
        function useStrictEquality(b, a) {
793
            if (b instanceof a.constructor || a instanceof b.constructor) {
794
                // to catch short annotaion VS 'new' annotation of a declaration
795
                // e.g. var i = 1;
796
                //      var j = new Number(1);
797
                return a == b;
798
            } else {
799
                return a === b;
800
            }
801
        }
802

    
803
        return {
804
            "string": useStrictEquality,
805
            "boolean": useStrictEquality,
806
            "number": useStrictEquality,
807
            "null": useStrictEquality,
808
            "undefined": useStrictEquality,
809

    
810
            "nan": function (b) {
811
                return isNaN(b);
812
            },
813

    
814
            "date": function (b, a) {
815
                return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf();
816
            },
817

    
818
            "regexp": function (b, a) {
819
                return QUnit.objectType(b) === "regexp" &&
820
                    a.source === b.source && // the regex itself
821
                    a.global === b.global && // and its modifers (gmi) ...
822
                    a.ignoreCase === b.ignoreCase &&
823
                    a.multiline === b.multiline;
824
            },
825

    
826
            // - skip when the property is a method of an instance (OOP)
827
            // - abort otherwise,
828
            //   initial === would have catch identical references anyway
829
            "function": function () {
830
                var caller = callers[callers.length - 1];
831
                return caller !== Object &&
832
                        typeof caller !== "undefined";
833
            },
834

    
835
            "array": function (b, a) {
836
                var i, j, loop;
837
                var len;
838

    
839
                // b could be an object literal here
840
                if ( ! (QUnit.objectType(b) === "array")) {
841
                    return false;
842
                }   
843
                
844
                len = a.length;
845
                if (len !== b.length) { // safe and faster
846
                    return false;
847
                }
848
                
849
                //track reference to avoid circular references
850
                parents.push(a);
851
                for (i = 0; i < len; i++) {
852
                    loop = false;
853
                    for(j=0;j<parents.length;j++){
854
                        if(parents[j] === a[i]){
855
                            loop = true;//dont rewalk array
856
                        }
857
                    }
858
                    if (!loop && ! innerEquiv(a[i], b[i])) {
859
                        parents.pop();
860
                        return false;
861
                    }
862
                }
863
                parents.pop();
864
                return true;
865
            },
866

    
867
            "object": function (b, a) {
868
                var i, j, loop;
869
                var eq = true; // unless we can proove it
870
                var aProperties = [], bProperties = []; // collection of strings
871

    
872
                // comparing constructors is more strict than using instanceof
873
                if ( a.constructor !== b.constructor) {
874
                    return false;
875
                }
876

    
877
                // stack constructor before traversing properties
878
                callers.push(a.constructor);
879
                //track reference to avoid circular references
880
                parents.push(a);
881
                
882
                for (i in a) { // be strict: don't ensures hasOwnProperty and go deep
883
                    loop = false;
884
                    for(j=0;j<parents.length;j++){
885
                        if(parents[j] === a[i])
886
                            loop = true; //don't go down the same path twice
887
                    }
888
                    aProperties.push(i); // collect a's properties
889

    
890
                    if (!loop && ! innerEquiv(a[i], b[i])) {
891
                        eq = false;
892
                        break;
893
                    }
894
                }
895

    
896
                callers.pop(); // unstack, we are done
897
                parents.pop();
898

    
899
                for (i in b) {
900
                    bProperties.push(i); // collect b's properties
901
                }
902

    
903
                // Ensures identical properties name
904
                return eq && innerEquiv(aProperties.sort(), bProperties.sort());
905
            }
906
        };
907
    }();
908

    
909
    innerEquiv = function () { // can take multiple arguments
910
        var args = Array.prototype.slice.apply(arguments);
911
        if (args.length < 2) {
912
            return true; // end transition
913
        }
914

    
915
        return (function (a, b) {
916
            if (a === b) {
917
                return true; // catch the most you can
918
            } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || QUnit.objectType(a) !== QUnit.objectType(b)) {
919
                return false; // don't lose time with error prone cases
920
            } else {
921
                return bindCallbacks(a, callbacks, [b, a]);
922
            }
923

    
924
        // apply transition with (1..n) arguments
925
        })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1));
926
    };
927

    
928
    return innerEquiv;
929

    
930
}();
931

    
932
/**
933
 * jsDump
934
 * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
935
 * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
936
 * Date: 5/15/2008
937
 * @projectDescription Advanced and extensible data dumping for Javascript.
938
 * @version 1.0.0
939
 * @author Ariel Flesler
940
 * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
941
 */
942
QUnit.jsDump = (function() {
943
	function quote( str ) {
944
		return '"' + str.toString().replace(/"/g, '\\"') + '"';
945
	};
946
	function literal( o ) {
947
		return o + '';	
948
	};
949
	function join( pre, arr, post ) {
950
		var s = jsDump.separator(),
951
			base = jsDump.indent(),
952
			inner = jsDump.indent(1);
953
		if ( arr.join )
954
			arr = arr.join( ',' + s + inner );
955
		if ( !arr )
956
			return pre + post;
957
		return [ pre, inner + arr, base + post ].join(s);
958
	};
959
	function array( arr ) {
960
		var i = arr.length,	ret = Array(i);					
961
		this.up();
962
		while ( i-- )
963
			ret[i] = this.parse( arr[i] );				
964
		this.down();
965
		return join( '[', ret, ']' );
966
	};
967
	
968
	var reName = /^function (\w+)/;
969
	
970
	var jsDump = {
971
		parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance
972
			var	parser = this.parsers[ type || this.typeOf(obj) ];
973
			type = typeof parser;			
974
			
975
			return type == 'function' ? parser.call( this, obj ) :
976
				   type == 'string' ? parser :
977
				   this.parsers.error;
978
		},
979
		typeOf:function( obj ) {
980
			var type;
981
			if ( obj === null ) {
982
				type = "null";
983
			} else if (typeof obj === "undefined") {
984
				type = "undefined";
985
			} else if (QUnit.is("RegExp", obj)) {
986
				type = "regexp";
987
			} else if (QUnit.is("Date", obj)) {
988
				type = "date";
989
			} else if (QUnit.is("Function", obj)) {
990
				type = "function";
991
			} else if (obj.setInterval && obj.document && !obj.nodeType) {
992
				type = "window";
993
			} else if (obj.nodeType === 9) {
994
				type = "document";
995
			} else if (obj.nodeType) {
996
				type = "node";
997
			} else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) {
998
				type = "array";
999
			} else {
1000
				type = typeof obj;
1001
			}
1002
			return type;
1003
		},
1004
		separator:function() {
1005
			return this.multiline ?	this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
1006
		},
1007
		indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
1008
			if ( !this.multiline )
1009
				return '';
1010
			var chr = this.indentChar;
1011
			if ( this.HTML )
1012
				chr = chr.replace(/\t/g,'   ').replace(/ /g,'&nbsp;');
1013
			return Array( this._depth_ + (extra||0) ).join(chr);
1014
		},
1015
		up:function( a ) {
1016
			this._depth_ += a || 1;
1017
		},
1018
		down:function( a ) {
1019
			this._depth_ -= a || 1;
1020
		},
1021
		setParser:function( name, parser ) {
1022
			this.parsers[name] = parser;
1023
		},
1024
		// The next 3 are exposed so you can use them
1025
		quote:quote, 
1026
		literal:literal,
1027
		join:join,
1028
		//
1029
		_depth_: 1,
1030
		// This is the list of parsers, to modify them, use jsDump.setParser
1031
		parsers:{
1032
			window: '[Window]',
1033
			document: '[Document]',
1034
			error:'[ERROR]', //when no parser is found, shouldn't happen
1035
			unknown: '[Unknown]',
1036
			'null':'null',
1037
			undefined:'undefined',
1038
			'function':function( fn ) {
1039
				var ret = 'function',
1040
					name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
1041
				if ( name )
1042
					ret += ' ' + name;
1043
				ret += '(';
1044
				
1045
				ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join('');
1046
				return join( ret, this.parse(fn,'functionCode'), '}' );
1047
			},
1048
			array: array,
1049
			nodelist: array,
1050
			arguments: array,
1051
			object:function( map ) {
1052
				var ret = [ ];
1053
				this.up();
1054
				for ( var key in map )
1055
					ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) );
1056
				this.down();
1057
				return join( '{', ret, '}' );
1058
			},
1059
			node:function( node ) {
1060
				var open = this.HTML ? '&lt;' : '<',
1061
					close = this.HTML ? '&gt;' : '>';
1062
					
1063
				var tag = node.nodeName.toLowerCase(),
1064
					ret = open + tag;
1065
					
1066
				for ( var a in this.DOMAttrs ) {
1067
					var val = node[this.DOMAttrs[a]];
1068
					if ( val )
1069
						ret += ' ' + a + '=' + this.parse( val, 'attribute' );
1070
				}
1071
				return ret + close + open + '/' + tag + close;
1072
			},
1073
			functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
1074
				var l = fn.length;
1075
				if ( !l ) return '';				
1076
				
1077
				var args = Array(l);
1078
				while ( l-- )
1079
					args[l] = String.fromCharCode(97+l);//97 is 'a'
1080
				return ' ' + args.join(', ') + ' ';
1081
			},
1082
			key:quote, //object calls it internally, the key part of an item in a map
1083
			functionCode:'[code]', //function calls it internally, it's the content of the function
1084
			attribute:quote, //node calls it internally, it's an html attribute value
1085
			string:quote,
1086
			date:quote,
1087
			regexp:literal, //regex
1088
			number:literal,
1089
			'boolean':literal
1090
		},
1091
		DOMAttrs:{//attributes to dump from nodes, name=>realName
1092
			id:'id',
1093
			name:'name',
1094
			'class':'className'
1095
		},
1096
		HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
1097
		indentChar:'   ',//indentation unit
1098
		multiline:false //if true, items in a collection, are separated by a \n, else just a space.
1099
	};
1100

    
1101
	return jsDump;
1102
})();
1103

    
1104
// from Sizzle.js
1105
function getText( elems ) {
1106
	var ret = "", elem;
1107

    
1108
	for ( var i = 0; elems[i]; i++ ) {
1109
		elem = elems[i];
1110

    
1111
		// Get the text from text nodes and CDATA nodes
1112
		if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
1113
			ret += elem.nodeValue;
1114

    
1115
		// Traverse everything else, except comment nodes
1116
		} else if ( elem.nodeType !== 8 ) {
1117
			ret += getText( elem.childNodes );
1118
		}
1119
	}
1120

    
1121
	return ret;
1122
};
1123

    
1124
/*
1125
 * Javascript Diff Algorithm
1126
 *  By John Resig (http://ejohn.org/)
1127
 *  Modified by Chu Alan "sprite"
1128
 *
1129
 * Released under the MIT license.
1130
 *
1131
 * More Info:
1132
 *  http://ejohn.org/projects/javascript-diff-algorithm/
1133
 *  
1134
 * Usage: QUnit.diff(expected, actual)
1135
 * 
1136
 * 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"
1137
 */
1138
QUnit.diff = (function() {
1139
	function diff(o, n){
1140
		var ns = new Object();
1141
		var os = new Object();
1142
		
1143
		for (var i = 0; i < n.length; i++) {
1144
			if (ns[n[i]] == null) 
1145
				ns[n[i]] = {
1146
					rows: new Array(),
1147
					o: null
1148
				};
1149
			ns[n[i]].rows.push(i);
1150
		}
1151
		
1152
		for (var i = 0; i < o.length; i++) {
1153
			if (os[o[i]] == null) 
1154
				os[o[i]] = {
1155
					rows: new Array(),
1156
					n: null
1157
				};
1158
			os[o[i]].rows.push(i);
1159
		}
1160
		
1161
		for (var i in ns) {
1162
			if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
1163
				n[ns[i].rows[0]] = {
1164
					text: n[ns[i].rows[0]],
1165
					row: os[i].rows[0]
1166
				};
1167
				o[os[i].rows[0]] = {
1168
					text: o[os[i].rows[0]],
1169
					row: ns[i].rows[0]
1170
				};
1171
			}
1172
		}
1173
		
1174
		for (var i = 0; i < n.length - 1; i++) {
1175
			if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
1176
			n[i + 1] == o[n[i].row + 1]) {
1177
				n[i + 1] = {
1178
					text: n[i + 1],
1179
					row: n[i].row + 1
1180
				};
1181
				o[n[i].row + 1] = {
1182
					text: o[n[i].row + 1],
1183
					row: i + 1
1184
				};
1185
			}
1186
		}
1187
		
1188
		for (var i = n.length - 1; i > 0; i--) {
1189
			if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
1190
			n[i - 1] == o[n[i].row - 1]) {
1191
				n[i - 1] = {
1192
					text: n[i - 1],
1193
					row: n[i].row - 1
1194
				};
1195
				o[n[i].row - 1] = {
1196
					text: o[n[i].row - 1],
1197
					row: i - 1
1198
				};
1199
			}
1200
		}
1201
		
1202
		return {
1203
			o: o,
1204
			n: n
1205
		};
1206
	}
1207
	
1208
	return function(o, n){
1209
		o = o.replace(/\s+$/, '');
1210
		n = n.replace(/\s+$/, '');
1211
		var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));
1212

    
1213
		var str = "";
1214
		
1215
		var oSpace = o.match(/\s+/g);
1216
		if (oSpace == null) {
1217
			oSpace = [" "];
1218
		}
1219
		else {
1220
			oSpace.push(" ");
1221
		}
1222
		var nSpace = n.match(/\s+/g);
1223
		if (nSpace == null) {
1224
			nSpace = [" "];
1225
		}
1226
		else {
1227
			nSpace.push(" ");
1228
		}
1229
		
1230
		if (out.n.length == 0) {
1231
			for (var i = 0; i < out.o.length; i++) {
1232
				str += '<del>' + out.o[i] + oSpace[i] + "</del>";
1233
			}
1234
		}
1235
		else {
1236
			if (out.n[0].text == null) {
1237
				for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
1238
					str += '<del>' + out.o[n] + oSpace[n] + "</del>";
1239
				}
1240
			}
1241
			
1242
			for (var i = 0; i < out.n.length; i++) {
1243
				if (out.n[i].text == null) {
1244
					str += '<ins>' + out.n[i] + nSpace[i] + "</ins>";
1245
				}
1246
				else {
1247
					var pre = "";
1248
					
1249
					for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
1250
						pre += '<del>' + out.o[n] + oSpace[n] + "</del>";
1251
					}
1252
					str += " " + out.n[i].text + nSpace[i] + pre;
1253
				}
1254
			}
1255
		}
1256
		
1257
		return str;
1258
	}
1259
})();
1260

    
1261
})(this);
(5-5/5)