1/*! 2 * QUnit 1.14.0 3 * http://qunitjs.com/ 4 * 5 * Copyright 2013 jQuery Foundation and other contributors 6 * Released under the MIT license 7 * http://jquery.org/license 8 * 9 * Date: 2014-01-31T16:40Z 10 */ 11 12(function( window ) { 13 14var QUnit, 15 assert, 16 config, 17 onErrorFnPrev, 18 testId = 0, 19 fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), 20 toString = Object.prototype.toString, 21 hasOwn = Object.prototype.hasOwnProperty, 22 // Keep a local reference to Date (GH-283) 23 Date = window.Date, 24 setTimeout = window.setTimeout, 25 clearTimeout = window.clearTimeout, 26 defined = { 27 document: typeof window.document !== "undefined", 28 setTimeout: typeof window.setTimeout !== "undefined", 29 sessionStorage: (function() { 30 var x = "qunit-test-string"; 31 try { 32 sessionStorage.setItem( x, x ); 33 sessionStorage.removeItem( x ); 34 return true; 35 } catch( e ) { 36 return false; 37 } 38 }()) 39 }, 40 /** 41 * Provides a normalized error string, correcting an issue 42 * with IE 7 (and prior) where Error.prototype.toString is 43 * not properly implemented 44 * 45 * Based on http://es5.github.com/#x15.11.4.4 46 * 47 * @param {String|Error} error 48 * @return {String} error message 49 */ 50 errorString = function( error ) { 51 var name, message, 52 errorString = error.toString(); 53 if ( errorString.substring( 0, 7 ) === "[object" ) { 54 name = error.name ? error.name.toString() : "Error"; 55 message = error.message ? error.message.toString() : ""; 56 if ( name && message ) { 57 return name + ": " + message; 58 } else if ( name ) { 59 return name; 60 } else if ( message ) { 61 return message; 62 } else { 63 return "Error"; 64 } 65 } else { 66 return errorString; 67 } 68 }, 69 /** 70 * Makes a clone of an object using only Array or Object as base, 71 * and copies over the own enumerable properties. 72 * 73 * @param {Object} obj 74 * @return {Object} New object with only the own properties (recursively). 75 */ 76 objectValues = function( obj ) { 77 // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392. 78 /*jshint newcap: false */ 79 var key, val, 80 vals = QUnit.is( "array", obj ) ? [] : {}; 81 for ( key in obj ) { 82 if ( hasOwn.call( obj, key ) ) { 83 val = obj[key]; 84 vals[key] = val === Object(val) ? objectValues(val) : val; 85 } 86 } 87 return vals; 88 }; 89 90 91// Root QUnit object. 92// `QUnit` initialized at top of scope 93QUnit = { 94 95 // call on start of module test to prepend name to all tests 96 module: function( name, testEnvironment ) { 97 config.currentModule = name; 98 config.currentModuleTestEnvironment = testEnvironment; 99 config.modules[name] = true; 100 }, 101 102 asyncTest: function( testName, expected, callback ) { 103 if ( arguments.length === 2 ) { 104 callback = expected; 105 expected = null; 106 } 107 108 QUnit.test( testName, expected, callback, true ); 109 }, 110 111 test: function( testName, expected, callback, async ) { 112 var test, 113 nameHtml = "<span class='test-name'>" + escapeText( testName ) + "</span>"; 114 115 if ( arguments.length === 2 ) { 116 callback = expected; 117 expected = null; 118 } 119 120 if ( config.currentModule ) { 121 nameHtml = "<span class='module-name'>" + escapeText( config.currentModule ) + "</span>: " + nameHtml; 122 } 123 124 test = new Test({ 125 nameHtml: nameHtml, 126 testName: testName, 127 expected: expected, 128 async: async, 129 callback: callback, 130 module: config.currentModule, 131 moduleTestEnvironment: config.currentModuleTestEnvironment, 132 stack: sourceFromStacktrace( 2 ) 133 }); 134 135 if ( !validTest( test ) ) { 136 return; 137 } 138 139 test.queue(); 140 }, 141 142 // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through. 143 expect: function( asserts ) { 144 if (arguments.length === 1) { 145 config.current.expected = asserts; 146 } else { 147 return config.current.expected; 148 } 149 }, 150 151 start: function( count ) { 152 // QUnit hasn't been initialized yet. 153 // Note: RequireJS (et al) may delay onLoad 154 if ( config.semaphore === undefined ) { 155 QUnit.begin(function() { 156 // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first 157 setTimeout(function() { 158 QUnit.start( count ); 159 }); 160 }); 161 return; 162 } 163 164 config.semaphore -= count || 1; 165 // don't start until equal number of stop-calls 166 if ( config.semaphore > 0 ) { 167 return; 168 } 169 // ignore if start is called more often then stop 170 if ( config.semaphore < 0 ) { 171 config.semaphore = 0; 172 QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) ); 173 return; 174 } 175 // A slight delay, to avoid any current callbacks 176 if ( defined.setTimeout ) { 177 setTimeout(function() { 178 if ( config.semaphore > 0 ) { 179 return; 180 } 181 if ( config.timeout ) { 182 clearTimeout( config.timeout ); 183 } 184 185 config.blocking = false; 186 process( true ); 187 }, 13); 188 } else { 189 config.blocking = false; 190 process( true ); 191 } 192 }, 193 194 stop: function( count ) { 195 config.semaphore += count || 1; 196 config.blocking = true; 197 198 if ( config.testTimeout && defined.setTimeout ) { 199 clearTimeout( config.timeout ); 200 config.timeout = setTimeout(function() { 201 QUnit.ok( false, "Test timed out" ); 202 config.semaphore = 1; 203 QUnit.start(); 204 }, config.testTimeout ); 205 } 206 } 207}; 208 209// We use the prototype to distinguish between properties that should 210// be exposed as globals (and in exports) and those that shouldn't 211(function() { 212 function F() {} 213 F.prototype = QUnit; 214 QUnit = new F(); 215 // Make F QUnit's constructor so that we can add to the prototype later 216 QUnit.constructor = F; 217}()); 218 219/** 220 * Config object: Maintain internal state 221 * Later exposed as QUnit.config 222 * `config` initialized at top of scope 223 */ 224config = { 225 // The queue of tests to run 226 queue: [], 227 228 // block until document ready 229 blocking: true, 230 231 // when enabled, show only failing tests 232 // gets persisted through sessionStorage and can be changed in UI via checkbox 233 hidepassed: false, 234 235 // by default, run previously failed tests first 236 // very useful in combination with "Hide passed tests" checked 237 reorder: true, 238 239 // by default, modify document.title when suite is done 240 altertitle: true, 241 242 // by default, scroll to top of the page when suite is done 243 scrolltop: true, 244 245 // when enabled, all tests must call expect() 246 requireExpects: false, 247 248 // add checkboxes that are persisted in the query-string 249 // when enabled, the id is set to `true` as a `QUnit.config` property 250 urlConfig: [ 251 { 252 id: "noglobals", 253 label: "Check for Globals", 254 tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." 255 }, 256 { 257 id: "notrycatch", 258 label: "No try-catch", 259 tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." 260 } 261 ], 262 263 // Set of all modules. 264 modules: {}, 265 266 // logging callback queues 267 begin: [], 268 done: [], 269 log: [], 270 testStart: [], 271 testDone: [], 272 moduleStart: [], 273 moduleDone: [] 274}; 275 276// Initialize more QUnit.config and QUnit.urlParams 277(function() { 278 var i, current, 279 location = window.location || { search: "", protocol: "file:" }, 280 params = location.search.slice( 1 ).split( "&" ), 281 length = params.length, 282 urlParams = {}; 283 284 if ( params[ 0 ] ) { 285 for ( i = 0; i < length; i++ ) { 286 current = params[ i ].split( "=" ); 287 current[ 0 ] = decodeURIComponent( current[ 0 ] ); 288 289 // allow just a key to turn on a flag, e.g., test.html?noglobals 290 current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; 291 if ( urlParams[ current[ 0 ] ] ) { 292 urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] ); 293 } else { 294 urlParams[ current[ 0 ] ] = current[ 1 ]; 295 } 296 } 297 } 298 299 QUnit.urlParams = urlParams; 300 301 // String search anywhere in moduleName+testName 302 config.filter = urlParams.filter; 303 304 // Exact match of the module name 305 config.module = urlParams.module; 306 307 config.testNumber = []; 308 if ( urlParams.testNumber ) { 309 310 // Ensure that urlParams.testNumber is an array 311 urlParams.testNumber = [].concat( urlParams.testNumber ); 312 for ( i = 0; i < urlParams.testNumber.length; i++ ) { 313 current = urlParams.testNumber[ i ]; 314 config.testNumber.push( parseInt( current, 10 ) ); 315 } 316 } 317 318 // Figure out if we're running the tests from a server or not 319 QUnit.isLocal = location.protocol === "file:"; 320}()); 321 322extend( QUnit, { 323 324 config: config, 325 326 // Initialize the configuration options 327 init: function() { 328 extend( config, { 329 stats: { all: 0, bad: 0 }, 330 moduleStats: { all: 0, bad: 0 }, 331 started: +new Date(), 332 updateRate: 1000, 333 blocking: false, 334 autostart: true, 335 autorun: false, 336 filter: "", 337 queue: [], 338 semaphore: 1 339 }); 340 341 var tests, banner, result, 342 qunit = id( "qunit" ); 343 344 if ( qunit ) { 345 qunit.innerHTML = 346 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" + 347 "<h2 id='qunit-banner'></h2>" + 348 "<div id='qunit-testrunner-toolbar'></div>" + 349 "<h2 id='qunit-userAgent'></h2>" + 350 "<ol id='qunit-tests'></ol>"; 351 } 352 353 tests = id( "qunit-tests" ); 354 banner = id( "qunit-banner" ); 355 result = id( "qunit-testresult" ); 356 357 if ( tests ) { 358 tests.innerHTML = ""; 359 } 360 361 if ( banner ) { 362 banner.className = ""; 363 } 364 365 if ( result ) { 366 result.parentNode.removeChild( result ); 367 } 368 369 if ( tests ) { 370 result = document.createElement( "p" ); 371 result.id = "qunit-testresult"; 372 result.className = "result"; 373 tests.parentNode.insertBefore( result, tests ); 374 result.innerHTML = "Running...<br/> "; 375 } 376 }, 377 378 // Resets the test setup. Useful for tests that modify the DOM. 379 /* 380 DEPRECATED: Use multiple tests instead of resetting inside a test. 381 Use testStart or testDone for custom cleanup. 382 This method will throw an error in 2.0, and will be removed in 2.1 383 */ 384 reset: function() { 385 var fixture = id( "qunit-fixture" ); 386 if ( fixture ) { 387 fixture.innerHTML = config.fixture; 388 } 389 }, 390 391 // Safe object type checking 392 is: function( type, obj ) { 393 return QUnit.objectType( obj ) === type; 394 }, 395 396 objectType: function( obj ) { 397 if ( typeof obj === "undefined" ) { 398 return "undefined"; 399 } 400 401 // Consider: typeof null === object 402 if ( obj === null ) { 403 return "null"; 404 } 405 406 var match = toString.call( obj ).match(/^\[object\s(.*)\]$/), 407 type = match && match[1] || ""; 408 409 switch ( type ) { 410 case "Number": 411 if ( isNaN(obj) ) { 412 return "nan"; 413 } 414 return "number"; 415 case "String": 416 case "Boolean": 417 case "Array": 418 case "Date": 419 case "RegExp": 420 case "Function": 421 return type.toLowerCase(); 422 } 423 if ( typeof obj === "object" ) { 424 return "object"; 425 } 426 return undefined; 427 }, 428 429 push: function( result, actual, expected, message ) { 430 if ( !config.current ) { 431 throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); 432 } 433 434 var output, source, 435 details = { 436 module: config.current.module, 437 name: config.current.testName, 438 result: result, 439 message: message, 440 actual: actual, 441 expected: expected 442 }; 443 444 message = escapeText( message ) || ( result ? "okay" : "failed" ); 445 message = "<span class='test-message'>" + message + "</span>"; 446 output = message; 447 448 if ( !result ) { 449 expected = escapeText( QUnit.jsDump.parse(expected) ); 450 actual = escapeText( QUnit.jsDump.parse(actual) ); 451 output += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>"; 452 453 if ( actual !== expected ) { 454 output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>"; 455 output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>"; 456 } 457 458 source = sourceFromStacktrace(); 459 460 if ( source ) { 461 details.source = source; 462 output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>"; 463 } 464 465 output += "</table>"; 466 } 467 468 runLoggingCallbacks( "log", QUnit, details ); 469 470 config.current.assertions.push({ 471 result: !!result, 472 message: output 473 }); 474 }, 475 476 pushFailure: function( message, source, actual ) { 477 if ( !config.current ) { 478 throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); 479 } 480 481 var output, 482 details = { 483 module: config.current.module, 484 name: config.current.testName, 485 result: false, 486 message: message 487 }; 488 489 message = escapeText( message ) || "error"; 490 message = "<span class='test-message'>" + message + "</span>"; 491 output = message; 492 493 output += "<table>"; 494 495 if ( actual ) { 496 output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText( actual ) + "</pre></td></tr>"; 497 } 498 499 if ( source ) { 500 details.source = source; 501 output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>"; 502 } 503 504 output += "</table>"; 505 506 runLoggingCallbacks( "log", QUnit, details ); 507 508 config.current.assertions.push({ 509 result: false, 510 message: output 511 }); 512 }, 513 514 url: function( params ) { 515 params = extend( extend( {}, QUnit.urlParams ), params ); 516 var key, 517 querystring = "?"; 518 519 for ( key in params ) { 520 if ( hasOwn.call( params, key ) ) { 521 querystring += encodeURIComponent( key ) + "=" + 522 encodeURIComponent( params[ key ] ) + "&"; 523 } 524 } 525 return window.location.protocol + "//" + window.location.host + 526 window.location.pathname + querystring.slice( 0, -1 ); 527 }, 528 529 extend: extend, 530 id: id, 531 addEvent: addEvent, 532 addClass: addClass, 533 hasClass: hasClass, 534 removeClass: removeClass 535 // load, equiv, jsDump, diff: Attached later 536}); 537 538/** 539 * @deprecated: Created for backwards compatibility with test runner that set the hook function 540 * into QUnit.{hook}, instead of invoking it and passing the hook function. 541 * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here. 542 * Doing this allows us to tell if the following methods have been overwritten on the actual 543 * QUnit object. 544 */ 545extend( QUnit.constructor.prototype, { 546 547 // Logging callbacks; all receive a single argument with the listed properties 548 // run test/logs.html for any related changes 549 begin: registerLoggingCallback( "begin" ), 550 551 // done: { failed, passed, total, runtime } 552 done: registerLoggingCallback( "done" ), 553 554 // log: { result, actual, expected, message } 555 log: registerLoggingCallback( "log" ), 556 557 // testStart: { name } 558 testStart: registerLoggingCallback( "testStart" ), 559 560 // testDone: { name, failed, passed, total, runtime } 561 testDone: registerLoggingCallback( "testDone" ), 562 563 // moduleStart: { name } 564 moduleStart: registerLoggingCallback( "moduleStart" ), 565 566 // moduleDone: { name, failed, passed, total } 567 moduleDone: registerLoggingCallback( "moduleDone" ) 568}); 569 570if ( !defined.document || document.readyState === "complete" ) { 571 config.autorun = true; 572} 573 574QUnit.load = function() { 575 runLoggingCallbacks( "begin", QUnit, {} ); 576 577 // Initialize the config, saving the execution queue 578 var banner, filter, i, j, label, len, main, ol, toolbar, val, selection, 579 urlConfigContainer, moduleFilter, userAgent, 580 numModules = 0, 581 moduleNames = [], 582 moduleFilterHtml = "", 583 urlConfigHtml = "", 584 oldconfig = extend( {}, config ); 585 586 QUnit.init(); 587 extend(config, oldconfig); 588 589 config.blocking = false; 590 591 len = config.urlConfig.length; 592 593 for ( i = 0; i < len; i++ ) { 594 val = config.urlConfig[i]; 595 if ( typeof val === "string" ) { 596 val = { 597 id: val, 598 label: val 599 }; 600 } 601 config[ val.id ] = QUnit.urlParams[ val.id ]; 602 if ( !val.value || typeof val.value === "string" ) { 603 urlConfigHtml += "<input id='qunit-urlconfig-" + escapeText( val.id ) + 604 "' name='" + escapeText( val.id ) + 605 "' type='checkbox'" + 606 ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) + 607 ( config[ val.id ] ? " checked='checked'" : "" ) + 608 " title='" + escapeText( val.tooltip ) + 609 "'><label for='qunit-urlconfig-" + escapeText( val.id ) + 610 "' title='" + escapeText( val.tooltip ) + "'>" + val.label + "</label>"; 611 } else { 612 urlConfigHtml += "<label for='qunit-urlconfig-" + escapeText( val.id ) + 613 "' title='" + escapeText( val.tooltip ) + 614 "'>" + val.label + 615 ": </label><select id='qunit-urlconfig-" + escapeText( val.id ) + 616 "' name='" + escapeText( val.id ) + 617 "' title='" + escapeText( val.tooltip ) + 618 "'><option></option>"; 619 selection = false; 620 if ( QUnit.is( "array", val.value ) ) { 621 for ( j = 0; j < val.value.length; j++ ) { 622 urlConfigHtml += "<option value='" + escapeText( val.value[j] ) + "'" + 623 ( config[ val.id ] === val.value[j] ? 624 (selection = true) && " selected='selected'" : 625 "" ) + 626 ">" + escapeText( val.value[j] ) + "</option>"; 627 } 628 } else { 629 for ( j in val.value ) { 630 if ( hasOwn.call( val.value, j ) ) { 631 urlConfigHtml += "<option value='" + escapeText( j ) + "'" + 632 ( config[ val.id ] === j ? 633 (selection = true) && " selected='selected'" : 634 "" ) + 635 ">" + escapeText( val.value[j] ) + "</option>"; 636 } 637 } 638 } 639 if ( config[ val.id ] && !selection ) { 640 urlConfigHtml += "<option value='" + escapeText( config[ val.id ] ) + 641 "' selected='selected' disabled='disabled'>" + 642 escapeText( config[ val.id ] ) + 643 "</option>"; 644 } 645 urlConfigHtml += "</select>"; 646 } 647 } 648 for ( i in config.modules ) { 649 if ( config.modules.hasOwnProperty( i ) ) { 650 moduleNames.push(i); 651 } 652 } 653 numModules = moduleNames.length; 654 moduleNames.sort( function( a, b ) { 655 return a.localeCompare( b ); 656 }); 657 moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " + 658 ( config.module === undefined ? "selected='selected'" : "" ) + 659 ">< All Modules ></option>"; 660 661 662 for ( i = 0; i < numModules; i++) { 663 moduleFilterHtml += "<option value='" + escapeText( encodeURIComponent(moduleNames[i]) ) + "' " + 664 ( config.module === moduleNames[i] ? "selected='selected'" : "" ) + 665 ">" + escapeText(moduleNames[i]) + "</option>"; 666 } 667 moduleFilterHtml += "</select>"; 668 669 // `userAgent` initialized at top of scope 670 userAgent = id( "qunit-userAgent" ); 671 if ( userAgent ) { 672 userAgent.innerHTML = navigator.userAgent; 673 } 674 675 // `banner` initialized at top of scope 676 banner = id( "qunit-header" ); 677 if ( banner ) { 678 banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> "; 679 } 680 681 // `toolbar` initialized at top of scope 682 toolbar = id( "qunit-testrunner-toolbar" ); 683 if ( toolbar ) { 684 // `filter` initialized at top of scope 685 filter = document.createElement( "input" ); 686 filter.type = "checkbox"; 687 filter.id = "qunit-filter-pass"; 688 689 addEvent( filter, "click", function() { 690 var tmp, 691 ol = id( "qunit-tests" ); 692 693 if ( filter.checked ) { 694 ol.className = ol.className + " hidepass"; 695 } else { 696 tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; 697 ol.className = tmp.replace( / hidepass /, " " ); 698 } 699 if ( defined.sessionStorage ) { 700 if (filter.checked) { 701 sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); 702 } else { 703 sessionStorage.removeItem( "qunit-filter-passed-tests" ); 704 } 705 } 706 }); 707 708 if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { 709 filter.checked = true; 710 // `ol` initialized at top of scope 711 ol = id( "qunit-tests" ); 712 ol.className = ol.className + " hidepass"; 713 } 714 toolbar.appendChild( filter ); 715 716 // `label` initialized at top of scope 717 label = document.createElement( "label" ); 718 label.setAttribute( "for", "qunit-filter-pass" ); 719 label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." ); 720 label.innerHTML = "Hide passed tests"; 721 toolbar.appendChild( label ); 722 723 urlConfigContainer = document.createElement("span"); 724 urlConfigContainer.innerHTML = urlConfigHtml; 725 // For oldIE support: 726 // * Add handlers to the individual elements instead of the container 727 // * Use "click" instead of "change" for checkboxes 728 // * Fallback from event.target to event.srcElement 729 addEvents( urlConfigContainer.getElementsByTagName("input"), "click", function( event ) { 730 var params = {}, 731 target = event.target || event.srcElement; 732 params[ target.name ] = target.checked ? 733 target.defaultValue || true : 734 undefined; 735 window.location = QUnit.url( params ); 736 }); 737 addEvents( urlConfigContainer.getElementsByTagName("select"), "change", function( event ) { 738 var params = {}, 739 target = event.target || event.srcElement; 740 params[ target.name ] = target.options[ target.selectedIndex ].value || undefined; 741 window.location = QUnit.url( params ); 742 }); 743 toolbar.appendChild( urlConfigContainer ); 744 745 if (numModules > 1) { 746 moduleFilter = document.createElement( "span" ); 747 moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); 748 moduleFilter.innerHTML = moduleFilterHtml; 749 addEvent( moduleFilter.lastChild, "change", function() { 750 var selectBox = moduleFilter.getElementsByTagName("select")[0], 751 selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); 752 753 window.location = QUnit.url({ 754 module: ( selectedModule === "" ) ? undefined : selectedModule, 755 // Remove any existing filters 756 filter: undefined, 757 testNumber: undefined 758 }); 759 }); 760 toolbar.appendChild(moduleFilter); 761 } 762 } 763 764 // `main` initialized at top of scope 765 main = id( "qunit-fixture" ); 766 if ( main ) { 767 config.fixture = main.innerHTML; 768 } 769 770 if ( config.autostart ) { 771 QUnit.start(); 772 } 773}; 774 775if ( defined.document ) { 776 addEvent( window, "load", QUnit.load ); 777} 778 779// `onErrorFnPrev` initialized at top of scope 780// Preserve other handlers 781onErrorFnPrev = window.onerror; 782 783// Cover uncaught exceptions 784// Returning true will suppress the default browser handler, 785// returning false will let it run. 786window.onerror = function ( error, filePath, linerNr ) { 787 var ret = false; 788 if ( onErrorFnPrev ) { 789 ret = onErrorFnPrev( error, filePath, linerNr ); 790 } 791 792 // Treat return value as window.onerror itself does, 793 // Only do our handling if not suppressed. 794 if ( ret !== true ) { 795 if ( QUnit.config.current ) { 796 if ( QUnit.config.current.ignoreGlobalErrors ) { 797 return true; 798 } 799 QUnit.pushFailure( error, filePath + ":" + linerNr ); 800 } else { 801 QUnit.test( "global failure", extend( function() { 802 QUnit.pushFailure( error, filePath + ":" + linerNr ); 803 }, { validTest: validTest } ) ); 804 } 805 return false; 806 } 807 808 return ret; 809}; 810 811function done() { 812 config.autorun = true; 813 814 // Log the last module results 815 if ( config.previousModule ) { 816 runLoggingCallbacks( "moduleDone", QUnit, { 817 name: config.previousModule, 818 failed: config.moduleStats.bad, 819 passed: config.moduleStats.all - config.moduleStats.bad, 820 total: config.moduleStats.all 821 }); 822 } 823 delete config.previousModule; 824 825 var i, key, 826 banner = id( "qunit-banner" ), 827 tests = id( "qunit-tests" ), 828 runtime = +new Date() - config.started, 829 passed = config.stats.all - config.stats.bad, 830 html = [ 831 "Tests completed in ", 832 runtime, 833 " milliseconds.<br/>", 834 "<span class='passed'>", 835 passed, 836 "</span> assertions of <span class='total'>", 837 config.stats.all, 838 "</span> passed, <span class='failed'>", 839 config.stats.bad, 840 "</span> failed." 841 ].join( "" ); 842 843 if ( banner ) { 844 banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" ); 845 } 846 847 if ( tests ) { 848 id( "qunit-testresult" ).innerHTML = html; 849 } 850 851 if ( config.altertitle && defined.document && document.title ) { 852 // show ✖ for good, ✔ for bad suite result in title 853 // use escape sequences in case file gets loaded with non-utf-8-charset 854 document.title = [ 855 ( config.stats.bad ? "\u2716" : "\u2714" ), 856 document.title.replace( /^[\u2714\u2716] /i, "" ) 857 ].join( " " ); 858 } 859 860 // clear own sessionStorage items if all tests passed 861 if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { 862 // `key` & `i` initialized at top of scope 863 for ( i = 0; i < sessionStorage.length; i++ ) { 864 key = sessionStorage.key( i++ ); 865 if ( key.indexOf( "qunit-test-" ) === 0 ) { 866 sessionStorage.removeItem( key ); 867 } 868 } 869 } 870 871 // scroll back to top to show results 872 if ( config.scrolltop && window.scrollTo ) { 873 window.scrollTo(0, 0); 874 } 875 876 runLoggingCallbacks( "done", QUnit, { 877 failed: config.stats.bad, 878 passed: passed, 879 total: config.stats.all, 880 runtime: runtime 881 }); 882} 883 884/** @return Boolean: true if this test should be ran */ 885function validTest( test ) { 886 var include, 887 filter = config.filter && config.filter.toLowerCase(), 888 module = config.module && config.module.toLowerCase(), 889 fullName = ( test.module + ": " + test.testName ).toLowerCase(); 890 891 // Internally-generated tests are always valid 892 if ( test.callback && test.callback.validTest === validTest ) { 893 delete test.callback.validTest; 894 return true; 895 } 896 897 if ( config.testNumber.length > 0 ) { 898 if ( inArray( test.testNumber, config.testNumber ) < 0 ) { 899 return false; 900 } 901 } 902 903 if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { 904 return false; 905 } 906 907 if ( !filter ) { 908 return true; 909 } 910 911 include = filter.charAt( 0 ) !== "!"; 912 if ( !include ) { 913 filter = filter.slice( 1 ); 914 } 915 916 // If the filter matches, we need to honour include 917 if ( fullName.indexOf( filter ) !== -1 ) { 918 return include; 919 } 920 921 // Otherwise, do the opposite 922 return !include; 923} 924 925// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions) 926// Later Safari and IE10 are supposed to support error.stack as well 927// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack 928function extractStacktrace( e, offset ) { 929 offset = offset === undefined ? 3 : offset; 930 931 var stack, include, i; 932 933 if ( e.stacktrace ) { 934 // Opera 935 return e.stacktrace.split( "\n" )[ offset + 3 ]; 936 } else if ( e.stack ) { 937 // Firefox, Chrome 938 stack = e.stack.split( "\n" ); 939 if (/^error$/i.test( stack[0] ) ) { 940 stack.shift(); 941 } 942 if ( fileName ) { 943 include = []; 944 for ( i = offset; i < stack.length; i++ ) { 945 if ( stack[ i ].indexOf( fileName ) !== -1 ) { 946 break; 947 } 948 include.push( stack[ i ] ); 949 } 950 if ( include.length ) { 951 return include.join( "\n" ); 952 } 953 } 954 return stack[ offset ]; 955 } else if ( e.sourceURL ) { 956 // Safari, PhantomJS 957 // hopefully one day Safari provides actual stacktraces 958 // exclude useless self-reference for generated Error objects 959 if ( /qunit.js$/.test( e.sourceURL ) ) { 960 return; 961 } 962 // for actual exceptions, this is useful 963 return e.sourceURL + ":" + e.line; 964 } 965} 966function sourceFromStacktrace( offset ) { 967 try { 968 throw new Error(); 969 } catch ( e ) { 970 return extractStacktrace( e, offset ); 971 } 972} 973 974/** 975 * Escape text for attribute or text content. 976 */ 977function escapeText( s ) { 978 if ( !s ) { 979 return ""; 980 } 981 s = s + ""; 982 // Both single quotes and double quotes (for attributes) 983 return s.replace( /['"<>&]/g, function( s ) { 984 switch( s ) { 985 case "'": 986 return "'"; 987 case "\"": 988 return """; 989 case "<": 990 return "<"; 991 case ">": 992 return ">"; 993 case "&": 994 return "&"; 995 } 996 }); 997} 998 999function synchronize( callback, last ) { 1000 config.queue.push( callback ); 1001 1002 if ( config.autorun && !config.blocking ) { 1003 process( last ); 1004 } 1005} 1006 1007function process( last ) { 1008 function next() { 1009 process( last ); 1010 } 1011 var start = new Date().getTime(); 1012 config.depth = config.depth ? config.depth + 1 : 1; 1013 1014 while ( config.queue.length && !config.blocking ) { 1015 if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { 1016 config.queue.shift()(); 1017 } else { 1018 setTimeout( next, 13 ); 1019 break; 1020 } 1021 } 1022 config.depth--; 1023 if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { 1024 done(); 1025 } 1026} 1027 1028function saveGlobal() { 1029 config.pollution = []; 1030 1031 if ( config.noglobals ) { 1032 for ( var key in window ) { 1033 if ( hasOwn.call( window, key ) ) { 1034 // in Opera sometimes DOM element ids show up here, ignore them 1035 if ( /^qunit-test-output/.test( key ) ) { 1036 continue; 1037 } 1038 config.pollution.push( key ); 1039 } 1040 } 1041 } 1042} 1043 1044function checkPollution() { 1045 var newGlobals, 1046 deletedGlobals, 1047 old = config.pollution; 1048 1049 saveGlobal(); 1050 1051 newGlobals = diff( config.pollution, old ); 1052 if ( newGlobals.length > 0 ) { 1053 QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); 1054 } 1055 1056 deletedGlobals = diff( old, config.pollution ); 1057 if ( deletedGlobals.length > 0 ) { 1058 QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); 1059 } 1060} 1061 1062// returns a new Array with the elements that are in a but not in b 1063function diff( a, b ) { 1064 var i, j, 1065 result = a.slice(); 1066 1067 for ( i = 0; i < result.length; i++ ) { 1068 for ( j = 0; j < b.length; j++ ) { 1069 if ( result[i] === b[j] ) { 1070 result.splice( i, 1 ); 1071 i--; 1072 break; 1073 } 1074 } 1075 } 1076 return result; 1077} 1078 1079function extend( a, b ) { 1080 for ( var prop in b ) { 1081 if ( hasOwn.call( b, prop ) ) { 1082 // Avoid "Member not found" error in IE8 caused by messing with window.constructor 1083 if ( !( prop === "constructor" && a === window ) ) { 1084 if ( b[ prop ] === undefined ) { 1085 delete a[ prop ]; 1086 } else { 1087 a[ prop ] = b[ prop ]; 1088 } 1089 } 1090 } 1091 } 1092 1093 return a; 1094} 1095 1096/** 1097 * @param {HTMLElement} elem 1098 * @param {string} type 1099 * @param {Function} fn 1100 */ 1101function addEvent( elem, type, fn ) { 1102 if ( elem.addEventListener ) { 1103 1104 // Standards-based browsers 1105 elem.addEventListener( type, fn, false ); 1106 } else if ( elem.attachEvent ) { 1107 1108 // support: IE <9 1109 elem.attachEvent( "on" + type, fn ); 1110 } else { 1111 1112 // Caller must ensure support for event listeners is present 1113 throw new Error( "addEvent() was called in a context without event listener support" ); 1114 } 1115} 1116 1117/** 1118 * @param {Array|NodeList} elems 1119 * @param {string} type 1120 * @param {Function} fn 1121 */ 1122function addEvents( elems, type, fn ) { 1123 var i = elems.length; 1124 while ( i-- ) { 1125 addEvent( elems[i], type, fn ); 1126 } 1127} 1128 1129function hasClass( elem, name ) { 1130 return (" " + elem.className + " ").indexOf(" " + name + " ") > -1; 1131} 1132 1133function addClass( elem, name ) { 1134 if ( !hasClass( elem, name ) ) { 1135 elem.className += (elem.className ? " " : "") + name; 1136 } 1137} 1138 1139function removeClass( elem, name ) { 1140 var set = " " + elem.className + " "; 1141 // Class name may appear multiple times 1142 while ( set.indexOf(" " + name + " ") > -1 ) { 1143 set = set.replace(" " + name + " " , " "); 1144 } 1145 // If possible, trim it for prettiness, but not necessarily 1146 elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, ""); 1147} 1148 1149function id( name ) { 1150 return defined.document && document.getElementById && document.getElementById( name ); 1151} 1152 1153function registerLoggingCallback( key ) { 1154 return function( callback ) { 1155 config[key].push( callback ); 1156 }; 1157} 1158 1159// Supports deprecated method of completely overwriting logging callbacks 1160function runLoggingCallbacks( key, scope, args ) { 1161 var i, callbacks; 1162 if ( QUnit.hasOwnProperty( key ) ) { 1163 QUnit[ key ].call(scope, args ); 1164 } else { 1165 callbacks = config[ key ]; 1166 for ( i = 0; i < callbacks.length; i++ ) { 1167 callbacks[ i ].call( scope, args ); 1168 } 1169 } 1170} 1171 1172// from jquery.js 1173function inArray( elem, array ) { 1174 if ( array.indexOf ) { 1175 return array.indexOf( elem ); 1176 } 1177 1178 for ( var i = 0, length = array.length; i < length; i++ ) { 1179 if ( array[ i ] === elem ) { 1180 return i; 1181 } 1182 } 1183 1184 return -1; 1185} 1186 1187function Test( settings ) { 1188 extend( this, settings ); 1189 this.assertions = []; 1190 this.testNumber = ++Test.count; 1191} 1192 1193Test.count = 0; 1194 1195Test.prototype = { 1196 init: function() { 1197 var a, b, li, 1198 tests = id( "qunit-tests" ); 1199 1200 if ( tests ) { 1201 b = document.createElement( "strong" ); 1202 b.innerHTML = this.nameHtml; 1203 1204 // `a` initialized at top of scope 1205 a = document.createElement( "a" ); 1206 a.innerHTML = "Rerun"; 1207 a.href = QUnit.url({ testNumber: this.testNumber }); 1208 1209 li = document.createElement( "li" ); 1210 li.appendChild( b ); 1211 li.appendChild( a ); 1212 li.className = "running"; 1213 li.id = this.id = "qunit-test-output" + testId++; 1214 1215 tests.appendChild( li ); 1216 } 1217 }, 1218 setup: function() { 1219 if ( 1220 // Emit moduleStart when we're switching from one module to another 1221 this.module !== config.previousModule || 1222 // They could be equal (both undefined) but if the previousModule property doesn't 1223 // yet exist it means this is the first test in a suite that isn't wrapped in a 1224 // module, in which case we'll just emit a moduleStart event for 'undefined'. 1225 // Without this, reporters can get testStart before moduleStart which is a problem. 1226 !hasOwn.call( config, "previousModule" ) 1227 ) { 1228 if ( hasOwn.call( config, "previousModule" ) ) { 1229 runLoggingCallbacks( "moduleDone", QUnit, { 1230 name: config.previousModule, 1231 failed: config.moduleStats.bad, 1232 passed: config.moduleStats.all - config.moduleStats.bad, 1233 total: config.moduleStats.all 1234 }); 1235 } 1236 config.previousModule = this.module; 1237 config.moduleStats = { all: 0, bad: 0 }; 1238 runLoggingCallbacks( "moduleStart", QUnit, { 1239 name: this.module 1240 }); 1241 } 1242 1243 config.current = this; 1244 1245 this.testEnvironment = extend({ 1246 setup: function() {}, 1247 teardown: function() {} 1248 }, this.moduleTestEnvironment ); 1249 1250 this.started = +new Date(); 1251 runLoggingCallbacks( "testStart", QUnit, { 1252 name: this.testName, 1253 module: this.module 1254 }); 1255 1256 /*jshint camelcase:false */ 1257 1258 1259 /** 1260 * Expose the current test environment. 1261 * 1262 * @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead. 1263 */ 1264 QUnit.current_testEnvironment = this.testEnvironment; 1265 1266 /*jshint camelcase:true */ 1267 1268 if ( !config.pollution ) { 1269 saveGlobal(); 1270 } 1271 if ( config.notrycatch ) { 1272 this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); 1273 return; 1274 } 1275 try { 1276 this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); 1277 } catch( e ) { 1278 QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); 1279 } 1280 }, 1281 run: function() { 1282 config.current = this; 1283 1284 var running = id( "qunit-testresult" ); 1285 1286 if ( running ) { 1287 running.innerHTML = "Running: <br/>" + this.nameHtml; 1288 } 1289 1290 if ( this.async ) { 1291 QUnit.stop(); 1292 } 1293 1294 this.callbackStarted = +new Date(); 1295 1296 if ( config.notrycatch ) { 1297 this.callback.call( this.testEnvironment, QUnit.assert ); 1298 this.callbackRuntime = +new Date() - this.callbackStarted; 1299 return; 1300 } 1301 1302 try { 1303 this.callback.call( this.testEnvironment, QUnit.assert ); 1304 this.callbackRuntime = +new Date() - this.callbackStarted; 1305 } catch( e ) { 1306 this.callbackRuntime = +new Date() - this.callbackStarted; 1307 1308 QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); 1309 // else next test will carry the responsibility 1310 saveGlobal(); 1311 1312 // Restart the tests if they're blocking 1313 if ( config.blocking ) { 1314 QUnit.start(); 1315 } 1316 } 1317 }, 1318 teardown: function() { 1319 config.current = this; 1320 if ( config.notrycatch ) { 1321 if ( typeof this.callbackRuntime === "undefined" ) { 1322 this.callbackRuntime = +new Date() - this.callbackStarted; 1323 } 1324 this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); 1325 return; 1326 } else { 1327 try { 1328 this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); 1329 } catch( e ) { 1330 QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); 1331 } 1332 } 1333 checkPollution(); 1334 }, 1335 finish: function() { 1336 config.current = this; 1337 if ( config.requireExpects && this.expected === null ) { 1338 QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); 1339 } else if ( this.expected !== null && this.expected !== this.assertions.length ) { 1340 QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); 1341 } else if ( this.expected === null && !this.assertions.length ) { 1342 QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); 1343 } 1344 1345 var i, assertion, a, b, time, li, ol, 1346 test = this, 1347 good = 0, 1348 bad = 0, 1349 tests = id( "qunit-tests" ); 1350 1351 this.runtime = +new Date() - this.started; 1352 config.stats.all += this.assertions.length; 1353 config.moduleStats.all += this.assertions.length; 1354 1355 if ( tests ) { 1356 ol = document.createElement( "ol" ); 1357 ol.className = "qunit-assert-list"; 1358 1359 for ( i = 0; i < this.assertions.length; i++ ) { 1360 assertion = this.assertions[i]; 1361 1362 li = document.createElement( "li" ); 1363 li.className = assertion.result ? "pass" : "fail"; 1364 li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); 1365 ol.appendChild( li ); 1366 1367 if ( assertion.result ) { 1368 good++; 1369 } else { 1370 bad++; 1371 config.stats.bad++; 1372 config.moduleStats.bad++; 1373 } 1374 } 1375 1376 // store result when possible 1377 if ( QUnit.config.reorder && defined.sessionStorage ) { 1378 if ( bad ) { 1379 sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); 1380 } else { 1381 sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); 1382 } 1383 } 1384 1385 if ( bad === 0 ) { 1386 addClass( ol, "qunit-collapsed" ); 1387 } 1388 1389 // `b` initialized at top of scope 1390 b = document.createElement( "strong" ); 1391 b.innerHTML = this.nameHtml + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>"; 1392 1393 addEvent(b, "click", function() { 1394 var next = b.parentNode.lastChild, 1395 collapsed = hasClass( next, "qunit-collapsed" ); 1396 ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" ); 1397 }); 1398 1399 addEvent(b, "dblclick", function( e ) { 1400 var target = e && e.target ? e.target : window.event.srcElement; 1401 if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) { 1402 target = target.parentNode; 1403 } 1404 if ( window.location && target.nodeName.toLowerCase() === "strong" ) { 1405 window.location = QUnit.url({ testNumber: test.testNumber }); 1406 } 1407 }); 1408 1409 // `time` initialized at top of scope 1410 time = document.createElement( "span" ); 1411 time.className = "runtime"; 1412 time.innerHTML = this.runtime + " ms"; 1413 1414 // `li` initialized at top of scope 1415 li = id( this.id ); 1416 li.className = bad ? "fail" : "pass"; 1417 li.removeChild( li.firstChild ); 1418 a = li.firstChild; 1419 li.appendChild( b ); 1420 li.appendChild( a ); 1421 li.appendChild( time ); 1422 li.appendChild( ol ); 1423 1424 } else { 1425 for ( i = 0; i < this.assertions.length; i++ ) { 1426 if ( !this.assertions[i].result ) { 1427 bad++; 1428 config.stats.bad++; 1429 config.moduleStats.bad++; 1430 } 1431 } 1432 } 1433 1434 runLoggingCallbacks( "testDone", QUnit, { 1435 name: this.testName, 1436 module: this.module, 1437 failed: bad, 1438 passed: this.assertions.length - bad, 1439 total: this.assertions.length, 1440 runtime: this.runtime, 1441 // DEPRECATED: this property will be removed in 2.0.0, use runtime instead 1442 duration: this.runtime 1443 }); 1444 1445 QUnit.reset(); 1446 1447 config.current = undefined; 1448 }, 1449 1450 queue: function() { 1451 var bad, 1452 test = this; 1453 1454 synchronize(function() { 1455 test.init(); 1456 }); 1457 function run() { 1458 // each of these can by async 1459 synchronize(function() { 1460 test.setup(); 1461 }); 1462 synchronize(function() { 1463 test.run(); 1464 }); 1465 synchronize(function() { 1466 test.teardown(); 1467 }); 1468 synchronize(function() { 1469 test.finish(); 1470 }); 1471 } 1472 1473 // `bad` initialized at top of scope 1474 // defer when previous test run passed, if storage is available 1475 bad = QUnit.config.reorder && defined.sessionStorage && 1476 +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); 1477 1478 if ( bad ) { 1479 run(); 1480 } else { 1481 synchronize( run, true ); 1482 } 1483 } 1484}; 1485 1486// `assert` initialized at top of scope 1487// Assert helpers 1488// All of these must either call QUnit.push() or manually do: 1489// - runLoggingCallbacks( "log", .. ); 1490// - config.current.assertions.push({ .. }); 1491assert = QUnit.assert = { 1492 /** 1493 * Asserts rough true-ish result. 1494 * @name ok 1495 * @function 1496 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 1497 */ 1498 ok: function( result, msg ) { 1499 if ( !config.current ) { 1500 throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); 1501 } 1502 result = !!result; 1503 msg = msg || ( result ? "okay" : "failed" ); 1504 1505 var source, 1506 details = { 1507 module: config.current.module, 1508 name: config.current.testName, 1509 result: result, 1510 message: msg 1511 }; 1512 1513 msg = "<span class='test-message'>" + escapeText( msg ) + "</span>"; 1514 1515 if ( !result ) { 1516 source = sourceFromStacktrace( 2 ); 1517 if ( source ) { 1518 details.source = source; 1519 msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + 1520 escapeText( source ) + 1521 "</pre></td></tr></table>"; 1522 } 1523 } 1524 runLoggingCallbacks( "log", QUnit, details ); 1525 config.current.assertions.push({ 1526 result: result, 1527 message: msg 1528 }); 1529 }, 1530 1531 /** 1532 * Assert that the first two arguments are equal, with an optional message. 1533 * Prints out both actual and expected values. 1534 * @name equal 1535 * @function 1536 * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); 1537 */ 1538 equal: function( actual, expected, message ) { 1539 /*jshint eqeqeq:false */ 1540 QUnit.push( expected == actual, actual, expected, message ); 1541 }, 1542 1543 /** 1544 * @name notEqual 1545 * @function 1546 */ 1547 notEqual: function( actual, expected, message ) { 1548 /*jshint eqeqeq:false */ 1549 QUnit.push( expected != actual, actual, expected, message ); 1550 }, 1551 1552 /** 1553 * @name propEqual 1554 * @function 1555 */ 1556 propEqual: function( actual, expected, message ) { 1557 actual = objectValues(actual); 1558 expected = objectValues(expected); 1559 QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); 1560 }, 1561 1562 /** 1563 * @name notPropEqual 1564 * @function 1565 */ 1566 notPropEqual: function( actual, expected, message ) { 1567 actual = objectValues(actual); 1568 expected = objectValues(expected); 1569 QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); 1570 }, 1571 1572 /** 1573 * @name deepEqual 1574 * @function 1575 */ 1576 deepEqual: function( actual, expected, message ) { 1577 QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); 1578 }, 1579 1580 /** 1581 * @name notDeepEqual 1582 * @function 1583 */ 1584 notDeepEqual: function( actual, expected, message ) { 1585 QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); 1586 }, 1587 1588 /** 1589 * @name strictEqual 1590 * @function 1591 */ 1592 strictEqual: function( actual, expected, message ) { 1593 QUnit.push( expected === actual, actual, expected, message ); 1594 }, 1595 1596 /** 1597 * @name notStrictEqual 1598 * @function 1599 */ 1600 notStrictEqual: function( actual, expected, message ) { 1601 QUnit.push( expected !== actual, actual, expected, message ); 1602 }, 1603 1604 "throws": function( block, expected, message ) { 1605 var actual, 1606 expectedOutput = expected, 1607 ok = false; 1608 1609 // 'expected' is optional 1610 if ( !message && typeof expected === "string" ) { 1611 message = expected; 1612 expected = null; 1613 } 1614 1615 config.current.ignoreGlobalErrors = true; 1616 try { 1617 block.call( config.current.testEnvironment ); 1618 } catch (e) { 1619 actual = e; 1620 } 1621 config.current.ignoreGlobalErrors = false; 1622 1623 if ( actual ) { 1624 1625 // we don't want to validate thrown error 1626 if ( !expected ) { 1627 ok = true; 1628 expectedOutput = null; 1629 1630 // expected is an Error object 1631 } else if ( expected instanceof Error ) { 1632 ok = actual instanceof Error && 1633 actual.name === expected.name && 1634 actual.message === expected.message; 1635 1636 // expected is a regexp 1637 } else if ( QUnit.objectType( expected ) === "regexp" ) { 1638 ok = expected.test( errorString( actual ) ); 1639 1640 // expected is a string 1641 } else if ( QUnit.objectType( expected ) === "string" ) { 1642 ok = expected === errorString( actual ); 1643 1644 // expected is a constructor 1645 } else if ( actual instanceof expected ) { 1646 ok = true; 1647 1648 // expected is a validation function which returns true is validation passed 1649 } else if ( expected.call( {}, actual ) === true ) { 1650 expectedOutput = null; 1651 ok = true; 1652 } 1653 1654 QUnit.push( ok, actual, expectedOutput, message ); 1655 } else { 1656 QUnit.pushFailure( message, null, "No exception was thrown." ); 1657 } 1658 } 1659}; 1660 1661/** 1662 * @deprecated since 1.8.0 1663 * Kept assertion helpers in root for backwards compatibility. 1664 */ 1665extend( QUnit.constructor.prototype, assert ); 1666 1667/** 1668 * @deprecated since 1.9.0 1669 * Kept to avoid TypeErrors for undefined methods. 1670 */ 1671QUnit.constructor.prototype.raises = function() { 1672 QUnit.push( false, false, false, "QUnit.raises has been deprecated since 2012 (fad3c1ea), use QUnit.throws instead" ); 1673}; 1674 1675/** 1676 * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 1677 * Kept to avoid TypeErrors for undefined methods. 1678 */ 1679QUnit.constructor.prototype.equals = function() { 1680 QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); 1681}; 1682QUnit.constructor.prototype.same = function() { 1683 QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); 1684}; 1685 1686// Test for equality any JavaScript type. 1687// Author: Philippe Rathé <prathe@gmail.com> 1688QUnit.equiv = (function() { 1689 1690 // Call the o related callback with the given arguments. 1691 function bindCallbacks( o, callbacks, args ) { 1692 var prop = QUnit.objectType( o ); 1693 if ( prop ) { 1694 if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { 1695 return callbacks[ prop ].apply( callbacks, args ); 1696 } else { 1697 return callbacks[ prop ]; // or undefined 1698 } 1699 } 1700 } 1701 1702 // the real equiv function 1703 var innerEquiv, 1704 // stack to decide between skip/abort functions 1705 callers = [], 1706 // stack to avoiding loops from circular referencing 1707 parents = [], 1708 parentsB = [], 1709 1710 getProto = Object.getPrototypeOf || function ( obj ) { 1711 /*jshint camelcase:false */ 1712 return obj.__proto__; 1713 }, 1714 callbacks = (function () { 1715 1716 // for string, boolean, number and null 1717 function useStrictEquality( b, a ) { 1718 /*jshint eqeqeq:false */ 1719 if ( b instanceof a.constructor || a instanceof b.constructor ) { 1720 // to catch short annotation VS 'new' annotation of a 1721 // declaration 1722 // e.g. var i = 1; 1723 // var j = new Number(1); 1724 return a == b; 1725 } else { 1726 return a === b; 1727 } 1728 } 1729 1730 return { 1731 "string": useStrictEquality, 1732 "boolean": useStrictEquality, 1733 "number": useStrictEquality, 1734 "null": useStrictEquality, 1735 "undefined": useStrictEquality, 1736 1737 "nan": function( b ) { 1738 return isNaN( b ); 1739 }, 1740 1741 "date": function( b, a ) { 1742 return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); 1743 }, 1744 1745 "regexp": function( b, a ) { 1746 return QUnit.objectType( b ) === "regexp" && 1747 // the regex itself 1748 a.source === b.source && 1749 // and its modifiers 1750 a.global === b.global && 1751 // (gmi) ... 1752 a.ignoreCase === b.ignoreCase && 1753 a.multiline === b.multiline && 1754 a.sticky === b.sticky; 1755 }, 1756 1757 // - skip when the property is a method of an instance (OOP) 1758 // - abort otherwise, 1759 // initial === would have catch identical references anyway 1760 "function": function() { 1761 var caller = callers[callers.length - 1]; 1762 return caller !== Object && typeof caller !== "undefined"; 1763 }, 1764 1765 "array": function( b, a ) { 1766 var i, j, len, loop, aCircular, bCircular; 1767 1768 // b could be an object literal here 1769 if ( QUnit.objectType( b ) !== "array" ) { 1770 return false; 1771 } 1772 1773 len = a.length; 1774 if ( len !== b.length ) { 1775 // safe and faster 1776 return false; 1777 } 1778 1779 // track reference to avoid circular references 1780 parents.push( a ); 1781 parentsB.push( b ); 1782 for ( i = 0; i < len; i++ ) { 1783 loop = false; 1784 for ( j = 0; j < parents.length; j++ ) { 1785 aCircular = parents[j] === a[i]; 1786 bCircular = parentsB[j] === b[i]; 1787 if ( aCircular || bCircular ) { 1788 if ( a[i] === b[i] || aCircular && bCircular ) { 1789 loop = true; 1790 } else { 1791 parents.pop(); 1792 parentsB.pop(); 1793 return false; 1794 } 1795 } 1796 } 1797 if ( !loop && !innerEquiv(a[i], b[i]) ) { 1798 parents.pop(); 1799 parentsB.pop(); 1800 return false; 1801 } 1802 } 1803 parents.pop(); 1804 parentsB.pop(); 1805 return true; 1806 }, 1807 1808 "object": function( b, a ) { 1809 /*jshint forin:false */ 1810 var i, j, loop, aCircular, bCircular, 1811 // Default to true 1812 eq = true, 1813 aProperties = [], 1814 bProperties = []; 1815 1816 // comparing constructors is more strict than using 1817 // instanceof 1818 if ( a.constructor !== b.constructor ) { 1819 // Allow objects with no prototype to be equivalent to 1820 // objects with Object as their constructor. 1821 if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || 1822 ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) { 1823 return false; 1824 } 1825 } 1826 1827 // stack constructor before traversing properties 1828 callers.push( a.constructor ); 1829 1830 // track reference to avoid circular references 1831 parents.push( a ); 1832 parentsB.push( b ); 1833 1834 // be strict: don't ensure hasOwnProperty and go deep 1835 for ( i in a ) { 1836 loop = false; 1837 for ( j = 0; j < parents.length; j++ ) { 1838 aCircular = parents[j] === a[i]; 1839 bCircular = parentsB[j] === b[i]; 1840 if ( aCircular || bCircular ) { 1841 if ( a[i] === b[i] || aCircular && bCircular ) { 1842 loop = true; 1843 } else { 1844 eq = false; 1845 break; 1846 } 1847 } 1848 } 1849 aProperties.push(i); 1850 if ( !loop && !innerEquiv(a[i], b[i]) ) { 1851 eq = false; 1852 break; 1853 } 1854 } 1855 1856 parents.pop(); 1857 parentsB.pop(); 1858 callers.pop(); // unstack, we are done 1859 1860 for ( i in b ) { 1861 bProperties.push( i ); // collect b's properties 1862 } 1863 1864 // Ensures identical properties name 1865 return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); 1866 } 1867 }; 1868 }()); 1869 1870 innerEquiv = function() { // can take multiple arguments 1871 var args = [].slice.apply( arguments ); 1872 if ( args.length < 2 ) { 1873 return true; // end transition 1874 } 1875 1876 return (function( a, b ) { 1877 if ( a === b ) { 1878 return true; // catch the most you can 1879 } else if ( a === null || b === null || typeof a === "undefined" || 1880 typeof b === "undefined" || 1881 QUnit.objectType(a) !== QUnit.objectType(b) ) { 1882 return false; // don't lose time with error prone cases 1883 } else { 1884 return bindCallbacks(a, callbacks, [ b, a ]); 1885 } 1886 1887 // apply transition with (1..n) arguments 1888 }( args[0], args[1] ) && innerEquiv.apply( this, args.splice(1, args.length - 1 )) ); 1889 }; 1890 1891 return innerEquiv; 1892}()); 1893 1894/** 1895 * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | 1896 * http://flesler.blogspot.com Licensed under BSD 1897 * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 1898 * 1899 * @projectDescription Advanced and extensible data dumping for Javascript. 1900 * @version 1.0.0 1901 * @author Ariel Flesler 1902 * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} 1903 */ 1904QUnit.jsDump = (function() { 1905 function quote( str ) { 1906 return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\""; 1907 } 1908 function literal( o ) { 1909 return o + ""; 1910 } 1911 function join( pre, arr, post ) { 1912 var s = jsDump.separator(), 1913 base = jsDump.indent(), 1914 inner = jsDump.indent(1); 1915 if ( arr.join ) { 1916 arr = arr.join( "," + s + inner ); 1917 } 1918 if ( !arr ) { 1919 return pre + post; 1920 } 1921 return [ pre, inner + arr, base + post ].join(s); 1922 } 1923 function array( arr, stack ) { 1924 var i = arr.length, ret = new Array(i); 1925 this.up(); 1926 while ( i-- ) { 1927 ret[i] = this.parse( arr[i] , undefined , stack); 1928 } 1929 this.down(); 1930 return join( "[", ret, "]" ); 1931 } 1932 1933 var reName = /^function (\w+)/, 1934 jsDump = { 1935 // type is used mostly internally, you can fix a (custom)type in advance 1936 parse: function( obj, type, stack ) { 1937 stack = stack || [ ]; 1938 var inStack, res, 1939 parser = this.parsers[ type || this.typeOf(obj) ]; 1940 1941 type = typeof parser; 1942 inStack = inArray( obj, stack ); 1943 1944 if ( inStack !== -1 ) { 1945 return "recursion(" + (inStack - stack.length) + ")"; 1946 } 1947 if ( type === "function" ) { 1948 stack.push( obj ); 1949 res = parser.call( this, obj, stack ); 1950 stack.pop(); 1951 return res; 1952 } 1953 return ( type === "string" ) ? parser : this.parsers.error; 1954 }, 1955 typeOf: function( obj ) { 1956 var type; 1957 if ( obj === null ) { 1958 type = "null"; 1959 } else if ( typeof obj === "undefined" ) { 1960 type = "undefined"; 1961 } else if ( QUnit.is( "regexp", obj) ) { 1962 type = "regexp"; 1963 } else if ( QUnit.is( "date", obj) ) { 1964 type = "date"; 1965 } else if ( QUnit.is( "function", obj) ) { 1966 type = "function"; 1967 } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { 1968 type = "window"; 1969 } else if ( obj.nodeType === 9 ) { 1970 type = "document"; 1971 } else if ( obj.nodeType ) { 1972 type = "node"; 1973 } else if ( 1974 // native arrays 1975 toString.call( obj ) === "[object Array]" || 1976 // NodeList objects 1977 ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) 1978 ) { 1979 type = "array"; 1980 } else if ( obj.constructor === Error.prototype.constructor ) { 1981 type = "error"; 1982 } else { 1983 type = typeof obj; 1984 } 1985 return type; 1986 }, 1987 separator: function() { 1988 return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? " " : " "; 1989 }, 1990 // extra can be a number, shortcut for increasing-calling-decreasing 1991 indent: function( extra ) { 1992 if ( !this.multiline ) { 1993 return ""; 1994 } 1995 var chr = this.indentChar; 1996 if ( this.HTML ) { 1997 chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); 1998 } 1999 return new Array( this.depth + ( extra || 0 ) ).join(chr); 2000 }, 2001 up: function( a ) { 2002 this.depth += a || 1; 2003 }, 2004 down: function( a ) { 2005 this.depth -= a || 1; 2006 }, 2007 setParser: function( name, parser ) { 2008 this.parsers[name] = parser; 2009 }, 2010 // The next 3 are exposed so you can use them 2011 quote: quote, 2012 literal: literal, 2013 join: join, 2014 // 2015 depth: 1, 2016 // This is the list of parsers, to modify them, use jsDump.setParser 2017 parsers: { 2018 window: "[Window]", 2019 document: "[Document]", 2020 error: function(error) { 2021 return "Error(\"" + error.message + "\")"; 2022 }, 2023 unknown: "[Unknown]", 2024 "null": "null", 2025 "undefined": "undefined", 2026 "function": function( fn ) { 2027 var ret = "function", 2028 // functions never have name in IE 2029 name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1]; 2030 2031 if ( name ) { 2032 ret += " " + name; 2033 } 2034 ret += "( "; 2035 2036 ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" ); 2037 return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" ); 2038 }, 2039 array: array, 2040 nodelist: array, 2041 "arguments": array, 2042 object: function( map, stack ) { 2043 /*jshint forin:false */ 2044 var ret = [ ], keys, key, val, i; 2045 QUnit.jsDump.up(); 2046 keys = []; 2047 for ( key in map ) { 2048 keys.push( key ); 2049 } 2050 keys.sort(); 2051 for ( i = 0; i < keys.length; i++ ) { 2052 key = keys[ i ]; 2053 val = map[ key ]; 2054 ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); 2055 } 2056 QUnit.jsDump.down(); 2057 return join( "{", ret, "}" ); 2058 }, 2059 node: function( node ) { 2060 var len, i, val, 2061 open = QUnit.jsDump.HTML ? "<" : "<", 2062 close = QUnit.jsDump.HTML ? ">" : ">", 2063 tag = node.nodeName.toLowerCase(), 2064 ret = open + tag, 2065 attrs = node.attributes; 2066 2067 if ( attrs ) { 2068 for ( i = 0, len = attrs.length; i < len; i++ ) { 2069 val = attrs[i].nodeValue; 2070 // IE6 includes all attributes in .attributes, even ones not explicitly set. 2071 // Those have values like undefined, null, 0, false, "" or "inherit". 2072 if ( val && val !== "inherit" ) { 2073 ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" ); 2074 } 2075 } 2076 } 2077 ret += close; 2078 2079 // Show content of TextNode or CDATASection 2080 if ( node.nodeType === 3 || node.nodeType === 4 ) { 2081 ret += node.nodeValue; 2082 } 2083 2084 return ret + open + "/" + tag + close; 2085 }, 2086 // function calls it internally, it's the arguments part of the function 2087 functionArgs: function( fn ) { 2088 var args, 2089 l = fn.length; 2090 2091 if ( !l ) { 2092 return ""; 2093 } 2094 2095 args = new Array(l); 2096 while ( l-- ) { 2097 // 97 is 'a' 2098 args[l] = String.fromCharCode(97+l); 2099 } 2100 return " " + args.join( ", " ) + " "; 2101 }, 2102 // object calls it internally, the key part of an item in a map 2103 key: quote, 2104 // function calls it internally, it's the content of the function 2105 functionCode: "[code]", 2106 // node calls it internally, it's an html attribute value 2107 attribute: quote, 2108 string: quote, 2109 date: quote, 2110 regexp: literal, 2111 number: literal, 2112 "boolean": literal 2113 }, 2114 // if true, entities are escaped ( <, >, \t, space and \n ) 2115 HTML: false, 2116 // indentation unit 2117 indentChar: " ", 2118 // if true, items in a collection, are separated by a \n, else just a space. 2119 multiline: true 2120 }; 2121 2122 return jsDump; 2123}()); 2124 2125/* 2126 * Javascript Diff Algorithm 2127 * By John Resig (http://ejohn.org/) 2128 * Modified by Chu Alan "sprite" 2129 * 2130 * Released under the MIT license. 2131 * 2132 * More Info: 2133 * http://ejohn.org/projects/javascript-diff-algorithm/ 2134 * 2135 * Usage: QUnit.diff(expected, actual) 2136 * 2137 * 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" 2138 */ 2139QUnit.diff = (function() { 2140 /*jshint eqeqeq:false, eqnull:true */ 2141 function diff( o, n ) { 2142 var i, 2143 ns = {}, 2144 os = {}; 2145 2146 for ( i = 0; i < n.length; i++ ) { 2147 if ( !hasOwn.call( ns, n[i] ) ) { 2148 ns[ n[i] ] = { 2149 rows: [], 2150 o: null 2151 }; 2152 } 2153 ns[ n[i] ].rows.push( i ); 2154 } 2155 2156 for ( i = 0; i < o.length; i++ ) { 2157 if ( !hasOwn.call( os, o[i] ) ) { 2158 os[ o[i] ] = { 2159 rows: [], 2160 n: null 2161 }; 2162 } 2163 os[ o[i] ].rows.push( i ); 2164 } 2165 2166 for ( i in ns ) { 2167 if ( hasOwn.call( ns, i ) ) { 2168 if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) { 2169 n[ ns[i].rows[0] ] = { 2170 text: n[ ns[i].rows[0] ], 2171 row: os[i].rows[0] 2172 }; 2173 o[ os[i].rows[0] ] = { 2174 text: o[ os[i].rows[0] ], 2175 row: ns[i].rows[0] 2176 }; 2177 } 2178 } 2179 } 2180 2181 for ( i = 0; i < n.length - 1; i++ ) { 2182 if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && 2183 n[ i + 1 ] == o[ n[i].row + 1 ] ) { 2184 2185 n[ i + 1 ] = { 2186 text: n[ i + 1 ], 2187 row: n[i].row + 1 2188 }; 2189 o[ n[i].row + 1 ] = { 2190 text: o[ n[i].row + 1 ], 2191 row: i + 1 2192 }; 2193 } 2194 } 2195 2196 for ( i = n.length - 1; i > 0; i-- ) { 2197 if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && 2198 n[ i - 1 ] == o[ n[i].row - 1 ]) { 2199 2200 n[ i - 1 ] = { 2201 text: n[ i - 1 ], 2202 row: n[i].row - 1 2203 }; 2204 o[ n[i].row - 1 ] = { 2205 text: o[ n[i].row - 1 ], 2206 row: i - 1 2207 }; 2208 } 2209 } 2210 2211 return { 2212 o: o, 2213 n: n 2214 }; 2215 } 2216 2217 return function( o, n ) { 2218 o = o.replace( /\s+$/, "" ); 2219 n = n.replace( /\s+$/, "" ); 2220 2221 var i, pre, 2222 str = "", 2223 out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ), 2224 oSpace = o.match(/\s+/g), 2225 nSpace = n.match(/\s+/g); 2226 2227 if ( oSpace == null ) { 2228 oSpace = [ " " ]; 2229 } 2230 else { 2231 oSpace.push( " " ); 2232 } 2233 2234 if ( nSpace == null ) { 2235 nSpace = [ " " ]; 2236 } 2237 else { 2238 nSpace.push( " " ); 2239 } 2240 2241 if ( out.n.length === 0 ) { 2242 for ( i = 0; i < out.o.length; i++ ) { 2243 str += "<del>" + out.o[i] + oSpace[i] + "</del>"; 2244 } 2245 } 2246 else { 2247 if ( out.n[0].text == null ) { 2248 for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) { 2249 str += "<del>" + out.o[n] + oSpace[n] + "</del>"; 2250 } 2251 } 2252 2253 for ( i = 0; i < out.n.length; i++ ) { 2254 if (out.n[i].text == null) { 2255 str += "<ins>" + out.n[i] + nSpace[i] + "</ins>"; 2256 } 2257 else { 2258 // `pre` initialized at top of scope 2259 pre = ""; 2260 2261 for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { 2262 pre += "<del>" + out.o[n] + oSpace[n] + "</del>"; 2263 } 2264 str += " " + out.n[i].text + nSpace[i] + pre; 2265 } 2266 } 2267 } 2268 2269 return str; 2270 }; 2271}()); 2272 2273// For browser, export only select globals 2274if ( typeof window !== "undefined" ) { 2275 extend( window, QUnit.constructor.prototype ); 2276 window.QUnit = QUnit; 2277} 2278 2279// For CommonJS environments, export everything 2280if ( typeof module !== "undefined" && module.exports ) { 2281 module.exports = QUnit; 2282} 2283 2284 2285// Get a reference to the global object, like window in browsers 2286}( (function() { 2287 return this; 2288})() )); 2289