Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80698 views
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
// Initialize the configuration options
16
init: function init() {
17
config = {
18
stats: { all: 0, bad: 0 },
19
moduleStats: { all: 0, bad: 0 },
20
started: +new Date,
21
blocking: false,
22
autorun: false,
23
assertions: [],
24
filters: [],
25
queue: []
26
};
27
28
var tests = id("qunit-tests"),
29
banner = id("qunit-banner"),
30
result = id("qunit-testresult");
31
32
if ( tests ) {
33
tests.innerHTML = "";
34
}
35
36
if ( banner ) {
37
banner.className = "";
38
}
39
40
if ( result ) {
41
result.parentNode.removeChild( result );
42
}
43
},
44
45
// call on start of module test to prepend name to all tests
46
module: function module(name, testEnvironment) {
47
48
synchronize(function() {
49
if ( config.currentModule ) {
50
QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
51
}
52
53
config.currentModule = name;
54
config.moduleTestEnvironment = testEnvironment;
55
config.moduleStats = { all: 0, bad: 0 };
56
57
QUnit.moduleStart( name, testEnvironment );
58
});
59
},
60
61
asyncTest: function asyncTest(testName, expected, callback) {
62
if ( arguments.length === 2 ) {
63
callback = expected;
64
expected = 0;
65
}
66
67
QUnit.test(testName, expected, callback, true);
68
},
69
70
test: function test(testName, expected, callback, async) {
71
var name = testName, testEnvironment = {};
72
73
if ( arguments.length === 2 ) {
74
callback = expected;
75
expected = null;
76
}
77
78
if ( config.currentModule ) {
79
name = config.currentModule + " module: " + name;
80
}
81
82
if ( !validTest(name) ) {
83
return;
84
}
85
86
synchronize(function() {
87
QUnit.testStart( testName );
88
89
testEnvironment = extend({
90
setup: function() {},
91
teardown: function() {}
92
}, config.moduleTestEnvironment);
93
94
config.assertions = [];
95
config.expected = null;
96
97
if ( arguments.length >= 3 ) {
98
config.expected = callback;
99
callback = arguments[2];
100
}
101
102
try {
103
if ( !config.pollution ) {
104
saveGlobal();
105
}
106
107
testEnvironment.setup.call(testEnvironment);
108
} catch(e) {
109
QUnit.ok( false, "Setup failed on " + name + ": " + e.message );
110
}
111
112
if ( async ) {
113
QUnit.stop();
114
}
115
116
try {
117
callback.call(testEnvironment);
118
} catch(e) {
119
fail("Test " + name + " died, exception and test follows", e, callback);
120
QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message );
121
// else next test will carry the responsibility
122
saveGlobal();
123
124
// Restart the tests if they're blocking
125
if ( config.blocking ) {
126
start();
127
}
128
}
129
});
130
131
synchronize(function() {
132
try {
133
checkPollution();
134
testEnvironment.teardown.call(testEnvironment);
135
} catch(e) {
136
QUnit.ok( false, "Teardown failed on " + name + ": " + e.message );
137
}
138
139
try {
140
QUnit.reset();
141
} catch(e) {
142
fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset);
143
}
144
145
if ( config.expected && config.expected != config.assertions.length ) {
146
QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" );
147
}
148
149
var good = 0, bad = 0,
150
tests = id("qunit-tests");
151
152
config.stats.all += config.assertions.length;
153
config.moduleStats.all += config.assertions.length;
154
155
if ( tests ) {
156
var ol = document.createElement("ol");
157
ol.style.display = "none";
158
159
for ( var i = 0; i < config.assertions.length; i++ ) {
160
var assertion = config.assertions[i];
161
162
var li = document.createElement("li");
163
li.className = assertion.result ? "pass" : "fail";
164
li.innerHTML = assertion.message || "(no message)";
165
ol.appendChild( li );
166
167
if ( assertion.result ) {
168
good++;
169
} else {
170
bad++;
171
config.stats.bad++;
172
config.moduleStats.bad++;
173
}
174
}
175
176
var b = document.createElement("strong");
177
b.innerHTML = name + " <b style='color:black;'>(<b class='fail'>" + bad + "</b>, <b class='pass'>" + good + "</b>, " + config.assertions.length + ")</b>";
178
179
addEvent(b, "click", function() {
180
var next = b.nextSibling, display = next.style.display;
181
next.style.display = display === "none" ? "block" : "none";
182
});
183
184
addEvent(b, "dblclick", function(e) {
185
var target = (e || window.event).target;
186
if ( target.nodeName.toLowerCase() === "strong" ) {
187
var text = "", node = target.firstChild;
188
189
while ( node.nodeType === 3 ) {
190
text += node.nodeValue;
191
node = node.nextSibling;
192
}
193
194
text = text.replace(/(^\s*|\s*$)/g, "");
195
196
if ( window.location ) {
197
window.location.href = window.location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent(text);
198
}
199
}
200
});
201
202
var li = document.createElement("li");
203
li.className = bad ? "fail" : "pass";
204
li.appendChild( b );
205
li.appendChild( ol );
206
tests.appendChild( li );
207
208
if ( bad ) {
209
var toolbar = id("qunit-testrunner-toolbar");
210
if ( toolbar ) {
211
toolbar.style.display = "block";
212
id("qunit-filter-pass").disabled = null;
213
id("qunit-filter-missing").disabled = null;
214
}
215
}
216
217
} else {
218
for ( var i = 0; i < config.assertions.length; i++ ) {
219
if ( !config.assertions[i].result ) {
220
bad++;
221
config.stats.bad++;
222
config.moduleStats.bad++;
223
}
224
}
225
}
226
227
QUnit.testDone( testName, bad, config.assertions.length );
228
229
if ( !window.setTimeout && !config.queue.length ) {
230
done();
231
}
232
});
233
234
if ( window.setTimeout && !config.doneTimer ) {
235
config.doneTimer = window.setTimeout(function(){
236
if ( !config.queue.length ) {
237
done();
238
} else {
239
synchronize( done );
240
}
241
}, 13);
242
}
243
},
244
245
/**
246
* Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
247
*/
248
expect: function expect(asserts) {
249
config.expected = asserts;
250
},
251
252
/**
253
* Asserts true.
254
* @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
255
*/
256
ok: function ok(a, msg) {
257
QUnit.log(a, msg);
258
259
config.assertions.push({
260
result: !!a,
261
message: msg
262
});
263
},
264
265
/**
266
* Checks that the first two arguments are equal, with an optional message.
267
* Prints out both actual and expected values.
268
*
269
* Prefered to ok( actual == expected, message )
270
*
271
* @example equals( format("Received {0} bytes.", 2), "Received 2 bytes." );
272
*
273
* @param Object actual
274
* @param Object expected
275
* @param String message (optional)
276
*/
277
equals: function equals(actual, expected, message) {
278
push(expected == actual, actual, expected, message);
279
},
280
281
same: function(a, b, message) {
282
push(QUnit.equiv(a, b), a, b, message);
283
},
284
285
start: function start() {
286
// A slight delay, to avoid any current callbacks
287
if ( window.setTimeout ) {
288
window.setTimeout(function() {
289
if ( config.timeout ) {
290
clearTimeout(config.timeout);
291
}
292
293
config.blocking = false;
294
process();
295
}, 13);
296
} else {
297
config.blocking = false;
298
process();
299
}
300
},
301
302
stop: function stop(timeout) {
303
config.blocking = true;
304
305
if ( timeout && window.setTimeout ) {
306
config.timeout = window.setTimeout(function() {
307
QUnit.ok( false, "Test timed out" );
308
QUnit.start();
309
}, timeout);
310
}
311
},
312
313
/**
314
* Resets the test setup. Useful for tests that modify the DOM.
315
*/
316
reset: function reset() {
317
if ( window.jQuery ) {
318
jQuery("#main").html( config.fixture );
319
jQuery.event.global = {};
320
jQuery.ajaxSettings = extend({}, config.ajaxSettings);
321
}
322
},
323
324
/**
325
* Trigger an event on an element.
326
*
327
* @example triggerEvent( document.body, "click" );
328
*
329
* @param DOMElement elem
330
* @param String type
331
*/
332
triggerEvent: function triggerEvent( elem, type, event ) {
333
if ( document.createEvent ) {
334
event = document.createEvent("MouseEvents");
335
event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
336
0, 0, 0, 0, 0, false, false, false, false, 0, null);
337
elem.dispatchEvent( event );
338
339
} else if ( elem.fireEvent ) {
340
elem.fireEvent("on"+type);
341
}
342
},
343
344
// Logging callbacks
345
done: function done(failures, total) {},
346
log: function log(result, message) {},
347
testStart: function testStart(name) {},
348
testDone: function testDone(name, failures, total) {},
349
moduleStart: function moduleStart(name, testEnvironment) {},
350
moduleDone: function moduleDone(name, failures, total) {}
351
};
352
353
// Maintain internal state
354
var config = {
355
// The queue of tests to run
356
queue: [],
357
358
// block until document ready
359
blocking: true
360
};
361
362
// Load paramaters
363
(function() {
364
var location = window.location || { search: "", protocol: "file:" },
365
GETParams = location.search.slice(1).split('&');
366
367
for ( var i = 0; i < GETParams.length; i++ ) {
368
GETParams[i] = decodeURIComponent( GETParams[i] );
369
if ( GETParams[i] === "noglobals" ) {
370
GETParams.splice( i, 1 );
371
i--;
372
config.noglobals = true;
373
}
374
}
375
376
// restrict modules/tests by get parameters
377
config.filters = GETParams;
378
379
// Figure out if we're running the tests from a server or not
380
QUnit.isLocal = !!(location.protocol === 'file:');
381
})();
382
383
// Expose the API as global variables, unless an 'exports'
384
// object exists, in that case we assume we're in CommonJS
385
if ( typeof exports === "undefined" || typeof require === "undefined" ) {
386
extend(window, QUnit);
387
window.QUnit = QUnit;
388
} else {
389
extend(exports, QUnit);
390
exports.QUnit = QUnit;
391
}
392
393
if ( typeof document === "undefined" || document.readyState === "complete" ) {
394
config.autorun = true;
395
}
396
397
addEvent(window, "load", function() {
398
// Initialize the config, saving the execution queue
399
var oldconfig = extend({}, config);
400
QUnit.init();
401
extend(config, oldconfig);
402
403
config.blocking = false;
404
405
var userAgent = id("qunit-userAgent");
406
if ( userAgent ) {
407
userAgent.innerHTML = navigator.userAgent;
408
}
409
410
var toolbar = id("qunit-testrunner-toolbar");
411
if ( toolbar ) {
412
toolbar.style.display = "none";
413
414
var filter = document.createElement("input");
415
filter.type = "checkbox";
416
filter.id = "qunit-filter-pass";
417
filter.disabled = true;
418
addEvent( filter, "click", function() {
419
var li = document.getElementsByTagName("li");
420
for ( var i = 0; i < li.length; i++ ) {
421
if ( li[i].className.indexOf("pass") > -1 ) {
422
li[i].style.display = filter.checked ? "none" : "block";
423
}
424
}
425
});
426
toolbar.appendChild( filter );
427
428
var label = document.createElement("label");
429
label.setAttribute("for", "filter-pass");
430
label.innerHTML = "Hide passed tests";
431
toolbar.appendChild( label );
432
433
var missing = document.createElement("input");
434
missing.type = "checkbox";
435
missing.id = "qunit-filter-missing";
436
missing.disabled = true;
437
addEvent( missing, "click", function() {
438
var li = document.getElementsByTagName("li");
439
for ( var i = 0; i < li.length; i++ ) {
440
if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) {
441
li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block";
442
}
443
}
444
});
445
toolbar.appendChild( missing );
446
447
label = document.createElement("label");
448
label.setAttribute("for", "filter-missing");
449
label.innerHTML = "Hide missing tests (untested code is broken code)";
450
toolbar.appendChild( label );
451
}
452
453
var main = id('main');
454
if ( main ) {
455
config.fixture = main.innerHTML;
456
}
457
458
if ( window.jQuery ) {
459
config.ajaxSettings = window.jQuery.ajaxSettings;
460
}
461
462
QUnit.start();
463
});
464
465
function done() {
466
if ( config.doneTimer && window.clearTimeout ) {
467
window.clearTimeout( config.doneTimer );
468
config.doneTimer = null;
469
}
470
471
if ( config.queue.length ) {
472
config.doneTimer = window.setTimeout(function(){
473
if ( !config.queue.length ) {
474
done();
475
} else {
476
synchronize( done );
477
}
478
}, 13);
479
480
return;
481
}
482
483
config.autorun = true;
484
485
// Log the last module results
486
if ( config.currentModule ) {
487
QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
488
}
489
490
var banner = id("qunit-banner"),
491
tests = id("qunit-tests"),
492
html = ['Tests completed in ',
493
+new Date - config.started, ' milliseconds.<br/>',
494
'<span class="bad">', config.stats.all - config.stats.bad, '</span> tests of <span class="all">', config.stats.all, '</span> passed, ', config.stats.bad,' failed.'].join('');
495
496
if ( banner ) {
497
banner.className += " " + (config.stats.bad ? "fail" : "pass");
498
}
499
500
if ( tests ) {
501
var result = id("qunit-testresult");
502
503
if ( !result ) {
504
result = document.createElement("p");
505
result.id = "qunit-testresult";
506
result.className = "result";
507
tests.parentNode.insertBefore( result, tests.nextSibling );
508
}
509
510
result.innerHTML = html;
511
}
512
513
QUnit.done( config.stats.bad, config.stats.all );
514
}
515
516
function validTest( name ) {
517
var i = config.filters.length,
518
run = false;
519
520
if ( !i ) {
521
return true;
522
}
523
524
while ( i-- ) {
525
var filter = config.filters[i],
526
not = filter.charAt(0) == '!';
527
528
if ( not ) {
529
filter = filter.slice(1);
530
}
531
532
if ( name.indexOf(filter) !== -1 ) {
533
return !not;
534
}
535
536
if ( not ) {
537
run = true;
538
}
539
}
540
541
return run;
542
}
543
544
function push(result, actual, expected, message) {
545
message = message || (result ? "okay" : "failed");
546
QUnit.ok( result, result ? message + ": " + expected : message + ", expected: " + QUnit.jsDump.parse(expected) + " result: " + QUnit.jsDump.parse(actual) );
547
}
548
549
function synchronize( callback ) {
550
config.queue.push( callback );
551
552
if ( config.autorun && !config.blocking ) {
553
process();
554
}
555
}
556
557
function process() {
558
while ( config.queue.length && !config.blocking ) {
559
config.queue.shift()();
560
}
561
}
562
563
function saveGlobal() {
564
config.pollution = [];
565
566
if ( config.noglobals ) {
567
for ( var key in window ) {
568
config.pollution.push( key );
569
}
570
}
571
}
572
573
function checkPollution( name ) {
574
var old = config.pollution;
575
saveGlobal();
576
577
var newGlobals = diff( old, config.pollution );
578
if ( newGlobals.length > 0 ) {
579
ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
580
config.expected++;
581
}
582
583
var deletedGlobals = diff( config.pollution, old );
584
if ( deletedGlobals.length > 0 ) {
585
ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
586
config.expected++;
587
}
588
}
589
590
// returns a new Array with the elements that are in a but not in b
591
function diff( a, b ) {
592
var result = a.slice();
593
for ( var i = 0; i < result.length; i++ ) {
594
for ( var j = 0; j < b.length; j++ ) {
595
if ( result[i] === b[j] ) {
596
result.splice(i, 1);
597
i--;
598
break;
599
}
600
}
601
}
602
return result;
603
}
604
605
function fail(message, exception, callback) {
606
if ( typeof console !== "undefined" && console.error && console.warn ) {
607
console.error(message);
608
console.error(exception);
609
console.warn(callback.toString());
610
611
} else if ( window.opera && opera.postError ) {
612
opera.postError(message, exception, callback.toString);
613
}
614
}
615
616
function extend(a, b) {
617
for ( var prop in b ) {
618
a[prop] = b[prop];
619
}
620
621
return a;
622
}
623
624
function addEvent(elem, type, fn) {
625
if ( elem.addEventListener ) {
626
elem.addEventListener( type, fn, false );
627
} else if ( elem.attachEvent ) {
628
elem.attachEvent( "on" + type, fn );
629
} else {
630
fn();
631
}
632
}
633
634
function id(name) {
635
return !!(typeof document !== "undefined" && document && document.getElementById) &&
636
document.getElementById( name );
637
}
638
639
// Test for equality any JavaScript type.
640
// Discussions and reference: http://philrathe.com/articles/equiv
641
// Test suites: http://philrathe.com/tests/equiv
642
// Author: Philippe Rathé <[email protected]>
643
QUnit.equiv = function () {
644
645
var innerEquiv; // the real equiv function
646
var callers = []; // stack to decide between skip/abort functions
647
648
649
// Determine what is o.
650
function hoozit(o) {
651
if (o.constructor === String) {
652
return "string";
653
654
} else if (o.constructor === Boolean) {
655
return "boolean";
656
657
} else if (o.constructor === Number) {
658
659
if (isNaN(o)) {
660
return "nan";
661
} else {
662
return "number";
663
}
664
665
} else if (typeof o === "undefined") {
666
return "undefined";
667
668
// consider: typeof null === object
669
} else if (o === null) {
670
return "null";
671
672
// consider: typeof [] === object
673
} else if (o instanceof Array) {
674
return "array";
675
676
// consider: typeof new Date() === object
677
} else if (o instanceof Date) {
678
return "date";
679
680
// consider: /./ instanceof Object;
681
// /./ instanceof RegExp;
682
// typeof /./ === "function"; // => false in IE and Opera,
683
// true in FF and Safari
684
} else if (o instanceof RegExp) {
685
return "regexp";
686
687
} else if (typeof o === "object") {
688
return "object";
689
690
} else if (o instanceof Function) {
691
return "function";
692
} else {
693
return undefined;
694
}
695
}
696
697
// Call the o related callback with the given arguments.
698
function handleEvents(o, callbacks, args) {
699
var prop = hoozit(o);
700
if (prop) {
701
if (hoozit(callbacks[prop]) === "function") {
702
return callbacks[prop].apply(callbacks, args);
703
} else {
704
return callbacks[prop]; // or undefined
705
}
706
}
707
}
708
709
var callbacks = function () {
710
711
// for string, boolean, number and null
712
function useStrictEquality(b, a) {
713
if (b instanceof a.constructor || a instanceof b.constructor) {
714
// to catch short annotaion VS 'new' annotation of a declaration
715
// e.g. var i = 1;
716
// var j = new Number(1);
717
return a == b;
718
} else {
719
return a === b;
720
}
721
}
722
723
return {
724
"string": useStrictEquality,
725
"boolean": useStrictEquality,
726
"number": useStrictEquality,
727
"null": useStrictEquality,
728
"undefined": useStrictEquality,
729
730
"nan": function (b) {
731
return isNaN(b);
732
},
733
734
"date": function (b, a) {
735
return hoozit(b) === "date" && a.valueOf() === b.valueOf();
736
},
737
738
"regexp": function (b, a) {
739
return hoozit(b) === "regexp" &&
740
a.source === b.source && // the regex itself
741
a.global === b.global && // and its modifers (gmi) ...
742
a.ignoreCase === b.ignoreCase &&
743
a.multiline === b.multiline;
744
},
745
746
// - skip when the property is a method of an instance (OOP)
747
// - abort otherwise,
748
// initial === would have catch identical references anyway
749
"function": function () {
750
var caller = callers[callers.length - 1];
751
return caller !== Object &&
752
typeof caller !== "undefined";
753
},
754
755
"array": function (b, a) {
756
var i;
757
var len;
758
759
// b could be an object literal here
760
if ( ! (hoozit(b) === "array")) {
761
return false;
762
}
763
764
len = a.length;
765
if (len !== b.length) { // safe and faster
766
return false;
767
}
768
for (i = 0; i < len; i++) {
769
if ( ! innerEquiv(a[i], b[i])) {
770
return false;
771
}
772
}
773
return true;
774
},
775
776
"object": function (b, a) {
777
var i;
778
var eq = true; // unless we can proove it
779
var aProperties = [], bProperties = []; // collection of strings
780
781
// comparing constructors is more strict than using instanceof
782
if ( a.constructor !== b.constructor) {
783
return false;
784
}
785
786
// stack constructor before traversing properties
787
callers.push(a.constructor);
788
789
for (i in a) { // be strict: don't ensures hasOwnProperty and go deep
790
791
aProperties.push(i); // collect a's properties
792
793
if ( ! innerEquiv(a[i], b[i])) {
794
eq = false;
795
}
796
}
797
798
callers.pop(); // unstack, we are done
799
800
for (i in b) {
801
bProperties.push(i); // collect b's properties
802
}
803
804
// Ensures identical properties name
805
return eq && innerEquiv(aProperties.sort(), bProperties.sort());
806
}
807
};
808
}();
809
810
innerEquiv = function () { // can take multiple arguments
811
var args = Array.prototype.slice.apply(arguments);
812
if (args.length < 2) {
813
return true; // end transition
814
}
815
816
return (function (a, b) {
817
if (a === b) {
818
return true; // catch the most you can
819
} else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || hoozit(a) !== hoozit(b)) {
820
return false; // don't lose time with error prone cases
821
} else {
822
return handleEvents(a, callbacks, [b, a]);
823
}
824
825
// apply transition with (1..n) arguments
826
})(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1));
827
};
828
829
return innerEquiv;
830
831
}();
832
833
/**
834
* jsDump
835
* Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
836
* Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
837
* Date: 5/15/2008
838
* @projectDescription Advanced and extensible data dumping for Javascript.
839
* @version 1.0.0
840
* @author Ariel Flesler
841
* @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
842
*/
843
QUnit.jsDump = (function() {
844
function quote( str ) {
845
return '"' + str.toString().replace(/"/g, '\\"') + '"';
846
};
847
function literal( o ) {
848
return o + '';
849
};
850
function join( pre, arr, post ) {
851
var s = jsDump.separator(),
852
base = jsDump.indent(),
853
inner = jsDump.indent(1);
854
if ( arr.join )
855
arr = arr.join( ',' + s + inner );
856
if ( !arr )
857
return pre + post;
858
return [ pre, inner + arr, base + post ].join(s);
859
};
860
function array( arr ) {
861
var i = arr.length, ret = Array(i);
862
this.up();
863
while ( i-- )
864
ret[i] = this.parse( arr[i] );
865
this.down();
866
return join( '[', ret, ']' );
867
};
868
869
var reName = /^function (\w+)/;
870
871
var jsDump = {
872
parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance
873
var parser = this.parsers[ type || this.typeOf(obj) ];
874
type = typeof parser;
875
876
return type == 'function' ? parser.call( this, obj ) :
877
type == 'string' ? parser :
878
this.parsers.error;
879
},
880
typeOf:function( obj ) {
881
var type = typeof obj,
882
f = 'function';//we'll use it 3 times, save it
883
return type != 'object' && type != f ? type :
884
!obj ? 'null' :
885
obj.exec ? 'regexp' :// some browsers (FF) consider regexps functions
886
obj.getHours ? 'date' :
887
obj.scrollBy ? 'window' :
888
obj.nodeName == '#document' ? 'document' :
889
obj.nodeName ? 'node' :
890
obj.item ? 'nodelist' : // Safari reports nodelists as functions
891
obj.callee ? 'arguments' :
892
obj.call || obj.constructor != Array && //an array would also fall on this hack
893
(obj+'').indexOf(f) != -1 ? f : //IE reports functions like alert, as objects
894
'length' in obj ? 'array' :
895
type;
896
},
897
separator:function() {
898
return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
899
},
900
indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
901
if ( !this.multiline )
902
return '';
903
var chr = this.indentChar;
904
if ( this.HTML )
905
chr = chr.replace(/\t/g,' ').replace(/ /g,'&nbsp;');
906
return Array( this._depth_ + (extra||0) ).join(chr);
907
},
908
up:function( a ) {
909
this._depth_ += a || 1;
910
},
911
down:function( a ) {
912
this._depth_ -= a || 1;
913
},
914
setParser:function( name, parser ) {
915
this.parsers[name] = parser;
916
},
917
// The next 3 are exposed so you can use them
918
quote:quote,
919
literal:literal,
920
join:join,
921
//
922
_depth_: 1,
923
// This is the list of parsers, to modify them, use jsDump.setParser
924
parsers:{
925
window: '[Window]',
926
document: '[Document]',
927
error:'[ERROR]', //when no parser is found, shouldn't happen
928
unknown: '[Unknown]',
929
'null':'null',
930
undefined:'undefined',
931
'function':function( fn ) {
932
var ret = 'function',
933
name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
934
if ( name )
935
ret += ' ' + name;
936
ret += '(';
937
938
ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join('');
939
return join( ret, this.parse(fn,'functionCode'), '}' );
940
},
941
array: array,
942
nodelist: array,
943
arguments: array,
944
object:function( map ) {
945
var ret = [ ];
946
this.up();
947
for ( var key in map )
948
ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) );
949
this.down();
950
return join( '{', ret, '}' );
951
},
952
node:function( node ) {
953
var open = this.HTML ? '&lt;' : '<',
954
close = this.HTML ? '&gt;' : '>';
955
956
var tag = node.nodeName.toLowerCase(),
957
ret = open + tag;
958
959
for ( var a in this.DOMAttrs ) {
960
var val = node[this.DOMAttrs[a]];
961
if ( val )
962
ret += ' ' + a + '=' + this.parse( val, 'attribute' );
963
}
964
return ret + close + open + '/' + tag + close;
965
},
966
functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
967
var l = fn.length;
968
if ( !l ) return '';
969
970
var args = Array(l);
971
while ( l-- )
972
args[l] = String.fromCharCode(97+l);//97 is 'a'
973
return ' ' + args.join(', ') + ' ';
974
},
975
key:quote, //object calls it internally, the key part of an item in a map
976
functionCode:'[code]', //function calls it internally, it's the content of the function
977
attribute:quote, //node calls it internally, it's an html attribute value
978
string:quote,
979
date:quote,
980
regexp:literal, //regex
981
number:literal,
982
'boolean':literal
983
},
984
DOMAttrs:{//attributes to dump from nodes, name=>realName
985
id:'id',
986
name:'name',
987
'class':'className'
988
},
989
HTML:true,//if true, entities are escaped ( <, >, \t, space and \n )
990
indentChar:' ',//indentation unit
991
multiline:true //if true, items in a collection, are separated by a \n, else just a space.
992
};
993
994
return jsDump;
995
})();
996
997
})(this);/*
998
* QUnit - A JavaScript Unit Testing Framework
999
*
1000
* http://docs.jquery.com/QUnit
1001
*
1002
* Copyright (c) 2009 John Resig, Jörn Zaefferer
1003
* Dual licensed under the MIT (MIT-LICENSE.txt)
1004
* and GPL (GPL-LICENSE.txt) licenses.
1005
*/
1006
1007
(function(window) {
1008
1009
var defined = {
1010
setTimeout: typeof window.setTimeout !== "undefined",
1011
sessionStorage: (function() {
1012
try {
1013
return !!sessionStorage.getItem;
1014
} catch(e){
1015
return false;
1016
}
1017
})()
1018
}
1019
1020
var testId = 0;
1021
1022
var Test = function(name, testName, expected, testEnvironmentArg, async, callback) {
1023
this.name = name;
1024
this.testName = testName;
1025
this.expected = expected;
1026
this.testEnvironmentArg = testEnvironmentArg;
1027
this.async = async;
1028
this.callback = callback;
1029
this.assertions = [];
1030
};
1031
Test.prototype = {
1032
init: function() {
1033
var tests = id("qunit-tests");
1034
if (tests) {
1035
var b = document.createElement("strong");
1036
b.innerHTML = "Running " + this.name;
1037
var li = document.createElement("li");
1038
li.appendChild( b );
1039
li.id = this.id = "test-output" + testId++;
1040
tests.appendChild( li );
1041
}
1042
},
1043
setup: function() {
1044
if (this.module != config.previousModule) {
1045
if ( this.previousModule ) {
1046
QUnit.moduleDone( this.module, config.moduleStats.bad, config.moduleStats.all );
1047
}
1048
config.previousModule = this.module;
1049
config.moduleStats = { all: 0, bad: 0 };
1050
QUnit.moduleStart( this.module, this.moduleTestEnvironment );
1051
}
1052
1053
config.current = this;
1054
this.testEnvironment = extend({
1055
setup: function() {},
1056
teardown: function() {}
1057
}, this.moduleTestEnvironment);
1058
if (this.testEnvironmentArg) {
1059
extend(this.testEnvironment, this.testEnvironmentArg);
1060
}
1061
1062
QUnit.testStart( this.testName, this.testEnvironment );
1063
1064
// allow utility functions to access the current test environment
1065
// TODO why??
1066
QUnit.current_testEnvironment = this.testEnvironment;
1067
1068
try {
1069
if ( !config.pollution ) {
1070
saveGlobal();
1071
}
1072
1073
this.testEnvironment.setup.call(this.testEnvironment);
1074
} catch(e) {
1075
// TODO use testName instead of name for no-markup message?
1076
QUnit.ok( false, "Setup failed on " + this.name + ": " + e.message );
1077
}
1078
},
1079
run: function() {
1080
if ( this.async ) {
1081
QUnit.stop();
1082
}
1083
1084
try {
1085
this.callback.call(this.testEnvironment);
1086
} catch(e) {
1087
// TODO use testName instead of name for no-markup message?
1088
fail("Test " + this.name + " died, exception and test follows", e, this.callback);
1089
QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) );
1090
// else next test will carry the responsibility
1091
saveGlobal();
1092
1093
// Restart the tests if they're blocking
1094
if ( config.blocking ) {
1095
start();
1096
}
1097
}
1098
},
1099
teardown: function() {
1100
try {
1101
checkPollution();
1102
this.testEnvironment.teardown.call(this.testEnvironment);
1103
} catch(e) {
1104
// TODO use testName instead of name for no-markup message?
1105
QUnit.ok( false, "Teardown failed on " + this.name + ": " + e.message );
1106
}
1107
},
1108
finish: function() {
1109
if ( this.expected && this.expected != this.assertions.length ) {
1110
QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
1111
}
1112
1113
var good = 0, bad = 0,
1114
tests = id("qunit-tests");
1115
1116
config.stats.all += this.assertions.length;
1117
config.moduleStats.all += this.assertions.length;
1118
1119
if ( tests ) {
1120
var ol = document.createElement("ol");
1121
1122
for ( var i = 0; i < this.assertions.length; i++ ) {
1123
var assertion = this.assertions[i];
1124
1125
var li = document.createElement("li");
1126
li.className = assertion.result ? "pass" : "fail";
1127
li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
1128
ol.appendChild( li );
1129
1130
if ( assertion.result ) {
1131
good++;
1132
} else {
1133
bad++;
1134
config.stats.bad++;
1135
config.moduleStats.bad++;
1136
}
1137
}
1138
1139
// store result when possible
1140
defined.sessionStorage && sessionStorage.setItem("qunit-" + this.testName, bad);
1141
1142
if (bad == 0) {
1143
ol.style.display = "none";
1144
}
1145
1146
var b = document.createElement("strong");
1147
b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
1148
1149
addEvent(b, "click", function() {
1150
var next = b.nextSibling, display = next.style.display;
1151
next.style.display = display === "none" ? "block" : "none";
1152
});
1153
1154
addEvent(b, "dblclick", function(e) {
1155
var target = e && e.target ? e.target : window.event.srcElement;
1156
if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
1157
target = target.parentNode;
1158
}
1159
if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
1160
window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, ""));
1161
}
1162
});
1163
1164
var li = id(this.id);
1165
li.className = bad ? "fail" : "pass";
1166
li.style.display = resultDisplayStyle(!bad);
1167
li.removeChild( li.firstChild );
1168
li.appendChild( b );
1169
li.appendChild( ol );
1170
1171
if ( bad ) {
1172
var toolbar = id("qunit-testrunner-toolbar");
1173
if ( toolbar ) {
1174
toolbar.style.display = "block";
1175
id("qunit-filter-pass").disabled = null;
1176
}
1177
}
1178
1179
} else {
1180
for ( var i = 0; i < this.assertions.length; i++ ) {
1181
if ( !this.assertions[i].result ) {
1182
bad++;
1183
config.stats.bad++;
1184
config.moduleStats.bad++;
1185
}
1186
}
1187
}
1188
1189
try {
1190
QUnit.reset();
1191
} catch(e) {
1192
// TODO use testName instead of name for no-markup message?
1193
fail("reset() failed, following Test " + this.name + ", exception and reset fn follows", e, QUnit.reset);
1194
}
1195
1196
QUnit.testDone( this.testName, bad, this.assertions.length );
1197
},
1198
1199
queue: function() {
1200
var test = this;
1201
synchronize(function() {
1202
test.init();
1203
});
1204
function run() {
1205
// each of these can by async
1206
synchronize(function() {
1207
test.setup();
1208
});
1209
synchronize(function() {
1210
test.run();
1211
});
1212
synchronize(function() {
1213
test.teardown();
1214
});
1215
synchronize(function() {
1216
test.finish();
1217
});
1218
}
1219
// defer when previous test run passed, if storage is available
1220
var bad = defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.testName);
1221
if (bad) {
1222
run();
1223
} else {
1224
synchronize(run);
1225
};
1226
}
1227
1228
}
1229
1230
var QUnit = {
1231
1232
// call on start of module test to prepend name to all tests
1233
module: function(name, testEnvironment) {
1234
config.previousModule = config.currentModule;
1235
config.currentModule = name;
1236
config.currentModuleTestEnviroment = testEnvironment;
1237
},
1238
1239
asyncTest: function(testName, expected, callback) {
1240
if ( arguments.length === 2 ) {
1241
callback = expected;
1242
expected = 0;
1243
}
1244
1245
QUnit.test(testName, expected, callback, true);
1246
},
1247
1248
test: function(testName, expected, callback, async) {
1249
var name = '<span class="test-name">' + testName + '</span>', testEnvironmentArg;
1250
1251
if ( arguments.length === 2 ) {
1252
callback = expected;
1253
expected = null;
1254
}
1255
// is 2nd argument a testEnvironment?
1256
if ( expected && typeof expected === 'object') {
1257
testEnvironmentArg = expected;
1258
expected = null;
1259
}
1260
1261
if ( config.currentModule ) {
1262
name = '<span class="module-name">' + config.currentModule + "</span>: " + name;
1263
}
1264
1265
if ( !validTest(config.currentModule + ": " + testName) ) {
1266
return;
1267
}
1268
1269
var test = new Test(name, testName, expected, testEnvironmentArg, async, callback);
1270
test.previousModule = config.previousModule;
1271
test.module = config.currentModule;
1272
test.moduleTestEnvironment = config.currentModuleTestEnviroment;
1273
test.queue();
1274
},
1275
1276
/**
1277
* Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
1278
*/
1279
expect: function(asserts) {
1280
config.current.expected = asserts;
1281
},
1282
1283
/**
1284
* Asserts true.
1285
* @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
1286
*/
1287
ok: function(a, msg) {
1288
a = !!a;
1289
var details = {
1290
result: a,
1291
message: msg
1292
};
1293
msg = escapeHtml(msg);
1294
QUnit.log(a, msg, details);
1295
config.current.assertions.push({
1296
result: a,
1297
message: msg
1298
});
1299
},
1300
1301
/**
1302
* Checks that the first two arguments are equal, with an optional message.
1303
* Prints out both actual and expected values.
1304
*
1305
* Prefered to ok( actual == expected, message )
1306
*
1307
* @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
1308
*
1309
* @param Object actual
1310
* @param Object expected
1311
* @param String message (optional)
1312
*/
1313
equal: function(actual, expected, message) {
1314
QUnit.push(expected == actual, actual, expected, message);
1315
},
1316
1317
notEqual: function(actual, expected, message) {
1318
QUnit.push(expected != actual, actual, expected, message);
1319
},
1320
1321
deepEqual: function(actual, expected, message) {
1322
QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
1323
},
1324
1325
notDeepEqual: function(actual, expected, message) {
1326
QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
1327
},
1328
1329
strictEqual: function(actual, expected, message) {
1330
QUnit.push(expected === actual, actual, expected, message);
1331
},
1332
1333
notStrictEqual: function(actual, expected, message) {
1334
QUnit.push(expected !== actual, actual, expected, message);
1335
},
1336
1337
raises: function(block, expected, message) {
1338
var actual, ok = false;
1339
1340
if (typeof expected === 'string') {
1341
message = expected;
1342
expected = null;
1343
}
1344
1345
try {
1346
block();
1347
} catch (e) {
1348
actual = e;
1349
}
1350
1351
if (actual) {
1352
// we don't want to validate thrown error
1353
if (!expected) {
1354
ok = true;
1355
// expected is a regexp
1356
} else if (QUnit.objectType(expected) === "regexp") {
1357
ok = expected.test(actual);
1358
// expected is a constructor
1359
} else if (actual instanceof expected) {
1360
ok = true;
1361
// expected is a validation function which returns true is validation passed
1362
} else if (expected.call({}, actual) === true) {
1363
ok = true;
1364
}
1365
}
1366
1367
QUnit.ok(ok, message);
1368
},
1369
1370
start: function() {
1371
// A slight delay, to avoid any current callbacks
1372
if ( defined.setTimeout ) {
1373
window.setTimeout(function() {
1374
if ( config.timeout ) {
1375
clearTimeout(config.timeout);
1376
}
1377
1378
config.blocking = false;
1379
process();
1380
}, 13);
1381
} else {
1382
config.blocking = false;
1383
process();
1384
}
1385
},
1386
1387
stop: function(timeout) {
1388
config.blocking = true;
1389
1390
if ( timeout && defined.setTimeout ) {
1391
config.timeout = window.setTimeout(function() {
1392
QUnit.ok( false, "Test timed out" );
1393
QUnit.start();
1394
}, timeout);
1395
}
1396
}
1397
1398
};
1399
1400
// Backwards compatibility, deprecated
1401
QUnit.equals = QUnit.equal;
1402
QUnit.same = QUnit.deepEqual;
1403
1404
// Maintain internal state
1405
var config = {
1406
// The queue of tests to run
1407
queue: [],
1408
1409
// block until document ready
1410
blocking: true
1411
};
1412
1413
// Load paramaters
1414
(function() {
1415
var location = window.location || { search: "", protocol: "file:" },
1416
GETParams = location.search.slice(1).split('&');
1417
1418
for ( var i = 0; i < GETParams.length; i++ ) {
1419
GETParams[i] = decodeURIComponent( GETParams[i] );
1420
if ( GETParams[i] === "noglobals" ) {
1421
GETParams.splice( i, 1 );
1422
i--;
1423
config.noglobals = true;
1424
} else if ( GETParams[i].search('=') > -1 ) {
1425
GETParams.splice( i, 1 );
1426
i--;
1427
}
1428
}
1429
1430
// restrict modules/tests by get parameters
1431
config.filters = GETParams;
1432
1433
// Figure out if we're running the tests from a server or not
1434
QUnit.isLocal = !!(location.protocol === 'file:');
1435
})();
1436
1437
// Expose the API as global variables, unless an 'exports'
1438
// object exists, in that case we assume we're in CommonJS
1439
if ( typeof exports === "undefined" || typeof require === "undefined" ) {
1440
extend(window, QUnit);
1441
window.QUnit = QUnit;
1442
} else {
1443
extend(exports, QUnit);
1444
exports.QUnit = QUnit;
1445
}
1446
1447
// define these after exposing globals to keep them in these QUnit namespace only
1448
extend(QUnit, {
1449
config: config,
1450
1451
// Initialize the configuration options
1452
init: function() {
1453
extend(config, {
1454
stats: { all: 0, bad: 0 },
1455
moduleStats: { all: 0, bad: 0 },
1456
started: +new Date,
1457
updateRate: 1000,
1458
blocking: false,
1459
autostart: true,
1460
autorun: false,
1461
filters: [],
1462
queue: []
1463
});
1464
1465
var tests = id("qunit-tests"),
1466
banner = id("qunit-banner"),
1467
result = id("qunit-testresult");
1468
1469
if ( tests ) {
1470
tests.innerHTML = "";
1471
}
1472
1473
if ( banner ) {
1474
banner.className = "";
1475
}
1476
1477
if ( result ) {
1478
result.parentNode.removeChild( result );
1479
}
1480
},
1481
1482
/**
1483
* Resets the test setup. Useful for tests that modify the DOM.
1484
*
1485
* If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
1486
*/
1487
reset: function() {
1488
if ( window.jQuery ) {
1489
jQuery( "#main, #qunit-fixture" ).html( config.fixture );
1490
} else {
1491
var main = id( 'main' ) || id( 'qunit-fixture' );
1492
if ( main ) {
1493
main.innerHTML = config.fixture;
1494
}
1495
}
1496
},
1497
1498
/**
1499
* Trigger an event on an element.
1500
*
1501
* @example triggerEvent( document.body, "click" );
1502
*
1503
* @param DOMElement elem
1504
* @param String type
1505
*/
1506
triggerEvent: function( elem, type, event ) {
1507
if ( document.createEvent ) {
1508
event = document.createEvent("MouseEvents");
1509
event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
1510
0, 0, 0, 0, 0, false, false, false, false, 0, null);
1511
elem.dispatchEvent( event );
1512
1513
} else if ( elem.fireEvent ) {
1514
elem.fireEvent("on"+type);
1515
}
1516
},
1517
1518
// Safe object type checking
1519
is: function( type, obj ) {
1520
return QUnit.objectType( obj ) == type;
1521
},
1522
1523
objectType: function( obj ) {
1524
if (typeof obj === "undefined") {
1525
return "undefined";
1526
1527
// consider: typeof null === object
1528
}
1529
if (obj === null) {
1530
return "null";
1531
}
1532
1533
var type = Object.prototype.toString.call( obj )
1534
.match(/^\[object\s(.*)\]$/)[1] || '';
1535
1536
switch (type) {
1537
case 'Number':
1538
if (isNaN(obj)) {
1539
return "nan";
1540
} else {
1541
return "number";
1542
}
1543
case 'String':
1544
case 'Boolean':
1545
case 'Array':
1546
case 'Date':
1547
case 'RegExp':
1548
case 'Function':
1549
return type.toLowerCase();
1550
}
1551
if (typeof obj === "object") {
1552
return "object";
1553
}
1554
return undefined;
1555
},
1556
1557
push: function(result, actual, expected, message) {
1558
var details = {
1559
result: result,
1560
message: message,
1561
actual: actual,
1562
expected: expected
1563
};
1564
1565
message = escapeHtml(message) || (result ? "okay" : "failed");
1566
message = '<span class="test-message">' + message + "</span>";
1567
expected = escapeHtml(QUnit.jsDump.parse(expected));
1568
actual = escapeHtml(QUnit.jsDump.parse(actual));
1569
var output = message + '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected + '</pre></td></tr>';
1570
if (actual != expected) {
1571
output += '<tr class="test-actual"><th>Result: </th><td><pre>' + actual + '</pre></td></tr>';
1572
output += '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit.diff(expected, actual) +'</pre></td></tr>';
1573
}
1574
if (!result) {
1575
var source = sourceFromStacktrace();
1576
if (source) {
1577
details.source = source;
1578
output += '<tr class="test-source"><th>Source: </th><td><pre>' + source +'</pre></td></tr>';
1579
}
1580
}
1581
output += "</table>";
1582
1583
QUnit.log(result, message, details);
1584
1585
config.current.assertions.push({
1586
result: !!result,
1587
message: output
1588
});
1589
},
1590
1591
// Logging callbacks
1592
begin: function() {},
1593
done: function(failures, total) {},
1594
log: function(result, message) {},
1595
testStart: function(name, testEnvironment) {},
1596
testDone: function(name, failures, total) {},
1597
moduleStart: function(name, testEnvironment) {},
1598
moduleDone: function(name, failures, total) {}
1599
});
1600
1601
if ( typeof document === "undefined" || document.readyState === "complete" ) {
1602
config.autorun = true;
1603
}
1604
1605
addEvent(window, "load", function() {
1606
QUnit.begin();
1607
1608
// Initialize the config, saving the execution queue
1609
var oldconfig = extend({}, config);
1610
QUnit.init();
1611
extend(config, oldconfig);
1612
1613
config.blocking = false;
1614
1615
var userAgent = id("qunit-userAgent");
1616
if ( userAgent ) {
1617
userAgent.innerHTML = navigator.userAgent;
1618
}
1619
var banner = id("qunit-header");
1620
if ( banner ) {
1621
var paramsIndex = location.href.lastIndexOf(location.search);
1622
if ( paramsIndex > -1 ) {
1623
var mainPageLocation = location.href.slice(0, paramsIndex);
1624
if ( mainPageLocation == location.href ) {
1625
banner.innerHTML = '<a href=""> ' + banner.innerHTML + '</a> ';
1626
} else {
1627
var testName = decodeURIComponent(location.search.slice(1));
1628
banner.innerHTML = '<a href="' + mainPageLocation + '">' + banner.innerHTML + '</a> &#8250; <a href="">' + testName + '</a>';
1629
}
1630
}
1631
}
1632
1633
var toolbar = id("qunit-testrunner-toolbar");
1634
if ( toolbar ) {
1635
toolbar.style.display = "none";
1636
1637
var filter = document.createElement("input");
1638
filter.type = "checkbox";
1639
filter.id = "qunit-filter-pass";
1640
filter.disabled = true;
1641
addEvent( filter, "click", function() {
1642
var li = document.getElementsByTagName("li");
1643
for ( var i = 0; i < li.length; i++ ) {
1644
if ( li[i].className.indexOf("pass") > -1 ) {
1645
li[i].style.display = filter.checked ? "none" : "";
1646
}
1647
}
1648
});
1649
toolbar.appendChild( filter );
1650
1651
var label = document.createElement("label");
1652
label.setAttribute("for", "qunit-filter-pass");
1653
label.innerHTML = "Hide passed tests";
1654
toolbar.appendChild( label );
1655
}
1656
1657
var main = id('main') || id('qunit-fixture');
1658
if ( main ) {
1659
config.fixture = main.innerHTML;
1660
}
1661
1662
if (config.autostart) {
1663
QUnit.start();
1664
}
1665
});
1666
1667
function done() {
1668
config.autorun = true;
1669
1670
// Log the last module results
1671
if ( config.currentModule ) {
1672
QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
1673
}
1674
1675
var banner = id("qunit-banner"),
1676
tests = id("qunit-tests"),
1677
html = ['Tests completed in ',
1678
+new Date - config.started, ' milliseconds.<br/>',
1679
'<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('');
1680
1681
if ( banner ) {
1682
banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
1683
}
1684
1685
if ( tests ) {
1686
var result = id("qunit-testresult");
1687
1688
if ( !result ) {
1689
result = document.createElement("p");
1690
result.id = "qunit-testresult";
1691
result.className = "result";
1692
tests.parentNode.insertBefore( result, tests.nextSibling );
1693
}
1694
1695
result.innerHTML = html;
1696
}
1697
1698
QUnit.done( config.stats.bad, config.stats.all );
1699
}
1700
1701
function validTest( name ) {
1702
var i = config.filters.length,
1703
run = false;
1704
1705
if ( !i ) {
1706
return true;
1707
}
1708
1709
while ( i-- ) {
1710
var filter = config.filters[i],
1711
not = filter.charAt(0) == '!';
1712
1713
if ( not ) {
1714
filter = filter.slice(1);
1715
}
1716
1717
if ( name.indexOf(filter) !== -1 ) {
1718
return !not;
1719
}
1720
1721
if ( not ) {
1722
run = true;
1723
}
1724
}
1725
1726
return run;
1727
}
1728
1729
// so far supports only Firefox, Chrome and Opera (buggy)
1730
// could be extended in the future to use something like https://github.com/csnover/TraceKit
1731
function sourceFromStacktrace() {
1732
try {
1733
throw new Error();
1734
} catch ( e ) {
1735
if (e.stacktrace) {
1736
// Opera
1737
return e.stacktrace.split("\n")[6];
1738
} else if (e.stack) {
1739
// Firefox, Chrome
1740
return e.stack.split("\n")[4];
1741
}
1742
}
1743
}
1744
1745
function resultDisplayStyle(passed) {
1746
return passed && id("qunit-filter-pass") && id("qunit-filter-pass").checked ? 'none' : '';
1747
}
1748
1749
function escapeHtml(s) {
1750
if (!s) {
1751
return "";
1752
}
1753
s = s + "";
1754
return s.replace(/[\&"<>\\]/g, function(s) {
1755
switch(s) {
1756
case "&": return "&amp;";
1757
case "\\": return "\\\\";
1758
case '"': return '\"';
1759
case "<": return "&lt;";
1760
case ">": return "&gt;";
1761
default: return s;
1762
}
1763
});
1764
}
1765
1766
function synchronize( callback ) {
1767
config.queue.push( callback );
1768
1769
if ( config.autorun && !config.blocking ) {
1770
process();
1771
}
1772
}
1773
1774
function process() {
1775
var start = (new Date()).getTime();
1776
1777
while ( config.queue.length && !config.blocking ) {
1778
if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) {
1779
config.queue.shift()();
1780
} else {
1781
window.setTimeout( process, 13 );
1782
break;
1783
}
1784
}
1785
if (!config.blocking && !config.queue.length) {
1786
done();
1787
}
1788
}
1789
1790
function saveGlobal() {
1791
config.pollution = [];
1792
1793
if ( config.noglobals ) {
1794
for ( var key in window ) {
1795
config.pollution.push( key );
1796
}
1797
}
1798
}
1799
1800
function checkPollution( name ) {
1801
var old = config.pollution;
1802
saveGlobal();
1803
1804
var newGlobals = diff( old, config.pollution );
1805
if ( newGlobals.length > 0 ) {
1806
ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
1807
config.current.expected++;
1808
}
1809
1810
var deletedGlobals = diff( config.pollution, old );
1811
if ( deletedGlobals.length > 0 ) {
1812
ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
1813
config.current.expected++;
1814
}
1815
}
1816
1817
// returns a new Array with the elements that are in a but not in b
1818
function diff( a, b ) {
1819
var result = a.slice();
1820
for ( var i = 0; i < result.length; i++ ) {
1821
for ( var j = 0; j < b.length; j++ ) {
1822
if ( result[i] === b[j] ) {
1823
result.splice(i, 1);
1824
i--;
1825
break;
1826
}
1827
}
1828
}
1829
return result;
1830
}
1831
1832
function fail(message, exception, callback) {
1833
if ( typeof console !== "undefined" && console.error && console.warn ) {
1834
console.error(message);
1835
console.error(exception);
1836
console.warn(callback.toString());
1837
1838
} else if ( window.opera && opera.postError ) {
1839
opera.postError(message, exception, callback.toString);
1840
}
1841
}
1842
1843
function extend(a, b) {
1844
for ( var prop in b ) {
1845
a[prop] = b[prop];
1846
}
1847
1848
return a;
1849
}
1850
1851
function addEvent(elem, type, fn) {
1852
if ( elem.addEventListener ) {
1853
elem.addEventListener( type, fn, false );
1854
} else if ( elem.attachEvent ) {
1855
elem.attachEvent( "on" + type, fn );
1856
} else {
1857
fn();
1858
}
1859
}
1860
1861
function id(name) {
1862
return !!(typeof document !== "undefined" && document && document.getElementById) &&
1863
document.getElementById( name );
1864
}
1865
1866
// Test for equality any JavaScript type.
1867
// Discussions and reference: http://philrathe.com/articles/equiv
1868
// Test suites: http://philrathe.com/tests/equiv
1869
// Author: Philippe Rathé <[email protected]>
1870
QUnit.equiv = function () {
1871
1872
var innerEquiv; // the real equiv function
1873
var callers = []; // stack to decide between skip/abort functions
1874
var parents = []; // stack to avoiding loops from circular referencing
1875
1876
// Call the o related callback with the given arguments.
1877
function bindCallbacks(o, callbacks, args) {
1878
var prop = QUnit.objectType(o);
1879
if (prop) {
1880
if (QUnit.objectType(callbacks[prop]) === "function") {
1881
return callbacks[prop].apply(callbacks, args);
1882
} else {
1883
return callbacks[prop]; // or undefined
1884
}
1885
}
1886
}
1887
1888
var callbacks = function () {
1889
1890
// for string, boolean, number and null
1891
function useStrictEquality(b, a) {
1892
if (b instanceof a.constructor || a instanceof b.constructor) {
1893
// to catch short annotaion VS 'new' annotation of a declaration
1894
// e.g. var i = 1;
1895
// var j = new Number(1);
1896
return a == b;
1897
} else {
1898
return a === b;
1899
}
1900
}
1901
1902
return {
1903
"string": useStrictEquality,
1904
"boolean": useStrictEquality,
1905
"number": useStrictEquality,
1906
"null": useStrictEquality,
1907
"undefined": useStrictEquality,
1908
1909
"nan": function (b) {
1910
return isNaN(b);
1911
},
1912
1913
"date": function (b, a) {
1914
return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf();
1915
},
1916
1917
"regexp": function (b, a) {
1918
return QUnit.objectType(b) === "regexp" &&
1919
a.source === b.source && // the regex itself
1920
a.global === b.global && // and its modifers (gmi) ...
1921
a.ignoreCase === b.ignoreCase &&
1922
a.multiline === b.multiline;
1923
},
1924
1925
// - skip when the property is a method of an instance (OOP)
1926
// - abort otherwise,
1927
// initial === would have catch identical references anyway
1928
"function": function () {
1929
var caller = callers[callers.length - 1];
1930
return caller !== Object &&
1931
typeof caller !== "undefined";
1932
},
1933
1934
"array": function (b, a) {
1935
var i, j, loop;
1936
var len;
1937
1938
// b could be an object literal here
1939
if ( ! (QUnit.objectType(b) === "array")) {
1940
return false;
1941
}
1942
1943
len = a.length;
1944
if (len !== b.length) { // safe and faster
1945
return false;
1946
}
1947
1948
//track reference to avoid circular references
1949
parents.push(a);
1950
for (i = 0; i < len; i++) {
1951
loop = false;
1952
for(j=0;j<parents.length;j++){
1953
if(parents[j] === a[i]){
1954
loop = true;//dont rewalk array
1955
}
1956
}
1957
if (!loop && ! innerEquiv(a[i], b[i])) {
1958
parents.pop();
1959
return false;
1960
}
1961
}
1962
parents.pop();
1963
return true;
1964
},
1965
1966
"object": function (b, a) {
1967
var i, j, loop;
1968
var eq = true; // unless we can proove it
1969
var aProperties = [], bProperties = []; // collection of strings
1970
1971
// comparing constructors is more strict than using instanceof
1972
if ( a.constructor !== b.constructor) {
1973
return false;
1974
}
1975
1976
// stack constructor before traversing properties
1977
callers.push(a.constructor);
1978
//track reference to avoid circular references
1979
parents.push(a);
1980
1981
for (i in a) { // be strict: don't ensures hasOwnProperty and go deep
1982
loop = false;
1983
for(j=0;j<parents.length;j++){
1984
if(parents[j] === a[i])
1985
loop = true; //don't go down the same path twice
1986
}
1987
aProperties.push(i); // collect a's properties
1988
1989
if (!loop && ! innerEquiv(a[i], b[i])) {
1990
eq = false;
1991
break;
1992
}
1993
}
1994
1995
callers.pop(); // unstack, we are done
1996
parents.pop();
1997
1998
for (i in b) {
1999
bProperties.push(i); // collect b's properties
2000
}
2001
2002
// Ensures identical properties name
2003
return eq && innerEquiv(aProperties.sort(), bProperties.sort());
2004
}
2005
};
2006
}();
2007
2008
innerEquiv = function () { // can take multiple arguments
2009
var args = Array.prototype.slice.apply(arguments);
2010
if (args.length < 2) {
2011
return true; // end transition
2012
}
2013
2014
return (function (a, b) {
2015
if (a === b) {
2016
return true; // catch the most you can
2017
} else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || QUnit.objectType(a) !== QUnit.objectType(b)) {
2018
return false; // don't lose time with error prone cases
2019
} else {
2020
return bindCallbacks(a, callbacks, [b, a]);
2021
}
2022
2023
// apply transition with (1..n) arguments
2024
})(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1));
2025
};
2026
2027
return innerEquiv;
2028
2029
}();
2030
2031
/**
2032
* jsDump
2033
* Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
2034
* Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
2035
* Date: 5/15/2008
2036
* @projectDescription Advanced and extensible data dumping for Javascript.
2037
* @version 1.0.0
2038
* @author Ariel Flesler
2039
* @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
2040
*/
2041
QUnit.jsDump = (function() {
2042
function quote( str ) {
2043
return '"' + str.toString().replace(/"/g, '\\"') + '"';
2044
};
2045
function literal( o ) {
2046
return o + '';
2047
};
2048
function join( pre, arr, post ) {
2049
var s = jsDump.separator(),
2050
base = jsDump.indent(),
2051
inner = jsDump.indent(1);
2052
if ( arr.join )
2053
arr = arr.join( ',' + s + inner );
2054
if ( !arr )
2055
return pre + post;
2056
return [ pre, inner + arr, base + post ].join(s);
2057
};
2058
function array( arr ) {
2059
var i = arr.length, ret = Array(i);
2060
this.up();
2061
while ( i-- )
2062
ret[i] = this.parse( arr[i] );
2063
this.down();
2064
return join( '[', ret, ']' );
2065
};
2066
2067
var reName = /^function (\w+)/;
2068
2069
var jsDump = {
2070
parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance
2071
var parser = this.parsers[ type || this.typeOf(obj) ];
2072
type = typeof parser;
2073
2074
return type == 'function' ? parser.call( this, obj ) :
2075
type == 'string' ? parser :
2076
this.parsers.error;
2077
},
2078
typeOf:function( obj ) {
2079
var type;
2080
if ( obj === null ) {
2081
type = "null";
2082
} else if (typeof obj === "undefined") {
2083
type = "undefined";
2084
} else if (QUnit.is("RegExp", obj)) {
2085
type = "regexp";
2086
} else if (QUnit.is("Date", obj)) {
2087
type = "date";
2088
} else if (QUnit.is("Function", obj)) {
2089
type = "function";
2090
} else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") {
2091
type = "window";
2092
} else if (obj.nodeType === 9) {
2093
type = "document";
2094
} else if (obj.nodeType) {
2095
type = "node";
2096
} else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) {
2097
type = "array";
2098
} else {
2099
type = typeof obj;
2100
}
2101
return type;
2102
},
2103
separator:function() {
2104
return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
2105
},
2106
indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
2107
if ( !this.multiline )
2108
return '';
2109
var chr = this.indentChar;
2110
if ( this.HTML )
2111
chr = chr.replace(/\t/g,' ').replace(/ /g,'&nbsp;');
2112
return Array( this._depth_ + (extra||0) ).join(chr);
2113
},
2114
up:function( a ) {
2115
this._depth_ += a || 1;
2116
},
2117
down:function( a ) {
2118
this._depth_ -= a || 1;
2119
},
2120
setParser:function( name, parser ) {
2121
this.parsers[name] = parser;
2122
},
2123
// The next 3 are exposed so you can use them
2124
quote:quote,
2125
literal:literal,
2126
join:join,
2127
//
2128
_depth_: 1,
2129
// This is the list of parsers, to modify them, use jsDump.setParser
2130
parsers:{
2131
window: '[Window]',
2132
document: '[Document]',
2133
error:'[ERROR]', //when no parser is found, shouldn't happen
2134
unknown: '[Unknown]',
2135
'null':'null',
2136
undefined:'undefined',
2137
'function':function( fn ) {
2138
var ret = 'function',
2139
name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
2140
if ( name )
2141
ret += ' ' + name;
2142
ret += '(';
2143
2144
ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join('');
2145
return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' );
2146
},
2147
array: array,
2148
nodelist: array,
2149
arguments: array,
2150
object:function( map ) {
2151
var ret = [ ];
2152
QUnit.jsDump.up();
2153
for ( var key in map )
2154
ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) );
2155
QUnit.jsDump.down();
2156
return join( '{', ret, '}' );
2157
},
2158
node:function( node ) {
2159
var open = QUnit.jsDump.HTML ? '&lt;' : '<',
2160
close = QUnit.jsDump.HTML ? '&gt;' : '>';
2161
2162
var tag = node.nodeName.toLowerCase(),
2163
ret = open + tag;
2164
2165
for ( var a in QUnit.jsDump.DOMAttrs ) {
2166
var val = node[QUnit.jsDump.DOMAttrs[a]];
2167
if ( val )
2168
ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' );
2169
}
2170
return ret + close + open + '/' + tag + close;
2171
},
2172
functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
2173
var l = fn.length;
2174
if ( !l ) return '';
2175
2176
var args = Array(l);
2177
while ( l-- )
2178
args[l] = String.fromCharCode(97+l);//97 is 'a'
2179
return ' ' + args.join(', ') + ' ';
2180
},
2181
key:quote, //object calls it internally, the key part of an item in a map
2182
functionCode:'[code]', //function calls it internally, it's the content of the function
2183
attribute:quote, //node calls it internally, it's an html attribute value
2184
string:quote,
2185
date:quote,
2186
regexp:literal, //regex
2187
number:literal,
2188
'boolean':literal
2189
},
2190
DOMAttrs:{//attributes to dump from nodes, name=>realName
2191
id:'id',
2192
name:'name',
2193
'class':'className'
2194
},
2195
HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
2196
indentChar:' ',//indentation unit
2197
multiline:true //if true, items in a collection, are separated by a \n, else just a space.
2198
};
2199
2200
return jsDump;
2201
})();
2202
2203
// from Sizzle.js
2204
function getText( elems ) {
2205
var ret = "", elem;
2206
2207
for ( var i = 0; elems[i]; i++ ) {
2208
elem = elems[i];
2209
2210
// Get the text from text nodes and CDATA nodes
2211
if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
2212
ret += elem.nodeValue;
2213
2214
// Traverse everything else, except comment nodes
2215
} else if ( elem.nodeType !== 8 ) {
2216
ret += getText( elem.childNodes );
2217
}
2218
}
2219
2220
return ret;
2221
};
2222
2223
/*
2224
* Javascript Diff Algorithm
2225
* By John Resig (http://ejohn.org/)
2226
* Modified by Chu Alan "sprite"
2227
*
2228
* Released under the MIT license.
2229
*
2230
* More Info:
2231
* http://ejohn.org/projects/javascript-diff-algorithm/
2232
*
2233
* Usage: QUnit.diff(expected, actual)
2234
*
2235
* 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"
2236
*/
2237
QUnit.diff = (function() {
2238
function diff(o, n){
2239
var ns = new Object();
2240
var os = new Object();
2241
2242
for (var i = 0; i < n.length; i++) {
2243
if (ns[n[i]] == null)
2244
ns[n[i]] = {
2245
rows: new Array(),
2246
o: null
2247
};
2248
ns[n[i]].rows.push(i);
2249
}
2250
2251
for (var i = 0; i < o.length; i++) {
2252
if (os[o[i]] == null)
2253
os[o[i]] = {
2254
rows: new Array(),
2255
n: null
2256
};
2257
os[o[i]].rows.push(i);
2258
}
2259
2260
for (var i in ns) {
2261
if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
2262
n[ns[i].rows[0]] = {
2263
text: n[ns[i].rows[0]],
2264
row: os[i].rows[0]
2265
};
2266
o[os[i].rows[0]] = {
2267
text: o[os[i].rows[0]],
2268
row: ns[i].rows[0]
2269
};
2270
}
2271
}
2272
2273
for (var i = 0; i < n.length - 1; i++) {
2274
if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
2275
n[i + 1] == o[n[i].row + 1]) {
2276
n[i + 1] = {
2277
text: n[i + 1],
2278
row: n[i].row + 1
2279
};
2280
o[n[i].row + 1] = {
2281
text: o[n[i].row + 1],
2282
row: i + 1
2283
};
2284
}
2285
}
2286
2287
for (var i = n.length - 1; i > 0; i--) {
2288
if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
2289
n[i - 1] == o[n[i].row - 1]) {
2290
n[i - 1] = {
2291
text: n[i - 1],
2292
row: n[i].row - 1
2293
};
2294
o[n[i].row - 1] = {
2295
text: o[n[i].row - 1],
2296
row: i - 1
2297
};
2298
}
2299
}
2300
2301
return {
2302
o: o,
2303
n: n
2304
};
2305
}
2306
2307
return function(o, n){
2308
o = o.replace(/\s+$/, '');
2309
n = n.replace(/\s+$/, '');
2310
var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));
2311
2312
var str = "";
2313
2314
var oSpace = o.match(/\s+/g);
2315
if (oSpace == null) {
2316
oSpace = [" "];
2317
}
2318
else {
2319
oSpace.push(" ");
2320
}
2321
var nSpace = n.match(/\s+/g);
2322
if (nSpace == null) {
2323
nSpace = [" "];
2324
}
2325
else {
2326
nSpace.push(" ");
2327
}
2328
2329
if (out.n.length == 0) {
2330
for (var i = 0; i < out.o.length; i++) {
2331
str += '<del>' + out.o[i] + oSpace[i] + "</del>";
2332
}
2333
}
2334
else {
2335
if (out.n[0].text == null) {
2336
for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
2337
str += '<del>' + out.o[n] + oSpace[n] + "</del>";
2338
}
2339
}
2340
2341
for (var i = 0; i < out.n.length; i++) {
2342
if (out.n[i].text == null) {
2343
str += '<ins>' + out.n[i] + nSpace[i] + "</ins>";
2344
}
2345
else {
2346
var pre = "";
2347
2348
for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
2349
pre += '<del>' + out.o[n] + oSpace[n] + "</del>";
2350
}
2351
str += " " + out.n[i].text + nSpace[i] + pre;
2352
}
2353
}
2354
}
2355
2356
return str;
2357
};
2358
})();
2359
2360
})(this);
2361