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