1 /** |
|
2 * QUnit - A JavaScript Unit Testing Framework |
|
3 * |
|
4 * http://docs.jquery.com/QUnit |
|
5 * |
|
6 * Copyright (c) 2011 John Resig, Jörn Zaefferer |
|
7 * Dual licensed under the MIT (MIT-LICENSE.txt) |
|
8 * or GPL (GPL-LICENSE.txt) licenses. |
|
9 * Pulled Live from Git Thu Oct 6 09:15:01 UTC 2011 |
|
10 * Last Commit: 3006fa77db4625c4293c368a8999ddb1abba7f48 |
|
11 */ |
|
12 |
|
13 (function(window) { |
|
14 |
|
15 var defined = { |
|
16 setTimeout: typeof window.setTimeout !== "undefined", |
|
17 sessionStorage: (function() { |
|
18 try { |
|
19 return !!sessionStorage.getItem; |
|
20 } catch(e) { |
|
21 return false; |
|
22 } |
|
23 })() |
|
24 }; |
|
25 |
|
26 var testId = 0; |
|
27 |
|
28 var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { |
|
29 this.name = name; |
|
30 this.testName = testName; |
|
31 this.expected = expected; |
|
32 this.testEnvironmentArg = testEnvironmentArg; |
|
33 this.async = async; |
|
34 this.callback = callback; |
|
35 this.assertions = []; |
|
36 }; |
|
37 Test.prototype = { |
|
38 init: function() { |
|
39 var tests = id("qunit-tests"); |
|
40 if (tests) { |
|
41 var b = document.createElement("strong"); |
|
42 b.innerHTML = "Running " + this.name; |
|
43 var li = document.createElement("li"); |
|
44 li.appendChild( b ); |
|
45 li.className = "running"; |
|
46 li.id = this.id = "test-output" + testId++; |
|
47 tests.appendChild( li ); |
|
48 } |
|
49 }, |
|
50 setup: function() { |
|
51 if (this.module != config.previousModule) { |
|
52 if ( config.previousModule ) { |
|
53 runLoggingCallbacks('moduleDone', QUnit, { |
|
54 name: config.previousModule, |
|
55 failed: config.moduleStats.bad, |
|
56 passed: config.moduleStats.all - config.moduleStats.bad, |
|
57 total: config.moduleStats.all |
|
58 } ); |
|
59 } |
|
60 config.previousModule = this.module; |
|
61 config.moduleStats = { all: 0, bad: 0 }; |
|
62 runLoggingCallbacks( 'moduleStart', QUnit, { |
|
63 name: this.module |
|
64 } ); |
|
65 } |
|
66 |
|
67 config.current = this; |
|
68 this.testEnvironment = extend({ |
|
69 setup: function() {}, |
|
70 teardown: function() {} |
|
71 }, this.moduleTestEnvironment); |
|
72 if (this.testEnvironmentArg) { |
|
73 extend(this.testEnvironment, this.testEnvironmentArg); |
|
74 } |
|
75 |
|
76 runLoggingCallbacks( 'testStart', QUnit, { |
|
77 name: this.testName, |
|
78 module: this.module |
|
79 }); |
|
80 |
|
81 // allow utility functions to access the current test environment |
|
82 // TODO why?? |
|
83 QUnit.current_testEnvironment = this.testEnvironment; |
|
84 |
|
85 try { |
|
86 if ( !config.pollution ) { |
|
87 saveGlobal(); |
|
88 } |
|
89 |
|
90 this.testEnvironment.setup.call(this.testEnvironment); |
|
91 } catch(e) { |
|
92 QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); |
|
93 } |
|
94 }, |
|
95 run: function() { |
|
96 if ( this.async ) { |
|
97 QUnit.stop(); |
|
98 } |
|
99 |
|
100 if ( config.notrycatch ) { |
|
101 this.callback.call(this.testEnvironment); |
|
102 return; |
|
103 } |
|
104 try { |
|
105 this.callback.call(this.testEnvironment); |
|
106 } catch(e) { |
|
107 fail("Test " + this.testName + " died, exception and test follows", e, this.callback); |
|
108 QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); |
|
109 // else next test will carry the responsibility |
|
110 saveGlobal(); |
|
111 |
|
112 // Restart the tests if they're blocking |
|
113 if ( config.blocking ) { |
|
114 start(); |
|
115 } |
|
116 } |
|
117 }, |
|
118 teardown: function() { |
|
119 try { |
|
120 this.testEnvironment.teardown.call(this.testEnvironment); |
|
121 checkPollution(); |
|
122 } catch(e) { |
|
123 QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); |
|
124 } |
|
125 }, |
|
126 finish: function() { |
|
127 if ( this.expected && this.expected != this.assertions.length ) { |
|
128 QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); |
|
129 } |
|
130 |
|
131 var good = 0, bad = 0, |
|
132 tests = id("qunit-tests"); |
|
133 |
|
134 config.stats.all += this.assertions.length; |
|
135 config.moduleStats.all += this.assertions.length; |
|
136 |
|
137 if ( tests ) { |
|
138 var ol = document.createElement("ol"); |
|
139 |
|
140 for ( var i = 0; i < this.assertions.length; i++ ) { |
|
141 var assertion = this.assertions[i]; |
|
142 |
|
143 var li = document.createElement("li"); |
|
144 li.className = assertion.result ? "pass" : "fail"; |
|
145 li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); |
|
146 ol.appendChild( li ); |
|
147 |
|
148 if ( assertion.result ) { |
|
149 good++; |
|
150 } else { |
|
151 bad++; |
|
152 config.stats.bad++; |
|
153 config.moduleStats.bad++; |
|
154 } |
|
155 } |
|
156 |
|
157 // store result when possible |
|
158 if ( QUnit.config.reorder && defined.sessionStorage ) { |
|
159 if (bad) { |
|
160 sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad); |
|
161 } else { |
|
162 sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName); |
|
163 } |
|
164 } |
|
165 |
|
166 if (bad == 0) { |
|
167 ol.style.display = "none"; |
|
168 } |
|
169 |
|
170 var b = document.createElement("strong"); |
|
171 b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>"; |
|
172 |
|
173 var a = document.createElement("a"); |
|
174 a.innerHTML = "Rerun"; |
|
175 a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); |
|
176 |
|
177 addEvent(b, "click", function() { |
|
178 var next = b.nextSibling.nextSibling, |
|
179 display = next.style.display; |
|
180 next.style.display = display === "none" ? "block" : "none"; |
|
181 }); |
|
182 |
|
183 addEvent(b, "dblclick", function(e) { |
|
184 var target = e && e.target ? e.target : window.event.srcElement; |
|
185 if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { |
|
186 target = target.parentNode; |
|
187 } |
|
188 if ( window.location && target.nodeName.toLowerCase() === "strong" ) { |
|
189 window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); |
|
190 } |
|
191 }); |
|
192 |
|
193 var li = id(this.id); |
|
194 li.className = bad ? "fail" : "pass"; |
|
195 li.removeChild( li.firstChild ); |
|
196 li.appendChild( b ); |
|
197 li.appendChild( a ); |
|
198 li.appendChild( ol ); |
|
199 |
|
200 } else { |
|
201 for ( var i = 0; i < this.assertions.length; i++ ) { |
|
202 if ( !this.assertions[i].result ) { |
|
203 bad++; |
|
204 config.stats.bad++; |
|
205 config.moduleStats.bad++; |
|
206 } |
|
207 } |
|
208 } |
|
209 |
|
210 try { |
|
211 QUnit.reset(); |
|
212 } catch(e) { |
|
213 fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); |
|
214 } |
|
215 |
|
216 runLoggingCallbacks( 'testDone', QUnit, { |
|
217 name: this.testName, |
|
218 module: this.module, |
|
219 failed: bad, |
|
220 passed: this.assertions.length - bad, |
|
221 total: this.assertions.length |
|
222 } ); |
|
223 }, |
|
224 |
|
225 queue: function() { |
|
226 var test = this; |
|
227 synchronize(function() { |
|
228 test.init(); |
|
229 }); |
|
230 function run() { |
|
231 // each of these can by async |
|
232 synchronize(function() { |
|
233 test.setup(); |
|
234 }); |
|
235 synchronize(function() { |
|
236 test.run(); |
|
237 }); |
|
238 synchronize(function() { |
|
239 test.teardown(); |
|
240 }); |
|
241 synchronize(function() { |
|
242 test.finish(); |
|
243 }); |
|
244 } |
|
245 // defer when previous test run passed, if storage is available |
|
246 var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName); |
|
247 if (bad) { |
|
248 run(); |
|
249 } else { |
|
250 synchronize(run); |
|
251 }; |
|
252 } |
|
253 |
|
254 }; |
|
255 |
|
256 var QUnit = { |
|
257 |
|
258 // call on start of module test to prepend name to all tests |
|
259 module: function(name, testEnvironment) { |
|
260 config.currentModule = name; |
|
261 config.currentModuleTestEnviroment = testEnvironment; |
|
262 }, |
|
263 |
|
264 asyncTest: function(testName, expected, callback) { |
|
265 if ( arguments.length === 2 ) { |
|
266 callback = expected; |
|
267 expected = 0; |
|
268 } |
|
269 |
|
270 QUnit.test(testName, expected, callback, true); |
|
271 }, |
|
272 |
|
273 test: function(testName, expected, callback, async) { |
|
274 var name = '<span class="test-name">' + testName + '</span>', testEnvironmentArg; |
|
275 |
|
276 if ( arguments.length === 2 ) { |
|
277 callback = expected; |
|
278 expected = null; |
|
279 } |
|
280 // is 2nd argument a testEnvironment? |
|
281 if ( expected && typeof expected === 'object') { |
|
282 testEnvironmentArg = expected; |
|
283 expected = null; |
|
284 } |
|
285 |
|
286 if ( config.currentModule ) { |
|
287 name = '<span class="module-name">' + config.currentModule + "</span>: " + name; |
|
288 } |
|
289 |
|
290 if ( !validTest(config.currentModule + ": " + testName) ) { |
|
291 return; |
|
292 } |
|
293 |
|
294 var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); |
|
295 test.module = config.currentModule; |
|
296 test.moduleTestEnvironment = config.currentModuleTestEnviroment; |
|
297 test.queue(); |
|
298 }, |
|
299 |
|
300 /** |
|
301 * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. |
|
302 */ |
|
303 expect: function(asserts) { |
|
304 config.current.expected = asserts; |
|
305 }, |
|
306 |
|
307 /** |
|
308 * Asserts true. |
|
309 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); |
|
310 */ |
|
311 ok: function(a, msg) { |
|
312 a = !!a; |
|
313 var details = { |
|
314 result: a, |
|
315 message: msg |
|
316 }; |
|
317 msg = escapeInnerText(msg); |
|
318 runLoggingCallbacks( 'log', QUnit, details ); |
|
319 config.current.assertions.push({ |
|
320 result: a, |
|
321 message: msg |
|
322 }); |
|
323 }, |
|
324 |
|
325 /** |
|
326 * Checks that the first two arguments are equal, with an optional message. |
|
327 * Prints out both actual and expected values. |
|
328 * |
|
329 * Prefered to ok( actual == expected, message ) |
|
330 * |
|
331 * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); |
|
332 * |
|
333 * @param Object actual |
|
334 * @param Object expected |
|
335 * @param String message (optional) |
|
336 */ |
|
337 equal: function(actual, expected, message) { |
|
338 QUnit.push(expected == actual, actual, expected, message); |
|
339 }, |
|
340 |
|
341 notEqual: function(actual, expected, message) { |
|
342 QUnit.push(expected != actual, actual, expected, message); |
|
343 }, |
|
344 |
|
345 deepEqual: function(actual, expected, message) { |
|
346 QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); |
|
347 }, |
|
348 |
|
349 notDeepEqual: function(actual, expected, message) { |
|
350 QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); |
|
351 }, |
|
352 |
|
353 strictEqual: function(actual, expected, message) { |
|
354 QUnit.push(expected === actual, actual, expected, message); |
|
355 }, |
|
356 |
|
357 notStrictEqual: function(actual, expected, message) { |
|
358 QUnit.push(expected !== actual, actual, expected, message); |
|
359 }, |
|
360 |
|
361 raises: function(block, expected, message) { |
|
362 var actual, ok = false; |
|
363 |
|
364 if (typeof expected === 'string') { |
|
365 message = expected; |
|
366 expected = null; |
|
367 } |
|
368 |
|
369 try { |
|
370 block(); |
|
371 } catch (e) { |
|
372 actual = e; |
|
373 } |
|
374 |
|
375 if (actual) { |
|
376 // we don't want to validate thrown error |
|
377 if (!expected) { |
|
378 ok = true; |
|
379 // expected is a regexp |
|
380 } else if (QUnit.objectType(expected) === "regexp") { |
|
381 ok = expected.test(actual); |
|
382 // expected is a constructor |
|
383 } else if (actual instanceof expected) { |
|
384 ok = true; |
|
385 // expected is a validation function which returns true is validation passed |
|
386 } else if (expected.call({}, actual) === true) { |
|
387 ok = true; |
|
388 } |
|
389 } |
|
390 |
|
391 QUnit.ok(ok, message); |
|
392 }, |
|
393 |
|
394 start: function(count) { |
|
395 config.semaphore -= count || 1; |
|
396 if (config.semaphore > 0) { |
|
397 // don't start until equal number of stop-calls |
|
398 return; |
|
399 } |
|
400 if (config.semaphore < 0) { |
|
401 // ignore if start is called more often then stop |
|
402 config.semaphore = 0; |
|
403 } |
|
404 // A slight delay, to avoid any current callbacks |
|
405 if ( defined.setTimeout ) { |
|
406 window.setTimeout(function() { |
|
407 if (config.semaphore > 0) { |
|
408 return; |
|
409 } |
|
410 if ( config.timeout ) { |
|
411 clearTimeout(config.timeout); |
|
412 } |
|
413 |
|
414 config.blocking = false; |
|
415 process(); |
|
416 }, 13); |
|
417 } else { |
|
418 config.blocking = false; |
|
419 process(); |
|
420 } |
|
421 }, |
|
422 |
|
423 stop: function(count) { |
|
424 config.semaphore += count || 1; |
|
425 config.blocking = true; |
|
426 |
|
427 if ( config.testTimeout && defined.setTimeout ) { |
|
428 clearTimeout(config.timeout); |
|
429 config.timeout = window.setTimeout(function() { |
|
430 QUnit.ok( false, "Test timed out" ); |
|
431 config.semaphore = 1; |
|
432 QUnit.start(); |
|
433 }, config.testTimeout); |
|
434 } |
|
435 } |
|
436 }; |
|
437 |
|
438 //We want access to the constructor's prototype |
|
439 (function() { |
|
440 function F(){}; |
|
441 F.prototype = QUnit; |
|
442 QUnit = new F(); |
|
443 //Make F QUnit's constructor so that we can add to the prototype later |
|
444 QUnit.constructor = F; |
|
445 })(); |
|
446 |
|
447 // Backwards compatibility, deprecated |
|
448 QUnit.equals = QUnit.equal; |
|
449 QUnit.same = QUnit.deepEqual; |
|
450 |
|
451 // Maintain internal state |
|
452 var config = { |
|
453 // The queue of tests to run |
|
454 queue: [], |
|
455 |
|
456 // block until document ready |
|
457 blocking: true, |
|
458 |
|
459 // when enabled, show only failing tests |
|
460 // gets persisted through sessionStorage and can be changed in UI via checkbox |
|
461 hidepassed: false, |
|
462 |
|
463 // by default, run previously failed tests first |
|
464 // very useful in combination with "Hide passed tests" checked |
|
465 reorder: true, |
|
466 |
|
467 // by default, modify document.title when suite is done |
|
468 altertitle: true, |
|
469 |
|
470 urlConfig: ['noglobals', 'notrycatch'], |
|
471 |
|
472 //logging callback queues |
|
473 begin: [], |
|
474 done: [], |
|
475 log: [], |
|
476 testStart: [], |
|
477 testDone: [], |
|
478 moduleStart: [], |
|
479 moduleDone: [] |
|
480 }; |
|
481 |
|
482 // Load paramaters |
|
483 (function() { |
|
484 var location = window.location || { search: "", protocol: "file:" }, |
|
485 params = location.search.slice( 1 ).split( "&" ), |
|
486 length = params.length, |
|
487 urlParams = {}, |
|
488 current; |
|
489 |
|
490 if ( params[ 0 ] ) { |
|
491 for ( var i = 0; i < length; i++ ) { |
|
492 current = params[ i ].split( "=" ); |
|
493 current[ 0 ] = decodeURIComponent( current[ 0 ] ); |
|
494 // allow just a key to turn on a flag, e.g., test.html?noglobals |
|
495 current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; |
|
496 urlParams[ current[ 0 ] ] = current[ 1 ]; |
|
497 } |
|
498 } |
|
499 |
|
500 QUnit.urlParams = urlParams; |
|
501 config.filter = urlParams.filter; |
|
502 |
|
503 // Figure out if we're running the tests from a server or not |
|
504 QUnit.isLocal = !!(location.protocol === 'file:'); |
|
505 })(); |
|
506 |
|
507 // Expose the API as global variables, unless an 'exports' |
|
508 // object exists, in that case we assume we're in CommonJS |
|
509 if ( typeof exports === "undefined" || typeof require === "undefined" ) { |
|
510 extend(window, QUnit); |
|
511 window.QUnit = QUnit; |
|
512 } else { |
|
513 extend(exports, QUnit); |
|
514 exports.QUnit = QUnit; |
|
515 } |
|
516 |
|
517 // define these after exposing globals to keep them in these QUnit namespace only |
|
518 extend(QUnit, { |
|
519 config: config, |
|
520 |
|
521 // Initialize the configuration options |
|
522 init: function() { |
|
523 extend(config, { |
|
524 stats: { all: 0, bad: 0 }, |
|
525 moduleStats: { all: 0, bad: 0 }, |
|
526 started: +new Date, |
|
527 updateRate: 1000, |
|
528 blocking: false, |
|
529 autostart: true, |
|
530 autorun: false, |
|
531 filter: "", |
|
532 queue: [], |
|
533 semaphore: 0 |
|
534 }); |
|
535 |
|
536 var tests = id( "qunit-tests" ), |
|
537 banner = id( "qunit-banner" ), |
|
538 result = id( "qunit-testresult" ); |
|
539 |
|
540 if ( tests ) { |
|
541 tests.innerHTML = ""; |
|
542 } |
|
543 |
|
544 if ( banner ) { |
|
545 banner.className = ""; |
|
546 } |
|
547 |
|
548 if ( result ) { |
|
549 result.parentNode.removeChild( result ); |
|
550 } |
|
551 |
|
552 if ( tests ) { |
|
553 result = document.createElement( "p" ); |
|
554 result.id = "qunit-testresult"; |
|
555 result.className = "result"; |
|
556 tests.parentNode.insertBefore( result, tests ); |
|
557 result.innerHTML = 'Running...<br/> '; |
|
558 } |
|
559 }, |
|
560 |
|
561 /** |
|
562 * Resets the test setup. Useful for tests that modify the DOM. |
|
563 * |
|
564 * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. |
|
565 */ |
|
566 reset: function() { |
|
567 if ( window.jQuery ) { |
|
568 jQuery( "#qunit-fixture" ).html( config.fixture ); |
|
569 } else { |
|
570 var main = id( 'qunit-fixture' ); |
|
571 if ( main ) { |
|
572 main.innerHTML = config.fixture; |
|
573 } |
|
574 } |
|
575 }, |
|
576 |
|
577 /** |
|
578 * Trigger an event on an element. |
|
579 * |
|
580 * @example triggerEvent( document.body, "click" ); |
|
581 * |
|
582 * @param DOMElement elem |
|
583 * @param String type |
|
584 */ |
|
585 triggerEvent: function( elem, type, event ) { |
|
586 if ( document.createEvent ) { |
|
587 event = document.createEvent("MouseEvents"); |
|
588 event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, |
|
589 0, 0, 0, 0, 0, false, false, false, false, 0, null); |
|
590 elem.dispatchEvent( event ); |
|
591 |
|
592 } else if ( elem.fireEvent ) { |
|
593 elem.fireEvent("on"+type); |
|
594 } |
|
595 }, |
|
596 |
|
597 // Safe object type checking |
|
598 is: function( type, obj ) { |
|
599 return QUnit.objectType( obj ) == type; |
|
600 }, |
|
601 |
|
602 objectType: function( obj ) { |
|
603 if (typeof obj === "undefined") { |
|
604 return "undefined"; |
|
605 |
|
606 // consider: typeof null === object |
|
607 } |
|
608 if (obj === null) { |
|
609 return "null"; |
|
610 } |
|
611 |
|
612 var type = Object.prototype.toString.call( obj ) |
|
613 .match(/^\[object\s(.*)\]$/)[1] || ''; |
|
614 |
|
615 switch (type) { |
|
616 case 'Number': |
|
617 if (isNaN(obj)) { |
|
618 return "nan"; |
|
619 } else { |
|
620 return "number"; |
|
621 } |
|
622 case 'String': |
|
623 case 'Boolean': |
|
624 case 'Array': |
|
625 case 'Date': |
|
626 case 'RegExp': |
|
627 case 'Function': |
|
628 return type.toLowerCase(); |
|
629 } |
|
630 if (typeof obj === "object") { |
|
631 return "object"; |
|
632 } |
|
633 return undefined; |
|
634 }, |
|
635 |
|
636 push: function(result, actual, expected, message) { |
|
637 var details = { |
|
638 result: result, |
|
639 message: message, |
|
640 actual: actual, |
|
641 expected: expected |
|
642 }; |
|
643 |
|
644 message = escapeInnerText(message) || (result ? "okay" : "failed"); |
|
645 message = '<span class="test-message">' + message + "</span>"; |
|
646 expected = escapeInnerText(QUnit.jsDump.parse(expected)); |
|
647 actual = escapeInnerText(QUnit.jsDump.parse(actual)); |
|
648 var output = message + '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected + '</pre></td></tr>'; |
|
649 if (actual != expected) { |
|
650 output += '<tr class="test-actual"><th>Result: </th><td><pre>' + actual + '</pre></td></tr>'; |
|
651 output += '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit.diff(expected, actual) +'</pre></td></tr>'; |
|
652 } |
|
653 if (!result) { |
|
654 var source = sourceFromStacktrace(); |
|
655 if (source) { |
|
656 details.source = source; |
|
657 output += '<tr class="test-source"><th>Source: </th><td><pre>' + escapeInnerText(source) + '</pre></td></tr>'; |
|
658 } |
|
659 } |
|
660 output += "</table>"; |
|
661 |
|
662 runLoggingCallbacks( 'log', QUnit, details ); |
|
663 |
|
664 config.current.assertions.push({ |
|
665 result: !!result, |
|
666 message: output |
|
667 }); |
|
668 }, |
|
669 |
|
670 url: function( params ) { |
|
671 params = extend( extend( {}, QUnit.urlParams ), params ); |
|
672 var querystring = "?", |
|
673 key; |
|
674 for ( key in params ) { |
|
675 querystring += encodeURIComponent( key ) + "=" + |
|
676 encodeURIComponent( params[ key ] ) + "&"; |
|
677 } |
|
678 return window.location.pathname + querystring.slice( 0, -1 ); |
|
679 }, |
|
680 |
|
681 extend: extend, |
|
682 id: id, |
|
683 addEvent: addEvent |
|
684 }); |
|
685 |
|
686 //QUnit.constructor is set to the empty F() above so that we can add to it's prototype later |
|
687 //Doing this allows us to tell if the following methods have been overwritten on the actual |
|
688 //QUnit object, which is a deprecated way of using the callbacks. |
|
689 extend(QUnit.constructor.prototype, { |
|
690 // Logging callbacks; all receive a single argument with the listed properties |
|
691 // run test/logs.html for any related changes |
|
692 begin: registerLoggingCallback('begin'), |
|
693 // done: { failed, passed, total, runtime } |
|
694 done: registerLoggingCallback('done'), |
|
695 // log: { result, actual, expected, message } |
|
696 log: registerLoggingCallback('log'), |
|
697 // testStart: { name } |
|
698 testStart: registerLoggingCallback('testStart'), |
|
699 // testDone: { name, failed, passed, total } |
|
700 testDone: registerLoggingCallback('testDone'), |
|
701 // moduleStart: { name } |
|
702 moduleStart: registerLoggingCallback('moduleStart'), |
|
703 // moduleDone: { name, failed, passed, total } |
|
704 moduleDone: registerLoggingCallback('moduleDone') |
|
705 }); |
|
706 |
|
707 if ( typeof document === "undefined" || document.readyState === "complete" ) { |
|
708 config.autorun = true; |
|
709 } |
|
710 |
|
711 QUnit.load = function() { |
|
712 runLoggingCallbacks( 'begin', QUnit, {} ); |
|
713 |
|
714 // Initialize the config, saving the execution queue |
|
715 var oldconfig = extend({}, config); |
|
716 QUnit.init(); |
|
717 extend(config, oldconfig); |
|
718 |
|
719 config.blocking = false; |
|
720 |
|
721 var urlConfigHtml = '', len = config.urlConfig.length; |
|
722 for ( var i = 0, val; i < len, val = config.urlConfig[i]; i++ ) { |
|
723 config[val] = QUnit.urlParams[val]; |
|
724 urlConfigHtml += '<label><input name="' + val + '" type="checkbox"' + ( config[val] ? ' checked="checked"' : '' ) + '>' + val + '</label>'; |
|
725 } |
|
726 |
|
727 var userAgent = id("qunit-userAgent"); |
|
728 if ( userAgent ) { |
|
729 userAgent.innerHTML = navigator.userAgent; |
|
730 } |
|
731 var banner = id("qunit-header"); |
|
732 if ( banner ) { |
|
733 banner.innerHTML = '<a href="' + QUnit.url({ filter: undefined }) + '"> ' + banner.innerHTML + '</a> ' + urlConfigHtml; |
|
734 addEvent( banner, "change", function( event ) { |
|
735 var params = {}; |
|
736 params[ event.target.name ] = event.target.checked ? true : undefined; |
|
737 window.location = QUnit.url( params ); |
|
738 }); |
|
739 } |
|
740 |
|
741 var toolbar = id("qunit-testrunner-toolbar"); |
|
742 if ( toolbar ) { |
|
743 var filter = document.createElement("input"); |
|
744 filter.type = "checkbox"; |
|
745 filter.id = "qunit-filter-pass"; |
|
746 addEvent( filter, "click", function() { |
|
747 var ol = document.getElementById("qunit-tests"); |
|
748 if ( filter.checked ) { |
|
749 ol.className = ol.className + " hidepass"; |
|
750 } else { |
|
751 var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; |
|
752 ol.className = tmp.replace(/ hidepass /, " "); |
|
753 } |
|
754 if ( defined.sessionStorage ) { |
|
755 if (filter.checked) { |
|
756 sessionStorage.setItem("qunit-filter-passed-tests", "true"); |
|
757 } else { |
|
758 sessionStorage.removeItem("qunit-filter-passed-tests"); |
|
759 } |
|
760 } |
|
761 }); |
|
762 if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { |
|
763 filter.checked = true; |
|
764 var ol = document.getElementById("qunit-tests"); |
|
765 ol.className = ol.className + " hidepass"; |
|
766 } |
|
767 toolbar.appendChild( filter ); |
|
768 |
|
769 var label = document.createElement("label"); |
|
770 label.setAttribute("for", "qunit-filter-pass"); |
|
771 label.innerHTML = "Hide passed tests"; |
|
772 toolbar.appendChild( label ); |
|
773 } |
|
774 |
|
775 var main = id('qunit-fixture'); |
|
776 if ( main ) { |
|
777 config.fixture = main.innerHTML; |
|
778 } |
|
779 |
|
780 if (config.autostart) { |
|
781 QUnit.start(); |
|
782 } |
|
783 }; |
|
784 |
|
785 addEvent(window, "load", QUnit.load); |
|
786 |
|
787 function done() { |
|
788 config.autorun = true; |
|
789 |
|
790 // Log the last module results |
|
791 if ( config.currentModule ) { |
|
792 runLoggingCallbacks( 'moduleDone', QUnit, { |
|
793 name: config.currentModule, |
|
794 failed: config.moduleStats.bad, |
|
795 passed: config.moduleStats.all - config.moduleStats.bad, |
|
796 total: config.moduleStats.all |
|
797 } ); |
|
798 } |
|
799 |
|
800 var banner = id("qunit-banner"), |
|
801 tests = id("qunit-tests"), |
|
802 runtime = +new Date - config.started, |
|
803 passed = config.stats.all - config.stats.bad, |
|
804 html = [ |
|
805 'Tests completed in ', |
|
806 runtime, |
|
807 ' milliseconds.<br/>', |
|
808 '<span class="passed">', |
|
809 passed, |
|
810 '</span> tests of <span class="total">', |
|
811 config.stats.all, |
|
812 '</span> passed, <span class="failed">', |
|
813 config.stats.bad, |
|
814 '</span> failed.' |
|
815 ].join(''); |
|
816 |
|
817 if ( banner ) { |
|
818 banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); |
|
819 } |
|
820 |
|
821 if ( tests ) { |
|
822 id( "qunit-testresult" ).innerHTML = html; |
|
823 } |
|
824 |
|
825 if ( config.altertitle && typeof document !== "undefined" && document.title ) { |
|
826 // show ✖ for good, ✔ for bad suite result in title |
|
827 // use escape sequences in case file gets loaded with non-utf-8-charset |
|
828 document.title = [ |
|
829 (config.stats.bad ? "\u2716" : "\u2714"), |
|
830 document.title.replace(/^[\u2714\u2716] /i, "") |
|
831 ].join(" "); |
|
832 } |
|
833 |
|
834 runLoggingCallbacks( 'done', QUnit, { |
|
835 failed: config.stats.bad, |
|
836 passed: passed, |
|
837 total: config.stats.all, |
|
838 runtime: runtime |
|
839 } ); |
|
840 } |
|
841 |
|
842 function validTest( name ) { |
|
843 var filter = config.filter, |
|
844 run = false; |
|
845 |
|
846 if ( !filter ) { |
|
847 return true; |
|
848 } |
|
849 |
|
850 var not = filter.charAt( 0 ) === "!"; |
|
851 if ( not ) { |
|
852 filter = filter.slice( 1 ); |
|
853 } |
|
854 |
|
855 if ( name.indexOf( filter ) !== -1 ) { |
|
856 return !not; |
|
857 } |
|
858 |
|
859 if ( not ) { |
|
860 run = true; |
|
861 } |
|
862 |
|
863 return run; |
|
864 } |
|
865 |
|
866 // so far supports only Firefox, Chrome and Opera (buggy) |
|
867 // could be extended in the future to use something like https://github.com/csnover/TraceKit |
|
868 function sourceFromStacktrace() { |
|
869 try { |
|
870 throw new Error(); |
|
871 } catch ( e ) { |
|
872 if (e.stacktrace) { |
|
873 // Opera |
|
874 return e.stacktrace.split("\n")[6]; |
|
875 } else if (e.stack) { |
|
876 // Firefox, Chrome |
|
877 return e.stack.split("\n")[4]; |
|
878 } else if (e.sourceURL) { |
|
879 // Safari, PhantomJS |
|
880 // TODO sourceURL points at the 'throw new Error' line above, useless |
|
881 //return e.sourceURL + ":" + e.line; |
|
882 } |
|
883 } |
|
884 } |
|
885 |
|
886 function escapeInnerText(s) { |
|
887 if (!s) { |
|
888 return ""; |
|
889 } |
|
890 s = s + ""; |
|
891 return s.replace(/[\&<>]/g, function(s) { |
|
892 switch(s) { |
|
893 case "&": return "&"; |
|
894 case "<": return "<"; |
|
895 case ">": return ">"; |
|
896 default: return s; |
|
897 } |
|
898 }); |
|
899 } |
|
900 |
|
901 function synchronize( callback ) { |
|
902 config.queue.push( callback ); |
|
903 |
|
904 if ( config.autorun && !config.blocking ) { |
|
905 process(); |
|
906 } |
|
907 } |
|
908 |
|
909 function process() { |
|
910 var start = (new Date()).getTime(); |
|
911 |
|
912 while ( config.queue.length && !config.blocking ) { |
|
913 if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { |
|
914 config.queue.shift()(); |
|
915 } else { |
|
916 window.setTimeout( process, 13 ); |
|
917 break; |
|
918 } |
|
919 } |
|
920 if (!config.blocking && !config.queue.length) { |
|
921 done(); |
|
922 } |
|
923 } |
|
924 |
|
925 function saveGlobal() { |
|
926 config.pollution = []; |
|
927 |
|
928 if ( config.noglobals ) { |
|
929 for ( var key in window ) { |
|
930 config.pollution.push( key ); |
|
931 } |
|
932 } |
|
933 } |
|
934 |
|
935 function checkPollution( name ) { |
|
936 var old = config.pollution; |
|
937 saveGlobal(); |
|
938 |
|
939 var newGlobals = diff( config.pollution, old ); |
|
940 if ( newGlobals.length > 0 ) { |
|
941 ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); |
|
942 } |
|
943 |
|
944 var deletedGlobals = diff( old, config.pollution ); |
|
945 if ( deletedGlobals.length > 0 ) { |
|
946 ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); |
|
947 } |
|
948 } |
|
949 |
|
950 // returns a new Array with the elements that are in a but not in b |
|
951 function diff( a, b ) { |
|
952 var result = a.slice(); |
|
953 for ( var i = 0; i < result.length; i++ ) { |
|
954 for ( var j = 0; j < b.length; j++ ) { |
|
955 if ( result[i] === b[j] ) { |
|
956 result.splice(i, 1); |
|
957 i--; |
|
958 break; |
|
959 } |
|
960 } |
|
961 } |
|
962 return result; |
|
963 } |
|
964 |
|
965 function fail(message, exception, callback) { |
|
966 if ( typeof console !== "undefined" && console.error && console.warn ) { |
|
967 console.error(message); |
|
968 console.error(exception); |
|
969 console.warn(callback.toString()); |
|
970 |
|
971 } else if ( window.opera && opera.postError ) { |
|
972 opera.postError(message, exception, callback.toString); |
|
973 } |
|
974 } |
|
975 |
|
976 function extend(a, b) { |
|
977 for ( var prop in b ) { |
|
978 if ( b[prop] === undefined ) { |
|
979 delete a[prop]; |
|
980 } else { |
|
981 a[prop] = b[prop]; |
|
982 } |
|
983 } |
|
984 |
|
985 return a; |
|
986 } |
|
987 |
|
988 function addEvent(elem, type, fn) { |
|
989 if ( elem.addEventListener ) { |
|
990 elem.addEventListener( type, fn, false ); |
|
991 } else if ( elem.attachEvent ) { |
|
992 elem.attachEvent( "on" + type, fn ); |
|
993 } else { |
|
994 fn(); |
|
995 } |
|
996 } |
|
997 |
|
998 function id(name) { |
|
999 return !!(typeof document !== "undefined" && document && document.getElementById) && |
|
1000 document.getElementById( name ); |
|
1001 } |
|
1002 |
|
1003 function registerLoggingCallback(key){ |
|
1004 return function(callback){ |
|
1005 config[key].push( callback ); |
|
1006 }; |
|
1007 } |
|
1008 |
|
1009 // Supports deprecated method of completely overwriting logging callbacks |
|
1010 function runLoggingCallbacks(key, scope, args) { |
|
1011 //debugger; |
|
1012 var callbacks; |
|
1013 if ( QUnit.hasOwnProperty(key) ) { |
|
1014 QUnit[key].call(scope, args); |
|
1015 } else { |
|
1016 callbacks = config[key]; |
|
1017 for( var i = 0; i < callbacks.length; i++ ) { |
|
1018 callbacks[i].call( scope, args ); |
|
1019 } |
|
1020 } |
|
1021 } |
|
1022 |
|
1023 // Test for equality any JavaScript type. |
|
1024 // Author: Philippe Rathé <prathe@gmail.com> |
|
1025 QUnit.equiv = function () { |
|
1026 |
|
1027 var innerEquiv; // the real equiv function |
|
1028 var callers = []; // stack to decide between skip/abort functions |
|
1029 var parents = []; // stack to avoiding loops from circular referencing |
|
1030 |
|
1031 // Call the o related callback with the given arguments. |
|
1032 function bindCallbacks(o, callbacks, args) { |
|
1033 var prop = QUnit.objectType(o); |
|
1034 if (prop) { |
|
1035 if (QUnit.objectType(callbacks[prop]) === "function") { |
|
1036 return callbacks[prop].apply(callbacks, args); |
|
1037 } else { |
|
1038 return callbacks[prop]; // or undefined |
|
1039 } |
|
1040 } |
|
1041 } |
|
1042 |
|
1043 var callbacks = function () { |
|
1044 |
|
1045 // for string, boolean, number and null |
|
1046 function useStrictEquality(b, a) { |
|
1047 if (b instanceof a.constructor || a instanceof b.constructor) { |
|
1048 // to catch short annotaion VS 'new' annotation of a |
|
1049 // declaration |
|
1050 // e.g. var i = 1; |
|
1051 // var j = new Number(1); |
|
1052 return a == b; |
|
1053 } else { |
|
1054 return a === b; |
|
1055 } |
|
1056 } |
|
1057 |
|
1058 return { |
|
1059 "string" : useStrictEquality, |
|
1060 "boolean" : useStrictEquality, |
|
1061 "number" : useStrictEquality, |
|
1062 "null" : useStrictEquality, |
|
1063 "undefined" : useStrictEquality, |
|
1064 |
|
1065 "nan" : function(b) { |
|
1066 return isNaN(b); |
|
1067 }, |
|
1068 |
|
1069 "date" : function(b, a) { |
|
1070 return QUnit.objectType(b) === "date" |
|
1071 && a.valueOf() === b.valueOf(); |
|
1072 }, |
|
1073 |
|
1074 "regexp" : function(b, a) { |
|
1075 return QUnit.objectType(b) === "regexp" |
|
1076 && a.source === b.source && // the regex itself |
|
1077 a.global === b.global && // and its modifers |
|
1078 // (gmi) ... |
|
1079 a.ignoreCase === b.ignoreCase |
|
1080 && a.multiline === b.multiline; |
|
1081 }, |
|
1082 |
|
1083 // - skip when the property is a method of an instance (OOP) |
|
1084 // - abort otherwise, |
|
1085 // initial === would have catch identical references anyway |
|
1086 "function" : function() { |
|
1087 var caller = callers[callers.length - 1]; |
|
1088 return caller !== Object && typeof caller !== "undefined"; |
|
1089 }, |
|
1090 |
|
1091 "array" : function(b, a) { |
|
1092 var i, j, loop; |
|
1093 var len; |
|
1094 |
|
1095 // b could be an object literal here |
|
1096 if (!(QUnit.objectType(b) === "array")) { |
|
1097 return false; |
|
1098 } |
|
1099 |
|
1100 len = a.length; |
|
1101 if (len !== b.length) { // safe and faster |
|
1102 return false; |
|
1103 } |
|
1104 |
|
1105 // track reference to avoid circular references |
|
1106 parents.push(a); |
|
1107 for (i = 0; i < len; i++) { |
|
1108 loop = false; |
|
1109 for (j = 0; j < parents.length; j++) { |
|
1110 if (parents[j] === a[i]) { |
|
1111 loop = true;// dont rewalk array |
|
1112 } |
|
1113 } |
|
1114 if (!loop && !innerEquiv(a[i], b[i])) { |
|
1115 parents.pop(); |
|
1116 return false; |
|
1117 } |
|
1118 } |
|
1119 parents.pop(); |
|
1120 return true; |
|
1121 }, |
|
1122 |
|
1123 "object" : function(b, a) { |
|
1124 var i, j, loop; |
|
1125 var eq = true; // unless we can proove it |
|
1126 var aProperties = [], bProperties = []; // collection of |
|
1127 // strings |
|
1128 |
|
1129 // comparing constructors is more strict than using |
|
1130 // instanceof |
|
1131 if (a.constructor !== b.constructor) { |
|
1132 return false; |
|
1133 } |
|
1134 |
|
1135 // stack constructor before traversing properties |
|
1136 callers.push(a.constructor); |
|
1137 // track reference to avoid circular references |
|
1138 parents.push(a); |
|
1139 |
|
1140 for (i in a) { // be strict: don't ensures hasOwnProperty |
|
1141 // and go deep |
|
1142 loop = false; |
|
1143 for (j = 0; j < parents.length; j++) { |
|
1144 if (parents[j] === a[i]) |
|
1145 loop = true; // don't go down the same path |
|
1146 // twice |
|
1147 } |
|
1148 aProperties.push(i); // collect a's properties |
|
1149 |
|
1150 if (!loop && !innerEquiv(a[i], b[i])) { |
|
1151 eq = false; |
|
1152 break; |
|
1153 } |
|
1154 } |
|
1155 |
|
1156 callers.pop(); // unstack, we are done |
|
1157 parents.pop(); |
|
1158 |
|
1159 for (i in b) { |
|
1160 bProperties.push(i); // collect b's properties |
|
1161 } |
|
1162 |
|
1163 // Ensures identical properties name |
|
1164 return eq |
|
1165 && innerEquiv(aProperties.sort(), bProperties |
|
1166 .sort()); |
|
1167 } |
|
1168 }; |
|
1169 }(); |
|
1170 |
|
1171 innerEquiv = function() { // can take multiple arguments |
|
1172 var args = Array.prototype.slice.apply(arguments); |
|
1173 if (args.length < 2) { |
|
1174 return true; // end transition |
|
1175 } |
|
1176 |
|
1177 return (function(a, b) { |
|
1178 if (a === b) { |
|
1179 return true; // catch the most you can |
|
1180 } else if (a === null || b === null || typeof a === "undefined" |
|
1181 || typeof b === "undefined" |
|
1182 || QUnit.objectType(a) !== QUnit.objectType(b)) { |
|
1183 return false; // don't lose time with error prone cases |
|
1184 } else { |
|
1185 return bindCallbacks(a, callbacks, [ b, a ]); |
|
1186 } |
|
1187 |
|
1188 // apply transition with (1..n) arguments |
|
1189 })(args[0], args[1]) |
|
1190 && arguments.callee.apply(this, args.splice(1, |
|
1191 args.length - 1)); |
|
1192 }; |
|
1193 |
|
1194 return innerEquiv; |
|
1195 |
|
1196 }(); |
|
1197 |
|
1198 /** |
|
1199 * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | |
|
1200 * http://flesler.blogspot.com Licensed under BSD |
|
1201 * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 |
|
1202 * |
|
1203 * @projectDescription Advanced and extensible data dumping for Javascript. |
|
1204 * @version 1.0.0 |
|
1205 * @author Ariel Flesler |
|
1206 * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} |
|
1207 */ |
|
1208 QUnit.jsDump = (function() { |
|
1209 function quote( str ) { |
|
1210 return '"' + str.toString().replace(/"/g, '\\"') + '"'; |
|
1211 }; |
|
1212 function literal( o ) { |
|
1213 return o + ''; |
|
1214 }; |
|
1215 function join( pre, arr, post ) { |
|
1216 var s = jsDump.separator(), |
|
1217 base = jsDump.indent(), |
|
1218 inner = jsDump.indent(1); |
|
1219 if ( arr.join ) |
|
1220 arr = arr.join( ',' + s + inner ); |
|
1221 if ( !arr ) |
|
1222 return pre + post; |
|
1223 return [ pre, inner + arr, base + post ].join(s); |
|
1224 }; |
|
1225 function array( arr, stack ) { |
|
1226 var i = arr.length, ret = Array(i); |
|
1227 this.up(); |
|
1228 while ( i-- ) |
|
1229 ret[i] = this.parse( arr[i] , undefined , stack); |
|
1230 this.down(); |
|
1231 return join( '[', ret, ']' ); |
|
1232 }; |
|
1233 |
|
1234 var reName = /^function (\w+)/; |
|
1235 |
|
1236 var jsDump = { |
|
1237 parse:function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance |
|
1238 stack = stack || [ ]; |
|
1239 var parser = this.parsers[ type || this.typeOf(obj) ]; |
|
1240 type = typeof parser; |
|
1241 var inStack = inArray(obj, stack); |
|
1242 if (inStack != -1) { |
|
1243 return 'recursion('+(inStack - stack.length)+')'; |
|
1244 } |
|
1245 //else |
|
1246 if (type == 'function') { |
|
1247 stack.push(obj); |
|
1248 var res = parser.call( this, obj, stack ); |
|
1249 stack.pop(); |
|
1250 return res; |
|
1251 } |
|
1252 // else |
|
1253 return (type == 'string') ? parser : this.parsers.error; |
|
1254 }, |
|
1255 typeOf:function( obj ) { |
|
1256 var type; |
|
1257 if ( obj === null ) { |
|
1258 type = "null"; |
|
1259 } else if (typeof obj === "undefined") { |
|
1260 type = "undefined"; |
|
1261 } else if (QUnit.is("RegExp", obj)) { |
|
1262 type = "regexp"; |
|
1263 } else if (QUnit.is("Date", obj)) { |
|
1264 type = "date"; |
|
1265 } else if (QUnit.is("Function", obj)) { |
|
1266 type = "function"; |
|
1267 } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") { |
|
1268 type = "window"; |
|
1269 } else if (obj.nodeType === 9) { |
|
1270 type = "document"; |
|
1271 } else if (obj.nodeType) { |
|
1272 type = "node"; |
|
1273 } else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) { |
|
1274 type = "array"; |
|
1275 } else { |
|
1276 type = typeof obj; |
|
1277 } |
|
1278 return type; |
|
1279 }, |
|
1280 separator:function() { |
|
1281 return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? ' ' : ' '; |
|
1282 }, |
|
1283 indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing |
|
1284 if ( !this.multiline ) |
|
1285 return ''; |
|
1286 var chr = this.indentChar; |
|
1287 if ( this.HTML ) |
|
1288 chr = chr.replace(/\t/g,' ').replace(/ /g,' '); |
|
1289 return Array( this._depth_ + (extra||0) ).join(chr); |
|
1290 }, |
|
1291 up:function( a ) { |
|
1292 this._depth_ += a || 1; |
|
1293 }, |
|
1294 down:function( a ) { |
|
1295 this._depth_ -= a || 1; |
|
1296 }, |
|
1297 setParser:function( name, parser ) { |
|
1298 this.parsers[name] = parser; |
|
1299 }, |
|
1300 // The next 3 are exposed so you can use them |
|
1301 quote:quote, |
|
1302 literal:literal, |
|
1303 join:join, |
|
1304 // |
|
1305 _depth_: 1, |
|
1306 // This is the list of parsers, to modify them, use jsDump.setParser |
|
1307 parsers:{ |
|
1308 window: '[Window]', |
|
1309 document: '[Document]', |
|
1310 error:'[ERROR]', //when no parser is found, shouldn't happen |
|
1311 unknown: '[Unknown]', |
|
1312 'null':'null', |
|
1313 'undefined':'undefined', |
|
1314 'function':function( fn ) { |
|
1315 var ret = 'function', |
|
1316 name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE |
|
1317 if ( name ) |
|
1318 ret += ' ' + name; |
|
1319 ret += '('; |
|
1320 |
|
1321 ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); |
|
1322 return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); |
|
1323 }, |
|
1324 array: array, |
|
1325 nodelist: array, |
|
1326 arguments: array, |
|
1327 object:function( map, stack ) { |
|
1328 var ret = [ ]; |
|
1329 QUnit.jsDump.up(); |
|
1330 for ( var key in map ) { |
|
1331 var val = map[key]; |
|
1332 ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(val, undefined, stack)); |
|
1333 } |
|
1334 QUnit.jsDump.down(); |
|
1335 return join( '{', ret, '}' ); |
|
1336 }, |
|
1337 node:function( node ) { |
|
1338 var open = QUnit.jsDump.HTML ? '<' : '<', |
|
1339 close = QUnit.jsDump.HTML ? '>' : '>'; |
|
1340 |
|
1341 var tag = node.nodeName.toLowerCase(), |
|
1342 ret = open + tag; |
|
1343 |
|
1344 for ( var a in QUnit.jsDump.DOMAttrs ) { |
|
1345 var val = node[QUnit.jsDump.DOMAttrs[a]]; |
|
1346 if ( val ) |
|
1347 ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); |
|
1348 } |
|
1349 return ret + close + open + '/' + tag + close; |
|
1350 }, |
|
1351 functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function |
|
1352 var l = fn.length; |
|
1353 if ( !l ) return ''; |
|
1354 |
|
1355 var args = Array(l); |
|
1356 while ( l-- ) |
|
1357 args[l] = String.fromCharCode(97+l);//97 is 'a' |
|
1358 return ' ' + args.join(', ') + ' '; |
|
1359 }, |
|
1360 key:quote, //object calls it internally, the key part of an item in a map |
|
1361 functionCode:'[code]', //function calls it internally, it's the content of the function |
|
1362 attribute:quote, //node calls it internally, it's an html attribute value |
|
1363 string:quote, |
|
1364 date:quote, |
|
1365 regexp:literal, //regex |
|
1366 number:literal, |
|
1367 'boolean':literal |
|
1368 }, |
|
1369 DOMAttrs:{//attributes to dump from nodes, name=>realName |
|
1370 id:'id', |
|
1371 name:'name', |
|
1372 'class':'className' |
|
1373 }, |
|
1374 HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) |
|
1375 indentChar:' ',//indentation unit |
|
1376 multiline:true //if true, items in a collection, are separated by a \n, else just a space. |
|
1377 }; |
|
1378 |
|
1379 return jsDump; |
|
1380 })(); |
|
1381 |
|
1382 // from Sizzle.js |
|
1383 function getText( elems ) { |
|
1384 var ret = "", elem; |
|
1385 |
|
1386 for ( var i = 0; elems[i]; i++ ) { |
|
1387 elem = elems[i]; |
|
1388 |
|
1389 // Get the text from text nodes and CDATA nodes |
|
1390 if ( elem.nodeType === 3 || elem.nodeType === 4 ) { |
|
1391 ret += elem.nodeValue; |
|
1392 |
|
1393 // Traverse everything else, except comment nodes |
|
1394 } else if ( elem.nodeType !== 8 ) { |
|
1395 ret += getText( elem.childNodes ); |
|
1396 } |
|
1397 } |
|
1398 |
|
1399 return ret; |
|
1400 }; |
|
1401 |
|
1402 //from jquery.js |
|
1403 function inArray( elem, array ) { |
|
1404 if ( array.indexOf ) { |
|
1405 return array.indexOf( elem ); |
|
1406 } |
|
1407 |
|
1408 for ( var i = 0, length = array.length; i < length; i++ ) { |
|
1409 if ( array[ i ] === elem ) { |
|
1410 return i; |
|
1411 } |
|
1412 } |
|
1413 |
|
1414 return -1; |
|
1415 } |
|
1416 |
|
1417 /* |
|
1418 * Javascript Diff Algorithm |
|
1419 * By John Resig (http://ejohn.org/) |
|
1420 * Modified by Chu Alan "sprite" |
|
1421 * |
|
1422 * Released under the MIT license. |
|
1423 * |
|
1424 * More Info: |
|
1425 * http://ejohn.org/projects/javascript-diff-algorithm/ |
|
1426 * |
|
1427 * Usage: QUnit.diff(expected, actual) |
|
1428 * |
|
1429 * 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" |
|
1430 */ |
|
1431 QUnit.diff = (function() { |
|
1432 function diff(o, n) { |
|
1433 var ns = {}; |
|
1434 var os = {}; |
|
1435 |
|
1436 for (var i = 0; i < n.length; i++) { |
|
1437 if (ns[n[i]] == null) |
|
1438 ns[n[i]] = { |
|
1439 rows: [], |
|
1440 o: null |
|
1441 }; |
|
1442 ns[n[i]].rows.push(i); |
|
1443 } |
|
1444 |
|
1445 for (var i = 0; i < o.length; i++) { |
|
1446 if (os[o[i]] == null) |
|
1447 os[o[i]] = { |
|
1448 rows: [], |
|
1449 n: null |
|
1450 }; |
|
1451 os[o[i]].rows.push(i); |
|
1452 } |
|
1453 |
|
1454 for (var i in ns) { |
|
1455 if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { |
|
1456 n[ns[i].rows[0]] = { |
|
1457 text: n[ns[i].rows[0]], |
|
1458 row: os[i].rows[0] |
|
1459 }; |
|
1460 o[os[i].rows[0]] = { |
|
1461 text: o[os[i].rows[0]], |
|
1462 row: ns[i].rows[0] |
|
1463 }; |
|
1464 } |
|
1465 } |
|
1466 |
|
1467 for (var i = 0; i < n.length - 1; i++) { |
|
1468 if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && |
|
1469 n[i + 1] == o[n[i].row + 1]) { |
|
1470 n[i + 1] = { |
|
1471 text: n[i + 1], |
|
1472 row: n[i].row + 1 |
|
1473 }; |
|
1474 o[n[i].row + 1] = { |
|
1475 text: o[n[i].row + 1], |
|
1476 row: i + 1 |
|
1477 }; |
|
1478 } |
|
1479 } |
|
1480 |
|
1481 for (var i = n.length - 1; i > 0; i--) { |
|
1482 if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && |
|
1483 n[i - 1] == o[n[i].row - 1]) { |
|
1484 n[i - 1] = { |
|
1485 text: n[i - 1], |
|
1486 row: n[i].row - 1 |
|
1487 }; |
|
1488 o[n[i].row - 1] = { |
|
1489 text: o[n[i].row - 1], |
|
1490 row: i - 1 |
|
1491 }; |
|
1492 } |
|
1493 } |
|
1494 |
|
1495 return { |
|
1496 o: o, |
|
1497 n: n |
|
1498 }; |
|
1499 } |
|
1500 |
|
1501 return function(o, n) { |
|
1502 o = o.replace(/\s+$/, ''); |
|
1503 n = n.replace(/\s+$/, ''); |
|
1504 var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); |
|
1505 |
|
1506 var str = ""; |
|
1507 |
|
1508 var oSpace = o.match(/\s+/g); |
|
1509 if (oSpace == null) { |
|
1510 oSpace = [" "]; |
|
1511 } |
|
1512 else { |
|
1513 oSpace.push(" "); |
|
1514 } |
|
1515 var nSpace = n.match(/\s+/g); |
|
1516 if (nSpace == null) { |
|
1517 nSpace = [" "]; |
|
1518 } |
|
1519 else { |
|
1520 nSpace.push(" "); |
|
1521 } |
|
1522 |
|
1523 if (out.n.length == 0) { |
|
1524 for (var i = 0; i < out.o.length; i++) { |
|
1525 str += '<del>' + out.o[i] + oSpace[i] + "</del>"; |
|
1526 } |
|
1527 } |
|
1528 else { |
|
1529 if (out.n[0].text == null) { |
|
1530 for (n = 0; n < out.o.length && out.o[n].text == null; n++) { |
|
1531 str += '<del>' + out.o[n] + oSpace[n] + "</del>"; |
|
1532 } |
|
1533 } |
|
1534 |
|
1535 for (var i = 0; i < out.n.length; i++) { |
|
1536 if (out.n[i].text == null) { |
|
1537 str += '<ins>' + out.n[i] + nSpace[i] + "</ins>"; |
|
1538 } |
|
1539 else { |
|
1540 var pre = ""; |
|
1541 |
|
1542 for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { |
|
1543 pre += '<del>' + out.o[n] + oSpace[n] + "</del>"; |
|
1544 } |
|
1545 str += " " + out.n[i].text + nSpace[i] + pre; |
|
1546 } |
|
1547 } |
|
1548 } |
|
1549 |
|
1550 return str; |
|
1551 }; |
|
1552 })(); |
|
1553 |
|
1554 })(this); |
|