Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: chrome/content/tests/qunit.js

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

Powered by Google App Engine
This is Rietveld