Mercurial > hg > MPIWGThesaurus
comparison jquery-ui/development-bundle/external/qunit.js @ 0:b2e4605f20b2
beta version
author | dwinter |
---|---|
date | Thu, 30 Jun 2011 09:07:49 +0200 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:b2e4605f20b2 |
---|---|
1 /* | |
2 * QUnit - A JavaScript Unit Testing Framework | |
3 * | |
4 * http://docs.jquery.com/QUnit | |
5 * | |
6 * Copyright (c) 2009 John Resig, Jörn Zaefferer | |
7 * Dual licensed under the MIT (MIT-LICENSE.txt) | |
8 * and GPL (GPL-LICENSE.txt) licenses. | |
9 */ | |
10 | |
11 (function(window) { | |
12 | |
13 var QUnit = { | |
14 | |
15 // call on start of module test to prepend name to all tests | |
16 module: function(name, testEnvironment) { | |
17 config.currentModule = name; | |
18 | |
19 synchronize(function() { | |
20 if ( config.currentModule ) { | |
21 QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); | |
22 } | |
23 | |
24 config.currentModule = name; | |
25 config.moduleTestEnvironment = testEnvironment; | |
26 config.moduleStats = { all: 0, bad: 0 }; | |
27 | |
28 QUnit.moduleStart( name, testEnvironment ); | |
29 }); | |
30 }, | |
31 | |
32 asyncTest: function(testName, expected, callback) { | |
33 if ( arguments.length === 2 ) { | |
34 callback = expected; | |
35 expected = 0; | |
36 } | |
37 | |
38 QUnit.test(testName, expected, callback, true); | |
39 }, | |
40 | |
41 test: function(testName, expected, callback, async) { | |
42 var name = '<span class="test-name">' + testName + '</span>', testEnvironment, testEnvironmentArg; | |
43 | |
44 if ( arguments.length === 2 ) { | |
45 callback = expected; | |
46 expected = null; | |
47 } | |
48 // is 2nd argument a testEnvironment? | |
49 if ( expected && typeof expected === 'object') { | |
50 testEnvironmentArg = expected; | |
51 expected = null; | |
52 } | |
53 | |
54 if ( config.currentModule ) { | |
55 name = '<span class="module-name">' + config.currentModule + "</span>: " + name; | |
56 } | |
57 | |
58 if ( !validTest(config.currentModule + ": " + testName) ) { | |
59 return; | |
60 } | |
61 | |
62 synchronize(function() { | |
63 | |
64 testEnvironment = extend({ | |
65 setup: function() {}, | |
66 teardown: function() {} | |
67 }, config.moduleTestEnvironment); | |
68 if (testEnvironmentArg) { | |
69 extend(testEnvironment,testEnvironmentArg); | |
70 } | |
71 | |
72 QUnit.testStart( testName, testEnvironment ); | |
73 | |
74 // allow utility functions to access the current test environment | |
75 QUnit.current_testEnvironment = testEnvironment; | |
76 | |
77 config.assertions = []; | |
78 config.expected = expected; | |
79 | |
80 var tests = id("qunit-tests"); | |
81 if (tests) { | |
82 var b = document.createElement("strong"); | |
83 b.innerHTML = "Running " + name; | |
84 var li = document.createElement("li"); | |
85 li.appendChild( b ); | |
86 li.id = "current-test-output"; | |
87 tests.appendChild( li ) | |
88 } | |
89 | |
90 try { | |
91 if ( !config.pollution ) { | |
92 saveGlobal(); | |
93 } | |
94 | |
95 testEnvironment.setup.call(testEnvironment); | |
96 } catch(e) { | |
97 QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); | |
98 } | |
99 }); | |
100 | |
101 synchronize(function() { | |
102 if ( async ) { | |
103 QUnit.stop(); | |
104 } | |
105 | |
106 try { | |
107 callback.call(testEnvironment); | |
108 } catch(e) { | |
109 fail("Test " + name + " died, exception and test follows", e, callback); | |
110 QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message ); | |
111 // else next test will carry the responsibility | |
112 saveGlobal(); | |
113 | |
114 // Restart the tests if they're blocking | |
115 if ( config.blocking ) { | |
116 start(); | |
117 } | |
118 } | |
119 }); | |
120 | |
121 synchronize(function() { | |
122 try { | |
123 checkPollution(); | |
124 testEnvironment.teardown.call(testEnvironment); | |
125 } catch(e) { | |
126 QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); | |
127 } | |
128 }); | |
129 | |
130 synchronize(function() { | |
131 try { | |
132 QUnit.reset(); | |
133 } catch(e) { | |
134 fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset); | |
135 } | |
136 | |
137 if ( config.expected && config.expected != config.assertions.length ) { | |
138 QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); | |
139 } | |
140 | |
141 var good = 0, bad = 0, | |
142 tests = id("qunit-tests"); | |
143 | |
144 config.stats.all += config.assertions.length; | |
145 config.moduleStats.all += config.assertions.length; | |
146 | |
147 if ( tests ) { | |
148 var ol = document.createElement("ol"); | |
149 | |
150 for ( var i = 0; i < config.assertions.length; i++ ) { | |
151 var assertion = config.assertions[i]; | |
152 | |
153 var li = document.createElement("li"); | |
154 li.className = assertion.result ? "pass" : "fail"; | |
155 li.innerHTML = assertion.message || "(no message)"; | |
156 ol.appendChild( li ); | |
157 | |
158 if ( assertion.result ) { | |
159 good++; | |
160 } else { | |
161 bad++; | |
162 config.stats.bad++; | |
163 config.moduleStats.bad++; | |
164 } | |
165 } | |
166 if (bad == 0) { | |
167 ol.style.display = "none"; | |
168 } | |
169 | |
170 var b = document.createElement("strong"); | |
171 b.innerHTML = name + " <b style='color:black;'>(<b class='fail'>" + bad + "</b>, <b class='pass'>" + good + "</b>, " + config.assertions.length + ")</b>"; | |
172 | |
173 addEvent(b, "click", function() { | |
174 var next = b.nextSibling, display = next.style.display; | |
175 next.style.display = display === "none" ? "block" : "none"; | |
176 }); | |
177 | |
178 addEvent(b, "dblclick", function(e) { | |
179 var target = e && e.target ? e.target : window.event.srcElement; | |
180 if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { | |
181 target = target.parentNode; | |
182 } | |
183 if ( window.location && target.nodeName.toLowerCase() === "strong" ) { | |
184 window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, "")); | |
185 } | |
186 }); | |
187 | |
188 var li = id("current-test-output"); | |
189 li.id = ""; | |
190 li.className = bad ? "fail" : "pass"; | |
191 li.removeChild( li.firstChild ); | |
192 li.appendChild( b ); | |
193 li.appendChild( ol ); | |
194 | |
195 if ( bad ) { | |
196 var toolbar = id("qunit-testrunner-toolbar"); | |
197 if ( toolbar ) { | |
198 toolbar.style.display = "block"; | |
199 id("qunit-filter-pass").disabled = null; | |
200 id("qunit-filter-missing").disabled = null; | |
201 } | |
202 } | |
203 | |
204 } else { | |
205 for ( var i = 0; i < config.assertions.length; i++ ) { | |
206 if ( !config.assertions[i].result ) { | |
207 bad++; | |
208 config.stats.bad++; | |
209 config.moduleStats.bad++; | |
210 } | |
211 } | |
212 } | |
213 | |
214 QUnit.testDone( testName, bad, config.assertions.length ); | |
215 | |
216 if ( !window.setTimeout && !config.queue.length ) { | |
217 done(); | |
218 } | |
219 }); | |
220 | |
221 synchronize( done ); | |
222 }, | |
223 | |
224 /** | |
225 * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. | |
226 */ | |
227 expect: function(asserts) { | |
228 config.expected = asserts; | |
229 }, | |
230 | |
231 /** | |
232 * Asserts true. | |
233 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); | |
234 */ | |
235 ok: function(a, msg) { | |
236 msg = escapeHtml(msg); | |
237 QUnit.log(a, msg); | |
238 | |
239 config.assertions.push({ | |
240 result: !!a, | |
241 message: msg | |
242 }); | |
243 }, | |
244 | |
245 /** | |
246 * Checks that the first two arguments are equal, with an optional message. | |
247 * Prints out both actual and expected values. | |
248 * | |
249 * Prefered to ok( actual == expected, message ) | |
250 * | |
251 * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); | |
252 * | |
253 * @param Object actual | |
254 * @param Object expected | |
255 * @param String message (optional) | |
256 */ | |
257 equal: function(actual, expected, message) { | |
258 push(expected == actual, actual, expected, message); | |
259 }, | |
260 | |
261 notEqual: function(actual, expected, message) { | |
262 push(expected != actual, actual, expected, message); | |
263 }, | |
264 | |
265 deepEqual: function(actual, expected, message) { | |
266 push(QUnit.equiv(actual, expected), actual, expected, message); | |
267 }, | |
268 | |
269 notDeepEqual: function(actual, expected, message) { | |
270 push(!QUnit.equiv(actual, expected), actual, expected, message); | |
271 }, | |
272 | |
273 strictEqual: function(actual, expected, message) { | |
274 push(expected === actual, actual, expected, message); | |
275 }, | |
276 | |
277 notStrictEqual: function(actual, expected, message) { | |
278 push(expected !== actual, actual, expected, message); | |
279 }, | |
280 | |
281 raises: function(fn, message) { | |
282 try { | |
283 fn(); | |
284 ok( false, message ); | |
285 } | |
286 catch (e) { | |
287 ok( true, message ); | |
288 } | |
289 }, | |
290 | |
291 start: function() { | |
292 // A slight delay, to avoid any current callbacks | |
293 if ( window.setTimeout ) { | |
294 window.setTimeout(function() { | |
295 if ( config.timeout ) { | |
296 clearTimeout(config.timeout); | |
297 } | |
298 | |
299 config.blocking = false; | |
300 process(); | |
301 }, 13); | |
302 } else { | |
303 config.blocking = false; | |
304 process(); | |
305 } | |
306 }, | |
307 | |
308 stop: function(timeout) { | |
309 config.blocking = true; | |
310 | |
311 if ( timeout && window.setTimeout ) { | |
312 config.timeout = window.setTimeout(function() { | |
313 QUnit.ok( false, "Test timed out" ); | |
314 QUnit.start(); | |
315 }, timeout); | |
316 } | |
317 } | |
318 | |
319 }; | |
320 | |
321 // Backwards compatibility, deprecated | |
322 QUnit.equals = QUnit.equal; | |
323 QUnit.same = QUnit.deepEqual; | |
324 | |
325 // Maintain internal state | |
326 var config = { | |
327 // The queue of tests to run | |
328 queue: [], | |
329 | |
330 // block until document ready | |
331 blocking: true | |
332 }; | |
333 | |
334 // Load paramaters | |
335 (function() { | |
336 var location = window.location || { search: "", protocol: "file:" }, | |
337 GETParams = location.search.slice(1).split('&'); | |
338 | |
339 for ( var i = 0; i < GETParams.length; i++ ) { | |
340 GETParams[i] = decodeURIComponent( GETParams[i] ); | |
341 if ( GETParams[i] === "noglobals" ) { | |
342 GETParams.splice( i, 1 ); | |
343 i--; | |
344 config.noglobals = true; | |
345 } else if ( GETParams[i].search('=') > -1 ) { | |
346 GETParams.splice( i, 1 ); | |
347 i--; | |
348 } | |
349 } | |
350 | |
351 // restrict modules/tests by get parameters | |
352 config.filters = GETParams; | |
353 | |
354 // Figure out if we're running the tests from a server or not | |
355 QUnit.isLocal = !!(location.protocol === 'file:'); | |
356 })(); | |
357 | |
358 // Expose the API as global variables, unless an 'exports' | |
359 // object exists, in that case we assume we're in CommonJS | |
360 if ( typeof exports === "undefined" || typeof require === "undefined" ) { | |
361 extend(window, QUnit); | |
362 window.QUnit = QUnit; | |
363 } else { | |
364 extend(exports, QUnit); | |
365 exports.QUnit = QUnit; | |
366 } | |
367 | |
368 // define these after exposing globals to keep them in these QUnit namespace only | |
369 extend(QUnit, { | |
370 config: config, | |
371 | |
372 // Initialize the configuration options | |
373 init: function() { | |
374 extend(config, { | |
375 stats: { all: 0, bad: 0 }, | |
376 moduleStats: { all: 0, bad: 0 }, | |
377 started: +new Date, | |
378 updateRate: 1000, | |
379 blocking: false, | |
380 autostart: true, | |
381 autorun: false, | |
382 assertions: [], | |
383 filters: [], | |
384 queue: [] | |
385 }); | |
386 | |
387 var tests = id("qunit-tests"), | |
388 banner = id("qunit-banner"), | |
389 result = id("qunit-testresult"); | |
390 | |
391 if ( tests ) { | |
392 tests.innerHTML = ""; | |
393 } | |
394 | |
395 if ( banner ) { | |
396 banner.className = ""; | |
397 } | |
398 | |
399 if ( result ) { | |
400 result.parentNode.removeChild( result ); | |
401 } | |
402 }, | |
403 | |
404 /** | |
405 * Resets the test setup. Useful for tests that modify the DOM. | |
406 */ | |
407 reset: function() { | |
408 if ( window.jQuery ) { | |
409 jQuery("#main, #qunit-fixture").html( config.fixture ); | |
410 } | |
411 }, | |
412 | |
413 /** | |
414 * Trigger an event on an element. | |
415 * | |
416 * @example triggerEvent( document.body, "click" ); | |
417 * | |
418 * @param DOMElement elem | |
419 * @param String type | |
420 */ | |
421 triggerEvent: function( elem, type, event ) { | |
422 if ( document.createEvent ) { | |
423 event = document.createEvent("MouseEvents"); | |
424 event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, | |
425 0, 0, 0, 0, 0, false, false, false, false, 0, null); | |
426 elem.dispatchEvent( event ); | |
427 | |
428 } else if ( elem.fireEvent ) { | |
429 elem.fireEvent("on"+type); | |
430 } | |
431 }, | |
432 | |
433 // Safe object type checking | |
434 is: function( type, obj ) { | |
435 return QUnit.objectType( obj ) == type; | |
436 }, | |
437 | |
438 objectType: function( obj ) { | |
439 if (typeof obj === "undefined") { | |
440 return "undefined"; | |
441 | |
442 // consider: typeof null === object | |
443 } | |
444 if (obj === null) { | |
445 return "null"; | |
446 } | |
447 | |
448 var type = Object.prototype.toString.call( obj ) | |
449 .match(/^\[object\s(.*)\]$/)[1] || ''; | |
450 | |
451 switch (type) { | |
452 case 'Number': | |
453 if (isNaN(obj)) { | |
454 return "nan"; | |
455 } else { | |
456 return "number"; | |
457 } | |
458 case 'String': | |
459 case 'Boolean': | |
460 case 'Array': | |
461 case 'Date': | |
462 case 'RegExp': | |
463 case 'Function': | |
464 return type.toLowerCase(); | |
465 } | |
466 if (typeof obj === "object") { | |
467 return "object"; | |
468 } | |
469 return undefined; | |
470 }, | |
471 | |
472 // Logging callbacks | |
473 begin: function() {}, | |
474 done: function(failures, total) {}, | |
475 log: function(result, message) {}, | |
476 testStart: function(name, testEnvironment) {}, | |
477 testDone: function(name, failures, total) {}, | |
478 moduleStart: function(name, testEnvironment) {}, | |
479 moduleDone: function(name, failures, total) {} | |
480 }); | |
481 | |
482 if ( typeof document === "undefined" || document.readyState === "complete" ) { | |
483 config.autorun = true; | |
484 } | |
485 | |
486 addEvent(window, "load", function() { | |
487 QUnit.begin(); | |
488 | |
489 // Initialize the config, saving the execution queue | |
490 var oldconfig = extend({}, config); | |
491 QUnit.init(); | |
492 extend(config, oldconfig); | |
493 | |
494 config.blocking = false; | |
495 | |
496 var userAgent = id("qunit-userAgent"); | |
497 if ( userAgent ) { | |
498 userAgent.innerHTML = navigator.userAgent; | |
499 } | |
500 var banner = id("qunit-header"); | |
501 if ( banner ) { | |
502 banner.innerHTML = '<a href="' + location.href + '">' + banner.innerHTML + '</a>'; | |
503 } | |
504 | |
505 var toolbar = id("qunit-testrunner-toolbar"); | |
506 if ( toolbar ) { | |
507 toolbar.style.display = "none"; | |
508 | |
509 var filter = document.createElement("input"); | |
510 filter.type = "checkbox"; | |
511 filter.id = "qunit-filter-pass"; | |
512 filter.disabled = true; | |
513 addEvent( filter, "click", function() { | |
514 var li = document.getElementsByTagName("li"); | |
515 for ( var i = 0; i < li.length; i++ ) { | |
516 if ( li[i].className.indexOf("pass") > -1 ) { | |
517 li[i].style.display = filter.checked ? "none" : ""; | |
518 } | |
519 } | |
520 }); | |
521 toolbar.appendChild( filter ); | |
522 | |
523 var label = document.createElement("label"); | |
524 label.setAttribute("for", "qunit-filter-pass"); | |
525 label.innerHTML = "Hide passed tests"; | |
526 toolbar.appendChild( label ); | |
527 | |
528 var missing = document.createElement("input"); | |
529 missing.type = "checkbox"; | |
530 missing.id = "qunit-filter-missing"; | |
531 missing.disabled = true; | |
532 addEvent( missing, "click", function() { | |
533 var li = document.getElementsByTagName("li"); | |
534 for ( var i = 0; i < li.length; i++ ) { | |
535 if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { | |
536 li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; | |
537 } | |
538 } | |
539 }); | |
540 toolbar.appendChild( missing ); | |
541 | |
542 label = document.createElement("label"); | |
543 label.setAttribute("for", "qunit-filter-missing"); | |
544 label.innerHTML = "Hide missing tests (untested code is broken code)"; | |
545 toolbar.appendChild( label ); | |
546 } | |
547 | |
548 var main = id('main') || id('qunit-fixture'); | |
549 if ( main ) { | |
550 config.fixture = main.innerHTML; | |
551 } | |
552 | |
553 if (config.autostart) { | |
554 QUnit.start(); | |
555 } | |
556 }); | |
557 | |
558 function done() { | |
559 if ( config.doneTimer && window.clearTimeout ) { | |
560 window.clearTimeout( config.doneTimer ); | |
561 config.doneTimer = null; | |
562 } | |
563 | |
564 if ( config.queue.length ) { | |
565 config.doneTimer = window.setTimeout(function(){ | |
566 if ( !config.queue.length ) { | |
567 done(); | |
568 } else { | |
569 synchronize( done ); | |
570 } | |
571 }, 13); | |
572 | |
573 return; | |
574 } | |
575 | |
576 config.autorun = true; | |
577 | |
578 // Log the last module results | |
579 if ( config.currentModule ) { | |
580 QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); | |
581 } | |
582 | |
583 var banner = id("qunit-banner"), | |
584 tests = id("qunit-tests"), | |
585 html = ['Tests completed in ', | |
586 +new Date - config.started, ' milliseconds.<br/>', | |
587 '<span class="passed">', config.stats.all - config.stats.bad, '</span> tests of <span class="total">', config.stats.all, '</span> passed, <span class="failed">', config.stats.bad,'</span> failed.'].join(''); | |
588 | |
589 if ( banner ) { | |
590 banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); | |
591 } | |
592 | |
593 if ( tests ) { | |
594 var result = id("qunit-testresult"); | |
595 | |
596 if ( !result ) { | |
597 result = document.createElement("p"); | |
598 result.id = "qunit-testresult"; | |
599 result.className = "result"; | |
600 tests.parentNode.insertBefore( result, tests.nextSibling ); | |
601 } | |
602 | |
603 result.innerHTML = html; | |
604 } | |
605 | |
606 QUnit.done( config.stats.bad, config.stats.all ); | |
607 } | |
608 | |
609 function validTest( name ) { | |
610 var i = config.filters.length, | |
611 run = false; | |
612 | |
613 if ( !i ) { | |
614 return true; | |
615 } | |
616 | |
617 while ( i-- ) { | |
618 var filter = config.filters[i], | |
619 not = filter.charAt(0) == '!'; | |
620 | |
621 if ( not ) { | |
622 filter = filter.slice(1); | |
623 } | |
624 | |
625 if ( name.indexOf(filter) !== -1 ) { | |
626 return !not; | |
627 } | |
628 | |
629 if ( not ) { | |
630 run = true; | |
631 } | |
632 } | |
633 | |
634 return run; | |
635 } | |
636 | |
637 function escapeHtml(s) { | |
638 s = s === null ? "" : s + ""; | |
639 return s.replace(/[\&"<>\\]/g, function(s) { | |
640 switch(s) { | |
641 case "&": return "&"; | |
642 case "\\": return "\\\\"; | |
643 case '"': return '\"'; | |
644 case "<": return "<"; | |
645 case ">": return ">"; | |
646 default: return s; | |
647 } | |
648 }); | |
649 } | |
650 | |
651 function push(result, actual, expected, message) { | |
652 message = escapeHtml(message) || (result ? "okay" : "failed"); | |
653 message = '<span class="test-message">' + message + "</span>"; | |
654 expected = escapeHtml(QUnit.jsDump.parse(expected)); | |
655 actual = escapeHtml(QUnit.jsDump.parse(actual)); | |
656 var output = message + ', expected: <span class="test-expected">' + expected + '</span>'; | |
657 if (actual != expected) { | |
658 output += ' result: <span class="test-actual">' + actual + '</span>, diff: ' + QUnit.diff(expected, actual); | |
659 } | |
660 | |
661 // can't use ok, as that would double-escape messages | |
662 QUnit.log(result, output); | |
663 config.assertions.push({ | |
664 result: !!result, | |
665 message: output | |
666 }); | |
667 } | |
668 | |
669 function synchronize( callback ) { | |
670 config.queue.push( callback ); | |
671 | |
672 if ( config.autorun && !config.blocking ) { | |
673 process(); | |
674 } | |
675 } | |
676 | |
677 function process() { | |
678 var start = (new Date()).getTime(); | |
679 | |
680 while ( config.queue.length && !config.blocking ) { | |
681 if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { | |
682 config.queue.shift()(); | |
683 | |
684 } else { | |
685 setTimeout( process, 13 ); | |
686 break; | |
687 } | |
688 } | |
689 } | |
690 | |
691 function saveGlobal() { | |
692 config.pollution = []; | |
693 | |
694 if ( config.noglobals ) { | |
695 for ( var key in window ) { | |
696 config.pollution.push( key ); | |
697 } | |
698 } | |
699 } | |
700 | |
701 function checkPollution( name ) { | |
702 var old = config.pollution; | |
703 saveGlobal(); | |
704 | |
705 var newGlobals = diff( old, config.pollution ); | |
706 if ( newGlobals.length > 0 ) { | |
707 ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); | |
708 config.expected++; | |
709 } | |
710 | |
711 var deletedGlobals = diff( config.pollution, old ); | |
712 if ( deletedGlobals.length > 0 ) { | |
713 ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); | |
714 config.expected++; | |
715 } | |
716 } | |
717 | |
718 // returns a new Array with the elements that are in a but not in b | |
719 function diff( a, b ) { | |
720 var result = a.slice(); | |
721 for ( var i = 0; i < result.length; i++ ) { | |
722 for ( var j = 0; j < b.length; j++ ) { | |
723 if ( result[i] === b[j] ) { | |
724 result.splice(i, 1); | |
725 i--; | |
726 break; | |
727 } | |
728 } | |
729 } | |
730 return result; | |
731 } | |
732 | |
733 function fail(message, exception, callback) { | |
734 if ( typeof console !== "undefined" && console.error && console.warn ) { | |
735 console.error(message); | |
736 console.error(exception); | |
737 console.warn(callback.toString()); | |
738 | |
739 } else if ( window.opera && opera.postError ) { | |
740 opera.postError(message, exception, callback.toString); | |
741 } | |
742 } | |
743 | |
744 function extend(a, b) { | |
745 for ( var prop in b ) { | |
746 a[prop] = b[prop]; | |
747 } | |
748 | |
749 return a; | |
750 } | |
751 | |
752 function addEvent(elem, type, fn) { | |
753 if ( elem.addEventListener ) { | |
754 elem.addEventListener( type, fn, false ); | |
755 } else if ( elem.attachEvent ) { | |
756 elem.attachEvent( "on" + type, fn ); | |
757 } else { | |
758 fn(); | |
759 } | |
760 } | |
761 | |
762 function id(name) { | |
763 return !!(typeof document !== "undefined" && document && document.getElementById) && | |
764 document.getElementById( name ); | |
765 } | |
766 | |
767 // Test for equality any JavaScript type. | |
768 // Discussions and reference: http://philrathe.com/articles/equiv | |
769 // Test suites: http://philrathe.com/tests/equiv | |
770 // Author: Philippe Rathé <prathe@gmail.com> | |
771 QUnit.equiv = function () { | |
772 | |
773 var innerEquiv; // the real equiv function | |
774 var callers = []; // stack to decide between skip/abort functions | |
775 var parents = []; // stack to avoiding loops from circular referencing | |
776 | |
777 // Call the o related callback with the given arguments. | |
778 function bindCallbacks(o, callbacks, args) { | |
779 var prop = QUnit.objectType(o); | |
780 if (prop) { | |
781 if (QUnit.objectType(callbacks[prop]) === "function") { | |
782 return callbacks[prop].apply(callbacks, args); | |
783 } else { | |
784 return callbacks[prop]; // or undefined | |
785 } | |
786 } | |
787 } | |
788 | |
789 var callbacks = function () { | |
790 | |
791 // for string, boolean, number and null | |
792 function useStrictEquality(b, a) { | |
793 if (b instanceof a.constructor || a instanceof b.constructor) { | |
794 // to catch short annotaion VS 'new' annotation of a declaration | |
795 // e.g. var i = 1; | |
796 // var j = new Number(1); | |
797 return a == b; | |
798 } else { | |
799 return a === b; | |
800 } | |
801 } | |
802 | |
803 return { | |
804 "string": useStrictEquality, | |
805 "boolean": useStrictEquality, | |
806 "number": useStrictEquality, | |
807 "null": useStrictEquality, | |
808 "undefined": useStrictEquality, | |
809 | |
810 "nan": function (b) { | |
811 return isNaN(b); | |
812 }, | |
813 | |
814 "date": function (b, a) { | |
815 return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); | |
816 }, | |
817 | |
818 "regexp": function (b, a) { | |
819 return QUnit.objectType(b) === "regexp" && | |
820 a.source === b.source && // the regex itself | |
821 a.global === b.global && // and its modifers (gmi) ... | |
822 a.ignoreCase === b.ignoreCase && | |
823 a.multiline === b.multiline; | |
824 }, | |
825 | |
826 // - skip when the property is a method of an instance (OOP) | |
827 // - abort otherwise, | |
828 // initial === would have catch identical references anyway | |
829 "function": function () { | |
830 var caller = callers[callers.length - 1]; | |
831 return caller !== Object && | |
832 typeof caller !== "undefined"; | |
833 }, | |
834 | |
835 "array": function (b, a) { | |
836 var i, j, loop; | |
837 var len; | |
838 | |
839 // b could be an object literal here | |
840 if ( ! (QUnit.objectType(b) === "array")) { | |
841 return false; | |
842 } | |
843 | |
844 len = a.length; | |
845 if (len !== b.length) { // safe and faster | |
846 return false; | |
847 } | |
848 | |
849 //track reference to avoid circular references | |
850 parents.push(a); | |
851 for (i = 0; i < len; i++) { | |
852 loop = false; | |
853 for(j=0;j<parents.length;j++){ | |
854 if(parents[j] === a[i]){ | |
855 loop = true;//dont rewalk array | |
856 } | |
857 } | |
858 if (!loop && ! innerEquiv(a[i], b[i])) { | |
859 parents.pop(); | |
860 return false; | |
861 } | |
862 } | |
863 parents.pop(); | |
864 return true; | |
865 }, | |
866 | |
867 "object": function (b, a) { | |
868 var i, j, loop; | |
869 var eq = true; // unless we can proove it | |
870 var aProperties = [], bProperties = []; // collection of strings | |
871 | |
872 // comparing constructors is more strict than using instanceof | |
873 if ( a.constructor !== b.constructor) { | |
874 return false; | |
875 } | |
876 | |
877 // stack constructor before traversing properties | |
878 callers.push(a.constructor); | |
879 //track reference to avoid circular references | |
880 parents.push(a); | |
881 | |
882 for (i in a) { // be strict: don't ensures hasOwnProperty and go deep | |
883 loop = false; | |
884 for(j=0;j<parents.length;j++){ | |
885 if(parents[j] === a[i]) | |
886 loop = true; //don't go down the same path twice | |
887 } | |
888 aProperties.push(i); // collect a's properties | |
889 | |
890 if (!loop && ! innerEquiv(a[i], b[i])) { | |
891 eq = false; | |
892 break; | |
893 } | |
894 } | |
895 | |
896 callers.pop(); // unstack, we are done | |
897 parents.pop(); | |
898 | |
899 for (i in b) { | |
900 bProperties.push(i); // collect b's properties | |
901 } | |
902 | |
903 // Ensures identical properties name | |
904 return eq && innerEquiv(aProperties.sort(), bProperties.sort()); | |
905 } | |
906 }; | |
907 }(); | |
908 | |
909 innerEquiv = function () { // can take multiple arguments | |
910 var args = Array.prototype.slice.apply(arguments); | |
911 if (args.length < 2) { | |
912 return true; // end transition | |
913 } | |
914 | |
915 return (function (a, b) { | |
916 if (a === b) { | |
917 return true; // catch the most you can | |
918 } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || QUnit.objectType(a) !== QUnit.objectType(b)) { | |
919 return false; // don't lose time with error prone cases | |
920 } else { | |
921 return bindCallbacks(a, callbacks, [b, a]); | |
922 } | |
923 | |
924 // apply transition with (1..n) arguments | |
925 })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1)); | |
926 }; | |
927 | |
928 return innerEquiv; | |
929 | |
930 }(); | |
931 | |
932 /** | |
933 * jsDump | |
934 * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com | |
935 * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php) | |
936 * Date: 5/15/2008 | |
937 * @projectDescription Advanced and extensible data dumping for Javascript. | |
938 * @version 1.0.0 | |
939 * @author Ariel Flesler | |
940 * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} | |
941 */ | |
942 QUnit.jsDump = (function() { | |
943 function quote( str ) { | |
944 return '"' + str.toString().replace(/"/g, '\\"') + '"'; | |
945 }; | |
946 function literal( o ) { | |
947 return o + ''; | |
948 }; | |
949 function join( pre, arr, post ) { | |
950 var s = jsDump.separator(), | |
951 base = jsDump.indent(), | |
952 inner = jsDump.indent(1); | |
953 if ( arr.join ) | |
954 arr = arr.join( ',' + s + inner ); | |
955 if ( !arr ) | |
956 return pre + post; | |
957 return [ pre, inner + arr, base + post ].join(s); | |
958 }; | |
959 function array( arr ) { | |
960 var i = arr.length, ret = Array(i); | |
961 this.up(); | |
962 while ( i-- ) | |
963 ret[i] = this.parse( arr[i] ); | |
964 this.down(); | |
965 return join( '[', ret, ']' ); | |
966 }; | |
967 | |
968 var reName = /^function (\w+)/; | |
969 | |
970 var jsDump = { | |
971 parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance | |
972 var parser = this.parsers[ type || this.typeOf(obj) ]; | |
973 type = typeof parser; | |
974 | |
975 return type == 'function' ? parser.call( this, obj ) : | |
976 type == 'string' ? parser : | |
977 this.parsers.error; | |
978 }, | |
979 typeOf:function( obj ) { | |
980 var type; | |
981 if ( obj === null ) { | |
982 type = "null"; | |
983 } else if (typeof obj === "undefined") { | |
984 type = "undefined"; | |
985 } else if (QUnit.is("RegExp", obj)) { | |
986 type = "regexp"; | |
987 } else if (QUnit.is("Date", obj)) { | |
988 type = "date"; | |
989 } else if (QUnit.is("Function", obj)) { | |
990 type = "function"; | |
991 } else if (obj.setInterval && obj.document && !obj.nodeType) { | |
992 type = "window"; | |
993 } else if (obj.nodeType === 9) { | |
994 type = "document"; | |
995 } else if (obj.nodeType) { | |
996 type = "node"; | |
997 } else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) { | |
998 type = "array"; | |
999 } else { | |
1000 type = typeof obj; | |
1001 } | |
1002 return type; | |
1003 }, | |
1004 separator:function() { | |
1005 return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? ' ' : ' '; | |
1006 }, | |
1007 indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing | |
1008 if ( !this.multiline ) | |
1009 return ''; | |
1010 var chr = this.indentChar; | |
1011 if ( this.HTML ) | |
1012 chr = chr.replace(/\t/g,' ').replace(/ /g,' '); | |
1013 return Array( this._depth_ + (extra||0) ).join(chr); | |
1014 }, | |
1015 up:function( a ) { | |
1016 this._depth_ += a || 1; | |
1017 }, | |
1018 down:function( a ) { | |
1019 this._depth_ -= a || 1; | |
1020 }, | |
1021 setParser:function( name, parser ) { | |
1022 this.parsers[name] = parser; | |
1023 }, | |
1024 // The next 3 are exposed so you can use them | |
1025 quote:quote, | |
1026 literal:literal, | |
1027 join:join, | |
1028 // | |
1029 _depth_: 1, | |
1030 // This is the list of parsers, to modify them, use jsDump.setParser | |
1031 parsers:{ | |
1032 window: '[Window]', | |
1033 document: '[Document]', | |
1034 error:'[ERROR]', //when no parser is found, shouldn't happen | |
1035 unknown: '[Unknown]', | |
1036 'null':'null', | |
1037 undefined:'undefined', | |
1038 'function':function( fn ) { | |
1039 var ret = 'function', | |
1040 name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE | |
1041 if ( name ) | |
1042 ret += ' ' + name; | |
1043 ret += '('; | |
1044 | |
1045 ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); | |
1046 return join( ret, this.parse(fn,'functionCode'), '}' ); | |
1047 }, | |
1048 array: array, | |
1049 nodelist: array, | |
1050 arguments: array, | |
1051 object:function( map ) { | |
1052 var ret = [ ]; | |
1053 this.up(); | |
1054 for ( var key in map ) | |
1055 ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); | |
1056 this.down(); | |
1057 return join( '{', ret, '}' ); | |
1058 }, | |
1059 node:function( node ) { | |
1060 var open = this.HTML ? '<' : '<', | |
1061 close = this.HTML ? '>' : '>'; | |
1062 | |
1063 var tag = node.nodeName.toLowerCase(), | |
1064 ret = open + tag; | |
1065 | |
1066 for ( var a in this.DOMAttrs ) { | |
1067 var val = node[this.DOMAttrs[a]]; | |
1068 if ( val ) | |
1069 ret += ' ' + a + '=' + this.parse( val, 'attribute' ); | |
1070 } | |
1071 return ret + close + open + '/' + tag + close; | |
1072 }, | |
1073 functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function | |
1074 var l = fn.length; | |
1075 if ( !l ) return ''; | |
1076 | |
1077 var args = Array(l); | |
1078 while ( l-- ) | |
1079 args[l] = String.fromCharCode(97+l);//97 is 'a' | |
1080 return ' ' + args.join(', ') + ' '; | |
1081 }, | |
1082 key:quote, //object calls it internally, the key part of an item in a map | |
1083 functionCode:'[code]', //function calls it internally, it's the content of the function | |
1084 attribute:quote, //node calls it internally, it's an html attribute value | |
1085 string:quote, | |
1086 date:quote, | |
1087 regexp:literal, //regex | |
1088 number:literal, | |
1089 'boolean':literal | |
1090 }, | |
1091 DOMAttrs:{//attributes to dump from nodes, name=>realName | |
1092 id:'id', | |
1093 name:'name', | |
1094 'class':'className' | |
1095 }, | |
1096 HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) | |
1097 indentChar:' ',//indentation unit | |
1098 multiline:false //if true, items in a collection, are separated by a \n, else just a space. | |
1099 }; | |
1100 | |
1101 return jsDump; | |
1102 })(); | |
1103 | |
1104 // from Sizzle.js | |
1105 function getText( elems ) { | |
1106 var ret = "", elem; | |
1107 | |
1108 for ( var i = 0; elems[i]; i++ ) { | |
1109 elem = elems[i]; | |
1110 | |
1111 // Get the text from text nodes and CDATA nodes | |
1112 if ( elem.nodeType === 3 || elem.nodeType === 4 ) { | |
1113 ret += elem.nodeValue; | |
1114 | |
1115 // Traverse everything else, except comment nodes | |
1116 } else if ( elem.nodeType !== 8 ) { | |
1117 ret += getText( elem.childNodes ); | |
1118 } | |
1119 } | |
1120 | |
1121 return ret; | |
1122 }; | |
1123 | |
1124 /* | |
1125 * Javascript Diff Algorithm | |
1126 * By John Resig (http://ejohn.org/) | |
1127 * Modified by Chu Alan "sprite" | |
1128 * | |
1129 * Released under the MIT license. | |
1130 * | |
1131 * More Info: | |
1132 * http://ejohn.org/projects/javascript-diff-algorithm/ | |
1133 * | |
1134 * Usage: QUnit.diff(expected, actual) | |
1135 * | |
1136 * 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" | |
1137 */ | |
1138 QUnit.diff = (function() { | |
1139 function diff(o, n){ | |
1140 var ns = new Object(); | |
1141 var os = new Object(); | |
1142 | |
1143 for (var i = 0; i < n.length; i++) { | |
1144 if (ns[n[i]] == null) | |
1145 ns[n[i]] = { | |
1146 rows: new Array(), | |
1147 o: null | |
1148 }; | |
1149 ns[n[i]].rows.push(i); | |
1150 } | |
1151 | |
1152 for (var i = 0; i < o.length; i++) { | |
1153 if (os[o[i]] == null) | |
1154 os[o[i]] = { | |
1155 rows: new Array(), | |
1156 n: null | |
1157 }; | |
1158 os[o[i]].rows.push(i); | |
1159 } | |
1160 | |
1161 for (var i in ns) { | |
1162 if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { | |
1163 n[ns[i].rows[0]] = { | |
1164 text: n[ns[i].rows[0]], | |
1165 row: os[i].rows[0] | |
1166 }; | |
1167 o[os[i].rows[0]] = { | |
1168 text: o[os[i].rows[0]], | |
1169 row: ns[i].rows[0] | |
1170 }; | |
1171 } | |
1172 } | |
1173 | |
1174 for (var i = 0; i < n.length - 1; i++) { | |
1175 if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && | |
1176 n[i + 1] == o[n[i].row + 1]) { | |
1177 n[i + 1] = { | |
1178 text: n[i + 1], | |
1179 row: n[i].row + 1 | |
1180 }; | |
1181 o[n[i].row + 1] = { | |
1182 text: o[n[i].row + 1], | |
1183 row: i + 1 | |
1184 }; | |
1185 } | |
1186 } | |
1187 | |
1188 for (var i = n.length - 1; i > 0; i--) { | |
1189 if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && | |
1190 n[i - 1] == o[n[i].row - 1]) { | |
1191 n[i - 1] = { | |
1192 text: n[i - 1], | |
1193 row: n[i].row - 1 | |
1194 }; | |
1195 o[n[i].row - 1] = { | |
1196 text: o[n[i].row - 1], | |
1197 row: i - 1 | |
1198 }; | |
1199 } | |
1200 } | |
1201 | |
1202 return { | |
1203 o: o, | |
1204 n: n | |
1205 }; | |
1206 } | |
1207 | |
1208 return function(o, n){ | |
1209 o = o.replace(/\s+$/, ''); | |
1210 n = n.replace(/\s+$/, ''); | |
1211 var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); | |
1212 | |
1213 var str = ""; | |
1214 | |
1215 var oSpace = o.match(/\s+/g); | |
1216 if (oSpace == null) { | |
1217 oSpace = [" "]; | |
1218 } | |
1219 else { | |
1220 oSpace.push(" "); | |
1221 } | |
1222 var nSpace = n.match(/\s+/g); | |
1223 if (nSpace == null) { | |
1224 nSpace = [" "]; | |
1225 } | |
1226 else { | |
1227 nSpace.push(" "); | |
1228 } | |
1229 | |
1230 if (out.n.length == 0) { | |
1231 for (var i = 0; i < out.o.length; i++) { | |
1232 str += '<del>' + out.o[i] + oSpace[i] + "</del>"; | |
1233 } | |
1234 } | |
1235 else { | |
1236 if (out.n[0].text == null) { | |
1237 for (n = 0; n < out.o.length && out.o[n].text == null; n++) { | |
1238 str += '<del>' + out.o[n] + oSpace[n] + "</del>"; | |
1239 } | |
1240 } | |
1241 | |
1242 for (var i = 0; i < out.n.length; i++) { | |
1243 if (out.n[i].text == null) { | |
1244 str += '<ins>' + out.n[i] + nSpace[i] + "</ins>"; | |
1245 } | |
1246 else { | |
1247 var pre = ""; | |
1248 | |
1249 for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { | |
1250 pre += '<del>' + out.o[n] + oSpace[n] + "</del>"; | |
1251 } | |
1252 str += " " + out.n[i].text + nSpace[i] + pre; | |
1253 } | |
1254 } | |
1255 } | |
1256 | |
1257 return str; | |
1258 } | |
1259 })(); | |
1260 | |
1261 })(this); |