Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80629 views
1
/**
2
* Copyright (c) 2014, Facebook, Inc. All rights reserved.
3
*
4
* This source code is licensed under the BSD-style license found in the
5
* LICENSE file in the root directory of this source tree. An additional grant
6
* of patent rights can be found in the PATENTS file in the same directory.
7
*/
8
'use strict';
9
10
var mocks = require('./moduleMocker');
11
12
var MS_IN_A_YEAR = 31536000000;
13
14
function FakeTimers(global, maxLoops) {
15
this._global = global;
16
this._uuidCounter = 1;
17
this._maxLoops = maxLoops || 100000;
18
19
this.reset();
20
21
// Store original timer APIs for future reference
22
this._originalTimerAPIs = {
23
setTimeout: global.setTimeout,
24
clearTimeout: global.clearTimeout,
25
setInterval: global.setInterval,
26
clearInterval: global.clearInterval
27
};
28
29
this._fakeTimerAPIs = {
30
setTimeout: mocks.getMockFn().mockImpl(
31
this._fakeSetTimeout.bind(this)
32
),
33
clearTimeout: mocks.getMockFn().mockImpl(
34
this._fakeClearTimer.bind(this)
35
),
36
setInterval: mocks.getMockFn().mockImpl(
37
this._fakeSetInterval.bind(this)
38
),
39
clearInterval: mocks.getMockFn().mockImpl(
40
this._fakeClearTimer.bind(this)
41
)
42
};
43
44
// If there's a process.nextTick on the global, mock it out
45
// (only applicable to node/node-emulating environments)
46
if (typeof global.process === 'object'
47
&& typeof global.process.nextTick === 'function') {
48
this._originalTimerAPIs.nextTick = global.process.nextTick;
49
this._fakeTimerAPIs.nextTick = mocks.getMockFn().mockImpl(
50
this._fakeNextTick.bind(this)
51
);
52
}
53
54
// If there's a global.setImmediate, mock it out
55
if (typeof global.setImmediate === 'function') {
56
this._originalTimerAPIs.setImmediate = global.setImmediate;
57
this._fakeTimerAPIs.setImmediate = mocks.getMockFn().mockImpl(
58
this._fakeSetImmediate.bind(this)
59
);
60
this._originalTimerAPIs.clearImmediate = global.clearImmediate;
61
this._fakeTimerAPIs.clearImmediate = mocks.getMockFn().mockImpl(
62
this._fakeClearImmediate.bind(this)
63
);
64
}
65
66
this.useFakeTimers();
67
68
// TODO: These globally-accessible function are now deprecated!
69
// They will go away very soon, so do not use them!
70
// Instead, use the versions available on the `jest` object
71
global.mockRunTicksRepeatedly = this.runAllTicks.bind(this);
72
global.mockRunTimersOnce = this.runOnlyPendingTimers.bind(this);
73
global.mockRunTimersToTime = this.runTimersToTime.bind(this);
74
global.mockRunTimersRepeatedly = this.runAllTimers.bind(this);
75
global.mockClearTimers = this.clearAllTimers.bind(this);
76
global.mockGetTimersCount = function() {
77
return Object.keys(this._timers).length;
78
}.bind(this);
79
}
80
81
FakeTimers.prototype.clearAllTimers = function() {
82
for (var uuid in this._timers) {
83
delete this._timers[uuid];
84
}
85
};
86
87
FakeTimers.prototype.reset = function() {
88
this._cancelledTicks = {};
89
this._cancelledImmediates = {};
90
this._now = 0;
91
this._ticks = [];
92
this._immediates = [];
93
this._timers = {};
94
};
95
96
// Used to be called runTicksRepeatedly
97
FakeTimers.prototype.runAllTicks = function() {
98
// Only run a generous number of ticks and then bail.
99
// This is just to help avoid recursive loops
100
101
for (var i = 0; i < this._maxLoops; i++) {
102
var tick = this._ticks.shift();
103
104
if (tick === undefined) {
105
break;
106
}
107
108
if (!this._cancelledTicks.hasOwnProperty(tick.uuid)) {
109
// Callback may throw, so update the map prior calling.
110
this._cancelledTicks[tick.uuid] = true;
111
tick.callback();
112
}
113
}
114
115
if (i === this._maxLoops) {
116
throw new Error(
117
'Ran ' + this._maxLoops + ' ticks, and there are still more! Assuming ' +
118
'we\'ve hit an infinite recursion and bailing out...'
119
);
120
}
121
};
122
123
FakeTimers.prototype.runAllImmediates = function() {
124
// Only run a generous number of immediates and then bail.
125
126
for (var i = 0; i < this._maxLoops; i++) {
127
var immediate = this._immediates.shift();
128
129
if (immediate === undefined) {
130
break;
131
}
132
133
if (!this._cancelledImmediates.hasOwnProperty(immediate.uuid)) {
134
// Callback may throw, so update the map prior calling.
135
this._cancelledImmediates[immediate.uuid] = true;
136
immediate.callback();
137
}
138
}
139
140
if (i === this._maxLoops) {
141
throw new Error(
142
'Ran ' + this._maxLoops +
143
' immediates, and there are still more! Assuming ' +
144
'we\'ve hit an infinite recursion and bailing out...'
145
);
146
}
147
};
148
149
// Used to be called runTimersRepeatedly
150
FakeTimers.prototype.runAllTimers = function() {
151
this.runAllTicks();
152
this.runAllImmediates();
153
154
// Only run a generous number of timers and then bail.
155
// This is just to help avoid recursive loops
156
157
for (var i = 0; i < this._maxLoops; i++) {
158
var nextTimerHandle = this._getNextTimerHandle();
159
160
// If there are no more timer handles, stop!
161
if (nextTimerHandle === null) {
162
break;
163
}
164
165
this._runTimerHandle(nextTimerHandle);
166
}
167
168
if (i === this._maxLoops) {
169
throw new Error(
170
'Ran ' + this._maxLoops + ' timers, and there are still more! Assuming ' +
171
'we\'ve hit an infinite recursion and bailing out...'
172
);
173
}
174
};
175
176
// Used to be called runTimersOnce
177
FakeTimers.prototype.runOnlyPendingTimers = function() {
178
var timers = this._timers;
179
Object.keys(timers)
180
.sort(function(left, right) {
181
return timers[left].expiry - timers[right].expiry;
182
})
183
.forEach(this._runTimerHandle, this);
184
};
185
186
// Use to be runTimersToTime
187
FakeTimers.prototype.runTimersToTime = function(msToRun) {
188
// Only run a generous number of timers and then bail.
189
// This is jsut to help avoid recursive loops
190
191
for (var i = 0; i < this._maxLoops; i++) {
192
var timerHandle = this._getNextTimerHandle();
193
194
// If there are no more timer handles, stop!
195
if (timerHandle === null) {
196
break;
197
}
198
199
var nextTimerExpiry = this._timers[timerHandle].expiry;
200
if (this._now + msToRun < nextTimerExpiry) {
201
// There are no timers between now and the target we're running to, so
202
// adjust our time cursor and quit
203
this._now += msToRun;
204
break;
205
} else {
206
msToRun -= (nextTimerExpiry - this._now);
207
this._now = nextTimerExpiry;
208
this._runTimerHandle(timerHandle);
209
}
210
}
211
212
if (i === this._maxLoops) {
213
throw new Error(
214
'Ran ' + this._maxLoops + ' timers, and there are still more! Assuming ' +
215
'we\'ve hit an infinite recursion and bailing out...'
216
);
217
}
218
};
219
220
FakeTimers.prototype.runWithRealTimers = function(cb) {
221
var hasNextTick =
222
typeof this._global.process === 'object'
223
&& typeof this._global.process.nextTick === 'function';
224
225
var hasSetImmediate = typeof this._global.setImmediate === 'function';
226
227
var prevSetTimeout = this._global.setTimeout;
228
var prevSetInterval = this._global.setInterval;
229
var prevClearTimeout = this._global.clearTimeout;
230
var prevClearInterval = this._global.clearInterval;
231
if (hasNextTick) {
232
var prevNextTick = this._global.process.nextTick;
233
}
234
if (hasSetImmediate) {
235
var prevSetImmediate = this._global.setImmediate;
236
var prevClearImmediate = this._global.clearImmediate;
237
}
238
239
this.useRealTimers();
240
241
var cbErr = null;
242
var errThrown = false;
243
try {
244
cb();
245
} catch (e) {
246
errThrown = true;
247
cbErr = e;
248
}
249
250
this._global.setTimeout = prevSetTimeout;
251
this._global.setInterval = prevSetInterval;
252
this._global.clearTimeout = prevClearTimeout;
253
this._global.clearInterval = prevClearInterval;
254
if (hasNextTick) {
255
this._global.process.nextTick = prevNextTick;
256
}
257
if (hasSetImmediate) {
258
this._global.setImmediate = prevSetImmediate;
259
this._global.clearImmediate = prevClearImmediate;
260
}
261
262
if (errThrown) {
263
throw cbErr;
264
}
265
};
266
267
FakeTimers.prototype.useRealTimers = function() {
268
var hasNextTick =
269
typeof this._global.process === 'object'
270
&& typeof this._global.process.nextTick === 'function';
271
272
var hasSetImmediate = typeof this._global.setImmediate === 'function';
273
274
this._global.setTimeout = this._originalTimerAPIs.setTimeout;
275
this._global.setInterval = this._originalTimerAPIs.setInterval;
276
this._global.clearTimeout = this._originalTimerAPIs.clearTimeout;
277
this._global.clearInterval = this._originalTimerAPIs.clearInterval;
278
if (hasNextTick) {
279
this._global.process.nextTick = this._originalTimerAPIs.nextTick;
280
}
281
if (hasSetImmediate) {
282
this._global.setImmediate = this._originalTimerAPIs.setImmediate;
283
this._global.clearImmediate = this._originalTimerAPIs.clearImmediate;
284
}
285
};
286
287
FakeTimers.prototype.useFakeTimers = function() {
288
var hasNextTick =
289
typeof this._global.process === 'object'
290
&& typeof this._global.process.nextTick === 'function';
291
292
var hasSetImmediate = typeof this._global.setImmediate === 'function';
293
294
this._global.setTimeout = this._fakeTimerAPIs.setTimeout;
295
this._global.setInterval = this._fakeTimerAPIs.setInterval;
296
this._global.clearTimeout = this._fakeTimerAPIs.clearTimeout;
297
this._global.clearInterval = this._fakeTimerAPIs.clearInterval;
298
if (hasNextTick) {
299
this._global.process.nextTick = this._fakeTimerAPIs.nextTick;
300
}
301
if (hasSetImmediate) {
302
this._global.setImmediate = this._fakeTimerAPIs.setImmediate;
303
this._global.clearImmediate = this._fakeTimerAPIs.clearImmediate;
304
}
305
};
306
307
FakeTimers.prototype._fakeClearTimer = function(uuid) {
308
if (this._timers.hasOwnProperty(uuid)) {
309
delete this._timers[uuid];
310
}
311
};
312
313
FakeTimers.prototype._fakeClearImmediate = function(uuid) {
314
this._cancelledImmediates[uuid] = true;
315
};
316
317
FakeTimers.prototype._fakeNextTick = function(callback) {
318
var uuid = this._uuidCounter++;
319
this._ticks.push({
320
uuid: uuid,
321
callback: callback
322
});
323
324
var cancelledTicks = this._cancelledTicks;
325
this._originalTimerAPIs.nextTick(function() {
326
if (!cancelledTicks.hasOwnProperty(uuid)) {
327
// Callback may throw, so update the map prior calling.
328
cancelledTicks[uuid] = true;
329
callback();
330
}
331
});
332
};
333
334
FakeTimers.prototype._fakeSetImmediate = function(callback) {
335
var args = [];
336
for (var ii = 1, ll = arguments.length; ii < ll; ii++) {
337
args.push(arguments[ii]);
338
}
339
340
var uuid = this._uuidCounter++;
341
342
this._immediates.push({
343
uuid: uuid,
344
callback: function() {
345
return callback.apply(null, args);
346
}
347
});
348
349
var cancelledImmediates = this._cancelledImmediates;
350
this._originalTimerAPIs.setImmediate(function() {
351
if (!cancelledImmediates.hasOwnProperty(uuid)) {
352
// Callback may throw, so update the map prior calling.
353
cancelledImmediates[uuid] = true;
354
callback();
355
}
356
});
357
358
return uuid;
359
};
360
361
FakeTimers.prototype._fakeSetInterval = function(callback, intervalDelay) {
362
if (intervalDelay === undefined || intervalDelay === null) {
363
intervalDelay = 0;
364
}
365
366
var args = [];
367
for (var ii = 2, ll = arguments.length; ii < ll; ii++) {
368
args.push(arguments[ii]);
369
}
370
371
var uuid = this._uuidCounter++;
372
373
this._timers[uuid] = {
374
type: 'interval',
375
callback: function() {
376
return callback.apply(null, args);
377
},
378
expiry: this._now + intervalDelay,
379
interval: intervalDelay
380
};
381
382
return uuid;
383
};
384
385
FakeTimers.prototype._fakeSetTimeout = function(callback, delay) {
386
if (delay === undefined || delay === null) {
387
delay = 0;
388
}
389
390
var args = [];
391
for (var ii = 2, ll = arguments.length; ii < ll; ii++) {
392
args.push(arguments[ii]);
393
}
394
395
var uuid = this._uuidCounter++;
396
397
this._timers[uuid] = {
398
type: 'timeout',
399
callback: function() {
400
return callback.apply(null, args);
401
},
402
expiry: this._now + delay,
403
interval: null
404
};
405
406
return uuid;
407
};
408
409
FakeTimers.prototype._getNextTimerHandle = function() {
410
var nextTimerHandle = null;
411
var uuid;
412
var soonestTime = MS_IN_A_YEAR;
413
414
var timer;
415
for (uuid in this._timers) {
416
timer = this._timers[uuid];
417
if (timer.expiry < soonestTime) {
418
soonestTime = timer.expiry;
419
nextTimerHandle = uuid;
420
}
421
}
422
423
return nextTimerHandle;
424
};
425
426
FakeTimers.prototype._runTimerHandle = function(timerHandle) {
427
var timer = this._timers[timerHandle];
428
429
switch (timer.type) {
430
case 'timeout':
431
var callback = timer.callback;
432
delete this._timers[timerHandle];
433
callback();
434
break;
435
436
case 'interval':
437
timer.expiry = this._now + timer.interval;
438
timer.callback();
439
break;
440
441
default:
442
throw new Error('Unexepcted timer type: ' + timer.type);
443
}
444
};
445
446
module.exports = FakeTimers;
447
448