| 1 | // Copyright 2007 The Closure Library Authors. All Rights Reserved. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS-IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | /** |
| 16 | * @fileoverview Mock Clock implementation for working with setTimeout, |
| 17 | * setInterval, clearTimeout and clearInterval within unit tests. |
| 18 | * |
| 19 | * Derived from jsUnitMockTimeout.js, contributed to JsUnit by |
| 20 | * Pivotal Computer Systems, www.pivotalsf.com |
| 21 | * |
| 22 | */ |
| 23 | |
| 24 | goog.provide('goog.testing.MockClock'); |
| 25 | |
| 26 | goog.require('goog.Disposable'); |
| 27 | goog.require('goog.async.run'); |
| 28 | goog.require('goog.testing.PropertyReplacer'); |
| 29 | goog.require('goog.testing.events'); |
| 30 | goog.require('goog.testing.events.Event'); |
| 31 | goog.require('goog.testing.watchers'); |
| 32 | |
| 33 | |
| 34 | |
| 35 | /** |
| 36 | * Class for unit testing code that uses setTimeout and clearTimeout. |
| 37 | * |
| 38 | * NOTE: If you are using MockClock to test code that makes use of |
| 39 | * goog.fx.Animation, then you must either: |
| 40 | * |
| 41 | * 1. Install and dispose of the MockClock in setUpPage() and tearDownPage() |
| 42 | * respectively (rather than setUp()/tearDown()). |
| 43 | * |
| 44 | * or |
| 45 | * |
| 46 | * 2. Ensure that every test clears the animation queue by calling |
| 47 | * mockClock.tick(x) at the end of each test function (where `x` is large |
| 48 | * enough to complete all animations). |
| 49 | * |
| 50 | * Otherwise, if any animation is left pending at the time that |
| 51 | * MockClock.dispose() is called, that will permanently prevent any future |
| 52 | * animations from playing on the page. |
| 53 | * |
| 54 | * @param {boolean=} opt_autoInstall Install the MockClock at construction time. |
| 55 | * @constructor |
| 56 | * @extends {goog.Disposable} |
| 57 | * @final |
| 58 | */ |
| 59 | goog.testing.MockClock = function(opt_autoInstall) { |
| 60 | goog.Disposable.call(this); |
| 61 | |
| 62 | /** |
| 63 | * Reverse-order queue of timers to fire. |
| 64 | * |
| 65 | * The last item of the queue is popped off. Insertion happens from the |
| 66 | * right. For example, the expiration times for each element of the queue |
| 67 | * might be in the order 300, 200, 200. |
| 68 | * |
| 69 | * @type {Array<Object>} |
| 70 | * @private |
| 71 | */ |
| 72 | this.queue_ = []; |
| 73 | |
| 74 | /** |
| 75 | * Set of timeouts that should be treated as cancelled. |
| 76 | * |
| 77 | * Rather than removing cancelled timers directly from the queue, this set |
| 78 | * simply marks them as deleted so that they can be ignored when their |
| 79 | * turn comes up. The keys are the timeout keys that are cancelled, each |
| 80 | * mapping to true. |
| 81 | * |
| 82 | * @type {Object} |
| 83 | * @private |
| 84 | */ |
| 85 | this.deletedKeys_ = {}; |
| 86 | |
| 87 | if (opt_autoInstall) { |
| 88 | this.install(); |
| 89 | } |
| 90 | }; |
| 91 | goog.inherits(goog.testing.MockClock, goog.Disposable); |
| 92 | |
| 93 | |
| 94 | /** |
| 95 | * Default wait timeout for mocking requestAnimationFrame (in milliseconds). |
| 96 | * |
| 97 | * @type {number} |
| 98 | * @const |
| 99 | */ |
| 100 | goog.testing.MockClock.REQUEST_ANIMATION_FRAME_TIMEOUT = 20; |
| 101 | |
| 102 | |
| 103 | /** |
| 104 | * ID to use for next timeout. Timeout IDs must never be reused, even across |
| 105 | * MockClock instances. |
| 106 | * @public {number} |
| 107 | */ |
| 108 | goog.testing.MockClock.nextId = Math.round(Math.random() * 10000); |
| 109 | |
| 110 | |
| 111 | /** |
| 112 | * Count of the number of timeouts made by this instance. |
| 113 | * @type {number} |
| 114 | * @private |
| 115 | */ |
| 116 | goog.testing.MockClock.prototype.timeoutsMade_ = 0; |
| 117 | |
| 118 | |
| 119 | /** |
| 120 | * PropertyReplacer instance which overwrites and resets setTimeout, |
| 121 | * setInterval, etc. or null if the MockClock is not installed. |
| 122 | * @type {goog.testing.PropertyReplacer} |
| 123 | * @private |
| 124 | */ |
| 125 | goog.testing.MockClock.prototype.replacer_ = null; |
| 126 | |
| 127 | |
| 128 | /** |
| 129 | * Map of deleted keys. These keys represents keys that were deleted in a |
| 130 | * clearInterval, timeoutid -> object. |
| 131 | * @type {Object} |
| 132 | * @private |
| 133 | */ |
| 134 | goog.testing.MockClock.prototype.deletedKeys_ = null; |
| 135 | |
| 136 | |
| 137 | /** |
| 138 | * The current simulated time in milliseconds. |
| 139 | * @type {number} |
| 140 | * @private |
| 141 | */ |
| 142 | goog.testing.MockClock.prototype.nowMillis_ = 0; |
| 143 | |
| 144 | |
| 145 | /** |
| 146 | * Additional delay between the time a timeout was set to fire, and the time |
| 147 | * it actually fires. Useful for testing workarounds for this Firefox 2 bug: |
| 148 | * https://bugzilla.mozilla.org/show_bug.cgi?id=291386 |
| 149 | * May be negative. |
| 150 | * @type {number} |
| 151 | * @private |
| 152 | */ |
| 153 | goog.testing.MockClock.prototype.timeoutDelay_ = 0; |
| 154 | |
| 155 | |
| 156 | /** |
| 157 | * The real set timeout for reference. |
| 158 | * @const @private {!Function} |
| 159 | */ |
| 160 | goog.testing.MockClock.REAL_SETTIMEOUT_ = goog.global.setTimeout; |
| 161 | |
| 162 | |
| 163 | /** |
| 164 | * Installs the MockClock by overriding the global object's implementation of |
| 165 | * setTimeout, setInterval, clearTimeout and clearInterval. |
| 166 | */ |
| 167 | goog.testing.MockClock.prototype.install = function() { |
| 168 | if (!this.replacer_) { |
| 169 | if (goog.testing.MockClock.REAL_SETTIMEOUT_ !== goog.global.setTimeout) { |
| 170 | if (typeof console !== 'undefined' && console.warn) { |
| 171 | console.warn('Non default setTimeout detected. ' + |
| 172 | 'Use of multiple MockClock instances or other clock mocking ' + |
| 173 | 'should be avoided due to unspecified behavior and ' + |
| 174 | 'the resulting fragility.'); |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | var r = this.replacer_ = new goog.testing.PropertyReplacer(); |
| 179 | r.set(goog.global, 'setTimeout', goog.bind(this.setTimeout_, this)); |
| 180 | r.set(goog.global, 'setInterval', goog.bind(this.setInterval_, this)); |
| 181 | r.set(goog.global, 'setImmediate', goog.bind(this.setImmediate_, this)); |
| 182 | r.set(goog.global, 'clearTimeout', goog.bind(this.clearTimeout_, this)); |
| 183 | r.set(goog.global, 'clearInterval', goog.bind(this.clearInterval_, this)); |
| 184 | // goog.Promise uses goog.async.run. In order to be able to test |
| 185 | // Promise-based code, we need to make sure that goog.async.run uses |
| 186 | // nextTick instead of native browser Promises. This means that it will |
| 187 | // default to setImmediate, which is replaced above. Note that we test for |
| 188 | // the presence of goog.async.run.forceNextTick to be resilient to the case |
| 189 | // where tests replace goog.async.run directly. |
| 190 | goog.async.run.forceNextTick && goog.async.run.forceNextTick( |
| 191 | goog.testing.MockClock.REAL_SETTIMEOUT_); |
| 192 | |
| 193 | // Replace the requestAnimationFrame functions. |
| 194 | this.replaceRequestAnimationFrame_(); |
| 195 | |
| 196 | // PropertyReplacer#set can't be called with renameable functions. |
| 197 | this.oldGoogNow_ = goog.now; |
| 198 | goog.now = goog.bind(this.getCurrentTime, this); |
| 199 | } |
| 200 | }; |
| 201 | |
| 202 | |
| 203 | /** |
| 204 | * Installs the mocks for requestAnimationFrame and cancelRequestAnimationFrame. |
| 205 | * @private |
| 206 | */ |
| 207 | goog.testing.MockClock.prototype.replaceRequestAnimationFrame_ = function() { |
| 208 | var r = this.replacer_; |
| 209 | var requestFuncs = ['requestAnimationFrame', |
| 210 | 'webkitRequestAnimationFrame', |
| 211 | 'mozRequestAnimationFrame', |
| 212 | 'oRequestAnimationFrame', |
| 213 | 'msRequestAnimationFrame']; |
| 214 | |
| 215 | var cancelFuncs = ['cancelAnimationFrame', |
| 216 | 'cancelRequestAnimationFrame', |
| 217 | 'webkitCancelRequestAnimationFrame', |
| 218 | 'mozCancelRequestAnimationFrame', |
| 219 | 'oCancelRequestAnimationFrame', |
| 220 | 'msCancelRequestAnimationFrame']; |
| 221 | |
| 222 | for (var i = 0; i < requestFuncs.length; ++i) { |
| 223 | if (goog.global && goog.global[requestFuncs[i]]) { |
| 224 | r.set(goog.global, requestFuncs[i], |
| 225 | goog.bind(this.requestAnimationFrame_, this)); |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | for (var i = 0; i < cancelFuncs.length; ++i) { |
| 230 | if (goog.global && goog.global[cancelFuncs[i]]) { |
| 231 | r.set(goog.global, cancelFuncs[i], |
| 232 | goog.bind(this.cancelRequestAnimationFrame_, this)); |
| 233 | } |
| 234 | } |
| 235 | }; |
| 236 | |
| 237 | |
| 238 | /** |
| 239 | * Removes the MockClock's hooks into the global object's functions and revert |
| 240 | * to their original values. |
| 241 | */ |
| 242 | goog.testing.MockClock.prototype.uninstall = function() { |
| 243 | if (this.replacer_) { |
| 244 | this.replacer_.reset(); |
| 245 | this.replacer_ = null; |
| 246 | goog.now = this.oldGoogNow_; |
| 247 | } |
| 248 | |
| 249 | this.fireResetEvent(); |
| 250 | }; |
| 251 | |
| 252 | |
| 253 | /** @override */ |
| 254 | goog.testing.MockClock.prototype.disposeInternal = function() { |
| 255 | this.uninstall(); |
| 256 | this.queue_ = null; |
| 257 | this.deletedKeys_ = null; |
| 258 | goog.testing.MockClock.superClass_.disposeInternal.call(this); |
| 259 | }; |
| 260 | |
| 261 | |
| 262 | /** |
| 263 | * Resets the MockClock, removing all timeouts that are scheduled and resets |
| 264 | * the fake timer count. |
| 265 | */ |
| 266 | goog.testing.MockClock.prototype.reset = function() { |
| 267 | this.queue_ = []; |
| 268 | this.deletedKeys_ = {}; |
| 269 | this.nowMillis_ = 0; |
| 270 | this.timeoutsMade_ = 0; |
| 271 | this.timeoutDelay_ = 0; |
| 272 | |
| 273 | this.fireResetEvent(); |
| 274 | }; |
| 275 | |
| 276 | |
| 277 | /** |
| 278 | * Signals that the mock clock has been reset, allowing objects that |
| 279 | * maintain their own internal state to reset. |
| 280 | */ |
| 281 | goog.testing.MockClock.prototype.fireResetEvent = function() { |
| 282 | goog.testing.watchers.signalClockReset(); |
| 283 | }; |
| 284 | |
| 285 | |
| 286 | /** |
| 287 | * Sets the amount of time between when a timeout is scheduled to fire and when |
| 288 | * it actually fires. |
| 289 | * @param {number} delay The delay in milliseconds. May be negative. |
| 290 | */ |
| 291 | goog.testing.MockClock.prototype.setTimeoutDelay = function(delay) { |
| 292 | this.timeoutDelay_ = delay; |
| 293 | }; |
| 294 | |
| 295 | |
| 296 | /** |
| 297 | * @return {number} delay The amount of time between when a timeout is |
| 298 | * scheduled to fire and when it actually fires, in milliseconds. May |
| 299 | * be negative. |
| 300 | */ |
| 301 | goog.testing.MockClock.prototype.getTimeoutDelay = function() { |
| 302 | return this.timeoutDelay_; |
| 303 | }; |
| 304 | |
| 305 | |
| 306 | /** |
| 307 | * Increments the MockClock's time by a given number of milliseconds, running |
| 308 | * any functions that are now overdue. |
| 309 | * @param {number=} opt_millis Number of milliseconds to increment the counter. |
| 310 | * If not specified, clock ticks 1 millisecond. |
| 311 | * @return {number} Current mock time in milliseconds. |
| 312 | */ |
| 313 | goog.testing.MockClock.prototype.tick = function(opt_millis) { |
| 314 | if (typeof opt_millis != 'number') { |
| 315 | opt_millis = 1; |
| 316 | } |
| 317 | var endTime = this.nowMillis_ + opt_millis; |
| 318 | this.runFunctionsWithinRange_(endTime); |
| 319 | this.nowMillis_ = endTime; |
| 320 | return endTime; |
| 321 | }; |
| 322 | |
| 323 | |
| 324 | /** |
| 325 | * Takes a promise and then ticks the mock clock. If the promise successfully |
| 326 | * resolves, returns the value produced by the promise. If the promise is |
| 327 | * rejected, it throws the rejection as an exception. If the promise is not |
| 328 | * resolved at all, throws an exception. |
| 329 | * Also ticks the general clock by the specified amount. |
| 330 | * |
| 331 | * @param {!goog.Thenable<T>} promise A promise that should be resolved after |
| 332 | * the mockClock is ticked for the given opt_millis. |
| 333 | * @param {number=} opt_millis Number of milliseconds to increment the counter. |
| 334 | * If not specified, clock ticks 1 millisecond. |
| 335 | * @return {T} |
| 336 | * @template T |
| 337 | */ |
| 338 | goog.testing.MockClock.prototype.tickPromise = function(promise, opt_millis) { |
| 339 | var value; |
| 340 | var error; |
| 341 | var resolved = false; |
| 342 | promise.then(function(v) { |
| 343 | value = v; |
| 344 | resolved = true; |
| 345 | }, function(e) { |
| 346 | error = e; |
| 347 | resolved = true; |
| 348 | }); |
| 349 | this.tick(opt_millis); |
| 350 | if (!resolved) { |
| 351 | throw new Error( |
| 352 | 'Promise was expected to be resolved after mock clock tick.'); |
| 353 | } |
| 354 | if (error) { |
| 355 | throw error; |
| 356 | } |
| 357 | return value; |
| 358 | }; |
| 359 | |
| 360 | |
| 361 | /** |
| 362 | * @return {number} The number of timeouts that have been scheduled. |
| 363 | */ |
| 364 | goog.testing.MockClock.prototype.getTimeoutsMade = function() { |
| 365 | return this.timeoutsMade_; |
| 366 | }; |
| 367 | |
| 368 | |
| 369 | /** |
| 370 | * @return {number} The MockClock's current time in milliseconds. |
| 371 | */ |
| 372 | goog.testing.MockClock.prototype.getCurrentTime = function() { |
| 373 | return this.nowMillis_; |
| 374 | }; |
| 375 | |
| 376 | |
| 377 | /** |
| 378 | * @param {number} timeoutKey The timeout key. |
| 379 | * @return {boolean} Whether the timer has been set and not cleared, |
| 380 | * independent of the timeout's expiration. In other words, the timeout |
| 381 | * could have passed or could be scheduled for the future. Either way, |
| 382 | * this function returns true or false depending only on whether the |
| 383 | * provided timeoutKey represents a timeout that has been set and not |
| 384 | * cleared. |
| 385 | */ |
| 386 | goog.testing.MockClock.prototype.isTimeoutSet = function(timeoutKey) { |
| 387 | return timeoutKey < goog.testing.MockClock.nextId && |
| 388 | timeoutKey >= goog.testing.MockClock.nextId - this.timeoutsMade_ && |
| 389 | !this.deletedKeys_[timeoutKey]; |
| 390 | }; |
| 391 | |
| 392 | |
| 393 | /** |
| 394 | * Runs any function that is scheduled before a certain time. Timeouts can |
| 395 | * be made to fire early or late if timeoutDelay_ is non-0. |
| 396 | * @param {number} endTime The latest time in the range, in milliseconds. |
| 397 | * @private |
| 398 | */ |
| 399 | goog.testing.MockClock.prototype.runFunctionsWithinRange_ = function( |
| 400 | endTime) { |
| 401 | var adjustedEndTime = endTime - this.timeoutDelay_; |
| 402 | |
| 403 | // Repeatedly pop off the last item since the queue is always sorted. |
| 404 | while (this.queue_ && this.queue_.length && |
| 405 | this.queue_[this.queue_.length - 1].runAtMillis <= adjustedEndTime) { |
| 406 | var timeout = this.queue_.pop(); |
| 407 | |
| 408 | if (!(timeout.timeoutKey in this.deletedKeys_)) { |
| 409 | // Only move time forwards. |
| 410 | this.nowMillis_ = Math.max(this.nowMillis_, |
| 411 | timeout.runAtMillis + this.timeoutDelay_); |
| 412 | // Call timeout in global scope and pass the timeout key as the argument. |
| 413 | timeout.funcToCall.call(goog.global, timeout.timeoutKey); |
| 414 | // In case the interval was cleared in the funcToCall |
| 415 | if (timeout.recurring) { |
| 416 | this.scheduleFunction_( |
| 417 | timeout.timeoutKey, timeout.funcToCall, timeout.millis, true); |
| 418 | } |
| 419 | } |
| 420 | } |
| 421 | }; |
| 422 | |
| 423 | |
| 424 | /** |
| 425 | * Schedules a function to be run at a certain time. |
| 426 | * @param {number} timeoutKey The timeout key. |
| 427 | * @param {Function} funcToCall The function to call. |
| 428 | * @param {number} millis The number of milliseconds to call it in. |
| 429 | * @param {boolean} recurring Whether to function call should recur. |
| 430 | * @private |
| 431 | */ |
| 432 | goog.testing.MockClock.prototype.scheduleFunction_ = function( |
| 433 | timeoutKey, funcToCall, millis, recurring) { |
| 434 | if (!goog.isFunction(funcToCall)) { |
| 435 | // Early error for debuggability rather than dying in the next .tick() |
| 436 | throw new TypeError('The provided callback must be a function, not a ' + |
| 437 | typeof funcToCall); |
| 438 | } |
| 439 | |
| 440 | var timeout = { |
| 441 | runAtMillis: this.nowMillis_ + millis, |
| 442 | funcToCall: funcToCall, |
| 443 | recurring: recurring, |
| 444 | timeoutKey: timeoutKey, |
| 445 | millis: millis |
| 446 | }; |
| 447 | |
| 448 | goog.testing.MockClock.insert_(timeout, this.queue_); |
| 449 | }; |
| 450 | |
| 451 | |
| 452 | /** |
| 453 | * Inserts a timer descriptor into a descending-order queue. |
| 454 | * |
| 455 | * Later-inserted duplicates appear at lower indices. For example, the |
| 456 | * asterisk in (5,4,*,3,2,1) would be the insertion point for 3. |
| 457 | * |
| 458 | * @param {Object} timeout The timeout to insert, with numerical runAtMillis |
| 459 | * property. |
| 460 | * @param {Array<Object>} queue The queue to insert into, with each element |
| 461 | * having a numerical runAtMillis property. |
| 462 | * @private |
| 463 | */ |
| 464 | goog.testing.MockClock.insert_ = function(timeout, queue) { |
| 465 | // Although insertion of N items is quadratic, requiring goog.structs.Heap |
| 466 | // from a unit test will make tests more prone to breakage. Since unit |
| 467 | // tests are normally small, scalability is not a primary issue. |
| 468 | |
| 469 | // Find an insertion point. Since the queue is in reverse order (so we |
| 470 | // can pop rather than unshift), and later timers with the same time stamp |
| 471 | // should be executed later, we look for the element strictly greater than |
| 472 | // the one we are inserting. |
| 473 | |
| 474 | for (var i = queue.length; i != 0; i--) { |
| 475 | if (queue[i - 1].runAtMillis > timeout.runAtMillis) { |
| 476 | break; |
| 477 | } |
| 478 | queue[i] = queue[i - 1]; |
| 479 | } |
| 480 | |
| 481 | queue[i] = timeout; |
| 482 | }; |
| 483 | |
| 484 | |
| 485 | /** |
| 486 | * Maximum 32-bit signed integer. |
| 487 | * |
| 488 | * Timeouts over this time return immediately in many browsers, due to integer |
| 489 | * overflow. Such known browsers include Firefox, Chrome, and Safari, but not |
| 490 | * IE. |
| 491 | * |
| 492 | * @type {number} |
| 493 | * @private |
| 494 | */ |
| 495 | goog.testing.MockClock.MAX_INT_ = 2147483647; |
| 496 | |
| 497 | |
| 498 | /** |
| 499 | * Schedules a function to be called after {@code millis} milliseconds. |
| 500 | * Mock implementation for setTimeout. |
| 501 | * @param {Function} funcToCall The function to call. |
| 502 | * @param {number=} opt_millis The number of milliseconds to call it after. |
| 503 | * @return {number} The number of timeouts created. |
| 504 | * @private |
| 505 | */ |
| 506 | goog.testing.MockClock.prototype.setTimeout_ = function( |
| 507 | funcToCall, opt_millis) { |
| 508 | var millis = opt_millis || 0; |
| 509 | if (millis > goog.testing.MockClock.MAX_INT_) { |
| 510 | throw Error( |
| 511 | 'Bad timeout value: ' + millis + '. Timeouts over MAX_INT ' + |
| 512 | '(24.8 days) cause timeouts to be fired ' + |
| 513 | 'immediately in most browsers, except for IE.'); |
| 514 | } |
| 515 | this.timeoutsMade_++; |
| 516 | this.scheduleFunction_(goog.testing.MockClock.nextId, funcToCall, millis, |
| 517 | false); |
| 518 | return goog.testing.MockClock.nextId++; |
| 519 | }; |
| 520 | |
| 521 | |
| 522 | /** |
| 523 | * Schedules a function to be called every {@code millis} milliseconds. |
| 524 | * Mock implementation for setInterval. |
| 525 | * @param {Function} funcToCall The function to call. |
| 526 | * @param {number=} opt_millis The number of milliseconds between calls. |
| 527 | * @return {number} The number of timeouts created. |
| 528 | * @private |
| 529 | */ |
| 530 | goog.testing.MockClock.prototype.setInterval_ = |
| 531 | function(funcToCall, opt_millis) { |
| 532 | var millis = opt_millis || 0; |
| 533 | this.timeoutsMade_++; |
| 534 | this.scheduleFunction_(goog.testing.MockClock.nextId, funcToCall, millis, |
| 535 | true); |
| 536 | return goog.testing.MockClock.nextId++; |
| 537 | }; |
| 538 | |
| 539 | |
| 540 | /** |
| 541 | * Schedules a function to be called when an animation frame is triggered. |
| 542 | * Mock implementation for requestAnimationFrame. |
| 543 | * @param {Function} funcToCall The function to call. |
| 544 | * @return {number} The number of timeouts created. |
| 545 | * @private |
| 546 | */ |
| 547 | goog.testing.MockClock.prototype.requestAnimationFrame_ = function(funcToCall) { |
| 548 | return this.setTimeout_(goog.bind(function() { |
| 549 | if (funcToCall) { |
| 550 | funcToCall(this.getCurrentTime()); |
| 551 | } else if (goog.global.mozRequestAnimationFrame) { |
| 552 | var event = new goog.testing.events.Event('MozBeforePaint', goog.global); |
| 553 | event['timeStamp'] = this.getCurrentTime(); |
| 554 | goog.testing.events.fireBrowserEvent(event); |
| 555 | } |
| 556 | }, this), goog.testing.MockClock.REQUEST_ANIMATION_FRAME_TIMEOUT); |
| 557 | }; |
| 558 | |
| 559 | |
| 560 | /** |
| 561 | * Schedules a function to be called immediately after the current JS |
| 562 | * execution. |
| 563 | * Mock implementation for setImmediate. |
| 564 | * @param {Function} funcToCall The function to call. |
| 565 | * @return {number} The number of timeouts created. |
| 566 | * @private |
| 567 | */ |
| 568 | goog.testing.MockClock.prototype.setImmediate_ = function(funcToCall) { |
| 569 | return this.setTimeout_(funcToCall, 0); |
| 570 | }; |
| 571 | |
| 572 | |
| 573 | /** |
| 574 | * Clears a timeout. |
| 575 | * Mock implementation for clearTimeout. |
| 576 | * @param {number} timeoutKey The timeout key to clear. |
| 577 | * @private |
| 578 | */ |
| 579 | goog.testing.MockClock.prototype.clearTimeout_ = function(timeoutKey) { |
| 580 | // Some common libraries register static state with timers. |
| 581 | // This is bad. It leads to all sorts of crazy test problems where |
| 582 | // 1) Test A sets up a new mock clock and a static timer. |
| 583 | // 2) Test B sets up a new mock clock, but re-uses the static timer |
| 584 | // from Test A. |
| 585 | // 3) A timeout key from test A gets cleared, breaking a timeout in |
| 586 | // Test B. |
| 587 | // |
| 588 | // For now, we just hackily fail silently if someone tries to clear a timeout |
| 589 | // key before we've allocated it. |
| 590 | // Ideally, we should throw an exception if we see this happening. |
| 591 | if (this.isTimeoutSet(timeoutKey)) { |
| 592 | this.deletedKeys_[timeoutKey] = true; |
| 593 | } |
| 594 | }; |
| 595 | |
| 596 | |
| 597 | /** |
| 598 | * Clears an interval. |
| 599 | * Mock implementation for clearInterval. |
| 600 | * @param {number} timeoutKey The interval key to clear. |
| 601 | * @private |
| 602 | */ |
| 603 | goog.testing.MockClock.prototype.clearInterval_ = function(timeoutKey) { |
| 604 | this.clearTimeout_(timeoutKey); |
| 605 | }; |
| 606 | |
| 607 | |
| 608 | /** |
| 609 | * Clears a requestAnimationFrame. |
| 610 | * Mock implementation for cancelRequestAnimationFrame. |
| 611 | * @param {number} timeoutKey The requestAnimationFrame key to clear. |
| 612 | * @private |
| 613 | */ |
| 614 | goog.testing.MockClock.prototype.cancelRequestAnimationFrame_ = |
| 615 | function(timeoutKey) { |
| 616 | this.clearTimeout_(timeoutKey); |
| 617 | }; |