OLD | NEW |
(Empty) | |
| 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 |
| 14 var QUnit, |
| 15 assert, |
| 16 config, |
| 17 onErrorFnPrev, |
| 18 testId = 0, |
| 19 fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replac
e(/.+\//, ""), |
| 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 |
| 93 QUnit = { |
| 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.sem
aphore 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 */ |
| 224 config = { |
| 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 ] ], curren
t[ 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 |
| 322 extend( 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 " + sourceFromStackt
race() ); |
| 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>" + escapeT
ext( 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 " + so
urceFromStacktrace(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>" + escapeTex
t( actual ) + "</pre></td></tr>"; |
| 497 } |
| 498 |
| 499 if ( source ) { |
| 500 details.source = source; |
| 501 output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeTex
t( 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 th
e 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 pr
ototype here. |
| 542 * Doing this allows us to tell if the following methods have been overwritten o
n the actual |
| 543 * QUnit object. |
| 544 */ |
| 545 extend( 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 |
| 570 if ( !defined.document || document.readyState === "complete" ) { |
| 571 config.autorun = true; |
| 572 } |
| 573 |
| 574 QUnit.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 i
d='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(mod
uleNames[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: unde
fined, 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. Stor
ed 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", functi
on( 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", func
tion( event ) { |
| 738 var params = {}, |
| 739 target = event.target || event.srcElement; |
| 740 params[ target.name ] = target.options[ target.selectedIndex ].value || un
defined; |
| 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.select
edIndex].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 |
| 775 if ( defined.document ) { |
| 776 addEvent( window, "load", QUnit.load ); |
| 777 } |
| 778 |
| 779 // `onErrorFnPrev` initialized at top of scope |
| 780 // Preserve other handlers |
| 781 onErrorFnPrev = window.onerror; |
| 782 |
| 783 // Cover uncaught exceptions |
| 784 // Returning true will suppress the default browser handler, |
| 785 // returning false will let it run. |
| 786 window.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 |
| 811 function 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 */ |
| 885 function 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 exce
ptions) |
| 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 |
| 928 function 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 } |
| 966 function 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 */ |
| 977 function 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 |
| 999 function synchronize( callback, last ) { |
| 1000 config.queue.push( callback ); |
| 1001 |
| 1002 if ( config.autorun && !config.blocking ) { |
| 1003 process( last ); |
| 1004 } |
| 1005 } |
| 1006 |
| 1007 function 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 |
| 1028 function 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 |
| 1044 function 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 |
| 1063 function 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 |
| 1079 function 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.con
structor |
| 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 */ |
| 1101 function 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 */ |
| 1122 function addEvents( elems, type, fn ) { |
| 1123 var i = elems.length; |
| 1124 while ( i-- ) { |
| 1125 addEvent( elems[i], type, fn ); |
| 1126 } |
| 1127 } |
| 1128 |
| 1129 function hasClass( elem, name ) { |
| 1130 return (" " + elem.className + " ").indexOf(" " + name + " ") > -1; |
| 1131 } |
| 1132 |
| 1133 function addClass( elem, name ) { |
| 1134 if ( !hasClass( elem, name ) ) { |
| 1135 elem.className += (elem.className ? " " : "") + name; |
| 1136 } |
| 1137 } |
| 1138 |
| 1139 function 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 |
| 1149 function id( name ) { |
| 1150 return defined.document && document.getElementById && document.getElementById(
name ); |
| 1151 } |
| 1152 |
| 1153 function registerLoggingCallback( key ) { |
| 1154 return function( callback ) { |
| 1155 config[key].push( callback ); |
| 1156 }; |
| 1157 } |
| 1158 |
| 1159 // Supports deprecated method of completely overwriting logging callbacks |
| 1160 function 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 |
| 1173 function 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 |
| 1187 function Test( settings ) { |
| 1188 extend( this, settings ); |
| 1189 this.assertions = []; |
| 1190 this.testNumber = ++Test.count; |
| 1191 } |
| 1192 |
| 1193 Test.count = 0; |
| 1194 |
| 1195 Test.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 proper
ty doesn't |
| 1223 // yet exist it means this is the first test in a suite that isn't wrapp
ed in a |
| 1224 // module, in which case we'll just emit a moduleStart event for 'undefi
ned'. |
| 1225 // Without this, reporters can get testStart before moduleStart which i
s 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 instea
d. |
| 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.me
ssage || 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 expec
t() was not called.", this.stack ); |
| 1339 } else if ( this.expected !== null && this.expected !== this.assertions.leng
th ) { |
| 1340 QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + thi
s.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 - c
all 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" : "faile
d" ); |
| 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.testN
ame, bad ); |
| 1380 } else { |
| 1381 sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.te
stName ); |
| 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'>" + b
ad + "</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.toLower
Case() === "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.te
stName ); |
| 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({ .. }); |
| 1491 assert = 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 " + sourceFromS
tacktrace(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.", "fo
rmat() 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 pass
ed |
| 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 */ |
| 1665 extend( QUnit.constructor.prototype, assert ); |
| 1666 |
| 1667 /** |
| 1668 * @deprecated since 1.9.0 |
| 1669 * Kept to avoid TypeErrors for undefined methods. |
| 1670 */ |
| 1671 QUnit.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 */ |
| 1679 QUnit.constructor.prototype.equals = function() { |
| 1680 QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009
(e88049a0), use QUnit.equal instead" ); |
| 1681 }; |
| 1682 QUnit.constructor.prototype.same = function() { |
| 1683 QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e
88049a0), use QUnit.deepEqual instead" ); |
| 1684 }; |
| 1685 |
| 1686 // Test for equality any JavaScript type. |
| 1687 // Author: Philippe Rathé <prathe@gmail.com> |
| 1688 QUnit.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-javascri
pt.html} |
| 1903 */ |
| 1904 QUnit.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.par
se( 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 expli
citly set. |
| 2071 // Those have values like undefined, null, 0, false, "" or "inheri
t". |
| 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 spac
e. |
| 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 */ |
| 2139 QUnit.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.lengt
h === 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.leng
th && 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 |
| 2274 if ( typeof window !== "undefined" ) { |
| 2275 extend( window, QUnit.constructor.prototype ); |
| 2276 window.QUnit = QUnit; |
| 2277 } |
| 2278 |
| 2279 // For CommonJS environments, export everything |
| 2280 if ( 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 })() )); |
OLD | NEW |