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