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