Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/testing/mockclock.js
4500 views
1
/**
2
* @license
3
* Copyright The Closure Library Authors.
4
* SPDX-License-Identifier: Apache-2.0
5
*/
6
7
/**
8
* @fileoverview Mock Clock implementation for working with setTimeout,
9
* setInterval, clearTimeout and clearInterval within unit tests.
10
*
11
* Derived from jsUnitMockTimeout.js, contributed to JsUnit by
12
* Pivotal Computer Systems, www.pivotalsf.com
13
*/
14
15
goog.setTestOnly('goog.testing.MockClock');
16
goog.provide('goog.testing.MockClock');
17
18
goog.require('goog.Disposable');
19
/** @suppress {extraRequire} */
20
goog.require('goog.Promise');
21
goog.require('goog.Thenable');
22
goog.require('goog.asserts');
23
goog.require('goog.async.nextTick');
24
goog.require('goog.async.run');
25
goog.require('goog.testing.PropertyReplacer');
26
goog.require('goog.testing.events');
27
goog.require('goog.testing.events.Event');
28
29
30
31
/**
32
* Class for unit testing code that uses setTimeout and clearTimeout.
33
*
34
* NOTE: If you are using MockClock to test code that makes use of
35
* goog.fx.Animation, then you must either:
36
*
37
* 1. Install and dispose of the MockClock in setUpPage() and tearDownPage()
38
* respectively (rather than setUp()/tearDown()).
39
*
40
* or
41
*
42
* 2. Ensure that every test clears the animation queue by calling
43
* mockClock.tick(x) at the end of each test function (where `x` is large
44
* enough to complete all animations).
45
*
46
* Otherwise, if any animation is left pending at the time that
47
* MockClock.dispose() is called, that will permanently prevent any future
48
* animations from playing on the page.
49
*
50
* @param {boolean=} opt_autoInstall Install the MockClock at construction time.
51
* @constructor
52
* @extends {goog.Disposable}
53
* @final
54
*/
55
goog.testing.MockClock = function(opt_autoInstall) {
56
'use strict';
57
goog.Disposable.call(this);
58
/**
59
* Reverse-order queue of timers to fire.
60
*
61
* The last item of the queue is popped off. Insertion happens from the
62
* right. For example, the expiration times for each element of the queue
63
* might be in the order 300, 200, 200.
64
*
65
* @type {?Array<!goog.testing.MockClock.QueueObjType_>}
66
* @private
67
*/
68
this.queue_ = [];
69
70
/**
71
* Set of timeouts that should be treated as cancelled.
72
*
73
* Rather than removing cancelled timers directly from the queue, this set
74
* simply marks them as deleted so that they can be ignored when their
75
* turn comes up. The keys are the timeout keys that are cancelled, each
76
* mapping to true.
77
*
78
* @private {?Object<number, boolean>}
79
*/
80
this.deletedKeys_ = {};
81
82
/**
83
* Whether we should skip mocking Date.now().
84
* @private {boolean}
85
*/
86
this.unmockDateNow_ = false;
87
88
if (opt_autoInstall) {
89
this.install();
90
}
91
};
92
goog.inherits(goog.testing.MockClock, goog.Disposable);
93
94
95
/**
96
* @typedef {{
97
* timeoutKey: number, millis: number,
98
* runAtMillis: number, funcToCall: !Function, recurring: boolean}}
99
* @private
100
*/
101
goog.testing.MockClock.QueueObjType_;
102
103
/**
104
* Default wait timeout for mocking requestAnimationFrame (in milliseconds).
105
*
106
* @type {number}
107
* @const
108
*/
109
goog.testing.MockClock.REQUEST_ANIMATION_FRAME_TIMEOUT = 20;
110
111
112
/**
113
* ID to use for next timeout. Timeout IDs must never be reused, even across
114
* MockClock instances.
115
* @public {number}
116
*/
117
goog.testing.MockClock.nextId = Math.round(Math.random() * 10000);
118
119
120
/**
121
* Count of the number of setTimeout/setInterval/etc. calls received by this
122
* instance.
123
* @type {number}
124
* @private
125
*/
126
goog.testing.MockClock.prototype.timeoutsMade_ = 0;
127
128
129
/**
130
* Count of the number of timeout/interval/etc. callbacks triggered by this
131
* instance.
132
* @type {number}
133
* @private
134
*/
135
goog.testing.MockClock.prototype.callbacksTriggered_ = 0;
136
137
138
/**
139
* PropertyReplacer instance which overwrites and resets setTimeout,
140
* setInterval, etc. or null if the MockClock is not installed.
141
* @type {?goog.testing.PropertyReplacer}
142
* @private
143
*/
144
goog.testing.MockClock.prototype.replacer_ = null;
145
146
147
/**
148
* The current simulated time in milliseconds.
149
* @type {number}
150
* @private
151
*/
152
goog.testing.MockClock.prototype.nowMillis_ = 0;
153
154
155
/**
156
* Additional delay between the time a timeout was set to fire, and the time
157
* it actually fires. Useful for testing workarounds for this Firefox 2 bug:
158
* https://bugzilla.mozilla.org/show_bug.cgi?id=291386
159
* May be negative.
160
* @type {number}
161
* @private
162
*/
163
goog.testing.MockClock.prototype.timeoutDelay_ = 0;
164
165
166
/**
167
* Whether the MockClock is allowed to use synchronous ticks.
168
*
169
* When this is true, MockClock will patch goog.async.run upon installation so
170
* that GoogPromises can be resolved synchronously.
171
* @type {boolean}
172
* @private
173
*/
174
goog.testing.MockClock.prototype.isSynchronous_ = true;
175
176
177
/**
178
* Creates an async-only MockClock that can only be ticked asynchronously.
179
*
180
* Async-only MockClocks rely on native Promise resolution instead of
181
* patching async run behavior to force GoogPromise to resolve synchronously.
182
* As a result, async MockClocks must be ticked with tickAsync() instead of
183
* tick().
184
*
185
* Async-only MockClocks will always use the default async scheduler and will
186
* never reset the async queue when uninstalled.
187
*
188
* @return {!goog.testing.MockClock}
189
*/
190
goog.testing.MockClock.createAsyncMockClock = function() {
191
const clock = new goog.testing.MockClock();
192
clock.isSynchronous_ = false;
193
return clock;
194
};
195
196
/**
197
* The real set timeout for reference.
198
* @const @private {!Function}
199
*/
200
goog.testing.MockClock.REAL_SETTIMEOUT_ = goog.global.setTimeout;
201
202
203
/** @private {function():number} */
204
goog.testing.MockClock.prototype.oldGoogNow_;
205
206
/**
207
* Installs the MockClock by overriding the global object's implementation of
208
* setTimeout, setInterval, clearTimeout and clearInterval.
209
*/
210
goog.testing.MockClock.prototype.install = function() {
211
'use strict';
212
if (!this.replacer_) {
213
if (goog.testing.MockClock.REAL_SETTIMEOUT_ !== goog.global.setTimeout) {
214
if (typeof console !== 'undefined' && console.warn) {
215
console.warn(
216
'Non default setTimeout detected. ' +
217
'Use of multiple MockClock instances or other clock mocking ' +
218
'should be avoided due to unspecified behavior and ' +
219
'the resulting fragility.');
220
}
221
}
222
223
var r = this.replacer_ = new goog.testing.PropertyReplacer();
224
r.set(goog.global, 'setTimeout', goog.bind(this.setTimeout_, this));
225
r.set(goog.global, 'setInterval', goog.bind(this.setInterval_, this));
226
r.set(goog.global, 'clearTimeout', goog.bind(this.clearTimeout_, this));
227
r.set(goog.global, 'clearInterval', goog.bind(this.clearInterval_, this));
228
if (!this.unmockDateNow_) {
229
r.set(Date, 'now', goog.bind(this.getCurrentTime, this));
230
}
231
// goog.async.nextTick depends on various internal browser APIs
232
// (setImmediate, MessageChannel, and setTimeout), but it's internal
233
// implementation uses local caching which makes stubbing the browser
234
// natives not feasible. Stub it directly instead.
235
r.set(
236
goog.async.nextTick, 'nextTickImpl',
237
goog.bind(this.setImmediate_, this));
238
// setImmediate is a deprecated API that does not exist in most browsers.
239
// Set it in the browser supports it.
240
// Preserve existing behavior of synchronous mock clocks to unconditionally
241
// stub setImmediate.
242
if (goog.global['setImmediate'] || this.isSynchronous_) {
243
r.set(goog.global, 'setImmediate', goog.bind(this.setImmediate_, this));
244
}
245
246
if (this.isSynchronous_) {
247
// goog.Promise uses goog.async.run. In order to be able to test
248
// Promise-based code synchronously, we need to make sure that
249
// goog.async.run uses nextTick instead of native browser Promises. Since
250
// nextTick calls setImmediate, it will be synchronously executed the
251
// next time the MockClock is ticked. Note that we test for the presence
252
// of goog.async.run.forceNextTick to be resilient to the case where
253
// tests replace goog.async.run directly.
254
goog.async.run.forceNextTick &&
255
goog.async.run.forceNextTick(goog.testing.MockClock.REAL_SETTIMEOUT_);
256
} else {
257
// Reset the scheduler in case a synchronous MockClock was previously
258
// installed. Otherwise goog.Promise resolution and other work scheduled
259
// with goog.async.run would be executed synchronously when ticking the
260
// clock.
261
goog.async.run.resetSchedulerForTest &&
262
goog.async.run.resetSchedulerForTest();
263
}
264
265
// Replace the requestAnimationFrame functions.
266
this.replaceRequestAnimationFrame_();
267
268
// PropertyReplacer#set can't be called with renameable functions.
269
this.oldGoogNow_ = goog.now;
270
goog.now = goog.bind(this.getCurrentTime, this);
271
}
272
};
273
274
275
/**
276
* Unmocks the Date.now() function for tests that aren't expecting it to be
277
* mocked. See b/141619890.
278
* @deprecated
279
*/
280
goog.testing.MockClock.prototype.unmockDateNow = function() {
281
'use strict';
282
this.unmockDateNow_ = true;
283
if (this.replacer_) {
284
try {
285
this.replacer_.restore(Date, 'now');
286
} catch (e) {
287
// Ignore error thrown if Date.now was not already mocked.
288
}
289
}
290
};
291
292
293
/**
294
* Installs the mocks for requestAnimationFrame and cancelRequestAnimationFrame.
295
* @private
296
*/
297
goog.testing.MockClock.prototype.replaceRequestAnimationFrame_ = function() {
298
'use strict';
299
var r = this.replacer_;
300
var requestFuncs = [
301
'requestAnimationFrame', 'webkitRequestAnimationFrame',
302
'mozRequestAnimationFrame', 'oRequestAnimationFrame',
303
'msRequestAnimationFrame'
304
];
305
306
var cancelFuncs = [
307
'cancelAnimationFrame', 'cancelRequestAnimationFrame',
308
'webkitCancelRequestAnimationFrame', 'mozCancelRequestAnimationFrame',
309
'oCancelRequestAnimationFrame', 'msCancelRequestAnimationFrame'
310
];
311
312
for (var i = 0; i < requestFuncs.length; ++i) {
313
if (goog.global && goog.global[requestFuncs[i]]) {
314
r.set(
315
goog.global, requestFuncs[i],
316
goog.bind(this.requestAnimationFrame_, this));
317
}
318
}
319
320
for (var i = 0; i < cancelFuncs.length; ++i) {
321
if (goog.global && goog.global[cancelFuncs[i]]) {
322
r.set(
323
goog.global, cancelFuncs[i],
324
goog.bind(this.cancelRequestAnimationFrame_, this));
325
}
326
}
327
};
328
329
330
/**
331
* Removes the MockClock's hooks into the global object's functions and revert
332
* to their original values.
333
*
334
* @param {boolean=} resetScheduler By default, a synchronous MockClock
335
* will not restore default goog.async behavior upon uninstallation and
336
* clear any pending async work. This can leave goog.Promises in a state
337
* where callbacks can never be executed. Set this flag to restore original
338
* scheduling behavior and retain the async queue. This argument is ignored
339
* for an async-only MockClock.
340
*/
341
goog.testing.MockClock.prototype.uninstall = function(resetScheduler) {
342
'use strict';
343
if (this.replacer_) {
344
this.replacer_.reset();
345
this.replacer_ = null;
346
goog.now = this.oldGoogNow_;
347
}
348
349
if (this.isSynchronous_) {
350
// Since async-only MockClock instances are always reset on installation,
351
// they don't need to be reset when uninstalled.
352
if (resetScheduler) {
353
// Check for presence of resetScheduler in case users have replaced
354
// goog.async.run.
355
goog.async.run.resetSchedulerForTest &&
356
goog.async.run.resetSchedulerForTest();
357
} else {
358
// If the overridden scheduler is not reset, then clear the work queue.
359
// This prevents any pending goog.Promise resolution or other work
360
// scheduled with goog.async.run from executing after uninstallation.
361
this.resetAsyncQueue_();
362
}
363
}
364
};
365
366
367
/** @override */
368
goog.testing.MockClock.prototype.disposeInternal = function() {
369
'use strict';
370
this.uninstall();
371
this.queue_ = null;
372
this.deletedKeys_ = null;
373
goog.testing.MockClock.superClass_.disposeInternal.call(this);
374
};
375
376
377
/**
378
* Resets the MockClock, removing all timeouts that are scheduled and resets
379
* the fake timer count.
380
* @param {boolean=} retainAsyncQueue By default, a synchronous MockClock
381
* will clear any pending async work when reset. This can leave
382
* goog.Promises in a state where callbacks can never be executed. Set this
383
* flag to restore original scheduling behavior and retain the async queue.
384
* This argument is ignored for an async-only MockClock.
385
*/
386
goog.testing.MockClock.prototype.reset = function(retainAsyncQueue) {
387
'use strict';
388
this.queue_ = [];
389
this.deletedKeys_ = {};
390
this.nowMillis_ = 0;
391
this.timeoutsMade_ = 0;
392
this.callbacksTriggered_ = 0;
393
this.timeoutDelay_ = 0;
394
395
if (this.isSynchronous_ && !retainAsyncQueue) {
396
// If the overridden scheduler is not intended to be reset, then clear the
397
// work queue. This prevents any pending async work queue items from
398
// executing after uninstallation.
399
this.resetAsyncQueue_();
400
}
401
};
402
403
404
/**
405
* Resets the async queue when a synchronous MockClock resets.
406
* @private
407
*/
408
goog.testing.MockClock.prototype.resetAsyncQueue_ = function() {
409
'use strict';
410
// Synchronous MockClock should reset the async queue so that pending tasks
411
// are not executed the next time the call stack is emptied.
412
goog.asserts.assert(
413
this.isSynchronous_,
414
'Async queue cannot be reset on async-only async MockClock.');
415
416
goog.async.run.resetQueue();
417
};
418
419
420
/**
421
* Sets the amount of time between when a timeout is scheduled to fire and when
422
* it actually fires.
423
* @param {number} delay The delay in milliseconds. May be negative.
424
*/
425
goog.testing.MockClock.prototype.setTimeoutDelay = function(delay) {
426
'use strict';
427
this.timeoutDelay_ = delay;
428
};
429
430
431
/**
432
* @return {number} delay The amount of time between when a timeout is
433
* scheduled to fire and when it actually fires, in milliseconds. May
434
* be negative.
435
*/
436
goog.testing.MockClock.prototype.getTimeoutDelay = function() {
437
'use strict';
438
return this.timeoutDelay_;
439
};
440
441
442
/**
443
* Increments the MockClock's time by a given number of milliseconds, running
444
* any functions that are now overdue.
445
* @param {number=} opt_millis Number of milliseconds to increment the counter.
446
* If not specified, clock ticks 1 millisecond.
447
* @return {number} Current mock time in milliseconds.
448
*/
449
goog.testing.MockClock.prototype.tick = function(opt_millis) {
450
'use strict';
451
goog.asserts.assert(
452
this.isSynchronous_,
453
'Async MockClock does not support tick. Use tickAsync() instead.');
454
if (typeof opt_millis != 'number') {
455
opt_millis = 1;
456
}
457
if (opt_millis < 0) {
458
throw new Error(
459
'Time cannot go backwards (cannot tick by ' + opt_millis + ')');
460
}
461
var endTime = this.nowMillis_ + opt_millis;
462
this.runFunctionsWithinRange_(endTime);
463
// If a scheduled callback called tick() reentrantly, don't rewind time.
464
this.nowMillis_ = Math.max(this.nowMillis_, endTime);
465
return endTime;
466
};
467
468
469
/**
470
* Takes a promise and then ticks the mock clock. If the promise successfully
471
* resolves, returns the value produced by the promise. If the promise is
472
* rejected, it throws the rejection as an exception. If the promise is not
473
* resolved at all, throws an exception.
474
* Also ticks the general clock by the specified amount.
475
* Only works with goog.Thenable, hence goog.Promise. Does NOT work with native
476
* browser promises.
477
*
478
* @param {!goog.Thenable<T>} promise A promise that should be resolved after
479
* the mockClock is ticked for the given opt_millis.
480
* @param {number=} opt_millis Number of milliseconds to increment the counter.
481
* If not specified, clock ticks 1 millisecond.
482
* @return {T}
483
* @template T
484
*
485
* @deprecated Treating Promises as synchronous values is incompatible with
486
* native promises and async functions. More generally, this code relies on
487
* promises "pumped" by setTimeout which is not done in production code,
488
* even for goog.Promise and results unnatural timing between resolved
489
* promises callback and setTimeout/setInterval callbacks in tests.
490
*/
491
goog.testing.MockClock.prototype.tickPromise = function(promise, opt_millis) {
492
'use strict';
493
goog.asserts.assert(
494
this.isSynchronous_, 'Async MockClock does not support tickPromise.');
495
496
let value;
497
let error;
498
let resolved = false;
499
promise.then(
500
function(v) {
501
'use strict';
502
value = v;
503
resolved = true;
504
},
505
function(e) {
506
'use strict';
507
error = e;
508
resolved = true;
509
});
510
this.tick(opt_millis);
511
if (!resolved) {
512
throw new Error(
513
'Promise was expected to be resolved after mock clock tick.');
514
}
515
if (error) {
516
throw error;
517
}
518
return value;
519
};
520
521
522
/**
523
* @return {number} The number of timeouts or intervals that have been
524
* scheduled. A setInterval call is only counted once.
525
*/
526
goog.testing.MockClock.prototype.getTimeoutsMade = function() {
527
'use strict';
528
return this.timeoutsMade_;
529
};
530
531
532
/**
533
* @return {number} The number of timeout or interval callbacks that have been
534
* triggered. For setInterval, each callback is counted separately.
535
*/
536
goog.testing.MockClock.prototype.getCallbacksTriggered = function() {
537
'use strict';
538
return this.callbacksTriggered_;
539
};
540
541
542
/**
543
* @return {number} The MockClock's current time in milliseconds.
544
*/
545
goog.testing.MockClock.prototype.getCurrentTime = function() {
546
'use strict';
547
return this.nowMillis_;
548
};
549
550
551
/**
552
* @param {number} timeoutKey The timeout key.
553
* @return {boolean} Whether the timer has been set and not cleared,
554
* independent of the timeout's expiration. In other words, the timeout
555
* could have passed or could be scheduled for the future. Either way,
556
* this function returns true or false depending only on whether the
557
* provided timeoutKey represents a timeout that has been set and not
558
* cleared.
559
*/
560
goog.testing.MockClock.prototype.isTimeoutSet = function(timeoutKey) {
561
'use strict';
562
return timeoutKey < goog.testing.MockClock.nextId &&
563
timeoutKey >= goog.testing.MockClock.nextId - this.timeoutsMade_ &&
564
!this.deletedKeys_[timeoutKey];
565
};
566
567
568
/**
569
* Whether the MockClock is configured to run synchronously.
570
*
571
* This allows MockClock consumers to decide whether to tick synchronously or
572
* asynchronously.
573
* @return {boolean}
574
*/
575
goog.testing.MockClock.prototype.isSynchronous = function() {
576
return this.isSynchronous_;
577
};
578
579
580
/**
581
* Runs any function that is scheduled before a certain time. Timeouts can
582
* be made to fire early or late if timeoutDelay_ is non-0.
583
* @param {number} endTime The latest time in the range, in milliseconds.
584
* @private
585
*/
586
goog.testing.MockClock.prototype.runFunctionsWithinRange_ = function(endTime) {
587
'use strict';
588
// Repeatedly pop off the last item since the queue is always sorted.
589
while (this.hasQueuedEntriesBefore_(endTime)) {
590
this.runNextQueuedTimeout_();
591
}
592
};
593
594
595
/**
596
* Increments the MockClock's time by a given number of milliseconds, running
597
* any functions that are now overdue.
598
* @param {number=} millis Number of milliseconds to increment the counter.
599
* If not specified, clock ticks 1 millisecond.
600
* @return {!Promise<number>} Current mock time in milliseconds.
601
*/
602
goog.testing.MockClock.prototype.tickAsync = async function(millis = 1) {
603
if (millis < 0) {
604
throw new Error(`Time cannot go backwards (cannot tick by ${millis})`);
605
}
606
const endTime = this.nowMillis_ + millis;
607
await this.runFunctionsWithinRangeAsync_(endTime);
608
// If a scheduled callback called tick() reentrantly, don't rewind time.
609
this.nowMillis_ = Math.max(this.nowMillis_, endTime);
610
return endTime;
611
};
612
613
614
/**
615
* Asynchronously increments the MockClock's time by a given number of
616
* milliseconds, returning the settled promise value.
617
* @param {number} millis Number of milliseconds to increment the counter.
618
* @param {!goog.Thenable<T>} promise A promise that should be resolved after
619
* the mockClock is ticked for the given opt_millis.
620
* @return {!Promise<T>} Resolved promise value.
621
* @throws {!goog.asserts.AssertionError} when the promise is not resolved after
622
* ticking.
623
* @throws {*} when the promise is rejected.
624
* @template T
625
*/
626
goog.testing.MockClock.prototype.tickAsyncMustSettlePromise =
627
async function(millis, promise) {
628
goog.asserts.assert(
629
!this.isSynchronous_,
630
'Synchronous MockClock does not support tickAsyncMustSettlePromise.');
631
632
let settled = false;
633
let value;
634
let error;
635
promise.then(
636
(v) => {
637
settled = true;
638
value = v;
639
},
640
(e) => {
641
settled = true;
642
error = e;
643
});
644
await this.tickAsync(millis);
645
goog.asserts.assert(
646
settled, 'Promise was expected to be resolved after mock clock tick.');
647
if (error !== undefined) {
648
throw error;
649
}
650
return value;
651
};
652
653
654
/**
655
* Instantly adjusts the clock's current time to a new timestamp. Unlike tick(),
656
* this method skips over the intervening time, so that `setInterval()` calls or
657
* recurring `setTimeout()`s will only run once.
658
*
659
* This mimics the behavior of setting the system clock, rather than waiting for
660
* time to pass.
661
*
662
* CAUTION: This is an advanced feature. Use this method to set the clock to be
663
* a specific date, which is much faster than calling tick() with a large value.
664
* This lets you test code against arbitrary dates.
665
*
666
* MOE:begin_strip
667
* See go/mockclock-time-travel for how & why to use this method.
668
* MOE:end_strip
669
*
670
* @param {!Date} newDate The new timestamp to set the clock to.
671
* @return {!Promise}
672
*/
673
goog.testing.MockClock.prototype.doTimeWarpAsync = async function(newDate) {
674
goog.asserts.assertInstanceof(
675
newDate, Date,
676
'doTimeWarpAsync() only accepts dates. Use tickAsync() instead.');
677
if (+newDate < this.nowMillis_) {
678
throw new Error(`Time cannot go backwards (cannot time warp from ${
679
new Date(this.nowMillis_)} to ${newDate})`);
680
}
681
// Adjust the clock before calling the functions, so that they schedule future
682
// callbacks from the new time.
683
this.nowMillis_ = +newDate;
684
await this.runFunctionsWithinRangeAsync_(this.nowMillis_);
685
};
686
687
688
/**
689
* Like runFunctionsWithinRange, but pauses to allow native promise callbacks to
690
* run correctly.
691
* @param {number} endTime The latest time in the range, in milliseconds.
692
* @return {!Promise}
693
* @private
694
*/
695
goog.testing.MockClock.prototype.runFunctionsWithinRangeAsync_ =
696
async function(endTime) {
697
'use strict';
698
// Let native promises set timers before we start ticking.
699
await goog.testing.MockClock.flushMicroTasks_();
700
701
// Repeatedly pop off the last item since the queue is always sorted.
702
while (this.hasQueuedEntriesBefore_(endTime)) {
703
if (this.runNextQueuedTimeout_()) {
704
await goog.testing.MockClock.flushMicroTasks_();
705
}
706
}
707
};
708
709
710
/**
711
* Pauses asynchronously to run all promise callbacks in the microtask queue.
712
*
713
* This is optimized to be correct, but to also not be too slow in IE. It waits
714
* for up to 50 chained `then()` callbacks at once. Microtasks callbacks are run
715
* in batches, so a series of `then()` callbacks scheduled at the same time will
716
* run at once. The loop is only necessary for to run very deep promise chains.
717
*
718
* Using `setTimeout()`, `setImmediate()`, or a polyfill would make this better,
719
* but also makes it 15x slower in IE. Without IE, setImmediate and polyfill is
720
* best option.
721
* @private
722
*/
723
goog.testing.MockClock.flushMicroTasks_ = async function() {
724
'use strict';
725
for (var i = 0; i < 50; i++) {
726
await Promise.resolve();
727
}
728
};
729
730
731
/**
732
* @param {number} endTime The latest time in the range, in milliseconds.
733
* @return {boolean}
734
* @private
735
*/
736
goog.testing.MockClock.prototype.hasQueuedEntriesBefore_ = function(endTime) {
737
'use strict';
738
var adjustedEndTime = endTime - this.timeoutDelay_;
739
return !!this.queue_ && !!this.queue_.length &&
740
this.queue_[this.queue_.length - 1].runAtMillis <= adjustedEndTime;
741
};
742
743
744
/**
745
* Runs the next timeout in the queue, advancing the clock.
746
* @return {boolean} False if the timeout was cancelled (and nothing happened).
747
* @private
748
*/
749
goog.testing.MockClock.prototype.runNextQueuedTimeout_ = function() {
750
'use strict';
751
var timeout = this.queue_.pop();
752
753
if (timeout.timeoutKey in this.deletedKeys_) return false;
754
755
// Only move time forwards.
756
this.nowMillis_ =
757
Math.max(this.nowMillis_, timeout.runAtMillis + this.timeoutDelay_);
758
// Call timeout in global scope and pass the timeout key as the argument.
759
this.callbacksTriggered_++;
760
timeout.funcToCall.call(goog.global, timeout.timeoutKey);
761
// In case the interval was cleared in the funcToCall
762
if (timeout.recurring) {
763
this.scheduleFunction_(
764
timeout.timeoutKey, timeout.funcToCall, timeout.millis, true);
765
}
766
return true;
767
};
768
769
770
/**
771
* Schedules a function to be run at a certain time.
772
* @param {number} timeoutKey The timeout key.
773
* @param {!Function} funcToCall The function to call.
774
* @param {number} millis The number of milliseconds to call it in.
775
* @param {boolean} recurring Whether to function call should recur.
776
* @private
777
*/
778
goog.testing.MockClock.prototype.scheduleFunction_ = function(
779
timeoutKey, funcToCall, millis, recurring) {
780
'use strict';
781
if (typeof funcToCall !== 'function') {
782
// Early error for debuggability rather than dying in the next .tick()
783
throw new TypeError(
784
'The provided callback must be a function, not a ' + typeof funcToCall);
785
}
786
787
var /** !goog.testing.MockClock.QueueObjType_ */ timeout = {
788
runAtMillis: this.nowMillis_ + millis,
789
funcToCall: funcToCall,
790
recurring: recurring,
791
timeoutKey: timeoutKey,
792
millis: millis
793
};
794
795
goog.testing.MockClock.insert_(timeout, goog.asserts.assert(this.queue_));
796
};
797
798
799
/**
800
* Inserts a timer descriptor into a descending-order queue.
801
*
802
* Later-inserted duplicates appear at lower indices. For example, the
803
* asterisk in (5,4,*,3,2,1) would be the insertion point for 3.
804
*
805
* @param {!goog.testing.MockClock.QueueObjType_} timeout The timeout to insert,
806
* with numerical runAtMillis property.
807
* @param {!Array<!goog.testing.MockClock.QueueObjType_>} queue The queue to
808
* insert into, with each element having a numerical runAtMillis property.
809
* @private
810
*/
811
goog.testing.MockClock.insert_ = function(timeout, queue) {
812
'use strict';
813
// Although insertion of N items is quadratic, requiring goog.structs.Heap
814
// from a unit test will make tests more prone to breakage. Since unit
815
// tests are normally small, scalability is not a primary issue.
816
817
// Find an insertion point. Since the queue is in reverse order (so we
818
// can pop rather than unshift), and later timers with the same time stamp
819
// should be executed later, we look for the element strictly greater than
820
// the one we are inserting.
821
822
for (var i = queue.length; i != 0; i--) {
823
if (queue[i - 1].runAtMillis > timeout.runAtMillis) {
824
break;
825
}
826
queue[i] = queue[i - 1];
827
}
828
829
queue[i] = timeout;
830
};
831
832
833
/**
834
* Maximum 32-bit signed integer.
835
*
836
* Timeouts over this time return immediately in many browsers, due to integer
837
* overflow. Such known browsers include Firefox, Chrome, and Safari, but not
838
* IE.
839
*
840
* @type {number}
841
* @private
842
*/
843
goog.testing.MockClock.MAX_INT_ = 2147483647;
844
845
846
/**
847
* Schedules a function to be called after `millis` milliseconds.
848
* Mock implementation for setTimeout.
849
* @param {!Function} funcToCall The function to call.
850
* @param {number=} opt_millis The number of milliseconds to call it after.
851
* @return {number} The number of timeouts created.
852
* @private
853
*/
854
goog.testing.MockClock.prototype.setTimeout_ = function(
855
funcToCall, opt_millis) {
856
'use strict';
857
var millis = opt_millis || 0;
858
if (millis > goog.testing.MockClock.MAX_INT_) {
859
throw new Error(
860
'Bad timeout value: ' + millis + '. Timeouts over MAX_INT ' +
861
'(24.8 days) cause timeouts to be fired ' +
862
'immediately in most browsers, except for IE.');
863
}
864
this.timeoutsMade_++;
865
this.scheduleFunction_(
866
goog.testing.MockClock.nextId, funcToCall, millis, false);
867
return goog.testing.MockClock.nextId++;
868
};
869
870
871
/**
872
* Schedules a function to be called every `millis` milliseconds.
873
* Mock implementation for setInterval.
874
* @param {!Function} funcToCall The function to call.
875
* @param {number=} opt_millis The number of milliseconds between calls.
876
* @return {number} The number of timeouts created.
877
* @private
878
*/
879
goog.testing.MockClock.prototype.setInterval_ = function(
880
funcToCall, opt_millis) {
881
'use strict';
882
var millis = opt_millis || 0;
883
this.timeoutsMade_++;
884
this.scheduleFunction_(
885
goog.testing.MockClock.nextId, funcToCall, millis, true);
886
return goog.testing.MockClock.nextId++;
887
};
888
889
890
/**
891
* Schedules a function to be called when an animation frame is triggered.
892
* Mock implementation for requestAnimationFrame.
893
* @param {!Function} funcToCall The function to call.
894
* @return {number} The number of timeouts created.
895
* @private
896
*/
897
goog.testing.MockClock.prototype.requestAnimationFrame_ = function(funcToCall) {
898
'use strict';
899
return this.setTimeout_(goog.bind(function() {
900
'use strict';
901
if (funcToCall) {
902
funcToCall(this.getCurrentTime());
903
} else if (goog.global.mozRequestAnimationFrame) {
904
var event = new goog.testing.events.Event('MozBeforePaint', goog.global);
905
event['timeStamp'] = this.getCurrentTime();
906
goog.testing.events.fireBrowserEvent(event);
907
}
908
}, this), goog.testing.MockClock.REQUEST_ANIMATION_FRAME_TIMEOUT);
909
};
910
911
912
/**
913
* Schedules a function to be called immediately after the current JS
914
* execution.
915
* Mock implementation for setImmediate.
916
* @param {!Function} funcToCall The function to call.
917
* @return {number} The number of timeouts created.
918
* @private
919
*/
920
goog.testing.MockClock.prototype.setImmediate_ = function(funcToCall) {
921
'use strict';
922
return this.setTimeout_(funcToCall, 0);
923
};
924
925
926
/**
927
* Clears a timeout.
928
* Mock implementation for clearTimeout.
929
* @param {number} timeoutKey The timeout key to clear.
930
* @private
931
*/
932
goog.testing.MockClock.prototype.clearTimeout_ = function(timeoutKey) {
933
'use strict';
934
// Some common libraries register static state with timers.
935
// This is bad. It leads to all sorts of crazy test problems where
936
// 1) Test A sets up a new mock clock and a static timer.
937
// 2) Test B sets up a new mock clock, but re-uses the static timer
938
// from Test A.
939
// 3) A timeout key from test A gets cleared, breaking a timeout in
940
// Test B.
941
//
942
// For now, we just hackily fail silently if someone tries to clear a timeout
943
// key before we've allocated it.
944
// Ideally, we should throw an exception if we see this happening.
945
if (this.isTimeoutSet(timeoutKey)) {
946
this.deletedKeys_[timeoutKey] = true;
947
}
948
};
949
950
951
/**
952
* Clears an interval.
953
* Mock implementation for clearInterval.
954
* @param {number} timeoutKey The interval key to clear.
955
* @private
956
*/
957
goog.testing.MockClock.prototype.clearInterval_ = function(timeoutKey) {
958
'use strict';
959
this.clearTimeout_(timeoutKey);
960
};
961
962
963
/**
964
* Clears a requestAnimationFrame.
965
* Mock implementation for cancelRequestAnimationFrame.
966
* @param {number} timeoutKey The requestAnimationFrame key to clear.
967
* @private
968
*/
969
goog.testing.MockClock.prototype.cancelRequestAnimationFrame_ = function(
970
timeoutKey) {
971
'use strict';
972
this.clearTimeout_(timeoutKey);
973
};
974
975