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