source: other-projects/nz-flag-design/trunk/main-form/bgrins-spectrum/test/qunit.js@ 29741

Last change on this file since 29741 was 29741, checked in by davidb, 9 years ago

Support JS library for colour picker

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