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

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

Issue 5721576209121280: Issue 2047 - Updated QUnit to version 1.17.1 (Closed)
Patch Set: Created Feb. 26, 2015, 12:28 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « chrome/content/qunit.css ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 /** 1 /*!
2 * QUnit v1.12.0 - A JavaScript Unit Testing Framework 2 * QUnit 1.17.1
3 * http://qunitjs.com/
3 * 4 *
4 * http://qunitjs.com 5 * Copyright jQuery Foundation and other contributors
6 * Released under the MIT license
7 * http://jquery.org/license
5 * 8 *
6 * Copyright 2013 jQuery Foundation and other contributors 9 * Date: 2015-01-20T19:39Z
7 * Released under the MIT license.
8 * https://jquery.org/license/
9 */ 10 */
10 11
11 (function( window ) { 12 (function( window ) {
12 13
13 var QUnit, 14 var QUnit,
14 assert,
15 config, 15 config,
16 onErrorFnPrev, 16 onErrorFnPrev,
17 » testId = 0, 17 » loggingCallbacks = {},
18 » fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, ""). replace(/.+\//, ""), 18 » fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ),
19 toString = Object.prototype.toString, 19 toString = Object.prototype.toString,
20 hasOwn = Object.prototype.hasOwnProperty, 20 hasOwn = Object.prototype.hasOwnProperty,
21 // Keep a local reference to Date (GH-283) 21 // Keep a local reference to Date (GH-283)
22 Date = window.Date, 22 Date = window.Date,
23 now = Date.now || function() {
24 return new Date().getTime();
25 },
26 globalStartCalled = false,
27 runStarted = false,
23 setTimeout = window.setTimeout, 28 setTimeout = window.setTimeout,
29 clearTimeout = window.clearTimeout,
24 defined = { 30 defined = {
25 » » setTimeout: typeof window.setTimeout !== "undefined", 31 » » document: window.document !== undefined,
32 » » setTimeout: window.setTimeout !== undefined,
26 sessionStorage: (function() { 33 sessionStorage: (function() {
27 var x = "qunit-test-string"; 34 var x = "qunit-test-string";
28 try { 35 try {
29 sessionStorage.setItem( x, x ); 36 sessionStorage.setItem( x, x );
30 sessionStorage.removeItem( x ); 37 sessionStorage.removeItem( x );
31 return true; 38 return true;
32 » » » } catch( e ) { 39 » » » } catch ( e ) {
33 return false; 40 return false;
34 } 41 }
35 }()) 42 }())
36 }, 43 },
37 /** 44 /**
38 * Provides a normalized error string, correcting an issue 45 * Provides a normalized error string, correcting an issue
39 * with IE 7 (and prior) where Error.prototype.toString is 46 * with IE 7 (and prior) where Error.prototype.toString is
40 * not properly implemented 47 * not properly implemented
41 * 48 *
42 * Based on http://es5.github.com/#x15.11.4.4 49 * Based on http://es5.github.com/#x15.11.4.4
(...skipping 21 matching lines...) Expand all
64 } 71 }
65 }, 72 },
66 /** 73 /**
67 * Makes a clone of an object using only Array or Object as base, 74 * Makes a clone of an object using only Array or Object as base,
68 * and copies over the own enumerable properties. 75 * and copies over the own enumerable properties.
69 * 76 *
70 * @param {Object} obj 77 * @param {Object} obj
71 * @return {Object} New object with only the own properties (recursively ). 78 * @return {Object} New object with only the own properties (recursively ).
72 */ 79 */
73 objectValues = function( obj ) { 80 objectValues = function( obj ) {
74 // Grunt 0.3.x uses an older version of jshint that still has js hint/jshint#392.
75 /*jshint newcap: false */
76 var key, val, 81 var key, val,
77 vals = QUnit.is( "array", obj ) ? [] : {}; 82 vals = QUnit.is( "array", obj ) ? [] : {};
78 for ( key in obj ) { 83 for ( key in obj ) {
79 if ( hasOwn.call( obj, key ) ) { 84 if ( hasOwn.call( obj, key ) ) {
80 » » » » val = obj[key]; 85 » » » » val = obj[ key ];
81 » » » » vals[key] = val === Object(val) ? objectValues(v al) : val; 86 » » » » vals[ key ] = val === Object( val ) ? objectValu es( val ) : val;
82 } 87 }
83 } 88 }
84 return vals; 89 return vals;
85 }; 90 };
86 91
87 function Test( settings ) { 92 QUnit = {};
88 extend( this, settings );
89 this.assertions = [];
90 this.testNumber = ++Test.count;
91 }
92
93 Test.count = 0;
94
95 Test.prototype = {
96 init: function() {
97 var a, b, li,
98 tests = id( "qunit-tests" );
99
100 if ( tests ) {
101 b = document.createElement( "strong" );
102 b.innerHTML = this.nameHtml;
103
104 // `a` initialized at top of scope
105 a = document.createElement( "a" );
106 a.innerHTML = "Rerun";
107 a.href = QUnit.url({ testNumber: this.testNumber });
108
109 li = document.createElement( "li" );
110 li.appendChild( b );
111 li.appendChild( a );
112 li.className = "running";
113 li.id = this.id = "qunit-test-output" + testId++;
114
115 tests.appendChild( li );
116 }
117 },
118 setup: function() {
119 if (
120 // Emit moduleStart when we're switching from one module to another
121 this.module !== config.previousModule ||
122 // They could be equal (both undefined) but if t he previousModule property doesn't
123 // yet exist it means this is the first test in a suite that isn't wrapped in a
124 // module, in which case we'll just emit a modul eStart event for 'undefined'.
125 // Without this, reporters can get testStart bef ore moduleStart which is a problem.
126 !hasOwn.call( config, "previousModule" )
127 ) {
128 if ( hasOwn.call( config, "previousModule" ) ) {
129 runLoggingCallbacks( "moduleDone", QUnit, {
130 name: config.previousModule,
131 failed: config.moduleStats.bad,
132 passed: config.moduleStats.all - config. moduleStats.bad,
133 total: config.moduleStats.all
134 });
135 }
136 config.previousModule = this.module;
137 config.moduleStats = { all: 0, bad: 0 };
138 runLoggingCallbacks( "moduleStart", QUnit, {
139 name: this.module
140 });
141 }
142
143 config.current = this;
144
145 this.testEnvironment = extend({
146 setup: function() {},
147 teardown: function() {}
148 }, this.moduleTestEnvironment );
149
150 this.started = +new Date();
151 runLoggingCallbacks( "testStart", QUnit, {
152 name: this.testName,
153 module: this.module
154 });
155
156 /*jshint camelcase:false */
157
158
159 /**
160 * Expose the current test environment.
161 *
162 * @deprecated since 1.12.0: Use QUnit.config.current.testEnviro nment instead.
163 */
164 QUnit.current_testEnvironment = this.testEnvironment;
165
166 /*jshint camelcase:true */
167
168 if ( !config.pollution ) {
169 saveGlobal();
170 }
171 if ( config.notrycatch ) {
172 this.testEnvironment.setup.call( this.testEnvironment, Q Unit.assert );
173 return;
174 }
175 try {
176 this.testEnvironment.setup.call( this.testEnvironment, Q Unit.assert );
177 } catch( e ) {
178 QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
179 }
180 },
181 run: function() {
182 config.current = this;
183
184 var running = id( "qunit-testresult" );
185
186 if ( running ) {
187 running.innerHTML = "Running: <br/>" + this.nameHtml;
188 }
189
190 if ( this.async ) {
191 QUnit.stop();
192 }
193
194 this.callbackStarted = +new Date();
195
196 if ( config.notrycatch ) {
197 this.callback.call( this.testEnvironment, QUnit.assert ) ;
198 this.callbackRuntime = +new Date() - this.callbackStarte d;
199 return;
200 }
201
202 try {
203 this.callback.call( this.testEnvironment, QUnit.assert ) ;
204 this.callbackRuntime = +new Date() - this.callbackStarte d;
205 } catch( e ) {
206 this.callbackRuntime = +new Date() - this.callbackStarte d;
207
208 QUnit.pushFailure( "Died on test #" + (this.assertions.l ength + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
209 // else next test will carry the responsibility
210 saveGlobal();
211
212 // Restart the tests if they're blocking
213 if ( config.blocking ) {
214 QUnit.start();
215 }
216 }
217 },
218 teardown: function() {
219 config.current = this;
220 if ( config.notrycatch ) {
221 if ( typeof this.callbackRuntime === "undefined" ) {
222 this.callbackRuntime = +new Date() - this.callba ckStarted;
223 }
224 this.testEnvironment.teardown.call( this.testEnvironment , QUnit.assert );
225 return;
226 } else {
227 try {
228 this.testEnvironment.teardown.call( this.testEnv ironment, QUnit.assert );
229 } catch( e ) {
230 QUnit.pushFailure( "Teardown failed on " + this. testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
231 }
232 }
233 checkPollution();
234 },
235 finish: function() {
236 config.current = this;
237 if ( config.requireExpects && this.expected === null ) {
238 QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
239 } else if ( this.expected !== null && this.expected !== this.ass ertions.length ) {
240 QUnit.pushFailure( "Expected " + this.expected + " asser tions, but " + this.assertions.length + " were run", this.stack );
241 } else if ( this.expected === null && !this.assertions.length ) {
242 QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
243 }
244
245 var i, assertion, a, b, time, li, ol,
246 test = this,
247 good = 0,
248 bad = 0,
249 tests = id( "qunit-tests" );
250
251 this.runtime = +new Date() - this.started;
252 config.stats.all += this.assertions.length;
253 config.moduleStats.all += this.assertions.length;
254
255 if ( tests ) {
256 ol = document.createElement( "ol" );
257 ol.className = "qunit-assert-list";
258
259 for ( i = 0; i < this.assertions.length; i++ ) {
260 assertion = this.assertions[i];
261
262 li = document.createElement( "li" );
263 li.className = assertion.result ? "pass" : "fail ";
264 li.innerHTML = assertion.message || ( assertion. result ? "okay" : "failed" );
265 ol.appendChild( li );
266
267 if ( assertion.result ) {
268 good++;
269 } else {
270 bad++;
271 config.stats.bad++;
272 config.moduleStats.bad++;
273 }
274 }
275
276 // store result when possible
277 if ( QUnit.config.reorder && defined.sessionStorage ) {
278 if ( bad ) {
279 sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad );
280 } else {
281 sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName );
282 }
283 }
284
285 if ( bad === 0 ) {
286 addClass( ol, "qunit-collapsed" );
287 }
288
289 // `b` initialized at top of scope
290 b = document.createElement( "strong" );
291 b.innerHTML = this.nameHtml + " <b class='counts'>(<b cl ass='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.asser tions.length + ")</b>";
292
293 addEvent(b, "click", function() {
294 var next = b.parentNode.lastChild,
295 collapsed = hasClass( next, "qunit-colla psed" );
296 ( collapsed ? removeClass : addClass )( next, "q unit-collapsed" );
297 });
298
299 addEvent(b, "dblclick", function( e ) {
300 var target = e && e.target ? e.target : window.e vent.srcElement;
301 if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) {
302 target = target.parentNode;
303 }
304 if ( window.location && target.nodeName.toLowerC ase() === "strong" ) {
305 window.location = QUnit.url({ testNumber : test.testNumber });
306 }
307 });
308
309 // `time` initialized at top of scope
310 time = document.createElement( "span" );
311 time.className = "runtime";
312 time.innerHTML = this.runtime + " ms";
313
314 // `li` initialized at top of scope
315 li = id( this.id );
316 li.className = bad ? "fail" : "pass";
317 li.removeChild( li.firstChild );
318 a = li.firstChild;
319 li.appendChild( b );
320 li.appendChild( a );
321 li.appendChild( time );
322 li.appendChild( ol );
323
324 } else {
325 for ( i = 0; i < this.assertions.length; i++ ) {
326 if ( !this.assertions[i].result ) {
327 bad++;
328 config.stats.bad++;
329 config.moduleStats.bad++;
330 }
331 }
332 }
333
334 runLoggingCallbacks( "testDone", QUnit, {
335 name: this.testName,
336 module: this.module,
337 failed: bad,
338 passed: this.assertions.length - bad,
339 total: this.assertions.length,
340 duration: this.runtime
341 });
342
343 QUnit.reset();
344
345 config.current = undefined;
346 },
347
348 queue: function() {
349 var bad,
350 test = this;
351
352 synchronize(function() {
353 test.init();
354 });
355 function run() {
356 // each of these can by async
357 synchronize(function() {
358 test.setup();
359 });
360 synchronize(function() {
361 test.run();
362 });
363 synchronize(function() {
364 test.teardown();
365 });
366 synchronize(function() {
367 test.finish();
368 });
369 }
370
371 // `bad` initialized at top of scope
372 // defer when previous test run passed, if storage is available
373 bad = QUnit.config.reorder && defined.sessionStorage &&
374 +sessionStorage.getItem( "qunit- test-" + this.module + "-" + this.testName );
375
376 if ( bad ) {
377 run();
378 } else {
379 synchronize( run, true );
380 }
381 }
382 };
383
384 // Root QUnit object.
385 // `QUnit` initialized at top of scope
386 QUnit = {
387
388 // call on start of module test to prepend name to all tests
389 module: function( name, testEnvironment ) {
390 config.currentModule = name;
391 config.currentModuleTestEnvironment = testEnvironment;
392 config.modules[name] = true;
393 },
394
395 asyncTest: function( testName, expected, callback ) {
396 if ( arguments.length === 2 ) {
397 callback = expected;
398 expected = null;
399 }
400
401 QUnit.test( testName, expected, callback, true );
402 },
403
404 test: function( testName, expected, callback, async ) {
405 var test,
406 nameHtml = "<span class='test-name'>" + escapeText( test Name ) + "</span>";
407
408 if ( arguments.length === 2 ) {
409 callback = expected;
410 expected = null;
411 }
412
413 if ( config.currentModule ) {
414 nameHtml = "<span class='module-name'>" + escapeText( co nfig.currentModule ) + "</span>: " + nameHtml;
415 }
416
417 test = new Test({
418 nameHtml: nameHtml,
419 testName: testName,
420 expected: expected,
421 async: async,
422 callback: callback,
423 module: config.currentModule,
424 moduleTestEnvironment: config.currentModuleTestEnvironme nt,
425 stack: sourceFromStacktrace( 2 )
426 });
427
428 if ( !validTest( test ) ) {
429 return;
430 }
431
432 test.queue();
433 },
434
435 // Specify the number of expected assertions to guarantee that failed te st (no assertions are run at all) don't slip through.
436 expect: function( asserts ) {
437 if (arguments.length === 1) {
438 config.current.expected = asserts;
439 } else {
440 return config.current.expected;
441 }
442 },
443
444 start: function( count ) {
445 // QUnit hasn't been initialized yet.
446 // Note: RequireJS (et al) may delay onLoad
447 if ( config.semaphore === undefined ) {
448 QUnit.begin(function() {
449 // This is triggered at the top of QUnit.load, p ush start() to the event loop, to allow QUnit.load to finish first
450 setTimeout(function() {
451 QUnit.start( count );
452 });
453 });
454 return;
455 }
456
457 config.semaphore -= count || 1;
458 // don't start until equal number of stop-calls
459 if ( config.semaphore > 0 ) {
460 return;
461 }
462 // ignore if start is called more often then stop
463 if ( config.semaphore < 0 ) {
464 config.semaphore = 0;
465 QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) );
466 return;
467 }
468 // A slight delay, to avoid any current callbacks
469 if ( defined.setTimeout ) {
470 setTimeout(function() {
471 if ( config.semaphore > 0 ) {
472 return;
473 }
474 if ( config.timeout ) {
475 clearTimeout( config.timeout );
476 }
477
478 config.blocking = false;
479 process( true );
480 }, 13);
481 } else {
482 config.blocking = false;
483 process( true );
484 }
485 },
486
487 stop: function( count ) {
488 config.semaphore += count || 1;
489 config.blocking = true;
490
491 if ( config.testTimeout && defined.setTimeout ) {
492 clearTimeout( config.timeout );
493 config.timeout = setTimeout(function() {
494 QUnit.ok( false, "Test timed out" );
495 config.semaphore = 1;
496 QUnit.start();
497 }, config.testTimeout );
498 }
499 }
500 };
501
502 // `assert` initialized at top of scope
503 // Assert helpers
504 // All of these must either call QUnit.push() or manually do:
505 // - runLoggingCallbacks( "log", .. );
506 // - config.current.assertions.push({ .. });
507 // We attach it to the QUnit object *after* we expose the public API,
508 // otherwise `assert` will become a global variable in browsers (#341).
509 assert = {
510 /**
511 * Asserts rough true-ish result.
512 * @name ok
513 * @function
514 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
515 */
516 ok: function( result, msg ) {
517 if ( !config.current ) {
518 throw new Error( "ok() assertion outside test context, w as " + sourceFromStacktrace(2) );
519 }
520 result = !!result;
521 msg = msg || (result ? "okay" : "failed" );
522
523 var source,
524 details = {
525 module: config.current.module,
526 name: config.current.testName,
527 result: result,
528 message: msg
529 };
530
531 msg = "<span class='test-message'>" + escapeText( msg ) + "</spa n>";
532
533 if ( !result ) {
534 source = sourceFromStacktrace( 2 );
535 if ( source ) {
536 details.source = source;
537 msg += "<table><tr class='test-source'><th>Sourc e: </th><td><pre>" + escapeText( source ) + "</pre></td></tr></table>";
538 }
539 }
540 runLoggingCallbacks( "log", QUnit, details );
541 config.current.assertions.push({
542 result: result,
543 message: msg
544 });
545 },
546
547 /**
548 * Assert that the first two arguments are equal, with an optional messa ge.
549 * Prints out both actual and expected values.
550 * @name equal
551 * @function
552 * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes. ", "format() replaces {0} with next argument" );
553 */
554 equal: function( actual, expected, message ) {
555 /*jshint eqeqeq:false */
556 QUnit.push( expected == actual, actual, expected, message );
557 },
558
559 /**
560 * @name notEqual
561 * @function
562 */
563 notEqual: function( actual, expected, message ) {
564 /*jshint eqeqeq:false */
565 QUnit.push( expected != actual, actual, expected, message );
566 },
567
568 /**
569 * @name propEqual
570 * @function
571 */
572 propEqual: function( actual, expected, message ) {
573 actual = objectValues(actual);
574 expected = objectValues(expected);
575 QUnit.push( QUnit.equiv(actual, expected), actual, expected, mes sage );
576 },
577
578 /**
579 * @name notPropEqual
580 * @function
581 */
582 notPropEqual: function( actual, expected, message ) {
583 actual = objectValues(actual);
584 expected = objectValues(expected);
585 QUnit.push( !QUnit.equiv(actual, expected), actual, expected, me ssage );
586 },
587
588 /**
589 * @name deepEqual
590 * @function
591 */
592 deepEqual: function( actual, expected, message ) {
593 QUnit.push( QUnit.equiv(actual, expected), actual, expected, mes sage );
594 },
595
596 /**
597 * @name notDeepEqual
598 * @function
599 */
600 notDeepEqual: function( actual, expected, message ) {
601 QUnit.push( !QUnit.equiv(actual, expected), actual, expected, me ssage );
602 },
603
604 /**
605 * @name strictEqual
606 * @function
607 */
608 strictEqual: function( actual, expected, message ) {
609 QUnit.push( expected === actual, actual, expected, message );
610 },
611
612 /**
613 * @name notStrictEqual
614 * @function
615 */
616 notStrictEqual: function( actual, expected, message ) {
617 QUnit.push( expected !== actual, actual, expected, message );
618 },
619
620 "throws": function( block, expected, message ) {
621 var actual,
622 expectedOutput = expected,
623 ok = false;
624
625 // 'expected' is optional
626 if ( typeof expected === "string" ) {
627 message = expected;
628 expected = null;
629 }
630
631 config.current.ignoreGlobalErrors = true;
632 try {
633 block.call( config.current.testEnvironment );
634 } catch (e) {
635 actual = e;
636 }
637 config.current.ignoreGlobalErrors = false;
638
639 if ( actual ) {
640 // we don't want to validate thrown error
641 if ( !expected ) {
642 ok = true;
643 expectedOutput = null;
644 // expected is a regexp
645 } else if ( QUnit.objectType( expected ) === "regexp" ) {
646 ok = expected.test( errorString( actual ) );
647 // expected is a constructor
648 } else if ( actual instanceof expected ) {
649 ok = true;
650 // expected is a validation function which returns true is validation passed
651 } else if ( expected.call( {}, actual ) === true ) {
652 expectedOutput = null;
653 ok = true;
654 }
655
656 QUnit.push( ok, actual, expectedOutput, message );
657 } else {
658 QUnit.pushFailure( message, null, "No exception was thro wn." );
659 }
660 }
661 };
662
663 /**
664 * @deprecated since 1.8.0
665 * Kept assertion helpers in root for backwards compatibility.
666 */
667 extend( QUnit, assert );
668
669 /**
670 * @deprecated since 1.9.0
671 * Kept root "raises()" for backwards compatibility.
672 * (Note that we don't introduce assert.raises).
673 */
674 QUnit.raises = assert[ "throws" ];
675
676 /**
677 * @deprecated since 1.0.0, replaced with error pushes since 1.3.0
678 * Kept to avoid TypeErrors for undefined methods.
679 */
680 QUnit.equals = function() {
681 QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
682 };
683 QUnit.same = function() {
684 QUnit.push( false, false, false, "QUnit.same has been deprecated since 2 009 (e88049a0), use QUnit.deepEqual instead" );
685 };
686
687 // We want access to the constructor's prototype
688 (function() {
689 function F() {}
690 F.prototype = QUnit;
691 QUnit = new F();
692 // Make F QUnit's constructor so that we can add to the prototype later
693 QUnit.constructor = F;
694 }());
695 93
696 /** 94 /**
697 * Config object: Maintain internal state 95 * Config object: Maintain internal state
698 * Later exposed as QUnit.config 96 * Later exposed as QUnit.config
699 * `config` initialized at top of scope 97 * `config` initialized at top of scope
700 */ 98 */
701 config = { 99 config = {
702 // The queue of tests to run 100 // The queue of tests to run
703 queue: [], 101 queue: [],
704 102
705 // block until document ready 103 // block until document ready
706 blocking: true, 104 blocking: true,
707 105
708 // when enabled, show only failing tests
709 // gets persisted through sessionStorage and can be changed in UI via ch eckbox
710 hidepassed: false,
711
712 // by default, run previously failed tests first 106 // by default, run previously failed tests first
713 // very useful in combination with "Hide passed tests" checked 107 // very useful in combination with "Hide passed tests" checked
714 reorder: true, 108 reorder: true,
715 109
716 // by default, modify document.title when suite is done 110 // by default, modify document.title when suite is done
717 altertitle: true, 111 altertitle: true,
718 112
113 // by default, scroll to top of the page when suite is done
114 scrolltop: true,
115
719 // when enabled, all tests must call expect() 116 // when enabled, all tests must call expect()
720 requireExpects: false, 117 requireExpects: false,
721 118
722 // add checkboxes that are persisted in the query-string 119 // add checkboxes that are persisted in the query-string
723 // when enabled, the id is set to `true` as a `QUnit.config` property 120 // when enabled, the id is set to `true` as a `QUnit.config` property
724 urlConfig: [ 121 urlConfig: [
725 { 122 {
123 id: "hidepassed",
124 label: "Hide passed tests",
125 tooltip: "Only show tests and assertions that fail. Stor ed as query-strings."
126 },
127 {
726 id: "noglobals", 128 id: "noglobals",
727 label: "Check for Globals", 129 label: "Check for Globals",
728 » » » tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." 130 » » » tooltip: "Enabling this will test if any test introduces new properties on the " +
131 » » » » "`window` object. Stored as query-strings."
729 }, 132 },
730 { 133 {
731 id: "notrycatch", 134 id: "notrycatch",
732 label: "No try-catch", 135 label: "No try-catch",
733 » » » tooltip: "Enabling this will run tests outside of a try- catch block. Makes debugging exceptions in IE reasonable. Stored as query-string s." 136 » » » tooltip: "Enabling this will run tests outside of a try- catch block. Makes debugging " +
137 » » » » "exceptions in IE reasonable. Stored as query-st rings."
734 } 138 }
735 ], 139 ],
736 140
737 // Set of all modules. 141 // Set of all modules.
738 » modules: {}, 142 » modules: [],
739 143
740 » // logging callback queues 144 » // The first unnamed module
741 » begin: [], 145 » currentModule: {
742 » done: [], 146 » » name: "",
743 » log: [], 147 » » tests: []
744 » testStart: [], 148 » },
745 » testDone: [], 149
746 » moduleStart: [], 150 » callbacks: {}
747 » moduleDone: []
748 }; 151 };
749 152
750 // Export global variables, unless an 'exports' object exists, 153 // Push a loose unnamed module to the modules collection
751 // in that case we assume we're in CommonJS (dealt with on the bottom of the scr ipt) 154 config.modules.push( config.currentModule );
752 if ( typeof exports === "undefined" ) {
753 » extend( window, QUnit.constructor.prototype );
754
755 » // Expose QUnit object
756 » window.QUnit = QUnit;
757 }
758 155
759 // Initialize more QUnit.config and QUnit.urlParams 156 // Initialize more QUnit.config and QUnit.urlParams
760 (function() { 157 (function() {
761 » var i, 158 » var i, current,
762 location = window.location || { search: "", protocol: "file:" }, 159 location = window.location || { search: "", protocol: "file:" },
763 params = location.search.slice( 1 ).split( "&" ), 160 params = location.search.slice( 1 ).split( "&" ),
764 length = params.length, 161 length = params.length,
765 » » urlParams = {}, 162 » » urlParams = {};
766 » » current;
767 163
768 if ( params[ 0 ] ) { 164 if ( params[ 0 ] ) {
769 for ( i = 0; i < length; i++ ) { 165 for ( i = 0; i < length; i++ ) {
770 current = params[ i ].split( "=" ); 166 current = params[ i ].split( "=" );
771 current[ 0 ] = decodeURIComponent( current[ 0 ] ); 167 current[ 0 ] = decodeURIComponent( current[ 0 ] );
168
772 // allow just a key to turn on a flag, e.g., test.html?n oglobals 169 // allow just a key to turn on a flag, e.g., test.html?n oglobals
773 current[ 1 ] = current[ 1 ] ? decodeURIComponent( curren t[ 1 ] ) : true; 170 current[ 1 ] = current[ 1 ] ? decodeURIComponent( curren t[ 1 ] ) : true;
774 » » » urlParams[ current[ 0 ] ] = current[ 1 ]; 171 » » » if ( urlParams[ current[ 0 ] ] ) {
775 » » } 172 » » » » urlParams[ current[ 0 ] ] = [].concat( urlParams [ current[ 0 ] ], current[ 1 ] );
173 » » » } else {
174 » » » » urlParams[ current[ 0 ] ] = current[ 1 ];
175 » » » }
176 » » }
177 » }
178
179 » if ( urlParams.filter === true ) {
180 » » delete urlParams.filter;
776 } 181 }
777 182
778 QUnit.urlParams = urlParams; 183 QUnit.urlParams = urlParams;
779 184
780 // String search anywhere in moduleName+testName 185 // String search anywhere in moduleName+testName
781 config.filter = urlParams.filter; 186 config.filter = urlParams.filter;
782 187
783 » // Exact match of the module name 188 » config.testId = [];
784 » config.module = urlParams.module; 189 » if ( urlParams.testId ) {
785 190
786 » config.testNumber = parseInt( urlParams.testNumber, 10 ) || null; 191 » » // Ensure that urlParams.testId is an array
192 » » urlParams.testId = [].concat( urlParams.testId );
193 » » for ( i = 0; i < urlParams.testId.length; i++ ) {
194 » » » config.testId.push( urlParams.testId[ i ] );
195 » » }
196 » }
787 197
788 // Figure out if we're running the tests from a server or not 198 // Figure out if we're running the tests from a server or not
789 QUnit.isLocal = location.protocol === "file:"; 199 QUnit.isLocal = location.protocol === "file:";
790 }()); 200 }());
791 201
792 // Extend QUnit object, 202 // Root QUnit object.
793 // these after set here because they should not be exposed as global functions 203 // `QUnit` initialized at top of scope
794 extend( QUnit, { 204 extend( QUnit, {
795 » assert: assert, 205
206 » // call on start of module test to prepend name to all tests
207 » module: function( name, testEnvironment ) {
208 » » var currentModule = {
209 » » » name: name,
210 » » » testEnvironment: testEnvironment,
211 » » » tests: []
212 » » };
213
214 » » // DEPRECATED: handles setup/teardown functions,
215 » » // beforeEach and afterEach should be used instead
216 » » if ( testEnvironment && testEnvironment.setup ) {
217 » » » testEnvironment.beforeEach = testEnvironment.setup;
218 » » » delete testEnvironment.setup;
219 » » }
220 » » if ( testEnvironment && testEnvironment.teardown ) {
221 » » » testEnvironment.afterEach = testEnvironment.teardown;
222 » » » delete testEnvironment.teardown;
223 » » }
224
225 » » config.modules.push( currentModule );
226 » » config.currentModule = currentModule;
227 » },
228
229 » // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
230 » asyncTest: function( testName, expected, callback ) {
231 » » if ( arguments.length === 2 ) {
232 » » » callback = expected;
233 » » » expected = null;
234 » » }
235
236 » » QUnit.test( testName, expected, callback, true );
237 » },
238
239 » test: function( testName, expected, callback, async ) {
240 » » var test;
241
242 » » if ( arguments.length === 2 ) {
243 » » » callback = expected;
244 » » » expected = null;
245 » » }
246
247 » » test = new Test({
248 » » » testName: testName,
249 » » » expected: expected,
250 » » » async: async,
251 » » » callback: callback
252 » » });
253
254 » » test.queue();
255 » },
256
257 » skip: function( testName ) {
258 » » var test = new Test({
259 » » » testName: testName,
260 » » » skip: true
261 » » });
262
263 » » test.queue();
264 » },
265
266 » // DEPRECATED: The functionality of QUnit.start() will be altered in QUn it 2.0.
267 » // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostar t` blocking behavior.
268 » start: function( count ) {
269 » » var globalStartAlreadyCalled = globalStartCalled;
270
271 » » if ( !config.current ) {
272 » » » globalStartCalled = true;
273
274 » » » if ( runStarted ) {
275 » » » » throw new Error( "Called start() outside of a te st context while already started" );
276 » » » } else if ( globalStartAlreadyCalled || count > 1 ) {
277 » » » » throw new Error( "Called start() outside of a te st context too many times" );
278 » » » } else if ( config.autostart ) {
279 » » » » throw new Error( "Called start() outside of a te st context when " +
280 » » » » » "QUnit.config.autostart was true" );
281 » » » } else if ( !config.pageLoaded ) {
282
283 » » » » // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
284 » » » » config.autostart = true;
285 » » » » return;
286 » » » }
287 » » } else {
288
289 » » » // If a test is running, adjust its semaphore
290 » » » config.current.semaphore -= count || 1;
291
292 » » » // Don't start until equal number of stop-calls
293 » » » if ( config.current.semaphore > 0 ) {
294 » » » » return;
295 » » » }
296
297 » » » // throw an Error if start is called more often than sto p
298 » » » if ( config.current.semaphore < 0 ) {
299 » » » » config.current.semaphore = 0;
300
301 » » » » QUnit.pushFailure(
302 » » » » » "Called start() while already started (t est's semaphore was 0 already)",
303 » » » » » sourceFromStacktrace( 2 )
304 » » » » );
305 » » » » return;
306 » » » }
307 » » }
308
309 » » resumeProcessing();
310 » },
311
312 » // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
313 » stop: function( count ) {
314
315 » » // If there isn't a test running, don't allow QUnit.stop() to be called
316 » » if ( !config.current ) {
317 » » » throw new Error( "Called stop() outside of a test contex t" );
318 » » }
319
320 » » // If a test is running, adjust its semaphore
321 » » config.current.semaphore += count || 1;
322
323 » » pauseProcessing();
324 » },
796 325
797 config: config, 326 config: config,
798 327
799 // Initialize the configuration options
800 init: function() {
801 extend( config, {
802 stats: { all: 0, bad: 0 },
803 moduleStats: { all: 0, bad: 0 },
804 started: +new Date(),
805 updateRate: 1000,
806 blocking: false,
807 autostart: true,
808 autorun: false,
809 filter: "",
810 queue: [],
811 semaphore: 1
812 });
813
814 var tests, banner, result,
815 qunit = id( "qunit" );
816
817 if ( qunit ) {
818 qunit.innerHTML =
819 "<h1 id='qunit-header'>" + escapeText( document. title ) + "</h1>" +
820 "<h2 id='qunit-banner'></h2>" +
821 "<div id='qunit-testrunner-toolbar'></div>" +
822 "<h2 id='qunit-userAgent'></h2>" +
823 "<ol id='qunit-tests'></ol>";
824 }
825
826 tests = id( "qunit-tests" );
827 banner = id( "qunit-banner" );
828 result = id( "qunit-testresult" );
829
830 if ( tests ) {
831 tests.innerHTML = "";
832 }
833
834 if ( banner ) {
835 banner.className = "";
836 }
837
838 if ( result ) {
839 result.parentNode.removeChild( result );
840 }
841
842 if ( tests ) {
843 result = document.createElement( "p" );
844 result.id = "qunit-testresult";
845 result.className = "result";
846 tests.parentNode.insertBefore( result, tests );
847 result.innerHTML = "Running...<br/>&nbsp;";
848 }
849 },
850
851 // Resets the test setup. Useful for tests that modify the DOM.
852 /*
853 DEPRECATED: Use multiple tests instead of resetting inside a test.
854 Use testStart or testDone for custom cleanup.
855 This method will throw an error in 2.0, and will be removed in 2.1
856 */
857 reset: function() {
858 var fixture = id( "qunit-fixture" );
859 if ( fixture ) {
860 fixture.innerHTML = config.fixture;
861 }
862 },
863
864 // Trigger an event on an element.
865 // @example triggerEvent( document.body, "click" );
866 triggerEvent: function( elem, type, event ) {
867 if ( document.createEvent ) {
868 event = document.createEvent( "MouseEvents" );
869 event.initMouseEvent(type, true, true, elem.ownerDocumen t.defaultView,
870 0, 0, 0, 0, 0, false, false, false, false, 0, nu ll);
871
872 elem.dispatchEvent( event );
873 } else if ( elem.fireEvent ) {
874 elem.fireEvent( "on" + type );
875 }
876 },
877
878 // Safe object type checking 328 // Safe object type checking
879 is: function( type, obj ) { 329 is: function( type, obj ) {
880 return QUnit.objectType( obj ) === type; 330 return QUnit.objectType( obj ) === type;
881 }, 331 },
882 332
883 objectType: function( obj ) { 333 objectType: function( obj ) {
884 if ( typeof obj === "undefined" ) { 334 if ( typeof obj === "undefined" ) {
885 » » » » return "undefined"; 335 » » » return "undefined";
886 » » // consider: typeof null === object
887 » » }
888 » » if ( obj === null ) {
889 » » » » return "null";
890 } 336 }
891 337
892 » » var match = toString.call( obj ).match(/^\[object\s(.*)\]$/), 338 » » // Consider: typeof null === object
893 » » » type = match && match[1] || ""; 339 » » if ( obj === null ) {
340 » » » return "null";
341 » » }
342
343 » » var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
344 » » » type = match && match[ 1 ] || "";
894 345
895 switch ( type ) { 346 switch ( type ) {
896 case "Number": 347 case "Number":
897 » » » » if ( isNaN(obj) ) { 348 » » » » if ( isNaN( obj ) ) {
898 return "nan"; 349 return "nan";
899 } 350 }
900 return "number"; 351 return "number";
901 case "String": 352 case "String":
902 case "Boolean": 353 case "Boolean":
903 case "Array": 354 case "Array":
904 case "Date": 355 case "Date":
905 case "RegExp": 356 case "RegExp":
906 case "Function": 357 case "Function":
907 return type.toLowerCase(); 358 return type.toLowerCase();
908 } 359 }
909 if ( typeof obj === "object" ) { 360 if ( typeof obj === "object" ) {
910 return "object"; 361 return "object";
911 } 362 }
912 return undefined; 363 return undefined;
913 }, 364 },
914 365
915 » push: function( result, actual, expected, message ) { 366 » extend: extend,
916 » » if ( !config.current ) { 367
917 » » » throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); 368 » load: function() {
369 » » config.pageLoaded = true;
370
371 » » // Initialize the configuration options
372 » » extend( config, {
373 » » » stats: { all: 0, bad: 0 },
374 » » » moduleStats: { all: 0, bad: 0 },
375 » » » started: 0,
376 » » » updateRate: 1000,
377 » » » autostart: true,
378 » » » filter: ""
379 » » }, true );
380
381 » » config.blocking = false;
382
383 » » if ( config.autostart ) {
384 » » » resumeProcessing();
385 » » }
386 » }
387 });
388
389 // Register logging callbacks
390 (function() {
391 » var i, l, key,
392 » » callbacks = [ "begin", "done", "log", "testStart", "testDone",
393 » » » "moduleStart", "moduleDone" ];
394
395 » function registerLoggingCallback( key ) {
396 » » var loggingCallback = function( callback ) {
397 » » » if ( QUnit.objectType( callback ) !== "function" ) {
398 » » » » throw new Error(
399 » » » » » "QUnit logging methods require a callbac k function as their first parameters."
400 » » » » );
401 » » » }
402
403 » » » config.callbacks[ key ].push( callback );
404 » » };
405
406 » » // DEPRECATED: This will be removed on QUnit 2.0.0+
407 » » // Stores the registered functions allowing restoring
408 » » // at verifyLoggingCallbacks() if modified
409 » » loggingCallbacks[ key ] = loggingCallback;
410
411 » » return loggingCallback;
412 » }
413
414 » for ( i = 0, l = callbacks.length; i < l; i++ ) {
415 » » key = callbacks[ i ];
416
417 » » // Initialize key collection of logging callback
418 » » if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
419 » » » config.callbacks[ key ] = [];
918 } 420 }
919 421
920 » » var output, source, 422 » » QUnit[ key ] = registerLoggingCallback( key );
921 » » » details = {
922 » » » » module: config.current.module,
923 » » » » name: config.current.testName,
924 » » » » result: result,
925 » » » » message: message,
926 » » » » actual: actual,
927 » » » » expected: expected
928 » » » };
929
930 » » message = escapeText( message ) || ( result ? "okay" : "failed" );
931 » » message = "<span class='test-message'>" + message + "</span>";
932 » » output = message;
933
934 » » if ( !result ) {
935 » » » expected = escapeText( QUnit.jsDump.parse(expected) );
936 » » » actual = escapeText( QUnit.jsDump.parse(actual) );
937 » » » output += "<table><tr class='test-expected'><th>Expected : </th><td><pre>" + expected + "</pre></td></tr>";
938
939 » » » if ( actual !== expected ) {
940 » » » » output += "<tr class='test-actual'><th>Result: < /th><td><pre>" + actual + "</pre></td></tr>";
941 » » » » output += "<tr class='test-diff'><th>Diff: </th> <td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>";
942 » » » }
943
944 » » » source = sourceFromStacktrace();
945
946 » » » if ( source ) {
947 » » » » details.source = source;
948 » » » » output += "<tr class='test-source'><th>Source: < /th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
949 » » » }
950
951 » » » output += "</table>";
952 » » }
953
954 » » runLoggingCallbacks( "log", QUnit, details );
955
956 » » config.current.assertions.push({
957 » » » result: !!result,
958 » » » message: output
959 » » });
960 » },
961
962 » pushFailure: function( message, source, actual ) {
963 » » if ( !config.current ) {
964 » » » throw new Error( "pushFailure() assertion outside test c ontext, was " + sourceFromStacktrace(2) );
965 » » }
966
967 » » var output,
968 » » » details = {
969 » » » » module: config.current.module,
970 » » » » name: config.current.testName,
971 » » » » result: false,
972 » » » » message: message
973 » » » };
974
975 » » message = escapeText( message ) || "error";
976 » » message = "<span class='test-message'>" + message + "</span>";
977 » » output = message;
978
979 » » output += "<table>";
980
981 » » if ( actual ) {
982 » » » output += "<tr class='test-actual'><th>Result: </th><td> <pre>" + escapeText( actual ) + "</pre></td></tr>";
983 » » }
984
985 » » if ( source ) {
986 » » » details.source = source;
987 » » » output += "<tr class='test-source'><th>Source: </th><td> <pre>" + escapeText( source ) + "</pre></td></tr>";
988 » » }
989
990 » » output += "</table>";
991
992 » » runLoggingCallbacks( "log", QUnit, details );
993
994 » » config.current.assertions.push({
995 » » » result: false,
996 » » » message: output
997 » » });
998 » },
999
1000 » url: function( params ) {
1001 » » params = extend( extend( {}, QUnit.urlParams ), params );
1002 » » var key,
1003 » » » querystring = "?";
1004
1005 » » for ( key in params ) {
1006 » » » if ( hasOwn.call( params, key ) ) {
1007 » » » » querystring += encodeURIComponent( key ) + "=" +
1008 » » » » » encodeURIComponent( params[ key ] ) + "& ";
1009 » » » }
1010 » » }
1011 » » return window.location.protocol + "//" + window.location.host +
1012 » » » window.location.pathname + querystring.slice( 0, -1 );
1013 » },
1014
1015 » extend: extend,
1016 » id: id,
1017 » addEvent: addEvent,
1018 » addClass: addClass,
1019 » hasClass: hasClass,
1020 » removeClass: removeClass
1021 » // load, equiv, jsDump, diff: Attached later
1022 });
1023
1024 /**
1025 * @deprecated: Created for backwards compatibility with test runner that set th e hook function
1026 * into QUnit.{hook}, instead of invoking it and passing the hook function.
1027 * QUnit.constructor is set to the empty F() above so that we can add to it's pr ototype here.
1028 * Doing this allows us to tell if the following methods have been overwritten o n the actual
1029 * QUnit object.
1030 */
1031 extend( QUnit.constructor.prototype, {
1032
1033 » // Logging callbacks; all receive a single argument with the listed prop erties
1034 » // run test/logs.html for any related changes
1035 » begin: registerLoggingCallback( "begin" ),
1036
1037 » // done: { failed, passed, total, runtime }
1038 » done: registerLoggingCallback( "done" ),
1039
1040 » // log: { result, actual, expected, message }
1041 » log: registerLoggingCallback( "log" ),
1042
1043 » // testStart: { name }
1044 » testStart: registerLoggingCallback( "testStart" ),
1045
1046 » // testDone: { name, failed, passed, total, duration }
1047 » testDone: registerLoggingCallback( "testDone" ),
1048
1049 » // moduleStart: { name }
1050 » moduleStart: registerLoggingCallback( "moduleStart" ),
1051
1052 » // moduleDone: { name, failed, passed, total }
1053 » moduleDone: registerLoggingCallback( "moduleDone" )
1054 });
1055
1056 if ( typeof document === "undefined" || document.readyState === "complete" ) {
1057 » config.autorun = true;
1058 }
1059
1060 QUnit.load = function() {
1061 » runLoggingCallbacks( "begin", QUnit, {} );
1062
1063 » // Initialize the config, saving the execution queue
1064 » var banner, filter, i, label, len, main, ol, toolbar, userAgent, val,
1065 » » urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter,
1066 » » numModules = 0,
1067 » » moduleNames = [],
1068 » » moduleFilterHtml = "",
1069 » » urlConfigHtml = "",
1070 » » oldconfig = extend( {}, config );
1071
1072 » QUnit.init();
1073 » extend(config, oldconfig);
1074
1075 » config.blocking = false;
1076
1077 » len = config.urlConfig.length;
1078
1079 » for ( i = 0; i < len; i++ ) {
1080 » » val = config.urlConfig[i];
1081 » » if ( typeof val === "string" ) {
1082 » » » val = {
1083 » » » » id: val,
1084 » » » » label: val,
1085 » » » » tooltip: "[no tooltip available]"
1086 » » » };
1087 » » }
1088 » » config[ val.id ] = QUnit.urlParams[ val.id ];
1089 » » urlConfigHtml += "<input id='qunit-urlconfig-" + escapeText( val .id ) +
1090 » » » "' name='" + escapeText( val.id ) +
1091 » » » "' type='checkbox'" + ( config[ val.id ] ? " checked='ch ecked'" : "" ) +
1092 » » » " title='" + escapeText( val.tooltip ) +
1093 » » » "'><label for='qunit-urlconfig-" + escapeText( val.id ) +
1094 » » » "' title='" + escapeText( val.tooltip ) + "'>" + val.lab el + "</label>";
1095 } 423 }
1096 » for ( i in config.modules ) { 424 })();
1097 » » if ( config.modules.hasOwnProperty( i ) ) {
1098 » » » moduleNames.push(i);
1099 » » }
1100 » }
1101 » numModules = moduleNames.length;
1102 » moduleNames.sort( function( a, b ) {
1103 » » return a.localeCompare( b );
1104 » });
1105 » moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><se lect id='qunit-modulefilter' name='modulefilter'><option value='' " +
1106 » » ( config.module === undefined ? "selected='selected'" : "" ) +
1107 » » ">< All Modules ></option>";
1108
1109
1110 » for ( i = 0; i < numModules; i++) {
1111 » » » moduleFilterHtml += "<option value='" + escapeText( enco deURIComponent(moduleNames[i]) ) + "' " +
1112 » » » » ( config.module === moduleNames[i] ? "selected=' selected'" : "" ) +
1113 » » » » ">" + escapeText(moduleNames[i]) + "</option>";
1114 » }
1115 » moduleFilterHtml += "</select>";
1116
1117 » // `userAgent` initialized at top of scope
1118 » userAgent = id( "qunit-userAgent" );
1119 » if ( userAgent ) {
1120 » » userAgent.innerHTML = navigator.userAgent;
1121 » }
1122
1123 » // `banner` initialized at top of scope
1124 » banner = id( "qunit-header" );
1125 » if ( banner ) {
1126 » » banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> ";
1127 » }
1128
1129 » // `toolbar` initialized at top of scope
1130 » toolbar = id( "qunit-testrunner-toolbar" );
1131 » if ( toolbar ) {
1132 » » // `filter` initialized at top of scope
1133 » » filter = document.createElement( "input" );
1134 » » filter.type = "checkbox";
1135 » » filter.id = "qunit-filter-pass";
1136
1137 » » addEvent( filter, "click", function() {
1138 » » » var tmp,
1139 » » » » ol = document.getElementById( "qunit-tests" );
1140
1141 » » » if ( filter.checked ) {
1142 » » » » ol.className = ol.className + " hidepass";
1143 » » » } else {
1144 » » » » tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
1145 » » » » ol.className = tmp.replace( / hidepass /, " " );
1146 » » » }
1147 » » » if ( defined.sessionStorage ) {
1148 » » » » if (filter.checked) {
1149 » » » » » sessionStorage.setItem( "qunit-filter-pa ssed-tests", "true" );
1150 » » » » } else {
1151 » » » » » sessionStorage.removeItem( "qunit-filter -passed-tests" );
1152 » » » » }
1153 » » » }
1154 » » });
1155
1156 » » if ( config.hidepassed || defined.sessionStorage && sessionStora ge.getItem( "qunit-filter-passed-tests" ) ) {
1157 » » » filter.checked = true;
1158 » » » // `ol` initialized at top of scope
1159 » » » ol = document.getElementById( "qunit-tests" );
1160 » » » ol.className = ol.className + " hidepass";
1161 » » }
1162 » » toolbar.appendChild( filter );
1163
1164 » » // `label` initialized at top of scope
1165 » » label = document.createElement( "label" );
1166 » » label.setAttribute( "for", "qunit-filter-pass" );
1167 » » label.setAttribute( "title", "Only show tests and assertions tha t fail. Stored in sessionStorage." );
1168 » » label.innerHTML = "Hide passed tests";
1169 » » toolbar.appendChild( label );
1170
1171 » » urlConfigCheckboxesContainer = document.createElement("span");
1172 » » urlConfigCheckboxesContainer.innerHTML = urlConfigHtml;
1173 » » urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsBy TagName("input");
1174 » » // For oldIE support:
1175 » » // * Add handlers to the individual elements instead of the cont ainer
1176 » » // * Use "click" instead of "change"
1177 » » // * Fallback from event.target to event.srcElement
1178 » » addEvents( urlConfigCheckboxes, "click", function( event ) {
1179 » » » var params = {},
1180 » » » » target = event.target || event.srcElement;
1181 » » » params[ target.name ] = target.checked ? true : undefine d;
1182 » » » window.location = QUnit.url( params );
1183 » » });
1184 » » toolbar.appendChild( urlConfigCheckboxesContainer );
1185
1186 » » if (numModules > 1) {
1187 » » » moduleFilter = document.createElement( "span" );
1188 » » » moduleFilter.setAttribute( "id", "qunit-modulefilter-con tainer" );
1189 » » » moduleFilter.innerHTML = moduleFilterHtml;
1190 » » » addEvent( moduleFilter.lastChild, "change", function() {
1191 » » » » var selectBox = moduleFilter.getElementsByTagNam e("select")[0],
1192 » » » » » selectedModule = decodeURIComponent(sele ctBox.options[selectBox.selectedIndex].value);
1193
1194 » » » » window.location = QUnit.url({
1195 » » » » » module: ( selectedModule === "" ) ? unde fined : selectedModule,
1196 » » » » » // Remove any existing filters
1197 » » » » » filter: undefined,
1198 » » » » » testNumber: undefined
1199 » » » » });
1200 » » » });
1201 » » » toolbar.appendChild(moduleFilter);
1202 » » }
1203 » }
1204
1205 » // `main` initialized at top of scope
1206 » main = id( "qunit-fixture" );
1207 » if ( main ) {
1208 » » config.fixture = main.innerHTML;
1209 » }
1210
1211 » if ( config.autostart ) {
1212 » » QUnit.start();
1213 » }
1214 };
1215
1216 addEvent( window, "load", QUnit.load );
1217 425
1218 // `onErrorFnPrev` initialized at top of scope 426 // `onErrorFnPrev` initialized at top of scope
1219 // Preserve other handlers 427 // Preserve other handlers
1220 onErrorFnPrev = window.onerror; 428 onErrorFnPrev = window.onerror;
1221 429
1222 // Cover uncaught exceptions 430 // Cover uncaught exceptions
1223 // Returning true will suppress the default browser handler, 431 // Returning true will suppress the default browser handler,
1224 // returning false will let it run. 432 // returning false will let it run.
1225 window.onerror = function ( error, filePath, linerNr ) { 433 window.onerror = function( error, filePath, linerNr ) {
1226 var ret = false; 434 var ret = false;
1227 if ( onErrorFnPrev ) { 435 if ( onErrorFnPrev ) {
1228 ret = onErrorFnPrev( error, filePath, linerNr ); 436 ret = onErrorFnPrev( error, filePath, linerNr );
1229 } 437 }
1230 438
1231 // Treat return value as window.onerror itself does, 439 // Treat return value as window.onerror itself does,
1232 // Only do our handling if not suppressed. 440 // Only do our handling if not suppressed.
1233 if ( ret !== true ) { 441 if ( ret !== true ) {
1234 if ( QUnit.config.current ) { 442 if ( QUnit.config.current ) {
1235 if ( QUnit.config.current.ignoreGlobalErrors ) { 443 if ( QUnit.config.current.ignoreGlobalErrors ) {
1236 return true; 444 return true;
1237 } 445 }
1238 QUnit.pushFailure( error, filePath + ":" + linerNr ); 446 QUnit.pushFailure( error, filePath + ":" + linerNr );
1239 } else { 447 } else {
1240 » » » QUnit.test( "global failure", extend( function() { 448 » » » QUnit.test( "global failure", extend(function() {
1241 QUnit.pushFailure( error, filePath + ":" + liner Nr ); 449 QUnit.pushFailure( error, filePath + ":" + liner Nr );
1242 » » » }, { validTest: validTest } ) ); 450 » » » }, { validTest: true } ) );
1243 } 451 }
1244 return false; 452 return false;
1245 } 453 }
1246 454
1247 return ret; 455 return ret;
1248 }; 456 };
1249 457
1250 function done() { 458 function done() {
459 var runtime, passed;
460
1251 config.autorun = true; 461 config.autorun = true;
1252 462
1253 // Log the last module results 463 // Log the last module results
1254 » if ( config.currentModule ) { 464 » if ( config.previousModule ) {
1255 » » runLoggingCallbacks( "moduleDone", QUnit, { 465 » » runLoggingCallbacks( "moduleDone", {
1256 » » » name: config.currentModule, 466 » » » name: config.previousModule.name,
467 » » » tests: config.previousModule.tests,
1257 failed: config.moduleStats.bad, 468 failed: config.moduleStats.bad,
1258 passed: config.moduleStats.all - config.moduleStats.bad, 469 passed: config.moduleStats.all - config.moduleStats.bad,
1259 » » » total: config.moduleStats.all 470 » » » total: config.moduleStats.all,
471 » » » runtime: now() - config.moduleStats.started
1260 }); 472 });
1261 } 473 }
1262 delete config.previousModule; 474 delete config.previousModule;
1263 475
1264 » var i, key, 476 » runtime = now() - config.started;
1265 » » banner = id( "qunit-banner" ), 477 » passed = config.stats.all - config.stats.bad;
1266 » » tests = id( "qunit-tests" ),
1267 » » runtime = +new Date() - config.started,
1268 » » passed = config.stats.all - config.stats.bad,
1269 » » html = [
1270 » » » "Tests completed in ",
1271 » » » runtime,
1272 » » » " milliseconds.<br/>",
1273 » » » "<span class='passed'>",
1274 » » » passed,
1275 » » » "</span> assertions of <span class='total'>",
1276 » » » config.stats.all,
1277 » » » "</span> passed, <span class='failed'>",
1278 » » » config.stats.bad,
1279 » » » "</span> failed."
1280 » » ].join( "" );
1281 478
1282 » if ( banner ) { 479 » runLoggingCallbacks( "done", {
1283 » » banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pa ss" );
1284 » }
1285
1286 » if ( tests ) {
1287 » » id( "qunit-testresult" ).innerHTML = html;
1288 » }
1289
1290 » if ( config.altertitle && typeof document !== "undefined" && document.ti tle ) {
1291 » » // show ✖ for good, ✔ for bad suite result in title
1292 » » // use escape sequences in case file gets loaded with non-utf-8- charset
1293 » » document.title = [
1294 » » » ( config.stats.bad ? "\u2716" : "\u2714" ),
1295 » » » document.title.replace( /^[\u2714\u2716] /i, "" )
1296 » » ].join( " " );
1297 » }
1298
1299 » // clear own sessionStorage items if all tests passed
1300 » if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) {
1301 » » // `key` & `i` initialized at top of scope
1302 » » for ( i = 0; i < sessionStorage.length; i++ ) {
1303 » » » key = sessionStorage.key( i++ );
1304 » » » if ( key.indexOf( "qunit-test-" ) === 0 ) {
1305 » » » » sessionStorage.removeItem( key );
1306 » » » }
1307 » » }
1308 » }
1309
1310 » // scroll back to top to show results
1311 » if ( window.scrollTo ) {
1312 » » window.scrollTo(0, 0);
1313 » }
1314
1315 » runLoggingCallbacks( "done", QUnit, {
1316 failed: config.stats.bad, 480 failed: config.stats.bad,
1317 passed: passed, 481 passed: passed,
1318 total: config.stats.all, 482 total: config.stats.all,
1319 runtime: runtime 483 runtime: runtime
1320 }); 484 });
1321 } 485 }
1322 486
1323 /** @return Boolean: true if this test should be ran */ 487 // Doesn't support IE6 to IE9
1324 function validTest( test ) {
1325 » var include,
1326 » » filter = config.filter && config.filter.toLowerCase(),
1327 » » module = config.module && config.module.toLowerCase(),
1328 » » fullName = (test.module + ": " + test.testName).toLowerCase();
1329
1330 » // Internally-generated tests are always valid
1331 » if ( test.callback && test.callback.validTest === validTest ) {
1332 » » delete test.callback.validTest;
1333 » » return true;
1334 » }
1335
1336 » if ( config.testNumber ) {
1337 » » return test.testNumber === config.testNumber;
1338 » }
1339
1340 » if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
1341 » » return false;
1342 » }
1343
1344 » if ( !filter ) {
1345 » » return true;
1346 » }
1347
1348 » include = filter.charAt( 0 ) !== "!";
1349 » if ( !include ) {
1350 » » filter = filter.slice( 1 );
1351 » }
1352
1353 » // If the filter matches, we need to honour include
1354 » if ( fullName.indexOf( filter ) !== -1 ) {
1355 » » return include;
1356 » }
1357
1358 » // Otherwise, do the opposite
1359 » return !include;
1360 }
1361
1362 // so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exce ptions)
1363 // Later Safari and IE10 are supposed to support error.stack as well
1364 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects /Error/Stack 488 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects /Error/Stack
1365 function extractStacktrace( e, offset ) { 489 function extractStacktrace( e, offset ) {
1366 » offset = offset === undefined ? 3 : offset; 490 » offset = offset === undefined ? 4 : offset;
1367 491
1368 var stack, include, i; 492 var stack, include, i;
1369 493
1370 if ( e.stacktrace ) { 494 if ( e.stacktrace ) {
1371 » » // Opera 495
496 » » // Opera 12.x
1372 return e.stacktrace.split( "\n" )[ offset + 3 ]; 497 return e.stacktrace.split( "\n" )[ offset + 3 ];
1373 } else if ( e.stack ) { 498 } else if ( e.stack ) {
1374 » » // Firefox, Chrome 499
500 » » // Firefox, Chrome, Safari 6+, IE10+, PhantomJS and Node
1375 stack = e.stack.split( "\n" ); 501 stack = e.stack.split( "\n" );
1376 » » if (/^error$/i.test( stack[0] ) ) { 502 » » if ( /^error$/i.test( stack[ 0 ] ) ) {
1377 stack.shift(); 503 stack.shift();
1378 } 504 }
1379 if ( fileName ) { 505 if ( fileName ) {
1380 include = []; 506 include = [];
1381 for ( i = offset; i < stack.length; i++ ) { 507 for ( i = offset; i < stack.length; i++ ) {
1382 if ( stack[ i ].indexOf( fileName ) !== -1 ) { 508 if ( stack[ i ].indexOf( fileName ) !== -1 ) {
1383 break; 509 break;
1384 } 510 }
1385 include.push( stack[ i ] ); 511 include.push( stack[ i ] );
1386 } 512 }
1387 if ( include.length ) { 513 if ( include.length ) {
1388 return include.join( "\n" ); 514 return include.join( "\n" );
1389 } 515 }
1390 } 516 }
1391 return stack[ offset ]; 517 return stack[ offset ];
1392 } else if ( e.sourceURL ) { 518 } else if ( e.sourceURL ) {
1393 » » // Safari, PhantomJS 519
1394 » » // hopefully one day Safari provides actual stacktraces 520 » » // Safari < 6
1395 // exclude useless self-reference for generated Error objects 521 // exclude useless self-reference for generated Error objects
1396 if ( /qunit.js$/.test( e.sourceURL ) ) { 522 if ( /qunit.js$/.test( e.sourceURL ) ) {
1397 return; 523 return;
1398 } 524 }
525
1399 // for actual exceptions, this is useful 526 // for actual exceptions, this is useful
1400 return e.sourceURL + ":" + e.line; 527 return e.sourceURL + ":" + e.line;
1401 } 528 }
1402 } 529 }
530
1403 function sourceFromStacktrace( offset ) { 531 function sourceFromStacktrace( offset ) {
1404 » try { 532 » var e = new Error();
1405 » » throw new Error(); 533 » if ( !e.stack ) {
1406 » } catch ( e ) { 534 » » try {
1407 » » return extractStacktrace( e, offset ); 535 » » » throw e;
536 » » } catch ( err ) {
537 » » » // This should already be true in most browsers
538 » » » e = err;
539 » » }
1408 } 540 }
1409 } 541 » return extractStacktrace( e, offset );
1410
1411 /**
1412 * Escape text for attribute or text content.
1413 */
1414 function escapeText( s ) {
1415 » if ( !s ) {
1416 » » return "";
1417 » }
1418 » s = s + "";
1419 » // Both single quotes and double quotes (for attributes)
1420 » return s.replace( /['"<>&]/g, function( s ) {
1421 » » switch( s ) {
1422 » » » case "'":
1423 » » » » return "&#039;";
1424 » » » case "\"":
1425 » » » » return "&quot;";
1426 » » » case "<":
1427 » » » » return "&lt;";
1428 » » » case ">":
1429 » » » » return "&gt;";
1430 » » » case "&":
1431 » » » » return "&amp;";
1432 » » }
1433 » });
1434 } 542 }
1435 543
1436 function synchronize( callback, last ) { 544 function synchronize( callback, last ) {
545 if ( QUnit.objectType( callback ) === "array" ) {
546 while ( callback.length ) {
547 synchronize( callback.shift() );
548 }
549 return;
550 }
1437 config.queue.push( callback ); 551 config.queue.push( callback );
1438 552
1439 if ( config.autorun && !config.blocking ) { 553 if ( config.autorun && !config.blocking ) {
1440 process( last ); 554 process( last );
1441 } 555 }
1442 } 556 }
1443 557
1444 function process( last ) { 558 function process( last ) {
1445 function next() { 559 function next() {
1446 process( last ); 560 process( last );
1447 } 561 }
1448 » var start = new Date().getTime(); 562 » var start = now();
1449 » config.depth = config.depth ? config.depth + 1 : 1; 563 » config.depth = ( config.depth || 0 ) + 1;
1450 564
1451 while ( config.queue.length && !config.blocking ) { 565 while ( config.queue.length && !config.blocking ) {
1452 » » if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Da te().getTime() - start ) < config.updateRate ) ) { 566 » » if ( !defined.setTimeout || config.updateRate <= 0 ||
567 » » » » ( ( now() - start ) < config.updateRate ) ) {
568 » » » if ( config.current ) {
569
570 » » » » // Reset async tracking for each phase of the Te st lifecycle
571 » » » » config.current.usedAsync = false;
572 » » » }
1453 config.queue.shift()(); 573 config.queue.shift()();
1454 } else { 574 } else {
1455 setTimeout( next, 13 ); 575 setTimeout( next, 13 );
1456 break; 576 break;
1457 } 577 }
1458 } 578 }
1459 config.depth--; 579 config.depth--;
1460 if ( last && !config.blocking && !config.queue.length && config.depth == = 0 ) { 580 if ( last && !config.blocking && !config.queue.length && config.depth == = 0 ) {
1461 done(); 581 done();
1462 } 582 }
1463 } 583 }
1464 584
585 function begin() {
586 var i, l,
587 modulesLog = [];
588
589 // If the test run hasn't officially begun yet
590 if ( !config.started ) {
591
592 // Record the time of the test run's beginning
593 config.started = now();
594
595 verifyLoggingCallbacks();
596
597 // Delete the loose unnamed module if unused.
598 if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].test s.length === 0 ) {
599 config.modules.shift();
600 }
601
602 // Avoid unnecessary information by not logging modules' test en vironments
603 for ( i = 0, l = config.modules.length; i < l; i++ ) {
604 modulesLog.push({
605 name: config.modules[ i ].name,
606 tests: config.modules[ i ].tests
607 });
608 }
609
610 // The test run is officially beginning now
611 runLoggingCallbacks( "begin", {
612 totalTests: Test.count,
613 modules: modulesLog
614 });
615 }
616
617 config.blocking = false;
618 process( true );
619 }
620
621 function resumeProcessing() {
622 runStarted = true;
623
624 // A slight delay to allow this iteration of the event loop to finish (m ore assertions, etc.)
625 if ( defined.setTimeout ) {
626 setTimeout(function() {
627 if ( config.current && config.current.semaphore > 0 ) {
628 return;
629 }
630 if ( config.timeout ) {
631 clearTimeout( config.timeout );
632 }
633
634 begin();
635 }, 13 );
636 } else {
637 begin();
638 }
639 }
640
641 function pauseProcessing() {
642 config.blocking = true;
643
644 if ( config.testTimeout && defined.setTimeout ) {
645 clearTimeout( config.timeout );
646 config.timeout = setTimeout(function() {
647 if ( config.current ) {
648 config.current.semaphore = 0;
649 QUnit.pushFailure( "Test timed out", sourceFromS tacktrace( 2 ) );
650 } else {
651 throw new Error( "Test timed out" );
652 }
653 resumeProcessing();
654 }, config.testTimeout );
655 }
656 }
657
1465 function saveGlobal() { 658 function saveGlobal() {
1466 config.pollution = []; 659 config.pollution = [];
1467 660
1468 if ( config.noglobals ) { 661 if ( config.noglobals ) {
1469 for ( var key in window ) { 662 for ( var key in window ) {
1470 if ( hasOwn.call( window, key ) ) { 663 if ( hasOwn.call( window, key ) ) {
1471 // in Opera sometimes DOM element ids show up he re, ignore them 664 // in Opera sometimes DOM element ids show up he re, ignore them
1472 if ( /^qunit-test-output/.test( key ) ) { 665 if ( /^qunit-test-output/.test( key ) ) {
1473 continue; 666 continue;
1474 } 667 }
1475 config.pollution.push( key ); 668 config.pollution.push( key );
1476 } 669 }
1477 } 670 }
1478 } 671 }
1479 } 672 }
1480 673
1481 function checkPollution() { 674 function checkPollution() {
1482 var newGlobals, 675 var newGlobals,
1483 deletedGlobals, 676 deletedGlobals,
1484 old = config.pollution; 677 old = config.pollution;
1485 678
1486 saveGlobal(); 679 saveGlobal();
1487 680
1488 newGlobals = diff( config.pollution, old ); 681 newGlobals = diff( config.pollution, old );
1489 if ( newGlobals.length > 0 ) { 682 if ( newGlobals.length > 0 ) {
1490 » » QUnit.pushFailure( "Introduced global variable(s): " + newGlobal s.join(", ") ); 683 » » QUnit.pushFailure( "Introduced global variable(s): " + newGlobal s.join( ", " ) );
1491 } 684 }
1492 685
1493 deletedGlobals = diff( old, config.pollution ); 686 deletedGlobals = diff( old, config.pollution );
1494 if ( deletedGlobals.length > 0 ) { 687 if ( deletedGlobals.length > 0 ) {
1495 » » QUnit.pushFailure( "Deleted global variable(s): " + deletedGloba ls.join(", ") ); 688 » » QUnit.pushFailure( "Deleted global variable(s): " + deletedGloba ls.join( ", " ) );
1496 } 689 }
1497 } 690 }
1498 691
1499 // returns a new Array with the elements that are in a but not in b 692 // returns a new Array with the elements that are in a but not in b
1500 function diff( a, b ) { 693 function diff( a, b ) {
1501 var i, j, 694 var i, j,
1502 result = a.slice(); 695 result = a.slice();
1503 696
1504 for ( i = 0; i < result.length; i++ ) { 697 for ( i = 0; i < result.length; i++ ) {
1505 for ( j = 0; j < b.length; j++ ) { 698 for ( j = 0; j < b.length; j++ ) {
1506 » » » if ( result[i] === b[j] ) { 699 » » » if ( result[ i ] === b[ j ] ) {
1507 result.splice( i, 1 ); 700 result.splice( i, 1 );
1508 i--; 701 i--;
1509 break; 702 break;
1510 } 703 }
1511 } 704 }
1512 } 705 }
1513 return result; 706 return result;
1514 } 707 }
1515 708
1516 function extend( a, b ) { 709 function extend( a, b, undefOnly ) {
1517 for ( var prop in b ) { 710 for ( var prop in b ) {
1518 if ( hasOwn.call( b, prop ) ) { 711 if ( hasOwn.call( b, prop ) ) {
712
1519 // Avoid "Member not found" error in IE8 caused by messi ng with window.constructor 713 // Avoid "Member not found" error in IE8 caused by messi ng with window.constructor
1520 if ( !( prop === "constructor" && a === window ) ) { 714 if ( !( prop === "constructor" && a === window ) ) {
1521 if ( b[ prop ] === undefined ) { 715 if ( b[ prop ] === undefined ) {
1522 delete a[ prop ]; 716 delete a[ prop ];
1523 » » » » } else { 717 » » » » } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
1524 a[ prop ] = b[ prop ]; 718 a[ prop ] = b[ prop ];
1525 } 719 }
1526 } 720 }
1527 } 721 }
1528 } 722 }
1529 723
1530 return a; 724 return a;
1531 } 725 }
1532 726
1533 /** 727 function runLoggingCallbacks( key, args ) {
1534 * @param {HTMLElement} elem 728 » var i, l, callbacks;
1535 * @param {string} type 729
1536 * @param {Function} fn 730 » callbacks = config.callbacks[ key ];
1537 */ 731 » for ( i = 0, l = callbacks.length; i < l; i++ ) {
1538 function addEvent( elem, type, fn ) { 732 » » callbacks[ i ]( args );
1539 » // Standards-based browsers 733 » }
1540 » if ( elem.addEventListener ) { 734 }
1541 » » elem.addEventListener( type, fn, false ); 735
1542 » // IE 736 // DEPRECATED: This will be removed on 2.0.0+
737 // This function verifies if the loggingCallbacks were modified by the user
738 // If so, it will restore it, assign the given callback and print a console warn ing
739 function verifyLoggingCallbacks() {
740 » var loggingCallback, userCallback;
741
742 » for ( loggingCallback in loggingCallbacks ) {
743 » » if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallb ack ] ) {
744
745 » » » userCallback = QUnit[ loggingCallback ];
746
747 » » » // Restore the callback function
748 » » » QUnit[ loggingCallback ] = loggingCallbacks[ loggingCall back ];
749
750 » » » // Assign the deprecated given callback
751 » » » QUnit[ loggingCallback ]( userCallback );
752
753 » » » if ( window.console && window.console.warn ) {
754 » » » » window.console.warn(
755 » » » » » "QUnit." + loggingCallback + " was repla ced with a new value.\n" +
756 » » » » » "Please, check out the documentation on how to apply logging callbacks.\n" +
757 » » » » » "Reference: http://api.qunitjs.com/categ ory/callbacks/"
758 » » » » );
759 » » » }
760 » » }
761 » }
762 }
763
764 // from jquery.js
765 function inArray( elem, array ) {
766 » if ( array.indexOf ) {
767 » » return array.indexOf( elem );
768 » }
769
770 » for ( var i = 0, length = array.length; i < length; i++ ) {
771 » » if ( array[ i ] === elem ) {
772 » » » return i;
773 » » }
774 » }
775
776 » return -1;
777 }
778
779 function Test( settings ) {
780 » var i, l;
781
782 » ++Test.count;
783
784 » extend( this, settings );
785 » this.assertions = [];
786 » this.semaphore = 0;
787 » this.usedAsync = false;
788 » this.module = config.currentModule;
789 » this.stack = sourceFromStacktrace( 3 );
790
791 » // Register unique strings
792 » for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
793 » » if ( this.module.tests[ i ].name === this.testName ) {
794 » » » this.testName += " ";
795 » » }
796 » }
797
798 » this.testId = generateHash( this.module.name, this.testName );
799
800 » this.module.tests.push({
801 » » name: this.testName,
802 » » testId: this.testId
803 » });
804
805 » if ( settings.skip ) {
806
807 » » // Skipped tests will fully ignore any sent callback
808 » » this.callback = function() {};
809 » » this.async = false;
810 » » this.expected = 0;
1543 } else { 811 } else {
1544 » » elem.attachEvent( "on" + type, fn ); 812 » » this.assert = new Assert( this );
1545 } 813 }
1546 } 814 }
1547 815
1548 /** 816 Test.count = 0;
1549 * @param {Array|NodeList} elems 817
1550 * @param {string} type 818 Test.prototype = {
1551 * @param {Function} fn 819 before: function() {
1552 */ 820 if (
1553 function addEvents( elems, type, fn ) { 821
1554 var i = elems.length; 822 // Emit moduleStart when we're switching from one module to another
1555 while ( i-- ) { 823 this.module !== config.previousModule ||
1556 addEvent( elems[i], type, fn ); 824
1557 } 825 // They could be equal (both undefined) but if t he previousModule property doesn't
826 // yet exist it means this is the first test in a suite that isn't wrapped in a
827 // module, in which case we'll just emit a modul eStart event for 'undefined'.
828 // Without this, reporters can get testStart bef ore moduleStart which is a problem.
829 !hasOwn.call( config, "previousModule" )
830 ) {
831 if ( hasOwn.call( config, "previousModule" ) ) {
832 runLoggingCallbacks( "moduleDone", {
833 name: config.previousModule.name,
834 tests: config.previousModule.tests,
835 failed: config.moduleStats.bad,
836 passed: config.moduleStats.all - config. moduleStats.bad,
837 total: config.moduleStats.all,
838 runtime: now() - config.moduleStats.star ted
839 });
840 }
841 config.previousModule = this.module;
842 config.moduleStats = { all: 0, bad: 0, started: now() };
843 runLoggingCallbacks( "moduleStart", {
844 name: this.module.name,
845 tests: this.module.tests
846 });
847 }
848
849 config.current = this;
850
851 this.testEnvironment = extend( {}, this.module.testEnvironment ) ;
852 delete this.testEnvironment.beforeEach;
853 delete this.testEnvironment.afterEach;
854
855 this.started = now();
856 runLoggingCallbacks( "testStart", {
857 name: this.testName,
858 module: this.module.name,
859 testId: this.testId
860 });
861
862 if ( !config.pollution ) {
863 saveGlobal();
864 }
865 },
866
867 run: function() {
868 var promise;
869
870 config.current = this;
871
872 if ( this.async ) {
873 QUnit.stop();
874 }
875
876 this.callbackStarted = now();
877
878 if ( config.notrycatch ) {
879 promise = this.callback.call( this.testEnvironment, this .assert );
880 this.resolvePromise( promise );
881 return;
882 }
883
884 try {
885 promise = this.callback.call( this.testEnvironment, this .assert );
886 this.resolvePromise( promise );
887 } catch ( e ) {
888 this.pushFailure( "Died on test #" + ( this.assertions.l ength + 1 ) + " " +
889 this.stack + ": " + ( e.message || e ), extractS tacktrace( e, 0 ) );
890
891 // else next test will carry the responsibility
892 saveGlobal();
893
894 // Restart the tests if they're blocking
895 if ( config.blocking ) {
896 QUnit.start();
897 }
898 }
899 },
900
901 after: function() {
902 checkPollution();
903 },
904
905 queueHook: function( hook, hookName ) {
906 var promise,
907 test = this;
908 return function runHook() {
909 config.current = test;
910 if ( config.notrycatch ) {
911 promise = hook.call( test.testEnvironment, test. assert );
912 test.resolvePromise( promise, hookName );
913 return;
914 }
915 try {
916 promise = hook.call( test.testEnvironment, test. assert );
917 test.resolvePromise( promise, hookName );
918 } catch ( error ) {
919 test.pushFailure( hookName + " failed on " + tes t.testName + ": " +
920 ( error.message || error ), extractStack trace( error, 0 ) );
921 }
922 };
923 },
924
925 // Currently only used for module level hooks, can be used to add global level ones
926 hooks: function( handler ) {
927 var hooks = [];
928
929 // Hooks are ignored on skipped tests
930 if ( this.skip ) {
931 return hooks;
932 }
933
934 if ( this.module.testEnvironment &&
935 QUnit.objectType( this.module.testEnvironment[ h andler ] ) === "function" ) {
936 hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
937 }
938
939 return hooks;
940 },
941
942 finish: function() {
943 config.current = this;
944 if ( config.requireExpects && this.expected === null ) {
945 this.pushFailure( "Expected number of assertions to be d efined, but expect() was " +
946 "not called.", this.stack );
947 } else if ( this.expected !== null && this.expected !== this.ass ertions.length ) {
948 this.pushFailure( "Expected " + this.expected + " assert ions, but " +
949 this.assertions.length + " were run", this.stack );
950 } else if ( this.expected === null && !this.assertions.length ) {
951 this.pushFailure( "Expected at least one assertion, but none were run - call " +
952 "expect(0) to accept zero assertions.", this.sta ck );
953 }
954
955 var i,
956 bad = 0;
957
958 this.runtime = now() - this.started;
959 config.stats.all += this.assertions.length;
960 config.moduleStats.all += this.assertions.length;
961
962 for ( i = 0; i < this.assertions.length; i++ ) {
963 if ( !this.assertions[ i ].result ) {
964 bad++;
965 config.stats.bad++;
966 config.moduleStats.bad++;
967 }
968 }
969
970 runLoggingCallbacks( "testDone", {
971 name: this.testName,
972 module: this.module.name,
973 skipped: !!this.skip,
974 failed: bad,
975 passed: this.assertions.length - bad,
976 total: this.assertions.length,
977 runtime: this.runtime,
978
979 // HTML Reporter use
980 assertions: this.assertions,
981 testId: this.testId,
982
983 // DEPRECATED: this property will be removed in 2.0.0, u se runtime instead
984 duration: this.runtime
985 });
986
987 // QUnit.reset() is deprecated and will be replaced for a new
988 // fixture reset function on QUnit 2.0/2.1.
989 // It's still called here for backwards compatibility handling
990 QUnit.reset();
991
992 config.current = undefined;
993 },
994
995 queue: function() {
996 var bad,
997 test = this;
998
999 if ( !this.valid() ) {
1000 return;
1001 }
1002
1003 function run() {
1004
1005 // each of these can by async
1006 synchronize([
1007 function() {
1008 test.before();
1009 },
1010
1011 test.hooks( "beforeEach" ),
1012
1013 function() {
1014 test.run();
1015 },
1016
1017 test.hooks( "afterEach" ).reverse(),
1018
1019 function() {
1020 test.after();
1021 },
1022 function() {
1023 test.finish();
1024 }
1025 ]);
1026 }
1027
1028 // `bad` initialized at top of scope
1029 // defer when previous test run passed, if storage is available
1030 bad = QUnit.config.reorder && defined.sessionStorage &&
1031 +sessionStorage.getItem( "qunit-test-" + this.mo dule.name + "-" + this.testName );
1032
1033 if ( bad ) {
1034 run();
1035 } else {
1036 synchronize( run, true );
1037 }
1038 },
1039
1040 push: function( result, actual, expected, message ) {
1041 var source,
1042 details = {
1043 module: this.module.name,
1044 name: this.testName,
1045 result: result,
1046 message: message,
1047 actual: actual,
1048 expected: expected,
1049 testId: this.testId,
1050 runtime: now() - this.started
1051 };
1052
1053 if ( !result ) {
1054 source = sourceFromStacktrace();
1055
1056 if ( source ) {
1057 details.source = source;
1058 }
1059 }
1060
1061 runLoggingCallbacks( "log", details );
1062
1063 this.assertions.push({
1064 result: !!result,
1065 message: message
1066 });
1067 },
1068
1069 pushFailure: function( message, source, actual ) {
1070 if ( !this instanceof Test ) {
1071 throw new Error( "pushFailure() assertion outside test c ontext, was " +
1072 sourceFromStacktrace( 2 ) );
1073 }
1074
1075 var details = {
1076 module: this.module.name,
1077 name: this.testName,
1078 result: false,
1079 message: message || "error",
1080 actual: actual || null,
1081 testId: this.testId,
1082 runtime: now() - this.started
1083 };
1084
1085 if ( source ) {
1086 details.source = source;
1087 }
1088
1089 runLoggingCallbacks( "log", details );
1090
1091 this.assertions.push({
1092 result: false,
1093 message: message
1094 });
1095 },
1096
1097 resolvePromise: function( promise, phase ) {
1098 var then, message,
1099 test = this;
1100 if ( promise != null ) {
1101 then = promise.then;
1102 if ( QUnit.objectType( then ) === "function" ) {
1103 QUnit.stop();
1104 then.call(
1105 promise,
1106 QUnit.start,
1107 function( error ) {
1108 message = "Promise rejected " +
1109 ( !phase ? "during" : ph ase.replace( /Each$/, "" ) ) +
1110 " " + test.testName + ": " + ( error.message || error );
1111 test.pushFailure( message, extra ctStacktrace( error, 0 ) );
1112
1113 // else next test will carry the responsibility
1114 saveGlobal();
1115
1116 // Unblock
1117 QUnit.start();
1118 }
1119 );
1120 }
1121 }
1122 },
1123
1124 valid: function() {
1125 var include,
1126 filter = config.filter,
1127 module = QUnit.urlParams.module && QUnit.urlParams.modul e.toLowerCase(),
1128 fullName = ( this.module.name + ": " + this.testName ).t oLowerCase();
1129
1130 // Internally-generated tests are always valid
1131 if ( this.callback && this.callback.validTest ) {
1132 return true;
1133 }
1134
1135 if ( config.testId.length > 0 && inArray( this.testId, config.te stId ) < 0 ) {
1136 return false;
1137 }
1138
1139 if ( module && ( !this.module.name || this.module.name.toLowerCa se() !== module ) ) {
1140 return false;
1141 }
1142
1143 if ( !filter ) {
1144 return true;
1145 }
1146
1147 include = filter.charAt( 0 ) !== "!";
1148 if ( !include ) {
1149 filter = filter.toLowerCase().slice( 1 );
1150 }
1151
1152 // If the filter matches, we need to honour include
1153 if ( fullName.indexOf( filter ) !== -1 ) {
1154 return include;
1155 }
1156
1157 // Otherwise, do the opposite
1158 return !include;
1159 }
1160
1161 };
1162
1163 // Resets the test setup. Useful for tests that modify the DOM.
1164 /*
1165 DEPRECATED: Use multiple tests instead of resetting inside a test.
1166 Use testStart or testDone for custom cleanup.
1167 This method will throw an error in 2.0, and will be removed in 2.1
1168 */
1169 QUnit.reset = function() {
1170
1171 // Return on non-browser environments
1172 // This is necessary to not break on node tests
1173 if ( typeof window === "undefined" ) {
1174 return;
1175 }
1176
1177 var fixture = defined.document && document.getElementById &&
1178 document.getElementById( "qunit-fixture" );
1179
1180 if ( fixture ) {
1181 fixture.innerHTML = config.fixture;
1182 }
1183 };
1184
1185 QUnit.pushFailure = function() {
1186 if ( !QUnit.config.current ) {
1187 throw new Error( "pushFailure() assertion outside test context, in " +
1188 sourceFromStacktrace( 2 ) );
1189 }
1190
1191 // Gets current test obj
1192 var currentTest = QUnit.config.current;
1193
1194 return currentTest.pushFailure.apply( currentTest, arguments );
1195 };
1196
1197 // Based on Java's String.hashCode, a simple but not
1198 // rigorously collision resistant hashing function
1199 function generateHash( module, testName ) {
1200 var hex,
1201 i = 0,
1202 hash = 0,
1203 str = module + "\x1C" + testName,
1204 len = str.length;
1205
1206 for ( ; i < len; i++ ) {
1207 hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
1208 hash |= 0;
1209 }
1210
1211 // Convert the possibly negative integer hash code into an 8 character h ex string, which isn't
1212 // strictly necessary but increases user understanding that the id is a SHA-like hash
1213 hex = ( 0x100000000 + hash ).toString( 16 );
1214 if ( hex.length < 8 ) {
1215 hex = "0000000" + hex;
1216 }
1217
1218 return hex.slice( -8 );
1558 } 1219 }
1559 1220
1560 function hasClass( elem, name ) { 1221 function Assert( testContext ) {
1561 » return (" " + elem.className + " ").indexOf(" " + name + " ") > -1; 1222 » this.test = testContext;
1562 } 1223 }
1563 1224
1564 function addClass( elem, name ) { 1225 // Assert helpers
1565 » if ( !hasClass( elem, name ) ) { 1226 QUnit.assert = Assert.prototype = {
1566 » » elem.className += (elem.className ? " " : "") + name; 1227
1567 » } 1228 » // Specify the number of expected assertions to guarantee that failed te st
1568 } 1229 » // (no assertions are run at all) don't slip through.
1569 1230 » expect: function( asserts ) {
1570 function removeClass( elem, name ) { 1231 » » if ( arguments.length === 1 ) {
1571 » var set = " " + elem.className + " "; 1232 » » » this.test.expected = asserts;
1572 » // Class name may appear multiple times 1233 » » } else {
1573 » while ( set.indexOf(" " + name + " ") > -1 ) { 1234 » » » return this.test.expected;
1574 » » set = set.replace(" " + name + " " , " "); 1235 » » }
1575 » } 1236 » },
1576 » // If possible, trim it for prettiness, but not necessarily 1237
1577 » elem.className = typeof set.trim === "function" ? set.trim() : set.repla ce(/^\s+|\s+$/g, ""); 1238 » // Increment this Test's semaphore counter, then return a single-use fun ction that
1578 } 1239 » // decrements that counter a maximum of once.
1579 1240 » async: function() {
1580 function id( name ) { 1241 » » var test = this.test,
1581 » return !!( typeof document !== "undefined" && document && document.getEl ementById ) && 1242 » » » popped = false;
1582 » » document.getElementById( name ); 1243
1583 } 1244 » » test.semaphore += 1;
1584 1245 » » test.usedAsync = true;
1585 function registerLoggingCallback( key ) { 1246 » » pauseProcessing();
1586 » return function( callback ) { 1247
1587 » » config[key].push( callback ); 1248 » » return function done() {
1588 » }; 1249 » » » if ( !popped ) {
1589 } 1250 » » » » test.semaphore -= 1;
1590 1251 » » » » popped = true;
1591 // Supports deprecated method of completely overwriting logging callbacks 1252 » » » » resumeProcessing();
1592 function runLoggingCallbacks( key, scope, args ) { 1253 » » » } else {
1593 » var i, callbacks; 1254 » » » » test.pushFailure( "Called the callback returned from `assert.async` more than once",
1594 » if ( QUnit.hasOwnProperty( key ) ) { 1255 » » » » » sourceFromStacktrace( 2 ) );
1595 » » QUnit[ key ].call(scope, args ); 1256 » » » }
1596 » } else { 1257 » » };
1597 » » callbacks = config[ key ]; 1258 » },
1598 » » for ( i = 0; i < callbacks.length; i++ ) { 1259
1599 » » » callbacks[ i ].call( scope, args ); 1260 » // Exports test.push() to the user API
1600 » » } 1261 » push: function( /* result, actual, expected, message */ ) {
1601 » } 1262 » » var assert = this,
1602 } 1263 » » » currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
1264
1265 » » // Backwards compatibility fix.
1266 » » // Allows the direct use of global exported assertions and QUnit .assert.*
1267 » » // Although, it's use is not recommended as it can leak assertio ns
1268 » » // to other tests from async tests, because we only get a refere nce to the current test,
1269 » » // not exactly the test where assertion were intended to be call ed.
1270 » » if ( !currentTest ) {
1271 » » » throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
1272 » » }
1273
1274 » » if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
1275 » » » currentTest.pushFailure( "Assertion after the final `ass ert.async` was resolved",
1276 » » » » sourceFromStacktrace( 2 ) );
1277
1278 » » » // Allow this assertion to continue running anyway...
1279 » » }
1280
1281 » » if ( !( assert instanceof Assert ) ) {
1282 » » » assert = currentTest.assert;
1283 » » }
1284 » » return assert.test.push.apply( assert.test, arguments );
1285 » },
1286
1287 » /**
1288 » * Asserts rough true-ish result.
1289 » * @name ok
1290 » * @function
1291 » * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
1292 » */
1293 » ok: function( result, message ) {
1294 » » message = message || ( result ? "okay" : "failed, expected argum ent to be truthy, was: " +
1295 » » » QUnit.dump.parse( result ) );
1296 » » this.push( !!result, result, true, message );
1297 » },
1298
1299 » /**
1300 » * Assert that the first two arguments are equal, with an optional messa ge.
1301 » * Prints out both actual and expected values.
1302 » * @name equal
1303 » * @function
1304 » * @example equal( format( "{0} bytes.", 2), "2 bytes.", "replaces {0} w ith next argument" );
1305 » */
1306 » equal: function( actual, expected, message ) {
1307 » » /*jshint eqeqeq:false */
1308 » » this.push( expected == actual, actual, expected, message );
1309 » },
1310
1311 » /**
1312 » * @name notEqual
1313 » * @function
1314 » */
1315 » notEqual: function( actual, expected, message ) {
1316 » » /*jshint eqeqeq:false */
1317 » » this.push( expected != actual, actual, expected, message );
1318 » },
1319
1320 » /**
1321 » * @name propEqual
1322 » * @function
1323 » */
1324 » propEqual: function( actual, expected, message ) {
1325 » » actual = objectValues( actual );
1326 » » expected = objectValues( expected );
1327 » » this.push( QUnit.equiv( actual, expected ), actual, expected, me ssage );
1328 » },
1329
1330 » /**
1331 » * @name notPropEqual
1332 » * @function
1333 » */
1334 » notPropEqual: function( actual, expected, message ) {
1335 » » actual = objectValues( actual );
1336 » » expected = objectValues( expected );
1337 » » this.push( !QUnit.equiv( actual, expected ), actual, expected, m essage );
1338 » },
1339
1340 » /**
1341 » * @name deepEqual
1342 » * @function
1343 » */
1344 » deepEqual: function( actual, expected, message ) {
1345 » » this.push( QUnit.equiv( actual, expected ), actual, expected, me ssage );
1346 » },
1347
1348 » /**
1349 » * @name notDeepEqual
1350 » * @function
1351 » */
1352 » notDeepEqual: function( actual, expected, message ) {
1353 » » this.push( !QUnit.equiv( actual, expected ), actual, expected, m essage );
1354 » },
1355
1356 » /**
1357 » * @name strictEqual
1358 » * @function
1359 » */
1360 » strictEqual: function( actual, expected, message ) {
1361 » » this.push( expected === actual, actual, expected, message );
1362 » },
1363
1364 » /**
1365 » * @name notStrictEqual
1366 » * @function
1367 » */
1368 » notStrictEqual: function( actual, expected, message ) {
1369 » » this.push( expected !== actual, actual, expected, message );
1370 » },
1371
1372 » "throws": function( block, expected, message ) {
1373 » » var actual, expectedType,
1374 » » » expectedOutput = expected,
1375 » » » ok = false;
1376
1377 » » // 'expected' is optional unless doing string comparison
1378 » » if ( message == null && typeof expected === "string" ) {
1379 » » » message = expected;
1380 » » » expected = null;
1381 » » }
1382
1383 » » this.test.ignoreGlobalErrors = true;
1384 » » try {
1385 » » » block.call( this.test.testEnvironment );
1386 » » } catch (e) {
1387 » » » actual = e;
1388 » » }
1389 » » this.test.ignoreGlobalErrors = false;
1390
1391 » » if ( actual ) {
1392 » » » expectedType = QUnit.objectType( expected );
1393
1394 » » » // we don't want to validate thrown error
1395 » » » if ( !expected ) {
1396 » » » » ok = true;
1397 » » » » expectedOutput = null;
1398
1399 » » » // expected is a regexp
1400 » » » } else if ( expectedType === "regexp" ) {
1401 » » » » ok = expected.test( errorString( actual ) );
1402
1403 » » » // expected is a string
1404 » » » } else if ( expectedType === "string" ) {
1405 » » » » ok = expected === errorString( actual );
1406
1407 » » » // expected is a constructor, maybe an Error constructor
1408 » » » } else if ( expectedType === "function" && actual instan ceof expected ) {
1409 » » » » ok = true;
1410
1411 » » » // expected is an Error object
1412 » » » } else if ( expectedType === "object" ) {
1413 » » » » ok = actual instanceof expected.constructor &&
1414 » » » » » actual.name === expected.name &&
1415 » » » » » actual.message === expected.message;
1416
1417 » » » // expected is a validation function which returns true if validation passed
1418 » » » } else if ( expectedType === "function" && expected.call ( {}, actual ) === true ) {
1419 » » » » expectedOutput = null;
1420 » » » » ok = true;
1421 » » » }
1422
1423 » » » this.push( ok, actual, expectedOutput, message );
1424 » » } else {
1425 » » » this.test.pushFailure( message, null, "No exception was thrown." );
1426 » » }
1427 » }
1428 };
1429
1430 // Provide an alternative to assert.throws(), for enviroments that consider thro ws a reserved word
1431 // Known to us are: Closure Compiler, Narwhal
1432 (function() {
1433 » /*jshint sub:true */
1434 » Assert.prototype.raises = Assert.prototype[ "throws" ];
1435 }());
1603 1436
1604 // Test for equality any JavaScript type. 1437 // Test for equality any JavaScript type.
1605 // Author: Philippe Rathé <prathe@gmail.com> 1438 // Author: Philippe Rathé <prathe@gmail.com>
1606 QUnit.equiv = (function() { 1439 QUnit.equiv = (function() {
1607 1440
1608 // Call the o related callback with the given arguments. 1441 // Call the o related callback with the given arguments.
1609 function bindCallbacks( o, callbacks, args ) { 1442 function bindCallbacks( o, callbacks, args ) {
1610 var prop = QUnit.objectType( o ); 1443 var prop = QUnit.objectType( o );
1611 if ( prop ) { 1444 if ( prop ) {
1612 if ( QUnit.objectType( callbacks[ prop ] ) === "function " ) { 1445 if ( QUnit.objectType( callbacks[ prop ] ) === "function " ) {
1613 return callbacks[ prop ].apply( callbacks, args ); 1446 return callbacks[ prop ].apply( callbacks, args );
1614 } else { 1447 } else {
1615 return callbacks[ prop ]; // or undefined 1448 return callbacks[ prop ]; // or undefined
1616 } 1449 }
1617 } 1450 }
1618 } 1451 }
1619 1452
1620 // the real equiv function 1453 // the real equiv function
1621 var innerEquiv, 1454 var innerEquiv,
1455
1622 // stack to decide between skip/abort functions 1456 // stack to decide between skip/abort functions
1623 callers = [], 1457 callers = [],
1458
1624 // stack to avoiding loops from circular referencing 1459 // stack to avoiding loops from circular referencing
1625 parents = [], 1460 parents = [],
1626 parentsB = [], 1461 parentsB = [],
1627 1462
1628 » » getProto = Object.getPrototypeOf || function ( obj ) { 1463 » » getProto = Object.getPrototypeOf || function( obj ) {
1629 » » » /*jshint camelcase:false */ 1464 » » » /* jshint camelcase: false, proto: true */
1630 return obj.__proto__; 1465 return obj.__proto__;
1631 }, 1466 },
1632 » » callbacks = (function () { 1467 » » callbacks = (function() {
1633 1468
1634 // for string, boolean, number and null 1469 // for string, boolean, number and null
1635 function useStrictEquality( b, a ) { 1470 function useStrictEquality( b, a ) {
1471
1636 /*jshint eqeqeq:false */ 1472 /*jshint eqeqeq:false */
1637 if ( b instanceof a.constructor || a instanceof b.constructor ) { 1473 if ( b instanceof a.constructor || a instanceof b.constructor ) {
1474
1638 // to catch short annotation VS 'new' an notation of a 1475 // to catch short annotation VS 'new' an notation of a
1639 // declaration 1476 // declaration
1640 // e.g. var i = 1; 1477 // e.g. var i = 1;
1641 // var j = new Number(1); 1478 // var j = new Number(1);
1642 return a == b; 1479 return a == b;
1643 } else { 1480 } else {
1644 return a === b; 1481 return a === b;
1645 } 1482 }
1646 } 1483 }
1647 1484
1648 return { 1485 return {
1649 "string": useStrictEquality, 1486 "string": useStrictEquality,
1650 "boolean": useStrictEquality, 1487 "boolean": useStrictEquality,
1651 "number": useStrictEquality, 1488 "number": useStrictEquality,
1652 "null": useStrictEquality, 1489 "null": useStrictEquality,
1653 "undefined": useStrictEquality, 1490 "undefined": useStrictEquality,
1654 1491
1655 "nan": function( b ) { 1492 "nan": function( b ) {
1656 return isNaN( b ); 1493 return isNaN( b );
1657 }, 1494 },
1658 1495
1659 "date": function( b, a ) { 1496 "date": function( b, a ) {
1660 return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); 1497 return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
1661 }, 1498 },
1662 1499
1663 "regexp": function( b, a ) { 1500 "regexp": function( b, a ) {
1664 return QUnit.objectType( b ) === "regexp " && 1501 return QUnit.objectType( b ) === "regexp " &&
1502
1665 // the regex itself 1503 // the regex itself
1666 a.source === b.source && 1504 a.source === b.source &&
1505
1667 // and its modifiers 1506 // and its modifiers
1668 a.global === b.global && 1507 a.global === b.global &&
1508
1669 // (gmi) ... 1509 // (gmi) ...
1670 a.ignoreCase === b.ignoreCase && 1510 a.ignoreCase === b.ignoreCase &&
1671 a.multiline === b.multiline && 1511 a.multiline === b.multiline &&
1672 a.sticky === b.sticky; 1512 a.sticky === b.sticky;
1673 }, 1513 },
1674 1514
1675 // - skip when the property is a method of an in stance (OOP) 1515 // - skip when the property is a method of an in stance (OOP)
1676 // - abort otherwise, 1516 // - abort otherwise,
1677 // initial === would have catch identical refere nces anyway 1517 // initial === would have catch identical refere nces anyway
1678 "function": function() { 1518 "function": function() {
1679 » » » » » var caller = callers[callers.length - 1] ; 1519 » » » » » var caller = callers[ callers.length - 1 ];
1680 return caller !== Object && typeof calle r !== "undefined"; 1520 return caller !== Object && typeof calle r !== "undefined";
1681 }, 1521 },
1682 1522
1683 "array": function( b, a ) { 1523 "array": function( b, a ) {
1684 var i, j, len, loop, aCircular, bCircula r; 1524 var i, j, len, loop, aCircular, bCircula r;
1685 1525
1686 // b could be an object literal here 1526 // b could be an object literal here
1687 if ( QUnit.objectType( b ) !== "array" ) { 1527 if ( QUnit.objectType( b ) !== "array" ) {
1688 return false; 1528 return false;
1689 } 1529 }
1690 1530
1691 len = a.length; 1531 len = a.length;
1692 if ( len !== b.length ) { 1532 if ( len !== b.length ) {
1693 // safe and faster 1533 // safe and faster
1694 return false; 1534 return false;
1695 } 1535 }
1696 1536
1697 // track reference to avoid circular ref erences 1537 // track reference to avoid circular ref erences
1698 parents.push( a ); 1538 parents.push( a );
1699 parentsB.push( b ); 1539 parentsB.push( b );
1700 for ( i = 0; i < len; i++ ) { 1540 for ( i = 0; i < len; i++ ) {
1701 loop = false; 1541 loop = false;
1702 for ( j = 0; j < parents.length; j++ ) { 1542 for ( j = 0; j < parents.length; j++ ) {
1703 » » » » » » » aCircular = parents[j] = == a[i]; 1543 » » » » » » » aCircular = parents[ j ] === a[ i ];
1704 » » » » » » » bCircular = parentsB[j] === b[i]; 1544 » » » » » » » bCircular = parentsB[ j ] === b[ i ];
1705 if ( aCircular || bCircu lar ) { 1545 if ( aCircular || bCircu lar ) {
1706 » » » » » » » » if ( a[i] === b[ i] || aCircular && bCircular ) { 1546 » » » » » » » » if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1707 loop = t rue; 1547 loop = t rue;
1708 } else { 1548 } else {
1709 parents. pop(); 1549 parents. pop();
1710 parentsB .pop(); 1550 parentsB .pop();
1711 return f alse; 1551 return f alse;
1712 } 1552 }
1713 } 1553 }
1714 } 1554 }
1715 » » » » » » if ( !loop && !innerEquiv(a[i], b[i]) ) { 1555 » » » » » » if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1716 parents.pop(); 1556 parents.pop();
1717 parentsB.pop(); 1557 parentsB.pop();
1718 return false; 1558 return false;
1719 } 1559 }
1720 } 1560 }
1721 parents.pop(); 1561 parents.pop();
1722 parentsB.pop(); 1562 parentsB.pop();
1723 return true; 1563 return true;
1724 }, 1564 },
1725 1565
1726 "object": function( b, a ) { 1566 "object": function( b, a ) {
1567
1727 /*jshint forin:false */ 1568 /*jshint forin:false */
1728 var i, j, loop, aCircular, bCircular, 1569 var i, j, loop, aCircular, bCircular,
1729 // Default to true 1570 // Default to true
1730 eq = true, 1571 eq = true,
1731 aProperties = [], 1572 aProperties = [],
1732 bProperties = []; 1573 bProperties = [];
1733 1574
1734 // comparing constructors is more strict than using 1575 // comparing constructors is more strict than using
1735 // instanceof 1576 // instanceof
1736 if ( a.constructor !== b.constructor ) { 1577 if ( a.constructor !== b.constructor ) {
1578
1737 // Allow objects with no prototy pe to be equivalent to 1579 // Allow objects with no prototy pe to be equivalent to
1738 // objects with Object as their constructor. 1580 // objects with Object as their constructor.
1739 » » » » » » if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || 1581 » » » » » » if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
1740 » » » » » » » ( getProto(b) === null & & getProto(a) === Object.prototype ) ) ) { 1582 » » » » » » » ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
1741 » » » » » » » » return false; 1583 » » » » » » » return false;
1742 } 1584 }
1743 } 1585 }
1744 1586
1745 // stack constructor before traversing p roperties 1587 // stack constructor before traversing p roperties
1746 callers.push( a.constructor ); 1588 callers.push( a.constructor );
1747 1589
1748 // track reference to avoid circular ref erences 1590 // track reference to avoid circular ref erences
1749 parents.push( a ); 1591 parents.push( a );
1750 parentsB.push( b ); 1592 parentsB.push( b );
1751 1593
1752 // be strict: don't ensure hasOwnPropert y and go deep 1594 // be strict: don't ensure hasOwnPropert y and go deep
1753 for ( i in a ) { 1595 for ( i in a ) {
1754 loop = false; 1596 loop = false;
1755 for ( j = 0; j < parents.length; j++ ) { 1597 for ( j = 0; j < parents.length; j++ ) {
1756 » » » » » » » aCircular = parents[j] = == a[i]; 1598 » » » » » » » aCircular = parents[ j ] === a[ i ];
1757 » » » » » » » bCircular = parentsB[j] === b[i]; 1599 » » » » » » » bCircular = parentsB[ j ] === b[ i ];
1758 if ( aCircular || bCircu lar ) { 1600 if ( aCircular || bCircu lar ) {
1759 » » » » » » » » if ( a[i] === b[ i] || aCircular && bCircular ) { 1601 » » » » » » » » if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1760 loop = t rue; 1602 loop = t rue;
1761 } else { 1603 } else {
1762 eq = fal se; 1604 eq = fal se;
1763 break; 1605 break;
1764 } 1606 }
1765 } 1607 }
1766 } 1608 }
1767 » » » » » » aProperties.push(i); 1609 » » » » » » aProperties.push( i );
1768 » » » » » » if ( !loop && !innerEquiv(a[i], b[i]) ) { 1610 » » » » » » if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1769 eq = false; 1611 eq = false;
1770 break; 1612 break;
1771 } 1613 }
1772 } 1614 }
1773 1615
1774 parents.pop(); 1616 parents.pop();
1775 parentsB.pop(); 1617 parentsB.pop();
1776 callers.pop(); // unstack, we are done 1618 callers.pop(); // unstack, we are done
1777 1619
1778 for ( i in b ) { 1620 for ( i in b ) {
1779 bProperties.push( i ); // collec t b's properties 1621 bProperties.push( i ); // collec t b's properties
1780 } 1622 }
1781 1623
1782 // Ensures identical properties name 1624 // Ensures identical properties name
1783 return eq && innerEquiv( aProperties.sor t(), bProperties.sort() ); 1625 return eq && innerEquiv( aProperties.sor t(), bProperties.sort() );
1784 } 1626 }
1785 }; 1627 };
1786 }()); 1628 }());
1787 1629
1788 innerEquiv = function() { // can take multiple arguments 1630 innerEquiv = function() { // can take multiple arguments
1789 var args = [].slice.apply( arguments ); 1631 var args = [].slice.apply( arguments );
1790 if ( args.length < 2 ) { 1632 if ( args.length < 2 ) {
1791 return true; // end transition 1633 return true; // end transition
1792 } 1634 }
1793 1635
1794 » » return (function( a, b ) { 1636 » » return ( (function( a, b ) {
1795 if ( a === b ) { 1637 if ( a === b ) {
1796 return true; // catch the most you can 1638 return true; // catch the most you can
1797 } else if ( a === null || b === null || typeof a === "un defined" || 1639 } else if ( a === null || b === null || typeof a === "un defined" ||
1798 typeof b === "undefined" || 1640 typeof b === "undefined" ||
1799 » » » » » QUnit.objectType(a) !== QUnit.objectType (b) ) { 1641 » » » » » QUnit.objectType( a ) !== QUnit.objectTy pe( b ) ) {
1800 » » » » return false; // don't lose time with error pron e cases 1642
1643 » » » » // don't lose time with error prone cases
1644 » » » » return false;
1801 } else { 1645 } else {
1802 » » » » return bindCallbacks(a, callbacks, [ b, a ]); 1646 » » » » return bindCallbacks( a, callbacks, [ b, a ] );
1803 } 1647 }
1804 1648
1805 // apply transition with (1..n) arguments 1649 // apply transition with (1..n) arguments
1806 » » }( args[0], args[1] ) && innerEquiv.apply( this, args.splice(1, args.length - 1 )) ); 1650 » » }( args[ 0 ], args[ 1 ] ) ) &&
1651 » » » innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
1807 }; 1652 };
1808 1653
1809 return innerEquiv; 1654 return innerEquiv;
1810 }()); 1655 }());
1811 1656
1812 /** 1657 // Based on jsDump by Ariel Flesler
1813 * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | 1658 // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
1814 * http://flesler.blogspot.com Licensed under BSD 1659 QUnit.dump = (function() {
1815 * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
1816 *
1817 * @projectDescription Advanced and extensible data dumping for Javascript.
1818 * @version 1.0.0
1819 * @author Ariel Flesler
1820 * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascri pt.html}
1821 */
1822 QUnit.jsDump = (function() {
1823 function quote( str ) { 1660 function quote( str ) {
1824 return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\""; 1661 return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
1825 } 1662 }
1826 function literal( o ) { 1663 function literal( o ) {
1827 return o + ""; 1664 return o + "";
1828 } 1665 }
1829 function join( pre, arr, post ) { 1666 function join( pre, arr, post ) {
1830 » » var s = jsDump.separator(), 1667 » » var s = dump.separator(),
1831 » » » base = jsDump.indent(), 1668 » » » base = dump.indent(),
1832 » » » inner = jsDump.indent(1); 1669 » » » inner = dump.indent( 1 );
1833 if ( arr.join ) { 1670 if ( arr.join ) {
1834 arr = arr.join( "," + s + inner ); 1671 arr = arr.join( "," + s + inner );
1835 } 1672 }
1836 if ( !arr ) { 1673 if ( !arr ) {
1837 return pre + post; 1674 return pre + post;
1838 } 1675 }
1839 » » return [ pre, inner + arr, base + post ].join(s); 1676 » » return [ pre, inner + arr, base + post ].join( s );
1840 } 1677 }
1841 function array( arr, stack ) { 1678 function array( arr, stack ) {
1842 » » var i = arr.length, ret = new Array(i); 1679 » » var i = arr.length,
1680 » » » ret = new Array( i );
1681
1682 » » if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1683 » » » return "[object Array]";
1684 » » }
1685
1843 this.up(); 1686 this.up();
1844 while ( i-- ) { 1687 while ( i-- ) {
1845 » » » ret[i] = this.parse( arr[i] , undefined , stack); 1688 » » » ret[ i ] = this.parse( arr[ i ], undefined, stack );
1846 } 1689 }
1847 this.down(); 1690 this.down();
1848 return join( "[", ret, "]" ); 1691 return join( "[", ret, "]" );
1849 } 1692 }
1850 1693
1851 var reName = /^function (\w+)/, 1694 var reName = /^function (\w+)/,
1852 » » jsDump = { 1695 » » dump = {
1853 » » » // type is used mostly internally, you can fix a (custom )type in advance
1854 » » » parse: function( obj, type, stack ) {
1855 » » » » stack = stack || [ ];
1856 » » » » var inStack, res,
1857 » » » » » parser = this.parsers[ type || this.type Of(obj) ];
1858 1696
1859 » » » » type = typeof parser; 1697 » » » // objType is used mostly internally, you can fix a (cus tom) type in advance
1860 » » » » inStack = inArray( obj, stack ); 1698 » » » parse: function( obj, objType, stack ) {
1699 » » » » stack = stack || [];
1700 » » » » var res, parser, parserType,
1701 » » » » » inStack = inArray( obj, stack );
1861 1702
1862 if ( inStack !== -1 ) { 1703 if ( inStack !== -1 ) {
1863 » » » » » return "recursion(" + (inStack - stack.l ength) + ")"; 1704 » » » » » return "recursion(" + ( inStack - stack. length ) + ")";
1864 } 1705 }
1865 » » » » if ( type === "function" ) { 1706
1707 » » » » objType = objType || this.typeOf( obj );
1708 » » » » parser = this.parsers[ objType ];
1709 » » » » parserType = typeof parser;
1710
1711 » » » » if ( parserType === "function" ) {
1866 stack.push( obj ); 1712 stack.push( obj );
1867 res = parser.call( this, obj, stack ); 1713 res = parser.call( this, obj, stack );
1868 stack.pop(); 1714 stack.pop();
1869 return res; 1715 return res;
1870 } 1716 }
1871 » » » » return ( type === "string" ) ? parser : this.par sers.error; 1717 » » » » return ( parserType === "string" ) ? parser : th is.parsers.error;
1872 }, 1718 },
1873 typeOf: function( obj ) { 1719 typeOf: function( obj ) {
1874 var type; 1720 var type;
1875 if ( obj === null ) { 1721 if ( obj === null ) {
1876 type = "null"; 1722 type = "null";
1877 } else if ( typeof obj === "undefined" ) { 1723 } else if ( typeof obj === "undefined" ) {
1878 type = "undefined"; 1724 type = "undefined";
1879 » » » » } else if ( QUnit.is( "regexp", obj) ) { 1725 » » » » } else if ( QUnit.is( "regexp", obj ) ) {
1880 type = "regexp"; 1726 type = "regexp";
1881 » » » » } else if ( QUnit.is( "date", obj) ) { 1727 » » » » } else if ( QUnit.is( "date", obj ) ) {
1882 type = "date"; 1728 type = "date";
1883 » » » » } else if ( QUnit.is( "function", obj) ) { 1729 » » » » } else if ( QUnit.is( "function", obj ) ) {
1884 type = "function"; 1730 type = "function";
1885 » » » » } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { 1731 » » » » } else if ( obj.setInterval !== undefined &&
1732 » » » » » » obj.document !== undefined &&
1733 » » » » » » obj.nodeType === undefined ) {
1886 type = "window"; 1734 type = "window";
1887 } else if ( obj.nodeType === 9 ) { 1735 } else if ( obj.nodeType === 9 ) {
1888 type = "document"; 1736 type = "document";
1889 } else if ( obj.nodeType ) { 1737 } else if ( obj.nodeType ) {
1890 type = "node"; 1738 type = "node";
1891 } else if ( 1739 } else if (
1740
1892 // native arrays 1741 // native arrays
1893 toString.call( obj ) === "[object Array] " || 1742 toString.call( obj ) === "[object Array] " ||
1743
1894 // NodeList objects 1744 // NodeList objects
1895 » » » » » ( 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" ) ) ) 1745 » » » » » ( typeof obj.length === "number" && obj. item !== undefined &&
1746 » » » » » ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
1747 » » » » » obj[ 0 ] === undefined ) ) )
1896 ) { 1748 ) {
1897 type = "array"; 1749 type = "array";
1898 } else if ( obj.constructor === Error.prototype. constructor ) { 1750 } else if ( obj.constructor === Error.prototype. constructor ) {
1899 type = "error"; 1751 type = "error";
1900 } else { 1752 } else {
1901 type = typeof obj; 1753 type = typeof obj;
1902 } 1754 }
1903 return type; 1755 return type;
1904 }, 1756 },
1905 separator: function() { 1757 separator: function() {
1906 » » » » return this.multiline ?»this.HTML ? "<br />" : " \n" : this.HTML ? "&nbsp;" : " "; 1758 » » » » return this.multiline ? this.HTML ? "<br />" : " \n" : this.HTML ? "&#160;" : " ";
1907 }, 1759 },
1908 // extra can be a number, shortcut for increasing-callin g-decreasing 1760 // extra can be a number, shortcut for increasing-callin g-decreasing
1909 indent: function( extra ) { 1761 indent: function( extra ) {
1910 if ( !this.multiline ) { 1762 if ( !this.multiline ) {
1911 return ""; 1763 return "";
1912 } 1764 }
1913 var chr = this.indentChar; 1765 var chr = this.indentChar;
1914 if ( this.HTML ) { 1766 if ( this.HTML ) {
1915 » » » » » chr = chr.replace( /\t/g, " " ).replac e( / /g, "&nbsp;" ); 1767 » » » » » chr = chr.replace( /\t/g, " " ).replac e( / /g, "&#160;" );
1916 } 1768 }
1917 » » » » return new Array( this.depth + ( extra || 0 ) ). join(chr); 1769 » » » » return new Array( this.depth + ( extra || 0 ) ). join( chr );
1918 }, 1770 },
1919 up: function( a ) { 1771 up: function( a ) {
1920 this.depth += a || 1; 1772 this.depth += a || 1;
1921 }, 1773 },
1922 down: function( a ) { 1774 down: function( a ) {
1923 this.depth -= a || 1; 1775 this.depth -= a || 1;
1924 }, 1776 },
1925 setParser: function( name, parser ) { 1777 setParser: function( name, parser ) {
1926 » » » » this.parsers[name] = parser; 1778 » » » » this.parsers[ name ] = parser;
1927 }, 1779 },
1928 // The next 3 are exposed so you can use them 1780 // The next 3 are exposed so you can use them
1929 quote: quote, 1781 quote: quote,
1930 literal: literal, 1782 literal: literal,
1931 join: join, 1783 join: join,
1932 // 1784 //
1933 depth: 1, 1785 depth: 1,
1934 » » » // This is the list of parsers, to modify them, use jsDu mp.setParser 1786 » » » maxDepth: 5,
1787
1788 » » » // This is the list of parsers, to modify them, use dump .setParser
1935 parsers: { 1789 parsers: {
1936 window: "[Window]", 1790 window: "[Window]",
1937 document: "[Document]", 1791 document: "[Document]",
1938 » » » » error: function(error) { 1792 » » » » error: function( error ) {
1939 return "Error(\"" + error.message + "\") "; 1793 return "Error(\"" + error.message + "\") ";
1940 }, 1794 },
1941 unknown: "[Unknown]", 1795 unknown: "[Unknown]",
1942 "null": "null", 1796 "null": "null",
1943 "undefined": "undefined", 1797 "undefined": "undefined",
1944 "function": function( fn ) { 1798 "function": function( fn ) {
1945 var ret = "function", 1799 var ret = "function",
1800
1946 // functions never have name in IE 1801 // functions never have name in IE
1947 » » » » » » name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1]; 1802 » » » » » » name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
1948 1803
1949 if ( name ) { 1804 if ( name ) {
1950 ret += " " + name; 1805 ret += " " + name;
1951 } 1806 }
1952 ret += "( "; 1807 ret += "( ";
1953 1808
1954 » » » » » ret = [ ret, QUnit.jsDump.parse( fn, "fu nctionArgs" ), "){" ].join( "" ); 1809 » » » » » ret = [ ret, dump.parse( fn, "functionAr gs" ), "){" ].join( "" );
1955 » » » » » return join( ret, QUnit.jsDump.parse(fn, "functionCode" ), "}" ); 1810 » » » » » return join( ret, dump.parse( fn, "funct ionCode" ), "}" );
1956 }, 1811 },
1957 array: array, 1812 array: array,
1958 nodelist: array, 1813 nodelist: array,
1959 "arguments": array, 1814 "arguments": array,
1960 object: function( map, stack ) { 1815 object: function( map, stack ) {
1961 » » » » » /*jshint forin:false */ 1816 » » » » » var keys, key, val, i, nonEnumerableProp erties,
1962 » » » » » var ret = [ ], keys, key, val, i; 1817 » » » » » » ret = [];
1963 » » » » » QUnit.jsDump.up(); 1818
1819 » » » » » if ( dump.maxDepth && dump.depth > dump. maxDepth ) {
1820 » » » » » » return "[object Object]";
1821 » » » » » }
1822
1823 » » » » » dump.up();
1964 keys = []; 1824 keys = [];
1965 for ( key in map ) { 1825 for ( key in map ) {
1966 keys.push( key ); 1826 keys.push( key );
1967 } 1827 }
1828
1829 // Some properties are not always enumer able on Error objects.
1830 nonEnumerableProperties = [ "message", " name" ];
1831 for ( i in nonEnumerableProperties ) {
1832 key = nonEnumerableProperties[ i ];
1833 if ( key in map && !( key in key s ) ) {
1834 keys.push( key );
1835 }
1836 }
1968 keys.sort(); 1837 keys.sort();
1969 for ( i = 0; i < keys.length; i++ ) { 1838 for ( i = 0; i < keys.length; i++ ) {
1970 key = keys[ i ]; 1839 key = keys[ i ];
1971 val = map[ key ]; 1840 val = map[ key ];
1972 » » » » » » ret.push( QUnit.jsDump.parse( ke y, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); 1841 » » » » » » ret.push( dump.parse( key, "key" ) + ": " +
1842 » » » » » » » dump.parse( val, undefin ed, stack ) );
1973 } 1843 }
1974 » » » » » QUnit.jsDump.down(); 1844 » » » » » dump.down();
1975 return join( "{", ret, "}" ); 1845 return join( "{", ret, "}" );
1976 }, 1846 },
1977 node: function( node ) { 1847 node: function( node ) {
1978 var len, i, val, 1848 var len, i, val,
1979 » » » » » » open = QUnit.jsDump.HTML ? "&lt; " : "<", 1849 » » » » » » open = dump.HTML ? "&lt;" : "<",
1980 » » » » » » close = QUnit.jsDump.HTML ? "&gt ;" : ">", 1850 » » » » » » close = dump.HTML ? "&gt;" : ">" ,
1981 tag = node.nodeName.toLowerCase( ), 1851 tag = node.nodeName.toLowerCase( ),
1982 ret = open + tag, 1852 ret = open + tag,
1983 attrs = node.attributes; 1853 attrs = node.attributes;
1984 1854
1985 if ( attrs ) { 1855 if ( attrs ) {
1986 for ( i = 0, len = attrs.length; i < len; i++ ) { 1856 for ( i = 0, len = attrs.length; i < len; i++ ) {
1987 » » » » » » » val = attrs[i].nodeValue ; 1857 » » » » » » » val = attrs[ i ].nodeVal ue;
1988 » » » » » » » // IE6 includes all attr ibutes in .attributes, even ones not explicitly set. 1858
1989 » » » » » » » // Those have values lik e undefined, null, 0, false, "" or "inherit". 1859 » » » » » » » // IE6 includes all attr ibutes in .attributes, even ones not explicitly
1860 » » » » » » » // set. Those have value s like undefined, null, 0, false, "" or
1861 » » » » » » » // "inherit".
1990 if ( val && val !== "inh erit" ) { 1862 if ( val && val !== "inh erit" ) {
1991 » » » » » » » » ret += " " + att rs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" ); 1863 » » » » » » » » ret += " " + att rs[ i ].nodeName + "=" +
1864 » » » » » » » » » dump.par se( val, "attribute" );
1992 } 1865 }
1993 } 1866 }
1994 } 1867 }
1995 ret += close; 1868 ret += close;
1996 1869
1997 // Show content of TextNode or CDATASect ion 1870 // Show content of TextNode or CDATASect ion
1998 if ( node.nodeType === 3 || node.nodeTyp e === 4 ) { 1871 if ( node.nodeType === 3 || node.nodeTyp e === 4 ) {
1999 ret += node.nodeValue; 1872 ret += node.nodeValue;
2000 } 1873 }
2001 1874
2002 return ret + open + "/" + tag + close; 1875 return ret + open + "/" + tag + close;
2003 }, 1876 },
1877
2004 // function calls it internally, it's the argume nts part of the function 1878 // function calls it internally, it's the argume nts part of the function
2005 functionArgs: function( fn ) { 1879 functionArgs: function( fn ) {
2006 var args, 1880 var args,
2007 l = fn.length; 1881 l = fn.length;
2008 1882
2009 if ( !l ) { 1883 if ( !l ) {
2010 return ""; 1884 return "";
2011 } 1885 }
2012 1886
2013 » » » » » args = new Array(l); 1887 » » » » » args = new Array( l );
2014 while ( l-- ) { 1888 while ( l-- ) {
1889
2015 // 97 is 'a' 1890 // 97 is 'a'
2016 » » » » » » args[l] = String.fromCharCode(97 +l); 1891 » » » » » » args[ l ] = String.fromCharCode( 97 + l );
2017 } 1892 }
2018 return " " + args.join( ", " ) + " "; 1893 return " " + args.join( ", " ) + " ";
2019 }, 1894 },
2020 // object calls it internally, the key part of a n item in a map 1895 // object calls it internally, the key part of a n item in a map
2021 key: quote, 1896 key: quote,
2022 // function calls it internally, it's the conten t of the function 1897 // function calls it internally, it's the conten t of the function
2023 functionCode: "[code]", 1898 functionCode: "[code]",
2024 // node calls it internally, it's an html attrib ute value 1899 // node calls it internally, it's an html attrib ute value
2025 attribute: quote, 1900 attribute: quote,
2026 string: quote, 1901 string: quote,
2027 date: quote, 1902 date: quote,
2028 regexp: literal, 1903 regexp: literal,
2029 number: literal, 1904 number: literal,
2030 "boolean": literal 1905 "boolean": literal
2031 }, 1906 },
2032 // if true, entities are escaped ( <, >, \t, space and \ n ) 1907 // if true, entities are escaped ( <, >, \t, space and \ n )
2033 HTML: false, 1908 HTML: false,
2034 // indentation unit 1909 // indentation unit
2035 indentChar: " ", 1910 indentChar: " ",
2036 // if true, items in a collection, are separated by a \n , else just a space. 1911 // if true, items in a collection, are separated by a \n , else just a space.
2037 multiline: true 1912 multiline: true
2038 }; 1913 };
2039 1914
2040 » return jsDump; 1915 » return dump;
2041 }()); 1916 }());
2042 1917
2043 // from jquery.js 1918 // back compat
2044 function inArray( elem, array ) { 1919 QUnit.jsDump = QUnit.dump;
2045 » if ( array.indexOf ) {
2046 » » return array.indexOf( elem );
2047 » }
2048 1920
2049 » for ( var i = 0, length = array.length; i < length; i++ ) { 1921 // For browser, export only select globals
2050 » » if ( array[ i ] === elem ) { 1922 if ( typeof window !== "undefined" ) {
2051 » » » return i; 1923
1924 » // Deprecated
1925 » // Extend assert methods to QUnit and Global scope through Backwards com patibility
1926 » (function() {
1927 » » var i,
1928 » » » assertions = Assert.prototype;
1929
1930 » » function applyCurrent( current ) {
1931 » » » return function() {
1932 » » » » var assert = new Assert( QUnit.config.current );
1933 » » » » current.apply( assert, arguments );
1934 » » » };
2052 } 1935 }
2053 }
2054 1936
2055 » return -1; 1937 » » for ( i in assertions ) {
1938 » » » QUnit[ i ] = applyCurrent( assertions[ i ] );
1939 » » }
1940 » })();
1941
1942 » (function() {
1943 » » var i, l,
1944 » » » keys = [
1945 » » » » "test",
1946 » » » » "module",
1947 » » » » "expect",
1948 » » » » "asyncTest",
1949 » » » » "start",
1950 » » » » "stop",
1951 » » » » "ok",
1952 » » » » "equal",
1953 » » » » "notEqual",
1954 » » » » "propEqual",
1955 » » » » "notPropEqual",
1956 » » » » "deepEqual",
1957 » » » » "notDeepEqual",
1958 » » » » "strictEqual",
1959 » » » » "notStrictEqual",
1960 » » » » "throws"
1961 » » » ];
1962
1963 » » for ( i = 0, l = keys.length; i < l; i++ ) {
1964 » » » window[ keys[ i ] ] = QUnit[ keys[ i ] ];
1965 » » }
1966 » })();
1967
1968 » window.QUnit = QUnit;
2056 } 1969 }
2057 1970
1971 // For nodejs
1972 if ( typeof module !== "undefined" && module && module.exports ) {
1973 module.exports = QUnit;
1974
1975 // For consistency with CommonJS environments' exports
1976 module.exports.QUnit = QUnit;
1977 }
1978
1979 // For CommonJS with exports, but without module.exports, like Rhino
1980 if ( typeof exports !== "undefined" && exports ) {
1981 exports.QUnit = QUnit;
1982 }
1983
1984 // Get a reference to the global object, like window in browsers
1985 }( (function() {
1986 return this;
1987 })() ));
1988
1989 /*istanbul ignore next */
1990 // jscs:disable maximumLineLength
2058 /* 1991 /*
2059 * Javascript Diff Algorithm 1992 * Javascript Diff Algorithm
2060 * By John Resig (http://ejohn.org/) 1993 * By John Resig (http://ejohn.org/)
2061 * Modified by Chu Alan "sprite" 1994 * Modified by Chu Alan "sprite"
2062 * 1995 *
2063 * Released under the MIT license. 1996 * Released under the MIT license.
2064 * 1997 *
2065 * More Info: 1998 * More Info:
2066 * http://ejohn.org/projects/javascript-diff-algorithm/ 1999 * http://ejohn.org/projects/javascript-diff-algorithm/
2067 * 2000 *
2068 * Usage: QUnit.diff(expected, actual) 2001 * Usage: QUnit.diff(expected, actual)
2069 * 2002 *
2070 * 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" 2003 * 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"
2071 */ 2004 */
2072 QUnit.diff = (function() { 2005 QUnit.diff = (function() {
2006 var hasOwn = Object.prototype.hasOwnProperty;
2007
2073 /*jshint eqeqeq:false, eqnull:true */ 2008 /*jshint eqeqeq:false, eqnull:true */
2074 function diff( o, n ) { 2009 function diff( o, n ) {
2075 var i, 2010 var i,
2076 ns = {}, 2011 ns = {},
2077 os = {}; 2012 os = {};
2078 2013
2079 for ( i = 0; i < n.length; i++ ) { 2014 for ( i = 0; i < n.length; i++ ) {
2080 » » » if ( !hasOwn.call( ns, n[i] ) ) { 2015 » » » if ( !hasOwn.call( ns, n[ i ] ) ) {
2081 » » » » ns[ n[i] ] = { 2016 » » » » ns[ n[ i ] ] = {
2082 rows: [], 2017 rows: [],
2083 o: null 2018 o: null
2084 }; 2019 };
2085 } 2020 }
2086 » » » ns[ n[i] ].rows.push( i ); 2021 » » » ns[ n[ i ] ].rows.push( i );
2087 } 2022 }
2088 2023
2089 for ( i = 0; i < o.length; i++ ) { 2024 for ( i = 0; i < o.length; i++ ) {
2090 » » » if ( !hasOwn.call( os, o[i] ) ) { 2025 » » » if ( !hasOwn.call( os, o[ i ] ) ) {
2091 » » » » os[ o[i] ] = { 2026 » » » » os[ o[ i ] ] = {
2092 rows: [], 2027 rows: [],
2093 n: null 2028 n: null
2094 }; 2029 };
2095 } 2030 }
2096 » » » os[ o[i] ].rows.push( i ); 2031 » » » os[ o[ i ] ].rows.push( i );
2097 } 2032 }
2098 2033
2099 for ( i in ns ) { 2034 for ( i in ns ) {
2100 if ( hasOwn.call( ns, i ) ) { 2035 if ( hasOwn.call( ns, i ) ) {
2101 » » » » if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) { 2036 » » » » if ( ns[ i ].rows.length === 1 && hasOwn.call( o s, i ) && os[ i ].rows.length === 1 ) {
2102 » » » » » n[ ns[i].rows[0] ] = { 2037 » » » » » n[ ns[ i ].rows[ 0 ] ] = {
2103 » » » » » » text: n[ ns[i].rows[0] ], 2038 » » » » » » text: n[ ns[ i ].rows[ 0 ] ],
2104 » » » » » » row: os[i].rows[0] 2039 » » » » » » row: os[ i ].rows[ 0 ]
2105 }; 2040 };
2106 » » » » » o[ os[i].rows[0] ] = { 2041 » » » » » o[ os[ i ].rows[ 0 ] ] = {
2107 » » » » » » text: o[ os[i].rows[0] ], 2042 » » » » » » text: o[ os[ i ].rows[ 0 ] ],
2108 » » » » » » row: ns[i].rows[0] 2043 » » » » » » row: ns[ i ].rows[ 0 ]
2109 }; 2044 };
2110 } 2045 }
2111 } 2046 }
2112 } 2047 }
2113 2048
2114 for ( i = 0; i < n.length - 1; i++ ) { 2049 for ( i = 0; i < n.length - 1; i++ ) {
2115 » » » if ( n[i].text != null && n[ i + 1 ].text == null && n[i ].row + 1 < o.length && o[ n[i].row + 1 ].text == null && 2050 » » » if ( n[ i ].text != null && n[ i + 1 ].text == null && n [ i ].row + 1 < o.length && o[ n[ i ].row + 1 ].text == null &&
2116 » » » » » » n[ i + 1 ] == o[ n[i].row + 1 ] ) { 2051 » » » » n[ i + 1 ] == o[ n[ i ].row + 1 ] ) {
2117 2052
2118 n[ i + 1 ] = { 2053 n[ i + 1 ] = {
2119 text: n[ i + 1 ], 2054 text: n[ i + 1 ],
2120 » » » » » row: n[i].row + 1 2055 » » » » » row: n[ i ].row + 1
2121 }; 2056 };
2122 » » » » o[ n[i].row + 1 ] = { 2057 » » » » o[ n[ i ].row + 1 ] = {
2123 » » » » » text: o[ n[i].row + 1 ], 2058 » » » » » text: o[ n[ i ].row + 1 ],
2124 row: i + 1 2059 row: i + 1
2125 }; 2060 };
2126 } 2061 }
2127 } 2062 }
2128 2063
2129 for ( i = n.length - 1; i > 0; i-- ) { 2064 for ( i = n.length - 1; i > 0; i-- ) {
2130 » » » if ( n[i].text != null && n[ i - 1 ].text == null && n[i ].row > 0 && o[ n[i].row - 1 ].text == null && 2065 » » » if ( n[ i ].text != null && n[ i - 1 ].text == null && n [ i ].row > 0 && o[ n[ i ].row - 1 ].text == null &&
2131 » » » » » » n[ i - 1 ] == o[ n[i].row - 1 ]) { 2066 » » » » n[ i - 1 ] == o[ n[ i ].row - 1 ] ) {
2132 2067
2133 n[ i - 1 ] = { 2068 n[ i - 1 ] = {
2134 text: n[ i - 1 ], 2069 text: n[ i - 1 ],
2135 » » » » » row: n[i].row - 1 2070 » » » » » row: n[ i ].row - 1
2136 }; 2071 };
2137 » » » » o[ n[i].row - 1 ] = { 2072 » » » » o[ n[ i ].row - 1 ] = {
2138 » » » » » text: o[ n[i].row - 1 ], 2073 » » » » » text: o[ n[ i ].row - 1 ],
2139 row: i - 1 2074 row: i - 1
2140 }; 2075 };
2141 } 2076 }
2142 } 2077 }
2143 2078
2144 return { 2079 return {
2145 o: o, 2080 o: o,
2146 n: n 2081 n: n
2147 }; 2082 };
2148 } 2083 }
2149 2084
2150 return function( o, n ) { 2085 return function( o, n ) {
2151 o = o.replace( /\s+$/, "" ); 2086 o = o.replace( /\s+$/, "" );
2152 n = n.replace( /\s+$/, "" ); 2087 n = n.replace( /\s+$/, "" );
2153 2088
2154 var i, pre, 2089 var i, pre,
2155 str = "", 2090 str = "",
2156 » » » out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [ ] : n.split(/\s+/) ), 2091 » » » out = diff( o === "" ? [] : o.split( /\s+/ ), n === "" ? [] : n.split( /\s+/ ) ),
2157 » » » oSpace = o.match(/\s+/g), 2092 » » » oSpace = o.match( /\s+/g ),
2158 » » » nSpace = n.match(/\s+/g); 2093 » » » nSpace = n.match( /\s+/g );
2159 2094
2160 if ( oSpace == null ) { 2095 if ( oSpace == null ) {
2161 oSpace = [ " " ]; 2096 oSpace = [ " " ];
2162 » » } 2097 » » } else {
2163 » » else {
2164 oSpace.push( " " ); 2098 oSpace.push( " " );
2165 } 2099 }
2166 2100
2167 if ( nSpace == null ) { 2101 if ( nSpace == null ) {
2168 nSpace = [ " " ]; 2102 nSpace = [ " " ];
2169 » » } 2103 » » } else {
2170 » » else {
2171 nSpace.push( " " ); 2104 nSpace.push( " " );
2172 } 2105 }
2173 2106
2174 if ( out.n.length === 0 ) { 2107 if ( out.n.length === 0 ) {
2175 for ( i = 0; i < out.o.length; i++ ) { 2108 for ( i = 0; i < out.o.length; i++ ) {
2176 » » » » str += "<del>" + out.o[i] + oSpace[i] + "</del>" ; 2109 » » » » str += "<del>" + out.o[ i ] + oSpace[ i ] + "</d el>";
2177 } 2110 }
2178 » » } 2111 » » } else {
2179 » » else { 2112 » » » if ( out.n[ 0 ].text == null ) {
2180 » » » if ( out.n[0].text == null ) { 2113 » » » » for ( n = 0; n < out.o.length && out.o[ n ].text == null; n++ ) {
2181 » » » » for ( n = 0; n < out.o.length && out.o[n].text = = null; n++ ) { 2114 » » » » » str += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>";
2182 » » » » » str += "<del>" + out.o[n] + oSpace[n] + "</del>";
2183 } 2115 }
2184 } 2116 }
2185 2117
2186 for ( i = 0; i < out.n.length; i++ ) { 2118 for ( i = 0; i < out.n.length; i++ ) {
2187 » » » » if (out.n[i].text == null) { 2119 » » » » if ( out.n[ i ].text == null ) {
2188 » » » » » str += "<ins>" + out.n[i] + nSpace[i] + "</ins>"; 2120 » » » » » str += "<ins>" + out.n[ i ] + nSpace[ i ] + "</ins>";
2189 » » » » } 2121 » » » » } else {
2190 » » » » else { 2122
2191 // `pre` initialized at top of scope 2123 // `pre` initialized at top of scope
2192 pre = ""; 2124 pre = "";
2193 2125
2194 » » » » » for ( n = out.n[i].row + 1; n < out.o.le ngth && out.o[n].text == null; n++ ) { 2126 » » » » » for ( n = out.n[ i ].row + 1; n < out.o. length && out.o[ n ].text == null; n++ ) {
2195 » » » » » » pre += "<del>" + out.o[n] + oSpa ce[n] + "</del>"; 2127 » » » » » » pre += "<del>" + out.o[ n ] + oS pace[ n ] + "</del>";
2196 } 2128 }
2197 » » » » » str += " " + out.n[i].text + nSpace[i] + pre; 2129 » » » » » str += " " + out.n[ i ].text + nSpace[ i ] + pre;
2198 } 2130 }
2199 } 2131 }
2200 } 2132 }
2201 2133
2202 return str; 2134 return str;
2203 }; 2135 };
2204 }()); 2136 }());
2205 2137 // jscs:enable
2206 // for CommonJS environments, export everything 2138
2207 if ( typeof exports !== "undefined" ) { 2139 (function() {
2208 extend( exports, QUnit.constructor.prototype ); 2140
2209 } 2141 // Deprecated QUnit.init - Ref #530
2210 2142 // Re-initialize the configuration options
2211 // get at whatever the global object is, like window in browsers 2143 QUnit.init = function() {
2212 }( (function() {return this;}.call()) )); 2144 var tests, banner, result, qunit,
2145 config = QUnit.config;
2146
2147 config.stats = { all: 0, bad: 0 };
2148 config.moduleStats = { all: 0, bad: 0 };
2149 config.started = 0;
2150 config.updateRate = 1000;
2151 config.blocking = false;
2152 config.autostart = true;
2153 config.autorun = false;
2154 config.filter = "";
2155 config.queue = [];
2156
2157 // Return on non-browser environments
2158 // This is necessary to not break on node tests
2159 if ( typeof window === "undefined" ) {
2160 return;
2161 }
2162
2163 qunit = id( "qunit" );
2164 if ( qunit ) {
2165 qunit.innerHTML =
2166 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
2167 "<h2 id='qunit-banner'></h2>" +
2168 "<div id='qunit-testrunner-toolbar'></div>" +
2169 "<h2 id='qunit-userAgent'></h2>" +
2170 "<ol id='qunit-tests'></ol>";
2171 }
2172
2173 tests = id( "qunit-tests" );
2174 banner = id( "qunit-banner" );
2175 result = id( "qunit-testresult" );
2176
2177 if ( tests ) {
2178 tests.innerHTML = "";
2179 }
2180
2181 if ( banner ) {
2182 banner.className = "";
2183 }
2184
2185 if ( result ) {
2186 result.parentNode.removeChild( result );
2187 }
2188
2189 if ( tests ) {
2190 result = document.createElement( "p" );
2191 result.id = "qunit-testresult";
2192 result.className = "result";
2193 tests.parentNode.insertBefore( result, tests );
2194 result.innerHTML = "Running...<br />&#160;";
2195 }
2196 };
2197
2198 // Don't load the HTML Reporter on non-Browser environments
2199 if ( typeof window === "undefined" ) {
2200 return;
2201 }
2202
2203 var config = QUnit.config,
2204 hasOwn = Object.prototype.hasOwnProperty,
2205 defined = {
2206 document: window.document !== undefined,
2207 sessionStorage: (function() {
2208 var x = "qunit-test-string";
2209 try {
2210 sessionStorage.setItem( x, x );
2211 sessionStorage.removeItem( x );
2212 return true;
2213 } catch ( e ) {
2214 return false;
2215 }
2216 }())
2217 },
2218 modulesList = [];
2219
2220 /**
2221 * Escape text for attribute or text content.
2222 */
2223 function escapeText( s ) {
2224 if ( !s ) {
2225 return "";
2226 }
2227 s = s + "";
2228
2229 // Both single quotes and double quotes (for attributes)
2230 return s.replace( /['"<>&]/g, function( s ) {
2231 switch ( s ) {
2232 case "'":
2233 return "&#039;";
2234 case "\"":
2235 return "&quot;";
2236 case "<":
2237 return "&lt;";
2238 case ">":
2239 return "&gt;";
2240 case "&":
2241 return "&amp;";
2242 }
2243 });
2244 }
2245
2246 /**
2247 * @param {HTMLElement} elem
2248 * @param {string} type
2249 * @param {Function} fn
2250 */
2251 function addEvent( elem, type, fn ) {
2252 if ( elem.addEventListener ) {
2253
2254 // Standards-based browsers
2255 elem.addEventListener( type, fn, false );
2256 } else if ( elem.attachEvent ) {
2257
2258 // support: IE <9
2259 elem.attachEvent( "on" + type, fn );
2260 }
2261 }
2262
2263 /**
2264 * @param {Array|NodeList} elems
2265 * @param {string} type
2266 * @param {Function} fn
2267 */
2268 function addEvents( elems, type, fn ) {
2269 var i = elems.length;
2270 while ( i-- ) {
2271 addEvent( elems[ i ], type, fn );
2272 }
2273 }
2274
2275 function hasClass( elem, name ) {
2276 return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
2277 }
2278
2279 function addClass( elem, name ) {
2280 if ( !hasClass( elem, name ) ) {
2281 elem.className += ( elem.className ? " " : "" ) + name;
2282 }
2283 }
2284
2285 function toggleClass( elem, name ) {
2286 if ( hasClass( elem, name ) ) {
2287 removeClass( elem, name );
2288 } else {
2289 addClass( elem, name );
2290 }
2291 }
2292
2293 function removeClass( elem, name ) {
2294 var set = " " + elem.className + " ";
2295
2296 // Class name may appear multiple times
2297 while ( set.indexOf( " " + name + " " ) >= 0 ) {
2298 set = set.replace( " " + name + " ", " " );
2299 }
2300
2301 // trim for prettiness
2302 elem.className = typeof set.trim === "function" ? set.trim() : set.repla ce( /^\s+|\s+$/g, "" );
2303 }
2304
2305 function id( name ) {
2306 return defined.document && document.getElementById && document.getElemen tById( name );
2307 }
2308
2309 function getUrlConfigHtml() {
2310 var i, j, val,
2311 escaped, escapedTooltip,
2312 selection = false,
2313 len = config.urlConfig.length,
2314 urlConfigHtml = "";
2315
2316 for ( i = 0; i < len; i++ ) {
2317 val = config.urlConfig[ i ];
2318 if ( typeof val === "string" ) {
2319 val = {
2320 id: val,
2321 label: val
2322 };
2323 }
2324
2325 escaped = escapeText( val.id );
2326 escapedTooltip = escapeText( val.tooltip );
2327
2328 if ( config[ val.id ] === undefined ) {
2329 config[ val.id ] = QUnit.urlParams[ val.id ];
2330 }
2331
2332 if ( !val.value || typeof val.value === "string" ) {
2333 urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
2334 "' name='" + escaped + "' type='checkbox'" +
2335 ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
2336 ( config[ val.id ] ? " checked='checked'" : "" ) +
2337 " title='" + escapedTooltip + "' /><label for='q unit-urlconfig-" + escaped +
2338 "' title='" + escapedTooltip + "'>" + val.label + "</label>";
2339 } else {
2340 urlConfigHtml += "<label for='qunit-urlconfig-" + escape d +
2341 "' title='" + escapedTooltip + "'>" + val.label +
2342 ": </label><select id='qunit-urlconfig-" + escap ed +
2343 "' name='" + escaped + "' title='" + escapedTool tip + "'><option></option>";
2344
2345 if ( QUnit.is( "array", val.value ) ) {
2346 for ( j = 0; j < val.value.length; j++ ) {
2347 escaped = escapeText( val.value[ j ] );
2348 urlConfigHtml += "<option value='" + esc aped + "'" +
2349 ( config[ val.id ] === val.value [ j ] ?
2350 ( selection = true ) && " selected='selected'" : "" ) +
2351 ">" + escaped + "</option>";
2352 }
2353 } else {
2354 for ( j in val.value ) {
2355 if ( hasOwn.call( val.value, j ) ) {
2356 urlConfigHtml += "<option value= '" + escapeText( j ) + "'" +
2357 ( config[ val.id ] === j ?
2358 ( selection = tr ue ) && " selected='selected'" : "" ) +
2359 ">" + escapeText( val.va lue[ j ] ) + "</option>";
2360 }
2361 }
2362 }
2363 if ( config[ val.id ] && !selection ) {
2364 escaped = escapeText( config[ val.id ] );
2365 urlConfigHtml += "<option value='" + escaped +
2366 "' selected='selected' disabled='disable d'>" + escaped + "</option>";
2367 }
2368 urlConfigHtml += "</select>";
2369 }
2370 }
2371
2372 return urlConfigHtml;
2373 }
2374
2375 // Handle "click" events on toolbar checkboxes and "change" for select menus.
2376 // Updates the URL with the new state of `config.urlConfig` values.
2377 function toolbarChanged() {
2378 var updatedUrl, value,
2379 field = this,
2380 params = {};
2381
2382 // Detect if field is a select menu or a checkbox
2383 if ( "selectedIndex" in field ) {
2384 value = field.options[ field.selectedIndex ].value || undefined;
2385 } else {
2386 value = field.checked ? ( field.defaultValue || true ) : undefin ed;
2387 }
2388
2389 params[ field.name ] = value;
2390 updatedUrl = setUrl( params );
2391
2392 if ( "hidepassed" === field.name && "replaceState" in window.history ) {
2393 config[ field.name ] = value || false;
2394 if ( value ) {
2395 addClass( id( "qunit-tests" ), "hidepass" );
2396 } else {
2397 removeClass( id( "qunit-tests" ), "hidepass" );
2398 }
2399
2400 // It is not necessary to refresh the whole page
2401 window.history.replaceState( null, "", updatedUrl );
2402 } else {
2403 window.location = updatedUrl;
2404 }
2405 }
2406
2407 function setUrl( params ) {
2408 var key,
2409 querystring = "?";
2410
2411 params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
2412
2413 for ( key in params ) {
2414 if ( hasOwn.call( params, key ) ) {
2415 if ( params[ key ] === undefined ) {
2416 continue;
2417 }
2418 querystring += encodeURIComponent( key );
2419 if ( params[ key ] !== true ) {
2420 querystring += "=" + encodeURIComponent( params[ key ] );
2421 }
2422 querystring += "&";
2423 }
2424 }
2425 return location.protocol + "//" + location.host +
2426 location.pathname + querystring.slice( 0, -1 );
2427 }
2428
2429 function applyUrlParams() {
2430 var selectBox = id( "qunit-modulefilter" ),
2431 selection = decodeURIComponent( selectBox.options[ selectBox.sel ectedIndex ].value ),
2432 filter = id( "qunit-filter-input" ).value;
2433
2434 window.location = setUrl({
2435 module: ( selection === "" ) ? undefined : selection,
2436 filter: ( filter === "" ) ? undefined : filter,
2437
2438 // Remove testId filter
2439 testId: undefined
2440 });
2441 }
2442
2443 function toolbarUrlConfigContainer() {
2444 var urlConfigContainer = document.createElement( "span" );
2445
2446 urlConfigContainer.innerHTML = getUrlConfigHtml();
2447 addClass( urlConfigContainer, "qunit-url-config" );
2448
2449 // For oldIE support:
2450 // * Add handlers to the individual elements instead of the container
2451 // * Use "click" instead of "change" for checkboxes
2452 addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
2453 addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change" , toolbarChanged );
2454
2455 return urlConfigContainer;
2456 }
2457
2458 function toolbarLooseFilter() {
2459 var filter = document.createElement( "form" ),
2460 label = document.createElement( "label" ),
2461 input = document.createElement( "input" ),
2462 button = document.createElement( "button" );
2463
2464 addClass( filter, "qunit-filter" );
2465
2466 label.innerHTML = "Filter: ";
2467
2468 input.type = "text";
2469 input.value = config.filter || "";
2470 input.name = "filter";
2471 input.id = "qunit-filter-input";
2472
2473 button.innerHTML = "Go";
2474
2475 label.appendChild( input );
2476
2477 filter.appendChild( label );
2478 filter.appendChild( button );
2479 addEvent( filter, "submit", function( ev ) {
2480 applyUrlParams();
2481
2482 if ( ev && ev.preventDefault ) {
2483 ev.preventDefault();
2484 }
2485
2486 return false;
2487 });
2488
2489 return filter;
2490 }
2491
2492 function toolbarModuleFilterHtml() {
2493 var i,
2494 moduleFilterHtml = "";
2495
2496 if ( !modulesList.length ) {
2497 return false;
2498 }
2499
2500 modulesList.sort(function( a, b ) {
2501 return a.localeCompare( b );
2502 });
2503
2504 moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
2505 "<select id='qunit-modulefilter' name='modulefilter'><option val ue='' " +
2506 ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
2507 ">< All Modules ></option>";
2508
2509 for ( i = 0; i < modulesList.length; i++ ) {
2510 moduleFilterHtml += "<option value='" +
2511 escapeText( encodeURIComponent( modulesList[ i ] ) ) + " ' " +
2512 ( QUnit.urlParams.module === modulesList[ i ] ? "selecte d='selected'" : "" ) +
2513 ">" + escapeText( modulesList[ i ] ) + "</option>";
2514 }
2515 moduleFilterHtml += "</select>";
2516
2517 return moduleFilterHtml;
2518 }
2519
2520 function toolbarModuleFilter() {
2521 var toolbar = id( "qunit-testrunner-toolbar" ),
2522 moduleFilter = document.createElement( "span" ),
2523 moduleFilterHtml = toolbarModuleFilterHtml();
2524
2525 if ( !toolbar || !moduleFilterHtml ) {
2526 return false;
2527 }
2528
2529 moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
2530 moduleFilter.innerHTML = moduleFilterHtml;
2531
2532 addEvent( moduleFilter.lastChild, "change", applyUrlParams );
2533
2534 toolbar.appendChild( moduleFilter );
2535 }
2536
2537 function appendToolbar() {
2538 var toolbar = id( "qunit-testrunner-toolbar" );
2539
2540 if ( toolbar ) {
2541 toolbar.appendChild( toolbarUrlConfigContainer() );
2542 toolbar.appendChild( toolbarLooseFilter() );
2543 }
2544 }
2545
2546 function appendHeader() {
2547 var header = id( "qunit-header" );
2548
2549 if ( header ) {
2550 header.innerHTML = "<a href='" +
2551 setUrl({ filter: undefined, module: undefined, testId: u ndefined }) +
2552 "'>" + header.innerHTML + "</a> ";
2553 }
2554 }
2555
2556 function appendBanner() {
2557 var banner = id( "qunit-banner" );
2558
2559 if ( banner ) {
2560 banner.className = "";
2561 }
2562 }
2563
2564 function appendTestResults() {
2565 var tests = id( "qunit-tests" ),
2566 result = id( "qunit-testresult" );
2567
2568 if ( result ) {
2569 result.parentNode.removeChild( result );
2570 }
2571
2572 if ( tests ) {
2573 tests.innerHTML = "";
2574 result = document.createElement( "p" );
2575 result.id = "qunit-testresult";
2576 result.className = "result";
2577 tests.parentNode.insertBefore( result, tests );
2578 result.innerHTML = "Running...<br />&#160;";
2579 }
2580 }
2581
2582 function storeFixture() {
2583 var fixture = id( "qunit-fixture" );
2584 if ( fixture ) {
2585 config.fixture = fixture.innerHTML;
2586 }
2587 }
2588
2589 function appendUserAgent() {
2590 var userAgent = id( "qunit-userAgent" );
2591 if ( userAgent ) {
2592 userAgent.innerHTML = "";
2593 userAgent.appendChild( document.createTextNode( navigator.userAg ent ) );
2594 }
2595 }
2596
2597 function appendTestsList( modules ) {
2598 var i, l, x, z, test, moduleObj;
2599
2600 for ( i = 0, l = modules.length; i < l; i++ ) {
2601 moduleObj = modules[ i ];
2602
2603 if ( moduleObj.name ) {
2604 modulesList.push( moduleObj.name );
2605 }
2606
2607 for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
2608 test = moduleObj.tests[ x ];
2609
2610 appendTest( test.name, test.testId, moduleObj.name );
2611 }
2612 }
2613 }
2614
2615 function appendTest( name, testId, moduleName ) {
2616 var title, rerunTrigger, testBlock, assertList,
2617 tests = id( "qunit-tests" );
2618
2619 if ( !tests ) {
2620 return;
2621 }
2622
2623 title = document.createElement( "strong" );
2624 title.innerHTML = getNameHtml( name, moduleName );
2625
2626 rerunTrigger = document.createElement( "a" );
2627 rerunTrigger.innerHTML = "Rerun";
2628 rerunTrigger.href = setUrl({ testId: testId });
2629
2630 testBlock = document.createElement( "li" );
2631 testBlock.appendChild( title );
2632 testBlock.appendChild( rerunTrigger );
2633 testBlock.id = "qunit-test-output-" + testId;
2634
2635 assertList = document.createElement( "ol" );
2636 assertList.className = "qunit-assert-list";
2637
2638 testBlock.appendChild( assertList );
2639
2640 tests.appendChild( testBlock );
2641 }
2642
2643 // HTML Reporter initialization and load
2644 QUnit.begin(function( details ) {
2645 var qunit = id( "qunit" );
2646
2647 // Fixture is the only one necessary to run without the #qunit element
2648 storeFixture();
2649
2650 if ( qunit ) {
2651 qunit.innerHTML =
2652 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
2653 "<h2 id='qunit-banner'></h2>" +
2654 "<div id='qunit-testrunner-toolbar'></div>" +
2655 "<h2 id='qunit-userAgent'></h2>" +
2656 "<ol id='qunit-tests'></ol>";
2657 }
2658
2659 appendHeader();
2660 appendBanner();
2661 appendTestResults();
2662 appendUserAgent();
2663 appendToolbar();
2664 appendTestsList( details.modules );
2665 toolbarModuleFilter();
2666
2667 if ( qunit && config.hidepassed ) {
2668 addClass( qunit.lastChild, "hidepass" );
2669 }
2670 });
2671
2672 QUnit.done(function( details ) {
2673 var i, key,
2674 banner = id( "qunit-banner" ),
2675 tests = id( "qunit-tests" ),
2676 html = [
2677 "Tests completed in ",
2678 details.runtime,
2679 " milliseconds.<br />",
2680 "<span class='passed'>",
2681 details.passed,
2682 "</span> assertions of <span class='total'>",
2683 details.total,
2684 "</span> passed, <span class='failed'>",
2685 details.failed,
2686 "</span> failed."
2687 ].join( "" );
2688
2689 if ( banner ) {
2690 banner.className = details.failed ? "qunit-fail" : "qunit-pass";
2691 }
2692
2693 if ( tests ) {
2694 id( "qunit-testresult" ).innerHTML = html;
2695 }
2696
2697 if ( config.altertitle && defined.document && document.title ) {
2698
2699 // show ✖ for good, ✔ for bad suite result in title
2700 // use escape sequences in case file gets loaded with non-utf-8- charset
2701 document.title = [
2702 ( details.failed ? "\u2716" : "\u2714" ),
2703 document.title.replace( /^[\u2714\u2716] /i, "" )
2704 ].join( " " );
2705 }
2706
2707 // clear own sessionStorage items if all tests passed
2708 if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
2709 for ( i = 0; i < sessionStorage.length; i++ ) {
2710 key = sessionStorage.key( i++ );
2711 if ( key.indexOf( "qunit-test-" ) === 0 ) {
2712 sessionStorage.removeItem( key );
2713 }
2714 }
2715 }
2716
2717 // scroll back to top to show results
2718 if ( config.scrolltop && window.scrollTo ) {
2719 window.scrollTo( 0, 0 );
2720 }
2721 });
2722
2723 function getNameHtml( name, module ) {
2724 var nameHtml = "";
2725
2726 if ( module ) {
2727 nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
2728 }
2729
2730 nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
2731
2732 return nameHtml;
2733 }
2734
2735 QUnit.testStart(function( details ) {
2736 var running, testBlock;
2737
2738 testBlock = id( "qunit-test-output-" + details.testId );
2739 if ( testBlock ) {
2740 testBlock.className = "running";
2741 } else {
2742
2743 // Report later registered tests
2744 appendTest( details.name, details.testId, details.module );
2745 }
2746
2747 running = id( "qunit-testresult" );
2748 if ( running ) {
2749 running.innerHTML = "Running: <br />" + getNameHtml( details.nam e, details.module );
2750 }
2751
2752 });
2753
2754 QUnit.log(function( details ) {
2755 var assertList, assertLi,
2756 message, expected, actual,
2757 testItem = id( "qunit-test-output-" + details.testId );
2758
2759 if ( !testItem ) {
2760 return;
2761 }
2762
2763 message = escapeText( details.message ) || ( details.result ? "okay" : " failed" );
2764 message = "<span class='test-message'>" + message + "</span>";
2765 message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
2766
2767 // pushFailure doesn't provide details.expected
2768 // when it calls, it's implicit to also not show expected and diff stuff
2769 // Also, we need to check details.expected existence, as it can exist an d be undefined
2770 if ( !details.result && hasOwn.call( details, "expected" ) ) {
2771 expected = escapeText( QUnit.dump.parse( details.expected ) );
2772 actual = escapeText( QUnit.dump.parse( details.actual ) );
2773 message += "<table><tr class='test-expected'><th>Expected: </th> <td><pre>" +
2774 expected +
2775 "</pre></td></tr>";
2776
2777 if ( actual !== expected ) {
2778 message += "<tr class='test-actual'><th>Result: </th><td ><pre>" +
2779 actual + "</pre></td></tr>" +
2780 "<tr class='test-diff'><th>Diff: </th><td><pre>" +
2781 QUnit.diff( expected, actual ) + "</pre></td></t r>";
2782 }
2783
2784 if ( details.source ) {
2785 message += "<tr class='test-source'><th>Source: </th><td ><pre>" +
2786 escapeText( details.source ) + "</pre></td></tr> ";
2787 }
2788
2789 message += "</table>";
2790
2791 // this occours when pushFailure is set and we have an extracted stack t race
2792 } else if ( !details.result && details.source ) {
2793 message += "<table>" +
2794 "<tr class='test-source'><th>Source: </th><td><pre>" +
2795 escapeText( details.source ) + "</pre></td></tr>" +
2796 "</table>";
2797 }
2798
2799 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
2800
2801 assertLi = document.createElement( "li" );
2802 assertLi.className = details.result ? "pass" : "fail";
2803 assertLi.innerHTML = message;
2804 assertList.appendChild( assertLi );
2805 });
2806
2807 QUnit.testDone(function( details ) {
2808 var testTitle, time, testItem, assertList,
2809 good, bad, testCounts, skipped,
2810 tests = id( "qunit-tests" );
2811
2812 if ( !tests ) {
2813 return;
2814 }
2815
2816 testItem = id( "qunit-test-output-" + details.testId );
2817
2818 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
2819
2820 good = details.passed;
2821 bad = details.failed;
2822
2823 // store result when possible
2824 if ( config.reorder && defined.sessionStorage ) {
2825 if ( bad ) {
2826 sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
2827 } else {
2828 sessionStorage.removeItem( "qunit-test-" + details.modul e + "-" + details.name );
2829 }
2830 }
2831
2832 if ( bad === 0 ) {
2833 addClass( assertList, "qunit-collapsed" );
2834 }
2835
2836 // testItem.firstChild is the test name
2837 testTitle = testItem.firstChild;
2838
2839 testCounts = bad ?
2840 "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + g ood + "</b>, " :
2841 "";
2842
2843 testTitle.innerHTML += " <b class='counts'>(" + testCounts +
2844 details.assertions.length + ")</b>";
2845
2846 if ( details.skipped ) {
2847 testItem.className = "skipped";
2848 skipped = document.createElement( "em" );
2849 skipped.className = "qunit-skipped-label";
2850 skipped.innerHTML = "skipped";
2851 testItem.insertBefore( skipped, testTitle );
2852 } else {
2853 addEvent( testTitle, "click", function() {
2854 toggleClass( assertList, "qunit-collapsed" );
2855 });
2856
2857 testItem.className = bad ? "fail" : "pass";
2858
2859 time = document.createElement( "span" );
2860 time.className = "runtime";
2861 time.innerHTML = details.runtime + " ms";
2862 testItem.insertBefore( time, assertList );
2863 }
2864 });
2865
2866 if ( !defined.document || document.readyState === "complete" ) {
2867 config.pageLoaded = true;
2868 config.autorun = true;
2869 }
2870
2871 if ( defined.document ) {
2872 addEvent( window, "load", QUnit.load );
2873 }
2874
2875 })();
OLDNEW
« no previous file with comments | « chrome/content/qunit.css ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld