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