source: other-projects/nz-flag-design/trunk/design-2d/Original editor.method.ac/test/qunit/qunit.js@ 29468

Last change on this file since 29468 was 29468, checked in by sjs49, 9 years ago

Initial commit for editor.method.ac for flag design

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