OLD | NEW |
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 Loading... |
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/> "; | |
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 "'"; | |
1424 » » » case "\"": | |
1425 » » » » return """; | |
1426 » » » case "<": | |
1427 » » » » return "<"; | |
1428 » » » case ">": | |
1429 » » » » return ">"; | |
1430 » » » case "&": | |
1431 » » » » return "&"; | |
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 ? " " : " "; | 1758 » » » » return this.multiline ? this.HTML ? "<br />" : "
\n" : this.HTML ? " " : " "; |
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, " " ); | 1767 » » » » » chr = chr.replace( /\t/g, " " ).replac
e( / /g, " " ); |
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 ? "<
" : "<", | 1849 » » » » » » open = dump.HTML ? "<" : "<", |
1980 » » » » » » close = QUnit.jsDump.HTML ? ">
;" : ">", | 1850 » » » » » » close = dump.HTML ? ">" : ">"
, |
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 /> "; |
| 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 "'"; |
| 2234 case "\"": |
| 2235 return """; |
| 2236 case "<": |
| 2237 return "<"; |
| 2238 case ">": |
| 2239 return ">"; |
| 2240 case "&": |
| 2241 return "&"; |
| 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 /> "; |
| 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 })(); |
OLD | NEW |