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