Changeset 32343
- Timestamp:
- 05/04/2015 07:43:38 PM (11 years ago)
- Location:
- trunk/tests/qunit/vendor
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/tests/qunit/vendor/qunit.css
r25166 r32343 1 /** 2 * QUnit v1.12.0 - A JavaScript Unit Testing Framework 1 /*! 2 * QUnit 1.18.0 3 * http://qunitjs.com/ 3 4 * 4 * http://qunitjs.com 5 * Copyright jQuery Foundation and other contributors 6 * Released under the MIT license 7 * http://jquery.org/license 5 8 * 6 * Copyright 2012 jQuery Foundation and other contributors 7 * Released under the MIT license. 8 * http://jquery.org/license 9 * Date: 2015-04-03T10:23Z 9 10 */ 10 11 … … 32 33 padding: 0.5em 0 0.5em 1em; 33 34 34 color: #8699 a4;35 background-color: #0 d3349;35 color: #8699A4; 36 background-color: #0D3349; 36 37 37 38 font-size: 1.5em; 38 39 line-height: 1em; 39 font-weight: normal;40 font-weight: 400; 40 41 41 42 border-radius: 5px 5px 0 0; 42 -moz-border-radius: 5px 5px 0 0;43 -webkit-border-top-right-radius: 5px;44 -webkit-border-top-left-radius: 5px;45 43 } 46 44 47 45 #qunit-header a { 48 46 text-decoration: none; 49 color: # c2ccd1;47 color: #C2CCD1; 50 48 } 51 49 52 50 #qunit-header a:hover, 53 51 #qunit-header a:focus { 54 color: # fff;52 color: #FFF; 55 53 } 56 54 57 55 #qunit-testrunner-toolbar label { 58 56 display: inline-block; 59 padding: 0 .5em 0.1em;57 padding: 0 0.5em 0 0.1em; 60 58 } 61 59 … … 65 63 66 64 #qunit-testrunner-toolbar { 67 padding: 0.5em 0 0.5em 2em;65 padding: 0.5em 1em 0.5em 1em; 68 66 color: #5E740B; 69 background-color: # eee;67 background-color: #EEE; 70 68 overflow: hidden; 71 69 } 72 70 73 71 #qunit-userAgent { 74 padding: 0.5em 0 0.5em 2.5em;75 background-color: #2 b81af;76 color: # fff;72 padding: 0.5em 1em 0.5em 1em; 73 background-color: #2B81AF; 74 color: #FFF; 77 75 text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 78 76 } … … 80 78 #qunit-modulefilter-container { 81 79 float: right; 80 padding: 0.2em; 81 } 82 83 .qunit-url-config { 84 display: inline-block; 85 padding: 0.1em; 86 } 87 88 .qunit-filter { 89 display: block; 90 float: right; 91 margin-left: 1em; 82 92 } 83 93 … … 89 99 90 100 #qunit-tests li { 91 padding: 0.4em 0.5em 0.4em 2.5em;92 border-bottom: 1px solid # fff;101 padding: 0.4em 1em 0.4em 1em; 102 border-bottom: 1px solid #FFF; 93 103 list-style-position: inside; 94 104 } 95 105 96 #qunit-tests .hidepass li.pass, #qunit-tests.hidepass li.running{106 #qunit-tests > li { 97 107 display: none; 108 } 109 110 #qunit-tests li.running, 111 #qunit-tests li.pass, 112 #qunit-tests li.fail, 113 #qunit-tests li.skipped { 114 display: list-item; 115 } 116 117 #qunit-tests.hidepass li.running, 118 #qunit-tests.hidepass li.pass { 119 visibility: hidden; 120 position: absolute; 121 width: 0px; 122 height: 0px; 123 padding: 0; 124 border: 0; 125 margin: 0; 98 126 } 99 127 … … 102 130 } 103 131 132 #qunit-tests li.skipped strong { 133 cursor: default; 134 } 135 104 136 #qunit-tests li a { 105 137 padding: 0.5em; 106 color: #c2ccd1; 107 text-decoration: none; 138 color: #C2CCD1; 139 text-decoration: none; 140 } 141 142 #qunit-tests li p a { 143 padding: 0.25em; 144 color: #6B6464; 108 145 } 109 146 #qunit-tests li a:hover, … … 121 158 padding: 0.5em; 122 159 123 background-color: # fff;160 background-color: #FFF; 124 161 125 162 border-radius: 5px; 126 -moz-border-radius: 5px;127 -webkit-border-radius: 5px;128 163 } 129 164 … … 134 169 #qunit-tests table { 135 170 border-collapse: collapse; 136 margin-top: .2em;171 margin-top: 0.2em; 137 172 } 138 173 … … 140 175 text-align: right; 141 176 vertical-align: top; 142 padding: 0 .5em 0 0;177 padding: 0 0.5em 0 0; 143 178 } 144 179 … … 154 189 155 190 #qunit-tests del { 156 background-color: # e0f2be;157 color: #374 e0c;191 background-color: #E0F2BE; 192 color: #374E0C; 158 193 text-decoration: none; 159 194 } 160 195 161 196 #qunit-tests ins { 162 background-color: # ffcaca;197 background-color: #FFCACA; 163 198 color: #500; 164 199 text-decoration: none; … … 167 202 /*** Test Counts */ 168 203 169 #qunit-tests b.counts { color: black; }204 #qunit-tests b.counts { color: #000; } 170 205 #qunit-tests b.passed { color: #5E740B; } 171 206 #qunit-tests b.failed { color: #710909; } … … 173 208 #qunit-tests li li { 174 209 padding: 5px; 175 background-color: # fff;210 background-color: #FFF; 176 211 border-bottom: none; 177 212 list-style-position: inside; … … 181 216 182 217 #qunit-tests li li.pass { 183 color: #3 c510c;184 background-color: # fff;218 color: #3C510C; 219 background-color: #FFF; 185 220 border-left: 10px solid #C6E746; 186 221 } … … 190 225 191 226 #qunit-tests .pass .test-actual, 192 #qunit-tests .pass .test-expected { color: #999 999; }227 #qunit-tests .pass .test-expected { color: #999; } 193 228 194 229 #qunit-banner.qunit-pass { background-color: #C6E746; } … … 198 233 #qunit-tests li li.fail { 199 234 color: #710909; 200 background-color: # fff;235 background-color: #FFF; 201 236 border-left: 10px solid #EE5757; 202 237 white-space: pre; … … 205 240 #qunit-tests > li:last-child { 206 241 border-radius: 0 0 5px 5px; 207 -moz-border-radius: 0 0 5px 5px; 208 -webkit-border-bottom-right-radius: 5px; 209 -webkit-border-bottom-left-radius: 5px; 210 } 211 212 #qunit-tests .fail { color: #000000; background-color: #EE5757; } 242 } 243 244 #qunit-tests .fail { color: #000; background-color: #EE5757; } 213 245 #qunit-tests .fail .test-name, 214 #qunit-tests .fail .module-name { color: #000 000; }246 #qunit-tests .fail .module-name { color: #000; } 215 247 216 248 #qunit-tests .fail .test-actual { color: #EE5757; } 217 #qunit-tests .fail .test-expected { color: green;}249 #qunit-tests .fail .test-expected { color: #008000; } 218 250 219 251 #qunit-banner.qunit-fail { background-color: #EE5757; } 220 252 253 /*** Skipped tests */ 254 255 #qunit-tests .skipped { 256 background-color: #EBECE9; 257 } 258 259 #qunit-tests .qunit-skipped-label { 260 background-color: #F4FF77; 261 display: inline-block; 262 font-style: normal; 263 color: #366097; 264 line-height: 1.8em; 265 padding: 0 0.5em; 266 margin: -0.4em 0.4em -0.4em 0; 267 } 221 268 222 269 /** Result */ 223 270 224 271 #qunit-testresult { 225 padding: 0.5em 0.5em 0.5em 2.5em;226 227 color: #2 b81af;272 padding: 0.5em 1em 0.5em 1em; 273 274 color: #2B81AF; 228 275 background-color: #D2E0E6; 229 276 230 border-bottom: 1px solid white;277 border-bottom: 1px solid #FFF; 231 278 } 232 279 #qunit-testresult .module-name { 233 font-weight: bold;280 font-weight: 700; 234 281 } 235 282 -
trunk/tests/qunit/vendor/qunit.js
r25166 r32343 1 /** 2 * QUnit v1.12.0 - A JavaScript Unit Testing Framework 1 /*! 2 * QUnit 1.18.0 3 * http://qunitjs.com/ 3 4 * 4 * http://qunitjs.com 5 * Copyright jQuery Foundation and other contributors 6 * Released under the MIT license 7 * http://jquery.org/license 5 8 * 6 * Copyright 2013 jQuery Foundation and other contributors 7 * Released under the MIT license. 8 * https://jquery.org/license/ 9 * Date: 2015-04-03T10:23Z 9 10 */ 10 11 … … 12 13 13 14 var QUnit, 14 assert,15 15 config, 16 16 onErrorFnPrev, 17 testId = 0,18 fileName = ( sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),17 loggingCallbacks = {}, 18 fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ), 19 19 toString = Object.prototype.toString, 20 20 hasOwn = Object.prototype.hasOwnProperty, 21 21 // Keep a local reference to Date (GH-283) 22 22 Date = window.Date, 23 now = Date.now || function() { 24 return new Date().getTime(); 25 }, 26 globalStartCalled = false, 27 runStarted = false, 23 28 setTimeout = window.setTimeout, 29 clearTimeout = window.clearTimeout, 24 30 defined = { 25 setTimeout: typeof window.setTimeout !== "undefined", 31 document: window.document !== undefined, 32 setTimeout: window.setTimeout !== undefined, 26 33 sessionStorage: (function() { 27 34 var x = "qunit-test-string"; … … 30 37 sessionStorage.removeItem( x ); 31 38 return true; 32 } catch ( e ) {39 } catch ( e ) { 33 40 return false; 34 41 } … … 72 79 */ 73 80 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 81 var key, val, 77 82 vals = QUnit.is( "array", obj ) ? [] : {}; 78 83 for ( key in obj ) { 79 84 if ( hasOwn.call( obj, key ) ) { 80 val = obj[ key];81 vals[ key] = val === Object(val) ? objectValues(val) : val;85 val = obj[ key ]; 86 vals[ key ] = val === Object( val ) ? objectValues( val ) : val; 82 87 } 83 88 } … … 85 90 }; 86 91 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 }()); 92 QUnit = {}; 695 93 696 94 /** … … 706 104 blocking: true, 707 105 708 // when enabled, show only failing tests709 // gets persisted through sessionStorage and can be changed in UI via checkbox710 hidepassed: false,711 712 106 // by default, run previously failed tests first 713 107 // very useful in combination with "Hide passed tests" checked … … 717 111 altertitle: true, 718 112 113 // by default, scroll to top of the page when suite is done 114 scrolltop: true, 115 719 116 // when enabled, all tests must call expect() 720 117 requireExpects: false, 118 119 // depth up-to which object will be dumped 120 maxDepth: 5, 721 121 722 122 // add checkboxes that are persisted in the query-string … … 724 124 urlConfig: [ 725 125 { 126 id: "hidepassed", 127 label: "Hide passed tests", 128 tooltip: "Only show tests and assertions that fail. Stored as query-strings." 129 }, 130 { 726 131 id: "noglobals", 727 132 label: "Check for Globals", 728 tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." 133 tooltip: "Enabling this will test if any test introduces new properties on the " + 134 "`window` object. Stored as query-strings." 729 135 }, 730 136 { 731 137 id: "notrycatch", 732 138 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." 139 tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " + 140 "exceptions in IE reasonable. Stored as query-strings." 734 141 } 735 142 ], 736 143 737 144 // 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: [] 145 modules: [], 146 147 // The first unnamed module 148 currentModule: { 149 name: "", 150 tests: [] 151 }, 152 153 callbacks: {} 748 154 }; 749 155 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 } 156 // Push a loose unnamed module to the modules collection 157 config.modules.push( config.currentModule ); 758 158 759 159 // Initialize more QUnit.config and QUnit.urlParams 760 160 (function() { 761 var i, 161 var i, current, 762 162 location = window.location || { search: "", protocol: "file:" }, 763 163 params = location.search.slice( 1 ).split( "&" ), 764 164 length = params.length, 765 urlParams = {}, 766 current; 165 urlParams = {}; 767 166 768 167 if ( params[ 0 ] ) { … … 770 169 current = params[ i ].split( "=" ); 771 170 current[ 0 ] = decodeURIComponent( current[ 0 ] ); 171 772 172 // allow just a key to turn on a flag, e.g., test.html?noglobals 773 173 current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; 774 urlParams[ current[ 0 ] ] = current[ 1 ]; 775 } 174 if ( urlParams[ current[ 0 ] ] ) { 175 urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] ); 176 } else { 177 urlParams[ current[ 0 ] ] = current[ 1 ]; 178 } 179 } 180 } 181 182 if ( urlParams.filter === true ) { 183 delete urlParams.filter; 776 184 } 777 185 … … 781 189 config.filter = urlParams.filter; 782 190 783 // Exact match of the module name 784 config.module = urlParams.module; 785 786 config.testNumber = parseInt( urlParams.testNumber, 10 ) || null; 191 if ( urlParams.maxDepth ) { 192 config.maxDepth = parseInt( urlParams.maxDepth, 10 ) === -1 ? 193 Number.POSITIVE_INFINITY : 194 urlParams.maxDepth; 195 } 196 197 config.testId = []; 198 if ( urlParams.testId ) { 199 200 // Ensure that urlParams.testId is an array 201 urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," ); 202 for ( i = 0; i < urlParams.testId.length; i++ ) { 203 config.testId.push( urlParams.testId[ i ] ); 204 } 205 } 787 206 788 207 // Figure out if we're running the tests from a server or not 789 208 QUnit.isLocal = location.protocol === "file:"; 209 210 // Expose the current QUnit version 211 QUnit.version = "1.18.0"; 790 212 }()); 791 213 792 // Extend QUnit object,793 // these after set here because they should not be exposed as global functions214 // Root QUnit object. 215 // `QUnit` initialized at top of scope 794 216 extend( QUnit, { 795 assert: assert, 217 218 // call on start of module test to prepend name to all tests 219 module: function( name, testEnvironment ) { 220 var currentModule = { 221 name: name, 222 testEnvironment: testEnvironment, 223 tests: [] 224 }; 225 226 // DEPRECATED: handles setup/teardown functions, 227 // beforeEach and afterEach should be used instead 228 if ( testEnvironment && testEnvironment.setup ) { 229 testEnvironment.beforeEach = testEnvironment.setup; 230 delete testEnvironment.setup; 231 } 232 if ( testEnvironment && testEnvironment.teardown ) { 233 testEnvironment.afterEach = testEnvironment.teardown; 234 delete testEnvironment.teardown; 235 } 236 237 config.modules.push( currentModule ); 238 config.currentModule = currentModule; 239 }, 240 241 // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0. 242 asyncTest: function( testName, expected, callback ) { 243 if ( arguments.length === 2 ) { 244 callback = expected; 245 expected = null; 246 } 247 248 QUnit.test( testName, expected, callback, true ); 249 }, 250 251 test: function( testName, expected, callback, async ) { 252 var test; 253 254 if ( arguments.length === 2 ) { 255 callback = expected; 256 expected = null; 257 } 258 259 test = new Test({ 260 testName: testName, 261 expected: expected, 262 async: async, 263 callback: callback 264 }); 265 266 test.queue(); 267 }, 268 269 skip: function( testName ) { 270 var test = new Test({ 271 testName: testName, 272 skip: true 273 }); 274 275 test.queue(); 276 }, 277 278 // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0. 279 // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior. 280 start: function( count ) { 281 var globalStartAlreadyCalled = globalStartCalled; 282 283 if ( !config.current ) { 284 globalStartCalled = true; 285 286 if ( runStarted ) { 287 throw new Error( "Called start() outside of a test context while already started" ); 288 } else if ( globalStartAlreadyCalled || count > 1 ) { 289 throw new Error( "Called start() outside of a test context too many times" ); 290 } else if ( config.autostart ) { 291 throw new Error( "Called start() outside of a test context when " + 292 "QUnit.config.autostart was true" ); 293 } else if ( !config.pageLoaded ) { 294 295 // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it 296 config.autostart = true; 297 return; 298 } 299 } else { 300 301 // If a test is running, adjust its semaphore 302 config.current.semaphore -= count || 1; 303 304 // Don't start until equal number of stop-calls 305 if ( config.current.semaphore > 0 ) { 306 return; 307 } 308 309 // throw an Error if start is called more often than stop 310 if ( config.current.semaphore < 0 ) { 311 config.current.semaphore = 0; 312 313 QUnit.pushFailure( 314 "Called start() while already started (test's semaphore was 0 already)", 315 sourceFromStacktrace( 2 ) 316 ); 317 return; 318 } 319 } 320 321 resumeProcessing(); 322 }, 323 324 // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0. 325 stop: function( count ) { 326 327 // If there isn't a test running, don't allow QUnit.stop() to be called 328 if ( !config.current ) { 329 throw new Error( "Called stop() outside of a test context" ); 330 } 331 332 // If a test is running, adjust its semaphore 333 config.current.semaphore += count || 1; 334 335 pauseProcessing(); 336 }, 796 337 797 338 config: config, 798 799 // Initialize the configuration options800 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: 1812 });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.1856 */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 339 878 340 // Safe object type checking … … 883 345 objectType: function( obj ) { 884 346 if ( typeof obj === "undefined" ) { 885 return "undefined"; 886 // consider: typeof null === object 887 } 347 return "undefined"; 348 } 349 350 // Consider: typeof null === object 888 351 if ( obj === null ) { 889 return "null";890 } 891 892 var match = toString.call( obj ).match( /^\[object\s(.*)\]$/),893 type = match && match[ 1] || "";352 return "null"; 353 } 354 355 var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ), 356 type = match && match[ 1 ] || ""; 894 357 895 358 switch ( type ) { 896 359 case "Number": 897 if ( isNaN( obj) ) {360 if ( isNaN( obj ) ) { 898 361 return "nan"; 899 362 } … … 913 376 }, 914 377 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>"; 378 extend: extend, 379 380 load: function() { 381 config.pageLoaded = true; 382 383 // Initialize the configuration options 384 extend( config, { 385 stats: { all: 0, bad: 0 }, 386 moduleStats: { all: 0, bad: 0 }, 387 started: 0, 388 updateRate: 1000, 389 autostart: true, 390 filter: "" 391 }, true ); 392 393 config.blocking = false; 394 395 if ( config.autostart ) { 396 resumeProcessing(); 397 } 398 } 399 }); 400 401 // Register logging callbacks 402 (function() { 403 var i, l, key, 404 callbacks = [ "begin", "done", "log", "testStart", "testDone", 405 "moduleStart", "moduleDone" ]; 406 407 function registerLoggingCallback( key ) { 408 var loggingCallback = function( callback ) { 409 if ( QUnit.objectType( callback ) !== "function" ) { 410 throw new Error( 411 "QUnit logging methods require a callback function as their first parameters." 412 ); 942 413 } 943 414 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 ); 415 config.callbacks[ key ].push( callback ); 416 }; 417 418 // DEPRECATED: This will be removed on QUnit 2.0.0+ 419 // Stores the registered functions allowing restoring 420 // at verifyLoggingCallbacks() if modified 421 loggingCallbacks[ key ] = loggingCallback; 422 423 return loggingCallback; 424 } 425 426 for ( i = 0, l = callbacks.length; i < l; i++ ) { 427 key = callbacks[ i ]; 428 429 // Initialize key collection of logging callback 430 if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) { 431 config.callbacks[ key ] = []; 432 } 433 434 QUnit[ key ] = registerLoggingCallback( key ); 435 } 436 })(); 1217 437 1218 438 // `onErrorFnPrev` initialized at top of scope … … 1223 443 // Returning true will suppress the default browser handler, 1224 444 // returning false will let it run. 1225 window.onerror = function ( error, filePath, linerNr ) {445 window.onerror = function( error, filePath, linerNr ) { 1226 446 var ret = false; 1227 447 if ( onErrorFnPrev ) { … … 1238 458 QUnit.pushFailure( error, filePath + ":" + linerNr ); 1239 459 } else { 1240 QUnit.test( "global failure", extend( function() {460 QUnit.test( "global failure", extend(function() { 1241 461 QUnit.pushFailure( error, filePath + ":" + linerNr ); 1242 }, { validTest: validTest} ) );462 }, { validTest: true } ) ); 1243 463 } 1244 464 return false; … … 1249 469 1250 470 function done() { 471 var runtime, passed; 472 1251 473 config.autorun = true; 1252 474 1253 475 // Log the last module results 1254 if ( config.currentModule ) { 1255 runLoggingCallbacks( "moduleDone", QUnit, { 1256 name: config.currentModule, 476 if ( config.previousModule ) { 477 runLoggingCallbacks( "moduleDone", { 478 name: config.previousModule.name, 479 tests: config.previousModule.tests, 1257 480 failed: config.moduleStats.bad, 1258 481 passed: config.moduleStats.all - config.moduleStats.bad, 1259 total: config.moduleStats.all 482 total: config.moduleStats.all, 483 runtime: now() - config.moduleStats.started 1260 484 }); 1261 485 } 1262 486 delete config.previousModule; 1263 487 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, { 488 runtime = now() - config.started; 489 passed = config.stats.all - config.stats.bad; 490 491 runLoggingCallbacks( "done", { 1316 492 failed: config.stats.bad, 1317 493 passed: passed, … … 1321 497 } 1322 498 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 499 // Doesn't support IE6 to IE9, it will return undefined on these browsers 1364 500 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack 1365 501 function extractStacktrace( e, offset ) { 1366 offset = offset === undefined ? 3: offset;502 offset = offset === undefined ? 4 : offset; 1367 503 1368 504 var stack, include, i; 1369 505 1370 if ( e.stacktrace ) { 1371 // Opera 1372 return e.stacktrace.split( "\n" )[ offset + 3 ]; 1373 } else if ( e.stack ) { 1374 // Firefox, Chrome 506 if ( e.stack ) { 1375 507 stack = e.stack.split( "\n" ); 1376 if ( /^error$/i.test( stack[0] ) ) {508 if ( /^error$/i.test( stack[ 0 ] ) ) { 1377 509 stack.shift(); 1378 510 } … … 1390 522 } 1391 523 return stack[ offset ]; 524 525 // Support: Safari <=6 only 1392 526 } else if ( e.sourceURL ) { 1393 // Safari, PhantomJS 1394 // hopefully one day Safari provides actual stacktraces 527 1395 528 // exclude useless self-reference for generated Error objects 1396 529 if ( /qunit.js$/.test( e.sourceURL ) ) { 1397 530 return; 1398 531 } 532 1399 533 // for actual exceptions, this is useful 1400 534 return e.sourceURL + ":" + e.line; 1401 535 } 1402 536 } 537 1403 538 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 }); 539 var error = new Error(); 540 541 // Support: Safari <=7 only, IE <=10 - 11 only 542 // Not all browsers generate the `stack` property for `new Error()`, see also #636 543 if ( !error.stack ) { 544 try { 545 throw error; 546 } catch ( err ) { 547 error = err; 548 } 549 } 550 551 return extractStacktrace( error, offset ); 1434 552 } 1435 553 1436 554 function synchronize( callback, last ) { 555 if ( QUnit.objectType( callback ) === "array" ) { 556 while ( callback.length ) { 557 synchronize( callback.shift() ); 558 } 559 return; 560 } 1437 561 config.queue.push( callback ); 1438 562 … … 1446 570 process( last ); 1447 571 } 1448 var start = n ew Date().getTime();1449 config.depth = config.depth ? config.depth + 1 :1;572 var start = now(); 573 config.depth = ( config.depth || 0 ) + 1; 1450 574 1451 575 while ( config.queue.length && !config.blocking ) { 1452 if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { 576 if ( !defined.setTimeout || config.updateRate <= 0 || 577 ( ( now() - start ) < config.updateRate ) ) { 578 if ( config.current ) { 579 580 // Reset async tracking for each phase of the Test lifecycle 581 config.current.usedAsync = false; 582 } 1453 583 config.queue.shift()(); 1454 584 } else { … … 1460 590 if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { 1461 591 done(); 592 } 593 } 594 595 function begin() { 596 var i, l, 597 modulesLog = []; 598 599 // If the test run hasn't officially begun yet 600 if ( !config.started ) { 601 602 // Record the time of the test run's beginning 603 config.started = now(); 604 605 verifyLoggingCallbacks(); 606 607 // Delete the loose unnamed module if unused. 608 if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) { 609 config.modules.shift(); 610 } 611 612 // Avoid unnecessary information by not logging modules' test environments 613 for ( i = 0, l = config.modules.length; i < l; i++ ) { 614 modulesLog.push({ 615 name: config.modules[ i ].name, 616 tests: config.modules[ i ].tests 617 }); 618 } 619 620 // The test run is officially beginning now 621 runLoggingCallbacks( "begin", { 622 totalTests: Test.count, 623 modules: modulesLog 624 }); 625 } 626 627 config.blocking = false; 628 process( true ); 629 } 630 631 function resumeProcessing() { 632 runStarted = true; 633 634 // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.) 635 if ( defined.setTimeout ) { 636 setTimeout(function() { 637 if ( config.current && config.current.semaphore > 0 ) { 638 return; 639 } 640 if ( config.timeout ) { 641 clearTimeout( config.timeout ); 642 } 643 644 begin(); 645 }, 13 ); 646 } else { 647 begin(); 648 } 649 } 650 651 function pauseProcessing() { 652 config.blocking = true; 653 654 if ( config.testTimeout && defined.setTimeout ) { 655 clearTimeout( config.timeout ); 656 config.timeout = setTimeout(function() { 657 if ( config.current ) { 658 config.current.semaphore = 0; 659 QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) ); 660 } else { 661 throw new Error( "Test timed out" ); 662 } 663 resumeProcessing(); 664 }, config.testTimeout ); 1462 665 } 1463 666 } … … 1488 691 newGlobals = diff( config.pollution, old ); 1489 692 if ( newGlobals.length > 0 ) { 1490 QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", ") );693 QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) ); 1491 694 } 1492 695 1493 696 deletedGlobals = diff( old, config.pollution ); 1494 697 if ( deletedGlobals.length > 0 ) { 1495 QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", ") );698 QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) ); 1496 699 } 1497 700 } … … 1504 707 for ( i = 0; i < result.length; i++ ) { 1505 708 for ( j = 0; j < b.length; j++ ) { 1506 if ( result[ i] === b[j] ) {709 if ( result[ i ] === b[ j ] ) { 1507 710 result.splice( i, 1 ); 1508 711 i--; … … 1514 717 } 1515 718 1516 function extend( a, b ) {719 function extend( a, b, undefOnly ) { 1517 720 for ( var prop in b ) { 1518 721 if ( hasOwn.call( b, prop ) ) { 722 1519 723 // Avoid "Member not found" error in IE8 caused by messing with window.constructor 1520 724 if ( !( prop === "constructor" && a === window ) ) { 1521 725 if ( b[ prop ] === undefined ) { 1522 726 delete a[ prop ]; 1523 } else {727 } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) { 1524 728 a[ prop ] = b[ prop ]; 1525 729 } … … 1531 735 } 1532 736 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 737 function runLoggingCallbacks( key, args ) { 738 var i, l, callbacks; 739 740 callbacks = config.callbacks[ key ]; 741 for ( i = 0, l = callbacks.length; i < l; i++ ) { 742 callbacks[ i ]( args ); 743 } 744 } 745 746 // DEPRECATED: This will be removed on 2.0.0+ 747 // This function verifies if the loggingCallbacks were modified by the user 748 // If so, it will restore it, assign the given callback and print a console warning 749 function verifyLoggingCallbacks() { 750 var loggingCallback, userCallback; 751 752 for ( loggingCallback in loggingCallbacks ) { 753 if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) { 754 755 userCallback = QUnit[ loggingCallback ]; 756 757 // Restore the callback function 758 QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ]; 759 760 // Assign the deprecated given callback 761 QUnit[ loggingCallback ]( userCallback ); 762 763 if ( window.console && window.console.warn ) { 764 window.console.warn( 765 "QUnit." + loggingCallback + " was replaced with a new value.\n" + 766 "Please, check out the documentation on how to apply logging callbacks.\n" + 767 "Reference: http://api.qunitjs.com/category/callbacks/" 768 ); 769 } 770 } 771 } 772 } 773 774 // from jquery.js 775 function inArray( elem, array ) { 776 if ( array.indexOf ) { 777 return array.indexOf( elem ); 778 } 779 780 for ( var i = 0, length = array.length; i < length; i++ ) { 781 if ( array[ i ] === elem ) { 782 return i; 783 } 784 } 785 786 return -1; 787 } 788 789 function Test( settings ) { 790 var i, l; 791 792 ++Test.count; 793 794 extend( this, settings ); 795 this.assertions = []; 796 this.semaphore = 0; 797 this.usedAsync = false; 798 this.module = config.currentModule; 799 this.stack = sourceFromStacktrace( 3 ); 800 801 // Register unique strings 802 for ( i = 0, l = this.module.tests; i < l.length; i++ ) { 803 if ( this.module.tests[ i ].name === this.testName ) { 804 this.testName += " "; 805 } 806 } 807 808 this.testId = generateHash( this.module.name, this.testName ); 809 810 this.module.tests.push({ 811 name: this.testName, 812 testId: this.testId 813 }); 814 815 if ( settings.skip ) { 816 817 // Skipped tests will fully ignore any sent callback 818 this.callback = function() {}; 819 this.async = false; 820 this.expected = 0; 1543 821 } 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 } 822 this.assert = new Assert( this ); 823 } 824 } 825 826 Test.count = 0; 827 828 Test.prototype = { 829 before: function() { 830 if ( 831 832 // Emit moduleStart when we're switching from one module to another 833 this.module !== config.previousModule || 834 835 // They could be equal (both undefined) but if the previousModule property doesn't 836 // yet exist it means this is the first test in a suite that isn't wrapped in a 837 // module, in which case we'll just emit a moduleStart event for 'undefined'. 838 // Without this, reporters can get testStart before moduleStart which is a problem. 839 !hasOwn.call( config, "previousModule" ) 840 ) { 841 if ( hasOwn.call( config, "previousModule" ) ) { 842 runLoggingCallbacks( "moduleDone", { 843 name: config.previousModule.name, 844 tests: config.previousModule.tests, 845 failed: config.moduleStats.bad, 846 passed: config.moduleStats.all - config.moduleStats.bad, 847 total: config.moduleStats.all, 848 runtime: now() - config.moduleStats.started 849 }); 850 } 851 config.previousModule = this.module; 852 config.moduleStats = { all: 0, bad: 0, started: now() }; 853 runLoggingCallbacks( "moduleStart", { 854 name: this.module.name, 855 tests: this.module.tests 856 }); 857 } 858 859 config.current = this; 860 861 this.testEnvironment = extend( {}, this.module.testEnvironment ); 862 delete this.testEnvironment.beforeEach; 863 delete this.testEnvironment.afterEach; 864 865 this.started = now(); 866 runLoggingCallbacks( "testStart", { 867 name: this.testName, 868 module: this.module.name, 869 testId: this.testId 870 }); 871 872 if ( !config.pollution ) { 873 saveGlobal(); 874 } 875 }, 876 877 run: function() { 878 var promise; 879 880 config.current = this; 881 882 if ( this.async ) { 883 QUnit.stop(); 884 } 885 886 this.callbackStarted = now(); 887 888 if ( config.notrycatch ) { 889 promise = this.callback.call( this.testEnvironment, this.assert ); 890 this.resolvePromise( promise ); 891 return; 892 } 893 894 try { 895 promise = this.callback.call( this.testEnvironment, this.assert ); 896 this.resolvePromise( promise ); 897 } catch ( e ) { 898 this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " + 899 this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); 900 901 // else next test will carry the responsibility 902 saveGlobal(); 903 904 // Restart the tests if they're blocking 905 if ( config.blocking ) { 906 QUnit.start(); 907 } 908 } 909 }, 910 911 after: function() { 912 checkPollution(); 913 }, 914 915 queueHook: function( hook, hookName ) { 916 var promise, 917 test = this; 918 return function runHook() { 919 config.current = test; 920 if ( config.notrycatch ) { 921 promise = hook.call( test.testEnvironment, test.assert ); 922 test.resolvePromise( promise, hookName ); 923 return; 924 } 925 try { 926 promise = hook.call( test.testEnvironment, test.assert ); 927 test.resolvePromise( promise, hookName ); 928 } catch ( error ) { 929 test.pushFailure( hookName + " failed on " + test.testName + ": " + 930 ( error.message || error ), extractStacktrace( error, 0 ) ); 931 } 932 }; 933 }, 934 935 // Currently only used for module level hooks, can be used to add global level ones 936 hooks: function( handler ) { 937 var hooks = []; 938 939 // Hooks are ignored on skipped tests 940 if ( this.skip ) { 941 return hooks; 942 } 943 944 if ( this.module.testEnvironment && 945 QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) { 946 hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) ); 947 } 948 949 return hooks; 950 }, 951 952 finish: function() { 953 config.current = this; 954 if ( config.requireExpects && this.expected === null ) { 955 this.pushFailure( "Expected number of assertions to be defined, but expect() was " + 956 "not called.", this.stack ); 957 } else if ( this.expected !== null && this.expected !== this.assertions.length ) { 958 this.pushFailure( "Expected " + this.expected + " assertions, but " + 959 this.assertions.length + " were run", this.stack ); 960 } else if ( this.expected === null && !this.assertions.length ) { 961 this.pushFailure( "Expected at least one assertion, but none were run - call " + 962 "expect(0) to accept zero assertions.", this.stack ); 963 } 964 965 var i, 966 bad = 0; 967 968 this.runtime = now() - this.started; 969 config.stats.all += this.assertions.length; 970 config.moduleStats.all += this.assertions.length; 971 972 for ( i = 0; i < this.assertions.length; i++ ) { 973 if ( !this.assertions[ i ].result ) { 974 bad++; 975 config.stats.bad++; 976 config.moduleStats.bad++; 977 } 978 } 979 980 runLoggingCallbacks( "testDone", { 981 name: this.testName, 982 module: this.module.name, 983 skipped: !!this.skip, 984 failed: bad, 985 passed: this.assertions.length - bad, 986 total: this.assertions.length, 987 runtime: this.runtime, 988 989 // HTML Reporter use 990 assertions: this.assertions, 991 testId: this.testId, 992 993 // DEPRECATED: this property will be removed in 2.0.0, use runtime instead 994 duration: this.runtime 995 }); 996 997 // QUnit.reset() is deprecated and will be replaced for a new 998 // fixture reset function on QUnit 2.0/2.1. 999 // It's still called here for backwards compatibility handling 1000 QUnit.reset(); 1001 1002 config.current = undefined; 1003 }, 1004 1005 queue: function() { 1006 var bad, 1007 test = this; 1008 1009 if ( !this.valid() ) { 1010 return; 1011 } 1012 1013 function run() { 1014 1015 // each of these can by async 1016 synchronize([ 1017 function() { 1018 test.before(); 1019 }, 1020 1021 test.hooks( "beforeEach" ), 1022 1023 function() { 1024 test.run(); 1025 }, 1026 1027 test.hooks( "afterEach" ).reverse(), 1028 1029 function() { 1030 test.after(); 1031 }, 1032 function() { 1033 test.finish(); 1034 } 1035 ]); 1036 } 1037 1038 // `bad` initialized at top of scope 1039 // defer when previous test run passed, if storage is available 1040 bad = QUnit.config.reorder && defined.sessionStorage && 1041 +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName ); 1042 1043 if ( bad ) { 1044 run(); 1045 } else { 1046 synchronize( run, true ); 1047 } 1048 }, 1049 1050 push: function( result, actual, expected, message ) { 1051 var source, 1052 details = { 1053 module: this.module.name, 1054 name: this.testName, 1055 result: result, 1056 message: message, 1057 actual: actual, 1058 expected: expected, 1059 testId: this.testId, 1060 runtime: now() - this.started 1061 }; 1062 1063 if ( !result ) { 1064 source = sourceFromStacktrace(); 1065 1066 if ( source ) { 1067 details.source = source; 1068 } 1069 } 1070 1071 runLoggingCallbacks( "log", details ); 1072 1073 this.assertions.push({ 1074 result: !!result, 1075 message: message 1076 }); 1077 }, 1078 1079 pushFailure: function( message, source, actual ) { 1080 if ( !this instanceof Test ) { 1081 throw new Error( "pushFailure() assertion outside test context, was " + 1082 sourceFromStacktrace( 2 ) ); 1083 } 1084 1085 var details = { 1086 module: this.module.name, 1087 name: this.testName, 1088 result: false, 1089 message: message || "error", 1090 actual: actual || null, 1091 testId: this.testId, 1092 runtime: now() - this.started 1093 }; 1094 1095 if ( source ) { 1096 details.source = source; 1097 } 1098 1099 runLoggingCallbacks( "log", details ); 1100 1101 this.assertions.push({ 1102 result: false, 1103 message: message 1104 }); 1105 }, 1106 1107 resolvePromise: function( promise, phase ) { 1108 var then, message, 1109 test = this; 1110 if ( promise != null ) { 1111 then = promise.then; 1112 if ( QUnit.objectType( then ) === "function" ) { 1113 QUnit.stop(); 1114 then.call( 1115 promise, 1116 QUnit.start, 1117 function( error ) { 1118 message = "Promise rejected " + 1119 ( !phase ? "during" : phase.replace( /Each$/, "" ) ) + 1120 " " + test.testName + ": " + ( error.message || error ); 1121 test.pushFailure( message, extractStacktrace( error, 0 ) ); 1122 1123 // else next test will carry the responsibility 1124 saveGlobal(); 1125 1126 // Unblock 1127 QUnit.start(); 1128 } 1129 ); 1130 } 1131 } 1132 }, 1133 1134 valid: function() { 1135 var include, 1136 filter = config.filter && config.filter.toLowerCase(), 1137 module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(), 1138 fullName = ( this.module.name + ": " + this.testName ).toLowerCase(); 1139 1140 // Internally-generated tests are always valid 1141 if ( this.callback && this.callback.validTest ) { 1142 return true; 1143 } 1144 1145 if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) { 1146 return false; 1147 } 1148 1149 if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) { 1150 return false; 1151 } 1152 1153 if ( !filter ) { 1154 return true; 1155 } 1156 1157 include = filter.charAt( 0 ) !== "!"; 1158 if ( !include ) { 1159 filter = filter.slice( 1 ); 1160 } 1161 1162 // If the filter matches, we need to honour include 1163 if ( fullName.indexOf( filter ) !== -1 ) { 1164 return include; 1165 } 1166 1167 // Otherwise, do the opposite 1168 return !include; 1169 } 1170 1171 }; 1172 1173 // Resets the test setup. Useful for tests that modify the DOM. 1174 /* 1175 DEPRECATED: Use multiple tests instead of resetting inside a test. 1176 Use testStart or testDone for custom cleanup. 1177 This method will throw an error in 2.0, and will be removed in 2.1 1178 */ 1179 QUnit.reset = function() { 1180 1181 // Return on non-browser environments 1182 // This is necessary to not break on node tests 1183 if ( typeof window === "undefined" ) { 1184 return; 1185 } 1186 1187 var fixture = defined.document && document.getElementById && 1188 document.getElementById( "qunit-fixture" ); 1189 1190 if ( fixture ) { 1191 fixture.innerHTML = config.fixture; 1192 } 1193 }; 1194 1195 QUnit.pushFailure = function() { 1196 if ( !QUnit.config.current ) { 1197 throw new Error( "pushFailure() assertion outside test context, in " + 1198 sourceFromStacktrace( 2 ) ); 1199 } 1200 1201 // Gets current test obj 1202 var currentTest = QUnit.config.current; 1203 1204 return currentTest.pushFailure.apply( currentTest, arguments ); 1205 }; 1206 1207 // Based on Java's String.hashCode, a simple but not 1208 // rigorously collision resistant hashing function 1209 function generateHash( module, testName ) { 1210 var hex, 1211 i = 0, 1212 hash = 0, 1213 str = module + "\x1C" + testName, 1214 len = str.length; 1215 1216 for ( ; i < len; i++ ) { 1217 hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i ); 1218 hash |= 0; 1219 } 1220 1221 // Convert the possibly negative integer hash code into an 8 character hex string, which isn't 1222 // strictly necessary but increases user understanding that the id is a SHA-like hash 1223 hex = ( 0x100000000 + hash ).toString( 16 ); 1224 if ( hex.length < 8 ) { 1225 hex = "0000000" + hex; 1226 } 1227 1228 return hex.slice( -8 ); 1229 } 1230 1231 function Assert( testContext ) { 1232 this.test = testContext; 1233 } 1234 1235 // Assert helpers 1236 QUnit.assert = Assert.prototype = { 1237 1238 // Specify the number of expected assertions to guarantee that failed test 1239 // (no assertions are run at all) don't slip through. 1240 expect: function( asserts ) { 1241 if ( arguments.length === 1 ) { 1242 this.test.expected = asserts; 1243 } else { 1244 return this.test.expected; 1245 } 1246 }, 1247 1248 // Increment this Test's semaphore counter, then return a single-use function that 1249 // decrements that counter a maximum of once. 1250 async: function() { 1251 var test = this.test, 1252 popped = false; 1253 1254 test.semaphore += 1; 1255 test.usedAsync = true; 1256 pauseProcessing(); 1257 1258 return function done() { 1259 if ( !popped ) { 1260 test.semaphore -= 1; 1261 popped = true; 1262 resumeProcessing(); 1263 } else { 1264 test.pushFailure( "Called the callback returned from `assert.async` more than once", 1265 sourceFromStacktrace( 2 ) ); 1266 } 1267 }; 1268 }, 1269 1270 // Exports test.push() to the user API 1271 push: function( /* result, actual, expected, message */ ) { 1272 var assert = this, 1273 currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current; 1274 1275 // Backwards compatibility fix. 1276 // Allows the direct use of global exported assertions and QUnit.assert.* 1277 // Although, it's use is not recommended as it can leak assertions 1278 // to other tests from async tests, because we only get a reference to the current test, 1279 // not exactly the test where assertion were intended to be called. 1280 if ( !currentTest ) { 1281 throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) ); 1282 } 1283 1284 if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) { 1285 currentTest.pushFailure( "Assertion after the final `assert.async` was resolved", 1286 sourceFromStacktrace( 2 ) ); 1287 1288 // Allow this assertion to continue running anyway... 1289 } 1290 1291 if ( !( assert instanceof Assert ) ) { 1292 assert = currentTest.assert; 1293 } 1294 return assert.test.push.apply( assert.test, arguments ); 1295 }, 1296 1297 ok: function( result, message ) { 1298 message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " + 1299 QUnit.dump.parse( result ) ); 1300 this.push( !!result, result, true, message ); 1301 }, 1302 1303 notOk: function( result, message ) { 1304 message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " + 1305 QUnit.dump.parse( result ) ); 1306 this.push( !result, result, false, message ); 1307 }, 1308 1309 equal: function( actual, expected, message ) { 1310 /*jshint eqeqeq:false */ 1311 this.push( expected == actual, actual, expected, message ); 1312 }, 1313 1314 notEqual: function( actual, expected, message ) { 1315 /*jshint eqeqeq:false */ 1316 this.push( expected != actual, actual, expected, message ); 1317 }, 1318 1319 propEqual: function( actual, expected, message ) { 1320 actual = objectValues( actual ); 1321 expected = objectValues( expected ); 1322 this.push( QUnit.equiv( actual, expected ), actual, expected, message ); 1323 }, 1324 1325 notPropEqual: function( actual, expected, message ) { 1326 actual = objectValues( actual ); 1327 expected = objectValues( expected ); 1328 this.push( !QUnit.equiv( actual, expected ), actual, expected, message ); 1329 }, 1330 1331 deepEqual: function( actual, expected, message ) { 1332 this.push( QUnit.equiv( actual, expected ), actual, expected, message ); 1333 }, 1334 1335 notDeepEqual: function( actual, expected, message ) { 1336 this.push( !QUnit.equiv( actual, expected ), actual, expected, message ); 1337 }, 1338 1339 strictEqual: function( actual, expected, message ) { 1340 this.push( expected === actual, actual, expected, message ); 1341 }, 1342 1343 notStrictEqual: function( actual, expected, message ) { 1344 this.push( expected !== actual, actual, expected, message ); 1345 }, 1346 1347 "throws": function( block, expected, message ) { 1348 var actual, expectedType, 1349 expectedOutput = expected, 1350 ok = false, 1351 currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current; 1352 1353 // 'expected' is optional unless doing string comparison 1354 if ( message == null && typeof expected === "string" ) { 1355 message = expected; 1356 expected = null; 1357 } 1358 1359 currentTest.ignoreGlobalErrors = true; 1360 try { 1361 block.call( currentTest.testEnvironment ); 1362 } catch (e) { 1363 actual = e; 1364 } 1365 currentTest.ignoreGlobalErrors = false; 1366 1367 if ( actual ) { 1368 expectedType = QUnit.objectType( expected ); 1369 1370 // we don't want to validate thrown error 1371 if ( !expected ) { 1372 ok = true; 1373 expectedOutput = null; 1374 1375 // expected is a regexp 1376 } else if ( expectedType === "regexp" ) { 1377 ok = expected.test( errorString( actual ) ); 1378 1379 // expected is a string 1380 } else if ( expectedType === "string" ) { 1381 ok = expected === errorString( actual ); 1382 1383 // expected is a constructor, maybe an Error constructor 1384 } else if ( expectedType === "function" && actual instanceof expected ) { 1385 ok = true; 1386 1387 // expected is an Error object 1388 } else if ( expectedType === "object" ) { 1389 ok = actual instanceof expected.constructor && 1390 actual.name === expected.name && 1391 actual.message === expected.message; 1392 1393 // expected is a validation function which returns true if validation passed 1394 } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) { 1395 expectedOutput = null; 1396 ok = true; 1397 } 1398 } 1399 1400 currentTest.assert.push( ok, actual, expectedOutput, message ); 1401 } 1402 }; 1403 1404 // Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word 1405 // Known to us are: Closure Compiler, Narwhal 1406 (function() { 1407 /*jshint sub:true */ 1408 Assert.prototype.raises = Assert.prototype[ "throws" ]; 1409 }()); 1603 1410 1604 1411 // Test for equality any JavaScript type. … … 1620 1427 // the real equiv function 1621 1428 var innerEquiv, 1429 1622 1430 // stack to decide between skip/abort functions 1623 1431 callers = [], 1432 1624 1433 // stack to avoiding loops from circular referencing 1625 1434 parents = [], 1626 1435 parentsB = [], 1627 1436 1628 getProto = Object.getPrototypeOf || function ( obj ) {1629 /* jshint camelcase:false */1437 getProto = Object.getPrototypeOf || function( obj ) { 1438 /* jshint camelcase: false, proto: true */ 1630 1439 return obj.__proto__; 1631 1440 }, 1632 callbacks = (function () {1441 callbacks = (function() { 1633 1442 1634 1443 // for string, boolean, number and null 1635 1444 function useStrictEquality( b, a ) { 1445 1636 1446 /*jshint eqeqeq:false */ 1637 1447 if ( b instanceof a.constructor || a instanceof b.constructor ) { 1448 1638 1449 // to catch short annotation VS 'new' annotation of a 1639 1450 // declaration … … 1663 1474 "regexp": function( b, a ) { 1664 1475 return QUnit.objectType( b ) === "regexp" && 1476 1665 1477 // the regex itself 1666 1478 a.source === b.source && 1479 1667 1480 // and its modifiers 1668 1481 a.global === b.global && 1482 1669 1483 // (gmi) ... 1670 1484 a.ignoreCase === b.ignoreCase && … … 1677 1491 // initial === would have catch identical references anyway 1678 1492 "function": function() { 1679 var caller = callers[ callers.length - 1];1493 var caller = callers[ callers.length - 1 ]; 1680 1494 return caller !== Object && typeof caller !== "undefined"; 1681 1495 }, … … 1701 1515 loop = false; 1702 1516 for ( j = 0; j < parents.length; j++ ) { 1703 aCircular = parents[ j] === a[i];1704 bCircular = parentsB[ j] === b[i];1517 aCircular = parents[ j ] === a[ i ]; 1518 bCircular = parentsB[ j ] === b[ i ]; 1705 1519 if ( aCircular || bCircular ) { 1706 if ( a[ i] === b[i] || aCircular && bCircular ) {1520 if ( a[ i ] === b[ i ] || aCircular && bCircular ) { 1707 1521 loop = true; 1708 1522 } else { … … 1713 1527 } 1714 1528 } 1715 if ( !loop && !innerEquiv( a[i], b[i]) ) {1529 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { 1716 1530 parents.pop(); 1717 1531 parentsB.pop(); … … 1725 1539 1726 1540 "object": function( b, a ) { 1541 1727 1542 /*jshint forin:false */ 1728 1543 var i, j, loop, aCircular, bCircular, … … 1735 1550 // instanceof 1736 1551 if ( a.constructor !== b.constructor ) { 1552 1737 1553 // Allow objects with no prototype to be equivalent to 1738 1554 // 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;1555 if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) || 1556 ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) { 1557 return false; 1742 1558 } 1743 1559 } … … 1754 1570 loop = false; 1755 1571 for ( j = 0; j < parents.length; j++ ) { 1756 aCircular = parents[ j] === a[i];1757 bCircular = parentsB[ j] === b[i];1572 aCircular = parents[ j ] === a[ i ]; 1573 bCircular = parentsB[ j ] === b[ i ]; 1758 1574 if ( aCircular || bCircular ) { 1759 if ( a[ i] === b[i] || aCircular && bCircular ) {1575 if ( a[ i ] === b[ i ] || aCircular && bCircular ) { 1760 1576 loop = true; 1761 1577 } else { … … 1765 1581 } 1766 1582 } 1767 aProperties.push( i);1768 if ( !loop && !innerEquiv( a[i], b[i]) ) {1583 aProperties.push( i ); 1584 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { 1769 1585 eq = false; 1770 1586 break; … … 1792 1608 } 1793 1609 1794 return ( function( a, b ) {1610 return ( (function( a, b ) { 1795 1611 if ( a === b ) { 1796 1612 return true; // catch the most you can 1797 1613 } else if ( a === null || b === null || typeof a === "undefined" || 1798 1614 typeof b === "undefined" || 1799 QUnit.objectType(a) !== QUnit.objectType(b) ) { 1800 return false; // don't lose time with error prone cases 1615 QUnit.objectType( a ) !== QUnit.objectType( b ) ) { 1616 1617 // don't lose time with error prone cases 1618 return false; 1801 1619 } else { 1802 return bindCallbacks( a, callbacks, [ b, a ]);1620 return bindCallbacks( a, callbacks, [ b, a ] ); 1803 1621 } 1804 1622 1805 1623 // apply transition with (1..n) arguments 1806 }( args[0], args[1] ) && innerEquiv.apply( this, args.splice(1, args.length - 1 )) ); 1624 }( args[ 0 ], args[ 1 ] ) ) && 1625 innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) ); 1807 1626 }; 1808 1627 … … 1810 1629 }()); 1811 1630 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() { 1631 // Based on jsDump by Ariel Flesler 1632 // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html 1633 QUnit.dump = (function() { 1823 1634 function quote( str ) { 1824 1635 return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\""; … … 1828 1639 } 1829 1640 function join( pre, arr, post ) { 1830 var s = jsDump.separator(),1831 base = jsDump.indent(),1832 inner = jsDump.indent(1);1641 var s = dump.separator(), 1642 base = dump.indent(), 1643 inner = dump.indent( 1 ); 1833 1644 if ( arr.join ) { 1834 1645 arr = arr.join( "," + s + inner ); … … 1837 1648 return pre + post; 1838 1649 } 1839 return [ pre, inner + arr, base + post ].join( s);1650 return [ pre, inner + arr, base + post ].join( s ); 1840 1651 } 1841 1652 function array( arr, stack ) { 1842 var i = arr.length, ret = new Array(i); 1653 var i = arr.length, 1654 ret = new Array( i ); 1655 1656 if ( dump.maxDepth && dump.depth > dump.maxDepth ) { 1657 return "[object Array]"; 1658 } 1659 1843 1660 this.up(); 1844 1661 while ( i-- ) { 1845 ret[ i] = this.parse( arr[i] , undefined , stack);1662 ret[ i ] = this.parse( arr[ i ], undefined, stack ); 1846 1663 } 1847 1664 this.down(); … … 1850 1667 1851 1668 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 ); 1669 dump = { 1670 1671 // objType is used mostly internally, you can fix a (custom) type in advance 1672 parse: function( obj, objType, stack ) { 1673 stack = stack || []; 1674 var res, parser, parserType, 1675 inStack = inArray( obj, stack ); 1861 1676 1862 1677 if ( inStack !== -1 ) { 1863 return "recursion(" + ( inStack - stack.length) + ")";1678 return "recursion(" + ( inStack - stack.length ) + ")"; 1864 1679 } 1865 if ( type === "function" ) { 1680 1681 objType = objType || this.typeOf( obj ); 1682 parser = this.parsers[ objType ]; 1683 parserType = typeof parser; 1684 1685 if ( parserType === "function" ) { 1866 1686 stack.push( obj ); 1867 1687 res = parser.call( this, obj, stack ); … … 1869 1689 return res; 1870 1690 } 1871 return ( type === "string" ) ? parser : this.parsers.error;1691 return ( parserType === "string" ) ? parser : this.parsers.error; 1872 1692 }, 1873 1693 typeOf: function( obj ) { … … 1877 1697 } else if ( typeof obj === "undefined" ) { 1878 1698 type = "undefined"; 1879 } else if ( QUnit.is( "regexp", obj ) ) {1699 } else if ( QUnit.is( "regexp", obj ) ) { 1880 1700 type = "regexp"; 1881 } else if ( QUnit.is( "date", obj ) ) {1701 } else if ( QUnit.is( "date", obj ) ) { 1882 1702 type = "date"; 1883 } else if ( QUnit.is( "function", obj ) ) {1703 } else if ( QUnit.is( "function", obj ) ) { 1884 1704 type = "function"; 1885 } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { 1705 } else if ( obj.setInterval !== undefined && 1706 obj.document !== undefined && 1707 obj.nodeType === undefined ) { 1886 1708 type = "window"; 1887 1709 } else if ( obj.nodeType === 9 ) { … … 1890 1712 type = "node"; 1891 1713 } else if ( 1714 1892 1715 // native arrays 1893 1716 toString.call( obj ) === "[object Array]" || 1717 1894 1718 // 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" ) ) ) 1719 ( typeof obj.length === "number" && obj.item !== undefined && 1720 ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null && 1721 obj[ 0 ] === undefined ) ) ) 1896 1722 ) { 1897 1723 type = "array"; … … 1904 1730 }, 1905 1731 separator: function() { 1906 return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? " " : " ";1732 return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? " " : " "; 1907 1733 }, 1908 1734 // extra can be a number, shortcut for increasing-calling-decreasing … … 1913 1739 var chr = this.indentChar; 1914 1740 if ( this.HTML ) { 1915 chr = chr.replace( /\t/g, " " ).replace( / /g, "& nbsp;" );1741 chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); 1916 1742 } 1917 return new Array( this.depth + ( extra || 0 ) ).join( chr);1743 return new Array( this.depth + ( extra || 0 ) ).join( chr ); 1918 1744 }, 1919 1745 up: function( a ) { … … 1924 1750 }, 1925 1751 setParser: function( name, parser ) { 1926 this.parsers[ name] = parser;1752 this.parsers[ name ] = parser; 1927 1753 }, 1928 1754 // The next 3 are exposed so you can use them … … 1932 1758 // 1933 1759 depth: 1, 1934 // This is the list of parsers, to modify them, use jsDump.setParser 1760 maxDepth: QUnit.config.maxDepth, 1761 1762 // This is the list of parsers, to modify them, use dump.setParser 1935 1763 parsers: { 1936 1764 window: "[Window]", 1937 1765 document: "[Document]", 1938 error: function( error) {1766 error: function( error ) { 1939 1767 return "Error(\"" + error.message + "\")"; 1940 1768 }, … … 1944 1772 "function": function( fn ) { 1945 1773 var ret = "function", 1774 1946 1775 // functions never have name in IE 1947 name = "name" in fn ? fn.name : ( reName.exec(fn) || [])[1];1776 name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ]; 1948 1777 1949 1778 if ( name ) { … … 1952 1781 ret += "( "; 1953 1782 1954 ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" );1955 return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" );1783 ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" ); 1784 return join( ret, dump.parse( fn, "functionCode" ), "}" ); 1956 1785 }, 1957 1786 array: array, … … 1959 1788 "arguments": array, 1960 1789 object: function( map, stack ) { 1961 /*jshint forin:false */ 1962 var ret = [ ], keys, key, val, i; 1963 QUnit.jsDump.up(); 1790 var keys, key, val, i, nonEnumerableProperties, 1791 ret = []; 1792 1793 if ( dump.maxDepth && dump.depth > dump.maxDepth ) { 1794 return "[object Object]"; 1795 } 1796 1797 dump.up(); 1964 1798 keys = []; 1965 1799 for ( key in map ) { 1966 1800 keys.push( key ); 1801 } 1802 1803 // Some properties are not always enumerable on Error objects. 1804 nonEnumerableProperties = [ "message", "name" ]; 1805 for ( i in nonEnumerableProperties ) { 1806 key = nonEnumerableProperties[ i ]; 1807 if ( key in map && inArray( key, keys ) < 0 ) { 1808 keys.push( key ); 1809 } 1967 1810 } 1968 1811 keys.sort(); … … 1970 1813 key = keys[ i ]; 1971 1814 val = map[ key ]; 1972 ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); 1815 ret.push( dump.parse( key, "key" ) + ": " + 1816 dump.parse( val, undefined, stack ) ); 1973 1817 } 1974 QUnit.jsDump.down();1818 dump.down(); 1975 1819 return join( "{", ret, "}" ); 1976 1820 }, 1977 1821 node: function( node ) { 1978 1822 var len, i, val, 1979 open = QUnit.jsDump.HTML ? "<" : "<",1980 close = QUnit.jsDump.HTML ? ">" : ">",1823 open = dump.HTML ? "<" : "<", 1824 close = dump.HTML ? ">" : ">", 1981 1825 tag = node.nodeName.toLowerCase(), 1982 1826 ret = open + tag, … … 1985 1829 if ( attrs ) { 1986 1830 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". 1831 val = attrs[ i ].nodeValue; 1832 1833 // IE6 includes all attributes in .attributes, even ones not explicitly 1834 // set. Those have values like undefined, null, 0, false, "" or 1835 // "inherit". 1990 1836 if ( val && val !== "inherit" ) { 1991 ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" ); 1837 ret += " " + attrs[ i ].nodeName + "=" + 1838 dump.parse( val, "attribute" ); 1992 1839 } 1993 1840 } … … 2002 1849 return ret + open + "/" + tag + close; 2003 1850 }, 1851 2004 1852 // function calls it internally, it's the arguments part of the function 2005 1853 functionArgs: function( fn ) { … … 2011 1859 } 2012 1860 2013 args = new Array( l);1861 args = new Array( l ); 2014 1862 while ( l-- ) { 1863 2015 1864 // 97 is 'a' 2016 args[ l] = String.fromCharCode(97+l);1865 args[ l ] = String.fromCharCode( 97 + l ); 2017 1866 } 2018 1867 return " " + args.join( ", " ) + " "; … … 2038 1887 }; 2039 1888 2040 return jsDump;1889 return dump; 2041 1890 }()); 2042 1891 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 1892 // back compat 1893 QUnit.jsDump = QUnit.dump; 1894 1895 // For browser, export only select globals 1896 if ( typeof window !== "undefined" ) { 1897 1898 // Deprecated 1899 // Extend assert methods to QUnit and Global scope through Backwards compatibility 1900 (function() { 1901 var i, 1902 assertions = Assert.prototype; 1903 1904 function applyCurrent( current ) { 1905 return function() { 1906 var assert = new Assert( QUnit.config.current ); 1907 current.apply( assert, arguments ); 1908 }; 1909 } 1910 1911 for ( i in assertions ) { 1912 QUnit[ i ] = applyCurrent( assertions[ i ] ); 1913 } 1914 })(); 1915 1916 (function() { 1917 var i, l, 1918 keys = [ 1919 "test", 1920 "module", 1921 "expect", 1922 "asyncTest", 1923 "start", 1924 "stop", 1925 "ok", 1926 "notOk", 1927 "equal", 1928 "notEqual", 1929 "propEqual", 1930 "notPropEqual", 1931 "deepEqual", 1932 "notDeepEqual", 1933 "strictEqual", 1934 "notStrictEqual", 1935 "throws" 1936 ]; 1937 1938 for ( i = 0, l = keys.length; i < l; i++ ) { 1939 window[ keys[ i ] ] = QUnit[ keys[ i ] ]; 1940 } 1941 })(); 1942 1943 window.QUnit = QUnit; 1944 } 1945 1946 // For nodejs 1947 if ( typeof module !== "undefined" && module && module.exports ) { 1948 module.exports = QUnit; 1949 1950 // For consistency with CommonJS environments' exports 1951 module.exports.QUnit = QUnit; 1952 } 1953 1954 // For CommonJS with exports, but without module.exports, like Rhino 1955 if ( typeof exports !== "undefined" && exports ) { 1956 exports.QUnit = QUnit; 1957 } 1958 1959 if ( typeof define === "function" && define.amd ) { 1960 define( function() { 1961 return QUnit; 1962 } ); 1963 QUnit.config.autostart = false; 1964 } 1965 1966 // Get a reference to the global object, like window in browsers 1967 }( (function() { 1968 return this; 1969 })() )); 1970 1971 /*istanbul ignore next */ 1972 // jscs:disable maximumLineLength 2058 1973 /* 2059 * Javascript Diff Algorithm2060 * By John Resig (http://ejohn.org/)2061 * Modified by Chu Alan "sprite"1974 * This file is a modified version of google-diff-match-patch's JavaScript implementation 1975 * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js), 1976 * modifications are licensed as more fully set forth in LICENSE.txt. 2062 1977 * 2063 * Released under the MIT license. 1978 * The original source of google-diff-match-patch is attributable and licensed as follows: 1979 * 1980 * Copyright 2006 Google Inc. 1981 * http://code.google.com/p/google-diff-match-patch/ 1982 * 1983 * Licensed under the Apache License, Version 2.0 (the "License"); 1984 * you may not use this file except in compliance with the License. 1985 * You may obtain a copy of the License at 1986 * 1987 * http://www.apache.org/licenses/LICENSE-2.0 1988 * 1989 * Unless required by applicable law or agreed to in writing, software 1990 * distributed under the License is distributed on an "AS IS" BASIS, 1991 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1992 * See the License for the specific language governing permissions and 1993 * limitations under the License. 2064 1994 * 2065 1995 * More Info: 2066 * http ://ejohn.org/projects/javascript-diff-algorithm/1996 * https://code.google.com/p/google-diff-match-patch/ 2067 1997 * 2068 1998 * Usage: QUnit.diff(expected, actual) 2069 1999 * 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"2000 * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) === "the quick <del>brown </del> fox jump<ins>s</ins><del>ed</del over" 2071 2001 */ 2072 2002 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 }; 2003 2004 function DiffMatchPatch() { 2005 2006 // Defaults. 2007 // Redefine these in your program to override the defaults. 2008 2009 // Number of seconds to map a diff before giving up (0 for infinity). 2010 this.DiffTimeout = 1.0; 2011 // Cost of an empty edit operation in terms of edit characters. 2012 this.DiffEditCost = 4; 2013 } 2014 2015 // DIFF FUNCTIONS 2016 2017 /** 2018 * The data structure representing a diff is an array of tuples: 2019 * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']] 2020 * which means: delete 'Hello', add 'Goodbye' and keep ' world.' 2021 */ 2022 var DIFF_DELETE = -1, 2023 DIFF_INSERT = 1, 2024 DIFF_EQUAL = 0; 2025 2026 /** 2027 * Find the differences between two texts. Simplifies the problem by stripping 2028 * any common prefix or suffix off the texts before diffing. 2029 * @param {string} text1 Old string to be diffed. 2030 * @param {string} text2 New string to be diffed. 2031 * @param {boolean=} optChecklines Optional speedup flag. If present and false, 2032 * then don't run a line-level diff first to identify the changed areas. 2033 * Defaults to true, which does a faster, slightly less optimal diff. 2034 * @param {number} optDeadline Optional time when the diff should be complete 2035 * by. Used internally for recursive calls. Users should set DiffTimeout 2036 * instead. 2037 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples. 2038 */ 2039 DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines, optDeadline ) { 2040 var deadline, checklines, commonlength, 2041 commonprefix, commonsuffix, diffs; 2042 // Set a deadline by which time the diff must be complete. 2043 if ( typeof optDeadline === "undefined" ) { 2044 if ( this.DiffTimeout <= 0 ) { 2045 optDeadline = Number.MAX_VALUE; 2046 } else { 2047 optDeadline = ( new Date() ).getTime() + this.DiffTimeout * 1000; 2048 } 2049 } 2050 deadline = optDeadline; 2051 2052 // Check for null inputs. 2053 if ( text1 === null || text2 === null ) { 2054 throw new Error( "Null input. (DiffMain)" ); 2055 } 2056 2057 // Check for equality (speedup). 2058 if ( text1 === text2 ) { 2059 if ( text1 ) { 2060 return [ 2061 [ DIFF_EQUAL, text1 ] 2062 ]; 2063 } 2064 return []; 2065 } 2066 2067 if ( typeof optChecklines === "undefined" ) { 2068 optChecklines = true; 2069 } 2070 2071 checklines = optChecklines; 2072 2073 // Trim off common prefix (speedup). 2074 commonlength = this.diffCommonPrefix( text1, text2 ); 2075 commonprefix = text1.substring( 0, commonlength ); 2076 text1 = text1.substring( commonlength ); 2077 text2 = text2.substring( commonlength ); 2078 2079 // Trim off common suffix (speedup). 2080 ///////// 2081 commonlength = this.diffCommonSuffix( text1, text2 ); 2082 commonsuffix = text1.substring( text1.length - commonlength ); 2083 text1 = text1.substring( 0, text1.length - commonlength ); 2084 text2 = text2.substring( 0, text2.length - commonlength ); 2085 2086 // Compute the diff on the middle block. 2087 diffs = this.diffCompute( text1, text2, checklines, deadline ); 2088 2089 // Restore the prefix and suffix. 2090 if ( commonprefix ) { 2091 diffs.unshift( [ DIFF_EQUAL, commonprefix ] ); 2092 } 2093 if ( commonsuffix ) { 2094 diffs.push( [ DIFF_EQUAL, commonsuffix ] ); 2095 } 2096 this.diffCleanupMerge( diffs ); 2097 return diffs; 2098 }; 2099 2100 /** 2101 * Reduce the number of edits by eliminating operationally trivial equalities. 2102 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples. 2103 */ 2104 DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) { 2105 var changes, equalities, equalitiesLength, lastequality, 2106 pointer, preIns, preDel, postIns, postDel; 2107 changes = false; 2108 equalities = []; // Stack of indices where equalities are found. 2109 equalitiesLength = 0; // Keeping our own length var is faster in JS. 2110 /** @type {?string} */ 2111 lastequality = null; 2112 // Always equal to diffs[equalities[equalitiesLength - 1]][1] 2113 pointer = 0; // Index of current position. 2114 // Is there an insertion operation before the last equality. 2115 preIns = false; 2116 // Is there a deletion operation before the last equality. 2117 preDel = false; 2118 // Is there an insertion operation after the last equality. 2119 postIns = false; 2120 // Is there a deletion operation after the last equality. 2121 postDel = false; 2122 while ( pointer < diffs.length ) { 2123 if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found. 2124 if ( diffs[ pointer ][ 1 ].length < this.DiffEditCost && ( postIns || postDel ) ) { 2125 // Candidate found. 2126 equalities[ equalitiesLength++ ] = pointer; 2127 preIns = postIns; 2128 preDel = postDel; 2129 lastequality = diffs[ pointer ][ 1 ]; 2130 } else { 2131 // Not a candidate, and can never become one. 2132 equalitiesLength = 0; 2133 lastequality = null; 2134 } 2135 postIns = postDel = false; 2136 } else { // An insertion or deletion. 2137 if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) { 2138 postDel = true; 2139 } else { 2140 postIns = true; 2141 } 2142 /* 2143 * Five types to be split: 2144 * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del> 2145 * <ins>A</ins>X<ins>C</ins><del>D</del> 2146 * <ins>A</ins><del>B</del>X<ins>C</ins> 2147 * <ins>A</del>X<ins>C</ins><del>D</del> 2148 * <ins>A</ins><del>B</del>X<del>C</del> 2149 */ 2150 if ( lastequality && ( ( preIns && preDel && postIns && postDel ) || 2151 ( ( lastequality.length < this.DiffEditCost / 2 ) && 2152 ( preIns + preDel + postIns + postDel ) === 3 ) ) ) { 2153 // Duplicate record. 2154 diffs.splice( equalities[equalitiesLength - 1], 0, [ DIFF_DELETE, lastequality ] ); 2155 // Change second copy to insert. 2156 diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT; 2157 equalitiesLength--; // Throw away the equality we just deleted; 2158 lastequality = null; 2159 if (preIns && preDel) { 2160 // No changes made which could affect previous entry, keep going. 2161 postIns = postDel = true; 2162 equalitiesLength = 0; 2163 } else { 2164 equalitiesLength--; // Throw away the previous equality. 2165 pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1; 2166 postIns = postDel = false; 2167 } 2168 changes = true; 2169 } 2170 } 2171 pointer++; 2172 } 2173 2174 if ( changes ) { 2175 this.diffCleanupMerge( diffs ); 2176 } 2177 }; 2178 2179 /** 2180 * Convert a diff array into a pretty HTML report. 2181 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples. 2182 * @param {integer} string to be beautified. 2183 * @return {string} HTML representation. 2184 */ 2185 DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) { 2186 var op, data, x, html = []; 2187 for ( x = 0; x < diffs.length; x++ ) { 2188 op = diffs[x][0]; // Operation (insert, delete, equal) 2189 data = diffs[x][1]; // Text of change. 2190 switch ( op ) { 2191 case DIFF_INSERT: 2192 html[x] = "<ins>" + data + "</ins>"; 2193 break; 2194 case DIFF_DELETE: 2195 html[x] = "<del>" + data + "</del>"; 2196 break; 2197 case DIFF_EQUAL: 2198 html[x] = "<span>" + data + "</span>"; 2199 break; 2200 } 2201 } 2202 return html.join(""); 2203 }; 2204 2205 /** 2206 * Determine the common prefix of two strings. 2207 * @param {string} text1 First string. 2208 * @param {string} text2 Second string. 2209 * @return {number} The number of characters common to the start of each 2210 * string. 2211 */ 2212 DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) { 2213 var pointermid, pointermax, pointermin, pointerstart; 2214 // Quick check for common null cases. 2215 if ( !text1 || !text2 || text1.charAt(0) !== text2.charAt(0) ) { 2216 return 0; 2217 } 2218 // Binary search. 2219 // Performance analysis: http://neil.fraser.name/news/2007/10/09/ 2220 pointermin = 0; 2221 pointermax = Math.min( text1.length, text2.length ); 2222 pointermid = pointermax; 2223 pointerstart = 0; 2224 while ( pointermin < pointermid ) { 2225 if ( text1.substring( pointerstart, pointermid ) === text2.substring( pointerstart, pointermid ) ) { 2226 pointermin = pointermid; 2227 pointerstart = pointermin; 2228 } else { 2229 pointermax = pointermid; 2230 } 2231 pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin ); 2232 } 2233 return pointermid; 2234 }; 2235 2236 /** 2237 * Determine the common suffix of two strings. 2238 * @param {string} text1 First string. 2239 * @param {string} text2 Second string. 2240 * @return {number} The number of characters common to the end of each string. 2241 */ 2242 DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) { 2243 var pointermid, pointermax, pointermin, pointerend; 2244 // Quick check for common null cases. 2245 if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) { 2246 return 0; 2247 } 2248 // Binary search. 2249 // Performance analysis: http://neil.fraser.name/news/2007/10/09/ 2250 pointermin = 0; 2251 pointermax = Math.min(text1.length, text2.length); 2252 pointermid = pointermax; 2253 pointerend = 0; 2254 while ( pointermin < pointermid ) { 2255 if (text1.substring( text1.length - pointermid, text1.length - pointerend ) === 2256 text2.substring( text2.length - pointermid, text2.length - pointerend ) ) { 2257 pointermin = pointermid; 2258 pointerend = pointermin; 2259 } else { 2260 pointermax = pointermid; 2261 } 2262 pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin ); 2263 } 2264 return pointermid; 2265 }; 2266 2267 /** 2268 * Find the differences between two texts. Assumes that the texts do not 2269 * have any common prefix or suffix. 2270 * @param {string} text1 Old string to be diffed. 2271 * @param {string} text2 New string to be diffed. 2272 * @param {boolean} checklines Speedup flag. If false, then don't run a 2273 * line-level diff first to identify the changed areas. 2274 * If true, then run a faster, slightly less optimal diff. 2275 * @param {number} deadline Time when the diff should be complete by. 2276 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples. 2277 * @private 2278 */ 2279 DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) { 2280 var diffs, longtext, shorttext, i, hm, 2281 text1A, text2A, text1B, text2B, 2282 midCommon, diffsA, diffsB; 2283 2284 if ( !text1 ) { 2285 // Just add some text (speedup). 2286 return [ 2287 [ DIFF_INSERT, text2 ] 2288 ]; 2289 } 2290 2291 if (!text2) { 2292 // Just delete some text (speedup). 2293 return [ 2294 [ DIFF_DELETE, text1 ] 2295 ]; 2296 } 2297 2298 longtext = text1.length > text2.length ? text1 : text2; 2299 shorttext = text1.length > text2.length ? text2 : text1; 2300 i = longtext.indexOf( shorttext ); 2301 if ( i !== -1 ) { 2302 // Shorter text is inside the longer text (speedup). 2303 diffs = [ 2304 [ DIFF_INSERT, longtext.substring( 0, i ) ], 2305 [ DIFF_EQUAL, shorttext ], 2306 [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ] 2307 ]; 2308 // Swap insertions for deletions if diff is reversed. 2309 if ( text1.length > text2.length ) { 2310 diffs[0][0] = diffs[2][0] = DIFF_DELETE; 2311 } 2312 return diffs; 2313 } 2314 2315 if ( shorttext.length === 1 ) { 2316 // Single character string. 2317 // After the previous speedup, the character can't be an equality. 2318 return [ 2319 [ DIFF_DELETE, text1 ], 2320 [ DIFF_INSERT, text2 ] 2321 ]; 2322 } 2323 2324 // Check to see if the problem can be split in two. 2325 hm = this.diffHalfMatch(text1, text2); 2326 if (hm) { 2327 // A half-match was found, sort out the return data. 2328 text1A = hm[0]; 2329 text1B = hm[1]; 2330 text2A = hm[2]; 2331 text2B = hm[3]; 2332 midCommon = hm[4]; 2333 // Send both pairs off for separate processing. 2334 diffsA = this.DiffMain(text1A, text2A, checklines, deadline); 2335 diffsB = this.DiffMain(text1B, text2B, checklines, deadline); 2336 // Merge the results. 2337 return diffsA.concat([ 2338 [ DIFF_EQUAL, midCommon ] 2339 ], diffsB); 2340 } 2341 2342 if (checklines && text1.length > 100 && text2.length > 100) { 2343 return this.diffLineMode(text1, text2, deadline); 2344 } 2345 2346 return this.diffBisect(text1, text2, deadline); 2347 }; 2348 2349 /** 2350 * Do the two texts share a substring which is at least half the length of the 2351 * longer text? 2352 * This speedup can produce non-minimal diffs. 2353 * @param {string} text1 First string. 2354 * @param {string} text2 Second string. 2355 * @return {Array.<string>} Five element Array, containing the prefix of 2356 * text1, the suffix of text1, the prefix of text2, the suffix of 2357 * text2 and the common middle. Or null if there was no match. 2358 * @private 2359 */ 2360 DiffMatchPatch.prototype.diffHalfMatch = function(text1, text2) { 2361 var longtext, shorttext, dmp, 2362 text1A, text2B, text2A, text1B, midCommon, 2363 hm1, hm2, hm; 2364 if (this.DiffTimeout <= 0) { 2365 // Don't risk returning a non-optimal diff if we have unlimited time. 2366 return null; 2367 } 2368 longtext = text1.length > text2.length ? text1 : text2; 2369 shorttext = text1.length > text2.length ? text2 : text1; 2370 if (longtext.length < 4 || shorttext.length * 2 < longtext.length) { 2371 return null; // Pointless. 2372 } 2373 dmp = this; // 'this' becomes 'window' in a closure. 2374 2375 /** 2376 * Does a substring of shorttext exist within longtext such that the substring 2377 * is at least half the length of longtext? 2378 * Closure, but does not reference any external variables. 2379 * @param {string} longtext Longer string. 2380 * @param {string} shorttext Shorter string. 2381 * @param {number} i Start index of quarter length substring within longtext. 2382 * @return {Array.<string>} Five element Array, containing the prefix of 2383 * longtext, the suffix of longtext, the prefix of shorttext, the suffix 2384 * of shorttext and the common middle. Or null if there was no match. 2385 * @private 2386 */ 2387 function diffHalfMatchI(longtext, shorttext, i) { 2388 var seed, j, bestCommon, prefixLength, suffixLength, 2389 bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB; 2390 // Start with a 1/4 length substring at position i as a seed. 2391 seed = longtext.substring(i, i + Math.floor(longtext.length / 4)); 2392 j = -1; 2393 bestCommon = ""; 2394 while ((j = shorttext.indexOf(seed, j + 1)) !== -1) { 2395 prefixLength = dmp.diffCommonPrefix(longtext.substring(i), 2396 shorttext.substring(j)); 2397 suffixLength = dmp.diffCommonSuffix(longtext.substring(0, i), 2398 shorttext.substring(0, j)); 2399 if (bestCommon.length < suffixLength + prefixLength) { 2400 bestCommon = shorttext.substring(j - suffixLength, j) + 2401 shorttext.substring(j, j + prefixLength); 2402 bestLongtextA = longtext.substring(0, i - suffixLength); 2403 bestLongtextB = longtext.substring(i + prefixLength); 2404 bestShorttextA = shorttext.substring(0, j - suffixLength); 2405 bestShorttextB = shorttext.substring(j + prefixLength); 2406 } 2407 } 2408 if (bestCommon.length * 2 >= longtext.length) { 2409 return [ bestLongtextA, bestLongtextB, 2410 bestShorttextA, bestShorttextB, bestCommon 2411 ]; 2412 } else { 2413 return null; 2414 } 2415 } 2416 2417 // First check if the second quarter is the seed for a half-match. 2418 hm1 = diffHalfMatchI(longtext, shorttext, 2419 Math.ceil(longtext.length / 4)); 2420 // Check again based on the third quarter. 2421 hm2 = diffHalfMatchI(longtext, shorttext, 2422 Math.ceil(longtext.length / 2)); 2423 if (!hm1 && !hm2) { 2424 return null; 2425 } else if (!hm2) { 2426 hm = hm1; 2427 } else if (!hm1) { 2428 hm = hm2; 2429 } else { 2430 // Both matched. Select the longest. 2431 hm = hm1[4].length > hm2[4].length ? hm1 : hm2; 2432 } 2433 2434 // A half-match was found, sort out the return data. 2435 text1A, text1B, text2A, text2B; 2436 if (text1.length > text2.length) { 2437 text1A = hm[0]; 2438 text1B = hm[1]; 2439 text2A = hm[2]; 2440 text2B = hm[3]; 2441 } else { 2442 text2A = hm[0]; 2443 text2B = hm[1]; 2444 text1A = hm[2]; 2445 text1B = hm[3]; 2446 } 2447 midCommon = hm[4]; 2448 return [ text1A, text1B, text2A, text2B, midCommon ]; 2449 }; 2450 2451 /** 2452 * Do a quick line-level diff on both strings, then rediff the parts for 2453 * greater accuracy. 2454 * This speedup can produce non-minimal diffs. 2455 * @param {string} text1 Old string to be diffed. 2456 * @param {string} text2 New string to be diffed. 2457 * @param {number} deadline Time when the diff should be complete by. 2458 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples. 2459 * @private 2460 */ 2461 DiffMatchPatch.prototype.diffLineMode = function(text1, text2, deadline) { 2462 var a, diffs, linearray, pointer, countInsert, 2463 countDelete, textInsert, textDelete, j; 2464 // Scan the text on a line-by-line basis first. 2465 a = this.diffLinesToChars(text1, text2); 2466 text1 = a.chars1; 2467 text2 = a.chars2; 2468 linearray = a.lineArray; 2469 2470 diffs = this.DiffMain(text1, text2, false, deadline); 2471 2472 // Convert the diff back to original text. 2473 this.diffCharsToLines(diffs, linearray); 2474 // Eliminate freak matches (e.g. blank lines) 2475 this.diffCleanupSemantic(diffs); 2476 2477 // Rediff any replacement blocks, this time character-by-character. 2478 // Add a dummy entry at the end. 2479 diffs.push( [ DIFF_EQUAL, "" ] ); 2480 pointer = 0; 2481 countDelete = 0; 2482 countInsert = 0; 2483 textDelete = ""; 2484 textInsert = ""; 2485 while (pointer < diffs.length) { 2486 switch ( diffs[pointer][0] ) { 2487 case DIFF_INSERT: 2488 countInsert++; 2489 textInsert += diffs[pointer][1]; 2490 break; 2491 case DIFF_DELETE: 2492 countDelete++; 2493 textDelete += diffs[pointer][1]; 2494 break; 2495 case DIFF_EQUAL: 2496 // Upon reaching an equality, check for prior redundancies. 2497 if (countDelete >= 1 && countInsert >= 1) { 2498 // Delete the offending records and add the merged ones. 2499 diffs.splice(pointer - countDelete - countInsert, 2500 countDelete + countInsert); 2501 pointer = pointer - countDelete - countInsert; 2502 a = this.DiffMain(textDelete, textInsert, false, deadline); 2503 for (j = a.length - 1; j >= 0; j--) { 2504 diffs.splice( pointer, 0, a[j] ); 2505 } 2506 pointer = pointer + a.length; 2507 } 2508 countInsert = 0; 2509 countDelete = 0; 2510 textDelete = ""; 2511 textInsert = ""; 2512 break; 2513 } 2514 pointer++; 2515 } 2516 diffs.pop(); // Remove the dummy entry at the end. 2517 2518 return diffs; 2519 }; 2520 2521 /** 2522 * Find the 'middle snake' of a diff, split the problem in two 2523 * and return the recursively constructed diff. 2524 * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. 2525 * @param {string} text1 Old string to be diffed. 2526 * @param {string} text2 New string to be diffed. 2527 * @param {number} deadline Time at which to bail if not yet complete. 2528 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples. 2529 * @private 2530 */ 2531 DiffMatchPatch.prototype.diffBisect = function(text1, text2, deadline) { 2532 var text1Length, text2Length, maxD, vOffset, vLength, 2533 v1, v2, x, delta, front, k1start, k1end, k2start, 2534 k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2; 2535 // Cache the text lengths to prevent multiple calls. 2536 text1Length = text1.length; 2537 text2Length = text2.length; 2538 maxD = Math.ceil((text1Length + text2Length) / 2); 2539 vOffset = maxD; 2540 vLength = 2 * maxD; 2541 v1 = new Array(vLength); 2542 v2 = new Array(vLength); 2543 // Setting all elements to -1 is faster in Chrome & Firefox than mixing 2544 // integers and undefined. 2545 for (x = 0; x < vLength; x++) { 2546 v1[x] = -1; 2547 v2[x] = -1; 2548 } 2549 v1[vOffset + 1] = 0; 2550 v2[vOffset + 1] = 0; 2551 delta = text1Length - text2Length; 2552 // If the total number of characters is odd, then the front path will collide 2553 // with the reverse path. 2554 front = (delta % 2 !== 0); 2555 // Offsets for start and end of k loop. 2556 // Prevents mapping of space beyond the grid. 2557 k1start = 0; 2558 k1end = 0; 2559 k2start = 0; 2560 k2end = 0; 2561 for (d = 0; d < maxD; d++) { 2562 // Bail out if deadline is reached. 2563 if ((new Date()).getTime() > deadline) { 2564 break; 2565 } 2566 2567 // Walk the front path one step. 2568 for (k1 = -d + k1start; k1 <= d - k1end; k1 += 2) { 2569 k1Offset = vOffset + k1; 2570 if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) { 2571 x1 = v1[k1Offset + 1]; 2572 } else { 2573 x1 = v1[k1Offset - 1] + 1; 2574 } 2575 y1 = x1 - k1; 2576 while (x1 < text1Length && y1 < text2Length && 2577 text1.charAt(x1) === text2.charAt(y1)) { 2578 x1++; 2579 y1++; 2580 } 2581 v1[k1Offset] = x1; 2582 if (x1 > text1Length) { 2583 // Ran off the right of the graph. 2584 k1end += 2; 2585 } else if (y1 > text2Length) { 2586 // Ran off the bottom of the graph. 2587 k1start += 2; 2588 } else if (front) { 2589 k2Offset = vOffset + delta - k1; 2590 if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) { 2591 // Mirror x2 onto top-left coordinate system. 2592 x2 = text1Length - v2[k2Offset]; 2593 if (x1 >= x2) { 2594 // Overlap detected. 2595 return this.diffBisectSplit(text1, text2, x1, y1, deadline); 2596 } 2597 } 2598 } 2599 } 2600 2601 // Walk the reverse path one step. 2602 for (k2 = -d + k2start; k2 <= d - k2end; k2 += 2) { 2603 k2Offset = vOffset + k2; 2604 if ( k2 === -d || (k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) { 2605 x2 = v2[k2Offset + 1]; 2606 } else { 2607 x2 = v2[k2Offset - 1] + 1; 2608 } 2609 y2 = x2 - k2; 2610 while (x2 < text1Length && y2 < text2Length && 2611 text1.charAt(text1Length - x2 - 1) === 2612 text2.charAt(text2Length - y2 - 1)) { 2613 x2++; 2614 y2++; 2615 } 2616 v2[k2Offset] = x2; 2617 if (x2 > text1Length) { 2618 // Ran off the left of the graph. 2619 k2end += 2; 2620 } else if (y2 > text2Length) { 2621 // Ran off the top of the graph. 2622 k2start += 2; 2623 } else if (!front) { 2624 k1Offset = vOffset + delta - k2; 2625 if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) { 2626 x1 = v1[k1Offset]; 2627 y1 = vOffset + x1 - k1Offset; 2628 // Mirror x2 onto top-left coordinate system. 2629 x2 = text1Length - x2; 2630 if (x1 >= x2) { 2631 // Overlap detected. 2632 return this.diffBisectSplit(text1, text2, x1, y1, deadline); 2633 } 2634 } 2635 } 2636 } 2637 } 2638 // Diff took too long and hit the deadline or 2639 // number of diffs equals number of characters, no commonality at all. 2640 return [ 2641 [ DIFF_DELETE, text1 ], 2642 [ DIFF_INSERT, text2 ] 2643 ]; 2644 }; 2645 2646 /** 2647 * Given the location of the 'middle snake', split the diff in two parts 2648 * and recurse. 2649 * @param {string} text1 Old string to be diffed. 2650 * @param {string} text2 New string to be diffed. 2651 * @param {number} x Index of split point in text1. 2652 * @param {number} y Index of split point in text2. 2653 * @param {number} deadline Time at which to bail if not yet complete. 2654 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples. 2655 * @private 2656 */ 2657 DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) { 2658 var text1a, text1b, text2a, text2b, diffs, diffsb; 2659 text1a = text1.substring(0, x); 2660 text2a = text2.substring(0, y); 2661 text1b = text1.substring(x); 2662 text2b = text2.substring(y); 2663 2664 // Compute both diffs serially. 2665 diffs = this.DiffMain(text1a, text2a, false, deadline); 2666 diffsb = this.DiffMain(text1b, text2b, false, deadline); 2667 2668 return diffs.concat(diffsb); 2669 }; 2670 2671 /** 2672 * Reduce the number of edits by eliminating semantically trivial equalities. 2673 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples. 2674 */ 2675 DiffMatchPatch.prototype.diffCleanupSemantic = function(diffs) { 2676 var changes, equalities, equalitiesLength, lastequality, 2677 pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1, 2678 lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2; 2679 changes = false; 2680 equalities = []; // Stack of indices where equalities are found. 2681 equalitiesLength = 0; // Keeping our own length var is faster in JS. 2682 /** @type {?string} */ 2683 lastequality = null; 2684 // Always equal to diffs[equalities[equalitiesLength - 1]][1] 2685 pointer = 0; // Index of current position. 2686 // Number of characters that changed prior to the equality. 2687 lengthInsertions1 = 0; 2688 lengthDeletions1 = 0; 2689 // Number of characters that changed after the equality. 2690 lengthInsertions2 = 0; 2691 lengthDeletions2 = 0; 2692 while (pointer < diffs.length) { 2693 if (diffs[pointer][0] === DIFF_EQUAL) { // Equality found. 2694 equalities[equalitiesLength++] = pointer; 2695 lengthInsertions1 = lengthInsertions2; 2696 lengthDeletions1 = lengthDeletions2; 2697 lengthInsertions2 = 0; 2698 lengthDeletions2 = 0; 2699 lastequality = diffs[pointer][1]; 2700 } else { // An insertion or deletion. 2701 if (diffs[pointer][0] === DIFF_INSERT) { 2702 lengthInsertions2 += diffs[pointer][1].length; 2703 } else { 2704 lengthDeletions2 += diffs[pointer][1].length; 2705 } 2706 // Eliminate an equality that is smaller or equal to the edits on both 2707 // sides of it. 2708 if (lastequality && (lastequality.length <= 2709 Math.max(lengthInsertions1, lengthDeletions1)) && 2710 (lastequality.length <= Math.max(lengthInsertions2, 2711 lengthDeletions2))) { 2712 // Duplicate record. 2713 diffs.splice( equalities[ equalitiesLength - 1 ], 0, [ DIFF_DELETE, lastequality ] ); 2714 // Change second copy to insert. 2715 diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; 2716 // Throw away the equality we just deleted. 2717 equalitiesLength--; 2718 // Throw away the previous equality (it needs to be reevaluated). 2719 equalitiesLength--; 2720 pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1; 2721 lengthInsertions1 = 0; // Reset the counters. 2722 lengthDeletions1 = 0; 2723 lengthInsertions2 = 0; 2724 lengthDeletions2 = 0; 2725 lastequality = null; 2726 changes = true; 2727 } 2728 } 2729 pointer++; 2730 } 2731 2732 // Normalize the diff. 2733 if (changes) { 2734 this.diffCleanupMerge(diffs); 2735 } 2736 2737 // Find any overlaps between deletions and insertions. 2738 // e.g: <del>abcxxx</del><ins>xxxdef</ins> 2739 // -> <del>abc</del>xxx<ins>def</ins> 2740 // e.g: <del>xxxabc</del><ins>defxxx</ins> 2741 // -> <ins>def</ins>xxx<del>abc</del> 2742 // Only extract an overlap if it is as big as the edit ahead or behind it. 2743 pointer = 1; 2744 while (pointer < diffs.length) { 2745 if (diffs[pointer - 1][0] === DIFF_DELETE && 2746 diffs[pointer][0] === DIFF_INSERT) { 2747 deletion = diffs[pointer - 1][1]; 2748 insertion = diffs[pointer][1]; 2749 overlapLength1 = this.diffCommonOverlap(deletion, insertion); 2750 overlapLength2 = this.diffCommonOverlap(insertion, deletion); 2751 if (overlapLength1 >= overlapLength2) { 2752 if (overlapLength1 >= deletion.length / 2 || 2753 overlapLength1 >= insertion.length / 2) { 2754 // Overlap found. Insert an equality and trim the surrounding edits. 2755 diffs.splice( pointer, 0, [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ] ); 2756 diffs[pointer - 1][1] = 2757 deletion.substring(0, deletion.length - overlapLength1); 2758 diffs[pointer + 1][1] = insertion.substring(overlapLength1); 2759 pointer++; 2760 } 2761 } else { 2762 if (overlapLength2 >= deletion.length / 2 || 2763 overlapLength2 >= insertion.length / 2) { 2764 // Reverse overlap found. 2765 // Insert an equality and swap and trim the surrounding edits. 2766 diffs.splice( pointer, 0, [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ] ); 2767 diffs[pointer - 1][0] = DIFF_INSERT; 2768 diffs[pointer - 1][1] = 2769 insertion.substring(0, insertion.length - overlapLength2); 2770 diffs[pointer + 1][0] = DIFF_DELETE; 2771 diffs[pointer + 1][1] = 2772 deletion.substring(overlapLength2); 2773 pointer++; 2774 } 2775 } 2776 pointer++; 2777 } 2778 pointer++; 2779 } 2780 }; 2781 2782 /** 2783 * Determine if the suffix of one string is the prefix of another. 2784 * @param {string} text1 First string. 2785 * @param {string} text2 Second string. 2786 * @return {number} The number of characters common to the end of the first 2787 * string and the start of the second string. 2788 * @private 2789 */ 2790 DiffMatchPatch.prototype.diffCommonOverlap = function(text1, text2) { 2791 var text1Length, text2Length, textLength, 2792 best, length, pattern, found; 2793 // Cache the text lengths to prevent multiple calls. 2794 text1Length = text1.length; 2795 text2Length = text2.length; 2796 // Eliminate the null case. 2797 if (text1Length === 0 || text2Length === 0) { 2798 return 0; 2799 } 2800 // Truncate the longer string. 2801 if (text1Length > text2Length) { 2802 text1 = text1.substring(text1Length - text2Length); 2803 } else if (text1Length < text2Length) { 2804 text2 = text2.substring(0, text1Length); 2805 } 2806 textLength = Math.min(text1Length, text2Length); 2807 // Quick check for the worst case. 2808 if (text1 === text2) { 2809 return textLength; 2810 } 2811 2812 // Start by looking for a single character match 2813 // and increase length until no match is found. 2814 // Performance analysis: http://neil.fraser.name/news/2010/11/04/ 2815 best = 0; 2816 length = 1; 2817 while (true) { 2818 pattern = text1.substring(textLength - length); 2819 found = text2.indexOf(pattern); 2820 if (found === -1) { 2821 return best; 2822 } 2823 length += found; 2824 if (found === 0 || text1.substring(textLength - length) === 2825 text2.substring(0, length)) { 2826 best = length; 2827 length++; 2828 } 2829 } 2830 }; 2831 2832 /** 2833 * Split two texts into an array of strings. Reduce the texts to a string of 2834 * hashes where each Unicode character represents one line. 2835 * @param {string} text1 First string. 2836 * @param {string} text2 Second string. 2837 * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}} 2838 * An object containing the encoded text1, the encoded text2 and 2839 * the array of unique strings. 2840 * The zeroth element of the array of unique strings is intentionally blank. 2841 * @private 2842 */ 2843 DiffMatchPatch.prototype.diffLinesToChars = function(text1, text2) { 2844 var lineArray, lineHash, chars1, chars2; 2845 lineArray = []; // e.g. lineArray[4] === 'Hello\n' 2846 lineHash = {}; // e.g. lineHash['Hello\n'] === 4 2847 2848 // '\x00' is a valid character, but various debuggers don't like it. 2849 // So we'll insert a junk entry to avoid generating a null character. 2850 lineArray[0] = ""; 2851 2852 /** 2853 * Split a text into an array of strings. Reduce the texts to a string of 2854 * hashes where each Unicode character represents one line. 2855 * Modifies linearray and linehash through being a closure. 2856 * @param {string} text String to encode. 2857 * @return {string} Encoded string. 2858 * @private 2859 */ 2860 function diffLinesToCharsMunge(text) { 2861 var chars, lineStart, lineEnd, lineArrayLength, line; 2862 chars = ""; 2863 // Walk the text, pulling out a substring for each line. 2864 // text.split('\n') would would temporarily double our memory footprint. 2865 // Modifying text would create many large strings to garbage collect. 2866 lineStart = 0; 2867 lineEnd = -1; 2868 // Keeping our own length variable is faster than looking it up. 2869 lineArrayLength = lineArray.length; 2870 while (lineEnd < text.length - 1) { 2871 lineEnd = text.indexOf("\n", lineStart); 2872 if (lineEnd === -1) { 2873 lineEnd = text.length - 1; 2874 } 2875 line = text.substring(lineStart, lineEnd + 1); 2876 lineStart = lineEnd + 1; 2877 2878 if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : 2879 (lineHash[line] !== undefined)) { 2880 chars += String.fromCharCode( lineHash[ line ] ); 2881 } else { 2882 chars += String.fromCharCode(lineArrayLength); 2883 lineHash[line] = lineArrayLength; 2884 lineArray[lineArrayLength++] = line; 2885 } 2886 } 2887 return chars; 2888 } 2889 2890 chars1 = diffLinesToCharsMunge(text1); 2891 chars2 = diffLinesToCharsMunge(text2); 2892 return { 2893 chars1: chars1, 2894 chars2: chars2, 2895 lineArray: lineArray 2896 }; 2897 }; 2898 2899 /** 2900 * Rehydrate the text in a diff from a string of line hashes to real lines of 2901 * text. 2902 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples. 2903 * @param {!Array.<string>} lineArray Array of unique strings. 2904 * @private 2905 */ 2906 DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) { 2907 var x, chars, text, y; 2908 for ( x = 0; x < diffs.length; x++ ) { 2909 chars = diffs[x][1]; 2910 text = []; 2911 for ( y = 0; y < chars.length; y++ ) { 2912 text[y] = lineArray[chars.charCodeAt(y)]; 2913 } 2914 diffs[x][1] = text.join(""); 2915 } 2916 }; 2917 2918 /** 2919 * Reorder and merge like edit sections. Merge equalities. 2920 * Any edit section can move as long as it doesn't cross an equality. 2921 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples. 2922 */ 2923 DiffMatchPatch.prototype.diffCleanupMerge = function(diffs) { 2924 var pointer, countDelete, countInsert, textInsert, textDelete, 2925 commonlength, changes; 2926 diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end. 2927 pointer = 0; 2928 countDelete = 0; 2929 countInsert = 0; 2930 textDelete = ""; 2931 textInsert = ""; 2932 commonlength; 2933 while (pointer < diffs.length) { 2934 switch ( diffs[ pointer ][ 0 ] ) { 2935 case DIFF_INSERT: 2936 countInsert++; 2937 textInsert += diffs[pointer][1]; 2938 pointer++; 2939 break; 2940 case DIFF_DELETE: 2941 countDelete++; 2942 textDelete += diffs[pointer][1]; 2943 pointer++; 2944 break; 2945 case DIFF_EQUAL: 2946 // Upon reaching an equality, check for prior redundancies. 2947 if (countDelete + countInsert > 1) { 2948 if (countDelete !== 0 && countInsert !== 0) { 2949 // Factor out any common prefixies. 2950 commonlength = this.diffCommonPrefix(textInsert, textDelete); 2951 if (commonlength !== 0) { 2952 if ((pointer - countDelete - countInsert) > 0 && 2953 diffs[pointer - countDelete - countInsert - 1][0] === 2954 DIFF_EQUAL) { 2955 diffs[pointer - countDelete - countInsert - 1][1] += 2956 textInsert.substring(0, commonlength); 2957 } else { 2958 diffs.splice( 0, 0, [ DIFF_EQUAL, 2959 textInsert.substring( 0, commonlength ) 2960 ] ); 2961 pointer++; 2962 } 2963 textInsert = textInsert.substring(commonlength); 2964 textDelete = textDelete.substring(commonlength); 2965 } 2966 // Factor out any common suffixies. 2967 commonlength = this.diffCommonSuffix(textInsert, textDelete); 2968 if (commonlength !== 0) { 2969 diffs[pointer][1] = textInsert.substring(textInsert.length - 2970 commonlength) + diffs[pointer][1]; 2971 textInsert = textInsert.substring(0, textInsert.length - 2972 commonlength); 2973 textDelete = textDelete.substring(0, textDelete.length - 2974 commonlength); 2975 } 2976 } 2977 // Delete the offending records and add the merged ones. 2978 if (countDelete === 0) { 2979 diffs.splice( pointer - countInsert, 2980 countDelete + countInsert, [ DIFF_INSERT, textInsert ] ); 2981 } else if (countInsert === 0) { 2982 diffs.splice( pointer - countDelete, 2983 countDelete + countInsert, [ DIFF_DELETE, textDelete ] ); 2984 } else { 2985 diffs.splice( pointer - countDelete - countInsert, 2986 countDelete + countInsert, [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ] ); 2987 } 2988 pointer = pointer - countDelete - countInsert + 2989 (countDelete ? 1 : 0) + (countInsert ? 1 : 0) + 1; 2990 } else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) { 2991 // Merge this equality with the previous one. 2992 diffs[pointer - 1][1] += diffs[pointer][1]; 2993 diffs.splice(pointer, 1); 2994 } else { 2995 pointer++; 2996 } 2997 countInsert = 0; 2998 countDelete = 0; 2999 textDelete = ""; 3000 textInsert = ""; 3001 break; 3002 } 3003 } 3004 if (diffs[diffs.length - 1][1] === "") { 3005 diffs.pop(); // Remove the dummy entry at the end. 3006 } 3007 3008 // Second pass: look for single edits surrounded on both sides by equalities 3009 // which can be shifted sideways to eliminate an equality. 3010 // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC 3011 changes = false; 3012 pointer = 1; 3013 // Intentionally ignore the first and last element (don't need checking). 3014 while (pointer < diffs.length - 1) { 3015 if (diffs[pointer - 1][0] === DIFF_EQUAL && 3016 diffs[pointer + 1][0] === DIFF_EQUAL) { 3017 // This is a single edit surrounded by equalities. 3018 if ( diffs[ pointer ][ 1 ].substring( diffs[ pointer ][ 1 ].length - 3019 diffs[ pointer - 1 ][ 1 ].length ) === diffs[ pointer - 1 ][ 1 ] ) { 3020 // Shift the edit over the previous equality. 3021 diffs[pointer][1] = diffs[pointer - 1][1] + 3022 diffs[pointer][1].substring(0, diffs[pointer][1].length - 3023 diffs[pointer - 1][1].length); 3024 diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1]; 3025 diffs.splice(pointer - 1, 1); 3026 changes = true; 3027 } else if ( diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer + 1 ][ 1 ].length ) === 3028 diffs[ pointer + 1 ][ 1 ] ) { 3029 // Shift the edit over the next equality. 3030 diffs[pointer - 1][1] += diffs[pointer + 1][1]; 3031 diffs[pointer][1] = 3032 diffs[pointer][1].substring(diffs[pointer + 1][1].length) + 3033 diffs[pointer + 1][1]; 3034 diffs.splice(pointer + 1, 1); 3035 changes = true; 3036 } 3037 } 3038 pointer++; 3039 } 3040 // If shifts were made, the diff needs reordering and another shift sweep. 3041 if (changes) { 3042 this.diffCleanupMerge(diffs); 3043 } 3044 }; 3045 3046 return function(o, n) { 3047 var diff, output, text; 3048 diff = new DiffMatchPatch(); 3049 output = diff.DiffMain(o, n); 3050 //console.log(output); 3051 diff.diffCleanupEfficiency(output); 3052 text = diff.diffPrettyHtml(output); 3053 3054 return text; 3055 }; 3056 }()); 3057 // jscs:enable 3058 3059 (function() { 3060 3061 // Deprecated QUnit.init - Ref #530 3062 // Re-initialize the configuration options 3063 QUnit.init = function() { 3064 var tests, banner, result, qunit, 3065 config = QUnit.config; 3066 3067 config.stats = { all: 0, bad: 0 }; 3068 config.moduleStats = { all: 0, bad: 0 }; 3069 config.started = 0; 3070 config.updateRate = 1000; 3071 config.blocking = false; 3072 config.autostart = true; 3073 config.autorun = false; 3074 config.filter = ""; 3075 config.queue = []; 3076 3077 // Return on non-browser environments 3078 // This is necessary to not break on node tests 3079 if ( typeof window === "undefined" ) { 3080 return; 3081 } 3082 3083 qunit = id( "qunit" ); 3084 if ( qunit ) { 3085 qunit.innerHTML = 3086 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" + 3087 "<h2 id='qunit-banner'></h2>" + 3088 "<div id='qunit-testrunner-toolbar'></div>" + 3089 "<h2 id='qunit-userAgent'></h2>" + 3090 "<ol id='qunit-tests'></ol>"; 3091 } 3092 3093 tests = id( "qunit-tests" ); 3094 banner = id( "qunit-banner" ); 3095 result = id( "qunit-testresult" ); 3096 3097 if ( tests ) { 3098 tests.innerHTML = ""; 3099 } 3100 3101 if ( banner ) { 3102 banner.className = ""; 3103 } 3104 3105 if ( result ) { 3106 result.parentNode.removeChild( result ); 3107 } 3108 3109 if ( tests ) { 3110 result = document.createElement( "p" ); 3111 result.id = "qunit-testresult"; 3112 result.className = "result"; 3113 tests.parentNode.insertBefore( result, tests ); 3114 result.innerHTML = "Running...<br /> "; 3115 } 3116 }; 3117 3118 // Don't load the HTML Reporter on non-Browser environments 3119 if ( typeof window === "undefined" ) { 3120 return; 3121 } 3122 3123 var config = QUnit.config, 3124 hasOwn = Object.prototype.hasOwnProperty, 3125 defined = { 3126 document: window.document !== undefined, 3127 sessionStorage: (function() { 3128 var x = "qunit-test-string"; 3129 try { 3130 sessionStorage.setItem( x, x ); 3131 sessionStorage.removeItem( x ); 3132 return true; 3133 } catch ( e ) { 3134 return false; 2085 3135 } 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 }; 3136 }()) 3137 }, 3138 modulesList = []; 3139 3140 /** 3141 * Escape text for attribute or text content. 3142 */ 3143 function escapeText( s ) { 3144 if ( !s ) { 3145 return ""; 3146 } 3147 s = s + ""; 3148 3149 // Both single quotes and double quotes (for attributes) 3150 return s.replace( /['"<>&]/g, function( s ) { 3151 switch ( s ) { 3152 case "'": 3153 return "'"; 3154 case "\"": 3155 return """; 3156 case "<": 3157 return "<"; 3158 case ">": 3159 return ">"; 3160 case "&": 3161 return "&"; 3162 } 3163 }); 3164 } 3165 3166 /** 3167 * @param {HTMLElement} elem 3168 * @param {string} type 3169 * @param {Function} fn 3170 */ 3171 function addEvent( elem, type, fn ) { 3172 if ( elem.addEventListener ) { 3173 3174 // Standards-based browsers 3175 elem.addEventListener( type, fn, false ); 3176 } else if ( elem.attachEvent ) { 3177 3178 // support: IE <9 3179 elem.attachEvent( "on" + type, function() { 3180 var event = window.event; 3181 if ( !event.target ) { 3182 event.target = event.srcElement || document; 2095 3183 } 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 }; 3184 3185 fn.call( elem, event ); 3186 }); 3187 } 3188 } 3189 3190 /** 3191 * @param {Array|NodeList} elems 3192 * @param {string} type 3193 * @param {Function} fn 3194 */ 3195 function addEvents( elems, type, fn ) { 3196 var i = elems.length; 3197 while ( i-- ) { 3198 addEvent( elems[ i ], type, fn ); 3199 } 3200 } 3201 3202 function hasClass( elem, name ) { 3203 return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0; 3204 } 3205 3206 function addClass( elem, name ) { 3207 if ( !hasClass( elem, name ) ) { 3208 elem.className += ( elem.className ? " " : "" ) + name; 3209 } 3210 } 3211 3212 function toggleClass( elem, name ) { 3213 if ( hasClass( elem, name ) ) { 3214 removeClass( elem, name ); 3215 } else { 3216 addClass( elem, name ); 3217 } 3218 } 3219 3220 function removeClass( elem, name ) { 3221 var set = " " + elem.className + " "; 3222 3223 // Class name may appear multiple times 3224 while ( set.indexOf( " " + name + " " ) >= 0 ) { 3225 set = set.replace( " " + name + " ", " " ); 3226 } 3227 3228 // trim for prettiness 3229 elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" ); 3230 } 3231 3232 function id( name ) { 3233 return defined.document && document.getElementById && document.getElementById( name ); 3234 } 3235 3236 function getUrlConfigHtml() { 3237 var i, j, val, 3238 escaped, escapedTooltip, 3239 selection = false, 3240 len = config.urlConfig.length, 3241 urlConfigHtml = ""; 3242 3243 for ( i = 0; i < len; i++ ) { 3244 val = config.urlConfig[ i ]; 3245 if ( typeof val === "string" ) { 3246 val = { 3247 id: val, 3248 label: val 3249 }; 3250 } 3251 3252 escaped = escapeText( val.id ); 3253 escapedTooltip = escapeText( val.tooltip ); 3254 3255 if ( config[ val.id ] === undefined ) { 3256 config[ val.id ] = QUnit.urlParams[ val.id ]; 3257 } 3258 3259 if ( !val.value || typeof val.value === "string" ) { 3260 urlConfigHtml += "<input id='qunit-urlconfig-" + escaped + 3261 "' name='" + escaped + "' type='checkbox'" + 3262 ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) + 3263 ( config[ val.id ] ? " checked='checked'" : "" ) + 3264 " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped + 3265 "' title='" + escapedTooltip + "'>" + val.label + "</label>"; 3266 } else { 3267 urlConfigHtml += "<label for='qunit-urlconfig-" + escaped + 3268 "' title='" + escapedTooltip + "'>" + val.label + 3269 ": </label><select id='qunit-urlconfig-" + escaped + 3270 "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>"; 3271 3272 if ( QUnit.is( "array", val.value ) ) { 3273 for ( j = 0; j < val.value.length; j++ ) { 3274 escaped = escapeText( val.value[ j ] ); 3275 urlConfigHtml += "<option value='" + escaped + "'" + 3276 ( config[ val.id ] === val.value[ j ] ? 3277 ( selection = true ) && " selected='selected'" : "" ) + 3278 ">" + escaped + "</option>"; 3279 } 3280 } else { 3281 for ( j in val.value ) { 3282 if ( hasOwn.call( val.value, j ) ) { 3283 urlConfigHtml += "<option value='" + escapeText( j ) + "'" + 3284 ( config[ val.id ] === j ? 3285 ( selection = true ) && " selected='selected'" : "" ) + 3286 ">" + escapeText( val.value[ j ] ) + "</option>"; 3287 } 2110 3288 } 2111 3289 } 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 }; 3290 if ( config[ val.id ] && !selection ) { 3291 escaped = escapeText( config[ val.id ] ); 3292 urlConfigHtml += "<option value='" + escaped + 3293 "' selected='selected' disabled='disabled'>" + escaped + "</option>"; 2126 3294 } 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 }; 3295 urlConfigHtml += "</select>"; 3296 } 3297 } 3298 3299 return urlConfigHtml; 3300 } 3301 3302 // Handle "click" events on toolbar checkboxes and "change" for select menus. 3303 // Updates the URL with the new state of `config.urlConfig` values. 3304 function toolbarChanged() { 3305 var updatedUrl, value, 3306 field = this, 3307 params = {}; 3308 3309 // Detect if field is a select menu or a checkbox 3310 if ( "selectedIndex" in field ) { 3311 value = field.options[ field.selectedIndex ].value || undefined; 3312 } else { 3313 value = field.checked ? ( field.defaultValue || true ) : undefined; 3314 } 3315 3316 params[ field.name ] = value; 3317 updatedUrl = setUrl( params ); 3318 3319 if ( "hidepassed" === field.name && "replaceState" in window.history ) { 3320 config[ field.name ] = value || false; 3321 if ( value ) { 3322 addClass( id( "qunit-tests" ), "hidepass" ); 3323 } else { 3324 removeClass( id( "qunit-tests" ), "hidepass" ); 3325 } 3326 3327 // It is not necessary to refresh the whole page 3328 window.history.replaceState( null, "", updatedUrl ); 3329 } else { 3330 window.location = updatedUrl; 3331 } 3332 } 3333 3334 function setUrl( params ) { 3335 var key, 3336 querystring = "?"; 3337 3338 params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params ); 3339 3340 for ( key in params ) { 3341 if ( hasOwn.call( params, key ) ) { 3342 if ( params[ key ] === undefined ) { 3343 continue; 2141 3344 } 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>"; 3345 querystring += encodeURIComponent( key ); 3346 if ( params[ key ] !== true ) { 3347 querystring += "=" + encodeURIComponent( params[ key ] ); 2177 3348 } 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 } 3349 querystring += "&"; 3350 } 3351 } 3352 return location.protocol + "//" + location.host + 3353 location.pathname + querystring.slice( 0, -1 ); 3354 } 3355 3356 function applyUrlParams() { 3357 var selectedModule, 3358 modulesList = id( "qunit-modulefilter" ), 3359 filter = id( "qunit-filter-input" ).value; 3360 3361 selectedModule = modulesList ? 3362 decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) : 3363 undefined; 3364 3365 window.location = setUrl({ 3366 module: ( selectedModule === "" ) ? undefined : selectedModule, 3367 filter: ( filter === "" ) ? undefined : filter, 3368 3369 // Remove testId filter 3370 testId: undefined 3371 }); 3372 } 3373 3374 function toolbarUrlConfigContainer() { 3375 var urlConfigContainer = document.createElement( "span" ); 3376 3377 urlConfigContainer.innerHTML = getUrlConfigHtml(); 3378 addClass( urlConfigContainer, "qunit-url-config" ); 3379 3380 // For oldIE support: 3381 // * Add handlers to the individual elements instead of the container 3382 // * Use "click" instead of "change" for checkboxes 3383 addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged ); 3384 addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged ); 3385 3386 return urlConfigContainer; 3387 } 3388 3389 function toolbarLooseFilter() { 3390 var filter = document.createElement( "form" ), 3391 label = document.createElement( "label" ), 3392 input = document.createElement( "input" ), 3393 button = document.createElement( "button" ); 3394 3395 addClass( filter, "qunit-filter" ); 3396 3397 label.innerHTML = "Filter: "; 3398 3399 input.type = "text"; 3400 input.value = config.filter || ""; 3401 input.name = "filter"; 3402 input.id = "qunit-filter-input"; 3403 3404 button.innerHTML = "Go"; 3405 3406 label.appendChild( input ); 3407 3408 filter.appendChild( label ); 3409 filter.appendChild( button ); 3410 addEvent( filter, "submit", function( ev ) { 3411 applyUrlParams(); 3412 3413 if ( ev && ev.preventDefault ) { 3414 ev.preventDefault(); 3415 } 3416 3417 return false; 3418 }); 3419 3420 return filter; 3421 } 3422 3423 function toolbarModuleFilterHtml() { 3424 var i, 3425 moduleFilterHtml = ""; 3426 3427 if ( !modulesList.length ) { 3428 return false; 3429 } 3430 3431 modulesList.sort(function( a, b ) { 3432 return a.localeCompare( b ); 3433 }); 3434 3435 moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" + 3436 "<select id='qunit-modulefilter' name='modulefilter'><option value='' " + 3437 ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) + 3438 ">< All Modules ></option>"; 3439 3440 for ( i = 0; i < modulesList.length; i++ ) { 3441 moduleFilterHtml += "<option value='" + 3442 escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " + 3443 ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) + 3444 ">" + escapeText( modulesList[ i ] ) + "</option>"; 3445 } 3446 moduleFilterHtml += "</select>"; 3447 3448 return moduleFilterHtml; 3449 } 3450 3451 function toolbarModuleFilter() { 3452 var toolbar = id( "qunit-testrunner-toolbar" ), 3453 moduleFilter = document.createElement( "span" ), 3454 moduleFilterHtml = toolbarModuleFilterHtml(); 3455 3456 if ( !toolbar || !moduleFilterHtml ) { 3457 return false; 3458 } 3459 3460 moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); 3461 moduleFilter.innerHTML = moduleFilterHtml; 3462 3463 addEvent( moduleFilter.lastChild, "change", applyUrlParams ); 3464 3465 toolbar.appendChild( moduleFilter ); 3466 } 3467 3468 function appendToolbar() { 3469 var toolbar = id( "qunit-testrunner-toolbar" ); 3470 3471 if ( toolbar ) { 3472 toolbar.appendChild( toolbarUrlConfigContainer() ); 3473 toolbar.appendChild( toolbarLooseFilter() ); 3474 } 3475 } 3476 3477 function appendHeader() { 3478 var header = id( "qunit-header" ); 3479 3480 if ( header ) { 3481 header.innerHTML = "<a href='" + 3482 setUrl({ filter: undefined, module: undefined, testId: undefined }) + 3483 "'>" + header.innerHTML + "</a> "; 3484 } 3485 } 3486 3487 function appendBanner() { 3488 var banner = id( "qunit-banner" ); 3489 3490 if ( banner ) { 3491 banner.className = ""; 3492 } 3493 } 3494 3495 function appendTestResults() { 3496 var tests = id( "qunit-tests" ), 3497 result = id( "qunit-testresult" ); 3498 3499 if ( result ) { 3500 result.parentNode.removeChild( result ); 3501 } 3502 3503 if ( tests ) { 3504 tests.innerHTML = ""; 3505 result = document.createElement( "p" ); 3506 result.id = "qunit-testresult"; 3507 result.className = "result"; 3508 tests.parentNode.insertBefore( result, tests ); 3509 result.innerHTML = "Running...<br /> "; 3510 } 3511 } 3512 3513 function storeFixture() { 3514 var fixture = id( "qunit-fixture" ); 3515 if ( fixture ) { 3516 config.fixture = fixture.innerHTML; 3517 } 3518 } 3519 3520 function appendUserAgent() { 3521 var userAgent = id( "qunit-userAgent" ); 3522 3523 if ( userAgent ) { 3524 userAgent.innerHTML = ""; 3525 userAgent.appendChild( 3526 document.createTextNode( 3527 "QUnit " + QUnit.version + "; " + navigator.userAgent 3528 ) 3529 ); 3530 } 3531 } 3532 3533 function appendTestsList( modules ) { 3534 var i, l, x, z, test, moduleObj; 3535 3536 for ( i = 0, l = modules.length; i < l; i++ ) { 3537 moduleObj = modules[ i ]; 3538 3539 if ( moduleObj.name ) { 3540 modulesList.push( moduleObj.name ); 3541 } 3542 3543 for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) { 3544 test = moduleObj.tests[ x ]; 3545 3546 appendTest( test.name, test.testId, moduleObj.name ); 3547 } 3548 } 3549 } 3550 3551 function appendTest( name, testId, moduleName ) { 3552 var title, rerunTrigger, testBlock, assertList, 3553 tests = id( "qunit-tests" ); 3554 3555 if ( !tests ) { 3556 return; 3557 } 3558 3559 title = document.createElement( "strong" ); 3560 title.innerHTML = getNameHtml( name, moduleName ); 3561 3562 rerunTrigger = document.createElement( "a" ); 3563 rerunTrigger.innerHTML = "Rerun"; 3564 rerunTrigger.href = setUrl({ testId: testId }); 3565 3566 testBlock = document.createElement( "li" ); 3567 testBlock.appendChild( title ); 3568 testBlock.appendChild( rerunTrigger ); 3569 testBlock.id = "qunit-test-output-" + testId; 3570 3571 assertList = document.createElement( "ol" ); 3572 assertList.className = "qunit-assert-list"; 3573 3574 testBlock.appendChild( assertList ); 3575 3576 tests.appendChild( testBlock ); 3577 } 3578 3579 // HTML Reporter initialization and load 3580 QUnit.begin(function( details ) { 3581 var qunit = id( "qunit" ); 3582 3583 // Fixture is the only one necessary to run without the #qunit element 3584 storeFixture(); 3585 3586 if ( qunit ) { 3587 qunit.innerHTML = 3588 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" + 3589 "<h2 id='qunit-banner'></h2>" + 3590 "<div id='qunit-testrunner-toolbar'></div>" + 3591 "<h2 id='qunit-userAgent'></h2>" + 3592 "<ol id='qunit-tests'></ol>"; 3593 } 3594 3595 appendHeader(); 3596 appendBanner(); 3597 appendTestResults(); 3598 appendUserAgent(); 3599 appendToolbar(); 3600 appendTestsList( details.modules ); 3601 toolbarModuleFilter(); 3602 3603 if ( qunit && config.hidepassed ) { 3604 addClass( qunit.lastChild, "hidepass" ); 3605 } 3606 }); 3607 3608 QUnit.done(function( details ) { 3609 var i, key, 3610 banner = id( "qunit-banner" ), 3611 tests = id( "qunit-tests" ), 3612 html = [ 3613 "Tests completed in ", 3614 details.runtime, 3615 " milliseconds.<br />", 3616 "<span class='passed'>", 3617 details.passed, 3618 "</span> assertions of <span class='total'>", 3619 details.total, 3620 "</span> passed, <span class='failed'>", 3621 details.failed, 3622 "</span> failed." 3623 ].join( "" ); 3624 3625 if ( banner ) { 3626 banner.className = details.failed ? "qunit-fail" : "qunit-pass"; 3627 } 3628 3629 if ( tests ) { 3630 id( "qunit-testresult" ).innerHTML = html; 3631 } 3632 3633 if ( config.altertitle && defined.document && document.title ) { 3634 3635 // show ✖ for good, ✔ for bad suite result in title 3636 // use escape sequences in case file gets loaded with non-utf-8-charset 3637 document.title = [ 3638 ( details.failed ? "\u2716" : "\u2714" ), 3639 document.title.replace( /^[\u2714\u2716] /i, "" ) 3640 ].join( " " ); 3641 } 3642 3643 // clear own sessionStorage items if all tests passed 3644 if ( config.reorder && defined.sessionStorage && details.failed === 0 ) { 3645 for ( i = 0; i < sessionStorage.length; i++ ) { 3646 key = sessionStorage.key( i++ ); 3647 if ( key.indexOf( "qunit-test-" ) === 0 ) { 3648 sessionStorage.removeItem( key ); 2184 3649 } 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 } 3650 } 3651 } 3652 3653 // scroll back to top to show results 3654 if ( config.scrolltop && window.scrollTo ) { 3655 window.scrollTo( 0, 0 ); 3656 } 3657 }); 3658 3659 function getNameHtml( name, module ) { 3660 var nameHtml = ""; 3661 3662 if ( module ) { 3663 nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: "; 3664 } 3665 3666 nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>"; 3667 3668 return nameHtml; 3669 } 3670 3671 QUnit.testStart(function( details ) { 3672 var running, testBlock, bad; 3673 3674 testBlock = id( "qunit-test-output-" + details.testId ); 3675 if ( testBlock ) { 3676 testBlock.className = "running"; 3677 } else { 3678 3679 // Report later registered tests 3680 appendTest( details.name, details.testId, details.module ); 3681 } 3682 3683 running = id( "qunit-testresult" ); 3684 if ( running ) { 3685 bad = QUnit.config.reorder && defined.sessionStorage && 3686 +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name ); 3687 3688 running.innerHTML = ( bad ? 3689 "Rerunning previously failed test: <br />" : 3690 "Running: <br />" ) + 3691 getNameHtml( details.name, details.module ); 3692 } 3693 3694 }); 3695 3696 QUnit.log(function( details ) { 3697 var assertList, assertLi, 3698 message, expected, actual, 3699 testItem = id( "qunit-test-output-" + details.testId ); 3700 3701 if ( !testItem ) { 3702 return; 3703 } 3704 3705 message = escapeText( details.message ) || ( details.result ? "okay" : "failed" ); 3706 message = "<span class='test-message'>" + message + "</span>"; 3707 message += "<span class='runtime'>@ " + details.runtime + " ms</span>"; 3708 3709 // pushFailure doesn't provide details.expected 3710 // when it calls, it's implicit to also not show expected and diff stuff 3711 // Also, we need to check details.expected existence, as it can exist and be undefined 3712 if ( !details.result && hasOwn.call( details, "expected" ) ) { 3713 expected = escapeText( QUnit.dump.parse( details.expected ) ); 3714 actual = escapeText( QUnit.dump.parse( details.actual ) ); 3715 message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + 3716 expected + 3717 "</pre></td></tr>"; 3718 3719 if ( actual !== expected ) { 3720 message += "<tr class='test-actual'><th>Result: </th><td><pre>" + 3721 actual + "</pre></td></tr>" + 3722 "<tr class='test-diff'><th>Diff: </th><td><pre>" + 3723 QUnit.diff( expected, actual ) + "</pre></td></tr>"; 3724 } else { 3725 if ( expected.indexOf( "[object Array]" ) !== -1 || 3726 expected.indexOf( "[object Object]" ) !== -1 ) { 3727 message += "<tr class='test-message'><th>Message: </th><td>" + 3728 "Diff suppressed as the depth of object is more than current max depth (" + 3729 QUnit.config.maxDepth + ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " + 3730 " run with a higher max depth or <a href='" + setUrl({ maxDepth: -1 }) + "'>" + 3731 "Rerun</a> without max depth.</p></td></tr>"; 2199 3732 } 2200 3733 } 2201 3734 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()) )); 3735 if ( details.source ) { 3736 message += "<tr class='test-source'><th>Source: </th><td><pre>" + 3737 escapeText( details.source ) + "</pre></td></tr>"; 3738 } 3739 3740 message += "</table>"; 3741 3742 // this occours when pushFailure is set and we have an extracted stack trace 3743 } else if ( !details.result && details.source ) { 3744 message += "<table>" + 3745 "<tr class='test-source'><th>Source: </th><td><pre>" + 3746 escapeText( details.source ) + "</pre></td></tr>" + 3747 "</table>"; 3748 } 3749 3750 assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; 3751 3752 assertLi = document.createElement( "li" ); 3753 assertLi.className = details.result ? "pass" : "fail"; 3754 assertLi.innerHTML = message; 3755 assertList.appendChild( assertLi ); 3756 }); 3757 3758 QUnit.testDone(function( details ) { 3759 var testTitle, time, testItem, assertList, 3760 good, bad, testCounts, skipped, 3761 tests = id( "qunit-tests" ); 3762 3763 if ( !tests ) { 3764 return; 3765 } 3766 3767 testItem = id( "qunit-test-output-" + details.testId ); 3768 3769 assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; 3770 3771 good = details.passed; 3772 bad = details.failed; 3773 3774 // store result when possible 3775 if ( config.reorder && defined.sessionStorage ) { 3776 if ( bad ) { 3777 sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad ); 3778 } else { 3779 sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name ); 3780 } 3781 } 3782 3783 if ( bad === 0 ) { 3784 addClass( assertList, "qunit-collapsed" ); 3785 } 3786 3787 // testItem.firstChild is the test name 3788 testTitle = testItem.firstChild; 3789 3790 testCounts = bad ? 3791 "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " : 3792 ""; 3793 3794 testTitle.innerHTML += " <b class='counts'>(" + testCounts + 3795 details.assertions.length + ")</b>"; 3796 3797 if ( details.skipped ) { 3798 testItem.className = "skipped"; 3799 skipped = document.createElement( "em" ); 3800 skipped.className = "qunit-skipped-label"; 3801 skipped.innerHTML = "skipped"; 3802 testItem.insertBefore( skipped, testTitle ); 3803 } else { 3804 addEvent( testTitle, "click", function() { 3805 toggleClass( assertList, "qunit-collapsed" ); 3806 }); 3807 3808 testItem.className = bad ? "fail" : "pass"; 3809 3810 time = document.createElement( "span" ); 3811 time.className = "runtime"; 3812 time.innerHTML = details.runtime + " ms"; 3813 testItem.insertBefore( time, assertList ); 3814 } 3815 }); 3816 3817 if ( defined.document ) { 3818 if ( document.readyState === "complete" ) { 3819 QUnit.load(); 3820 } else { 3821 addEvent( window, "load", QUnit.load ); 3822 } 3823 } else { 3824 config.pageLoaded = true; 3825 config.autorun = true; 3826 } 3827 3828 })();
Note: See TracChangeset
for help on using the changeset viewer.