UNPKG

14.8 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', {
4 value: true
5});
6exports.default = void 0;
7
8function _jestMessageUtil() {
9 const data = require('jest-message-util');
10
11 _jestMessageUtil = function () {
12 return data;
13 };
14
15 return data;
16}
17
18function _jestUtil() {
19 const data = require('jest-util');
20
21 _jestUtil = function () {
22 return data;
23 };
24
25 return data;
26}
27
28function _util() {
29 const data = _interopRequireDefault(require('util'));
30
31 _util = function () {
32 return data;
33 };
34
35 return data;
36}
37
38function _interopRequireDefault(obj) {
39 return obj && obj.__esModule ? obj : {default: obj};
40}
41
42function _defineProperty(obj, key, value) {
43 if (key in obj) {
44 Object.defineProperty(obj, key, {
45 value: value,
46 enumerable: true,
47 configurable: true,
48 writable: true
49 });
50 } else {
51 obj[key] = value;
52 }
53 return obj;
54}
55
56const MS_IN_A_YEAR = 31536000000;
57
58class FakeTimers {
59 constructor({global, moduleMocker, timerConfig, config, maxLoops}) {
60 _defineProperty(this, '_cancelledTicks', void 0);
61
62 _defineProperty(this, '_config', void 0);
63
64 _defineProperty(this, '_disposed', void 0);
65
66 _defineProperty(this, '_fakeTimerAPIs', void 0);
67
68 _defineProperty(this, '_global', void 0);
69
70 _defineProperty(this, '_immediates', void 0);
71
72 _defineProperty(this, '_maxLoops', void 0);
73
74 _defineProperty(this, '_moduleMocker', void 0);
75
76 _defineProperty(this, '_now', void 0);
77
78 _defineProperty(this, '_ticks', void 0);
79
80 _defineProperty(this, '_timerAPIs', void 0);
81
82 _defineProperty(this, '_timers', void 0);
83
84 _defineProperty(this, '_uuidCounter', void 0);
85
86 _defineProperty(this, '_timerConfig', void 0);
87
88 this._global = global;
89 this._timerConfig = timerConfig;
90 this._config = config;
91 this._maxLoops = maxLoops || 100000;
92 this._uuidCounter = 1;
93 this._moduleMocker = moduleMocker; // Store original timer APIs for future reference
94
95 this._timerAPIs = {
96 clearImmediate: global.clearImmediate,
97 clearInterval: global.clearInterval,
98 clearTimeout: global.clearTimeout,
99 nextTick: global.process && global.process.nextTick,
100 setImmediate: global.setImmediate,
101 setInterval: global.setInterval,
102 setTimeout: global.setTimeout
103 };
104 this.reset();
105
106 this._createMocks();
107 }
108
109 clearAllTimers() {
110 this._immediates = [];
111
112 this._timers.clear();
113 }
114
115 dispose() {
116 this._disposed = true;
117 this.clearAllTimers();
118 }
119
120 reset() {
121 this._cancelledTicks = {};
122 this._now = 0;
123 this._ticks = [];
124 this._immediates = [];
125 this._timers = new Map();
126 }
127
128 runAllTicks() {
129 this._checkFakeTimers(); // Only run a generous number of ticks and then bail.
130 // This is just to help avoid recursive loops
131
132 let i;
133
134 for (i = 0; i < this._maxLoops; i++) {
135 const tick = this._ticks.shift();
136
137 if (tick === undefined) {
138 break;
139 }
140
141 if (!this._cancelledTicks.hasOwnProperty(tick.uuid)) {
142 // Callback may throw, so update the map prior calling.
143 this._cancelledTicks[tick.uuid] = true;
144 tick.callback();
145 }
146 }
147
148 if (i === this._maxLoops) {
149 throw new Error(
150 'Ran ' +
151 this._maxLoops +
152 ' ticks, and there are still more! ' +
153 "Assuming we've hit an infinite recursion and bailing out..."
154 );
155 }
156 }
157
158 runAllImmediates() {
159 this._checkFakeTimers(); // Only run a generous number of immediates and then bail.
160
161 let i;
162
163 for (i = 0; i < this._maxLoops; i++) {
164 const immediate = this._immediates.shift();
165
166 if (immediate === undefined) {
167 break;
168 }
169
170 this._runImmediate(immediate);
171 }
172
173 if (i === this._maxLoops) {
174 throw new Error(
175 'Ran ' +
176 this._maxLoops +
177 ' immediates, and there are still more! Assuming ' +
178 "we've hit an infinite recursion and bailing out..."
179 );
180 }
181 }
182
183 _runImmediate(immediate) {
184 try {
185 immediate.callback();
186 } finally {
187 this._fakeClearImmediate(immediate.uuid);
188 }
189 }
190
191 runAllTimers() {
192 this._checkFakeTimers();
193
194 this.runAllTicks();
195 this.runAllImmediates(); // Only run a generous number of timers and then bail.
196 // This is just to help avoid recursive loops
197
198 let i;
199
200 for (i = 0; i < this._maxLoops; i++) {
201 const nextTimerHandle = this._getNextTimerHandle(); // If there are no more timer handles, stop!
202
203 if (nextTimerHandle === null) {
204 break;
205 }
206
207 this._runTimerHandle(nextTimerHandle); // Some of the immediate calls could be enqueued
208 // during the previous handling of the timers, we should
209 // run them as well.
210
211 if (this._immediates.length) {
212 this.runAllImmediates();
213 }
214
215 if (this._ticks.length) {
216 this.runAllTicks();
217 }
218 }
219
220 if (i === this._maxLoops) {
221 throw new Error(
222 'Ran ' +
223 this._maxLoops +
224 ' timers, and there are still more! ' +
225 "Assuming we've hit an infinite recursion and bailing out..."
226 );
227 }
228 }
229
230 runOnlyPendingTimers() {
231 // We need to hold the current shape of `this._timers` because existing
232 // timers can add new ones to the map and hence would run more than necessary.
233 // See https://github.com/facebook/jest/pull/4608 for details
234 const timerEntries = Array.from(this._timers.entries());
235
236 this._checkFakeTimers();
237
238 this._immediates.forEach(this._runImmediate, this);
239
240 timerEntries
241 .sort(([, left], [, right]) => left.expiry - right.expiry)
242 .forEach(([timerHandle]) => this._runTimerHandle(timerHandle));
243 }
244
245 advanceTimersToNextTimer(steps = 1) {
246 if (steps < 1) {
247 return;
248 }
249
250 const nextExpiry = Array.from(this._timers.values()).reduce(
251 (minExpiry, timer) => {
252 if (minExpiry === null || timer.expiry < minExpiry) return timer.expiry;
253 return minExpiry;
254 },
255 null
256 );
257
258 if (nextExpiry !== null) {
259 this.advanceTimersByTime(nextExpiry - this._now);
260 this.advanceTimersToNextTimer(steps - 1);
261 }
262 }
263
264 advanceTimersByTime(msToRun) {
265 this._checkFakeTimers(); // Only run a generous number of timers and then bail.
266 // This is just to help avoid recursive loops
267
268 let i;
269
270 for (i = 0; i < this._maxLoops; i++) {
271 const timerHandle = this._getNextTimerHandle(); // If there are no more timer handles, stop!
272
273 if (timerHandle === null) {
274 break;
275 }
276
277 const timerValue = this._timers.get(timerHandle);
278
279 if (timerValue === undefined) {
280 break;
281 }
282
283 const nextTimerExpiry = timerValue.expiry;
284
285 if (this._now + msToRun < nextTimerExpiry) {
286 // There are no timers between now and the target we're running to, so
287 // adjust our time cursor and quit
288 this._now += msToRun;
289 break;
290 } else {
291 msToRun -= nextTimerExpiry - this._now;
292 this._now = nextTimerExpiry;
293
294 this._runTimerHandle(timerHandle);
295 }
296 }
297
298 if (i === this._maxLoops) {
299 throw new Error(
300 'Ran ' +
301 this._maxLoops +
302 ' timers, and there are still more! ' +
303 "Assuming we've hit an infinite recursion and bailing out..."
304 );
305 }
306 }
307
308 runWithRealTimers(cb) {
309 const prevClearImmediate = this._global.clearImmediate;
310 const prevClearInterval = this._global.clearInterval;
311 const prevClearTimeout = this._global.clearTimeout;
312 const prevNextTick = this._global.process.nextTick;
313 const prevSetImmediate = this._global.setImmediate;
314 const prevSetInterval = this._global.setInterval;
315 const prevSetTimeout = this._global.setTimeout;
316 this.useRealTimers();
317 let cbErr = null;
318 let errThrown = false;
319
320 try {
321 cb();
322 } catch (e) {
323 errThrown = true;
324 cbErr = e;
325 }
326
327 this._global.clearImmediate = prevClearImmediate;
328 this._global.clearInterval = prevClearInterval;
329 this._global.clearTimeout = prevClearTimeout;
330 this._global.process.nextTick = prevNextTick;
331 this._global.setImmediate = prevSetImmediate;
332 this._global.setInterval = prevSetInterval;
333 this._global.setTimeout = prevSetTimeout;
334
335 if (errThrown) {
336 throw cbErr;
337 }
338 }
339
340 useRealTimers() {
341 const global = this._global;
342 (0, _jestUtil().setGlobal)(
343 global,
344 'clearImmediate',
345 this._timerAPIs.clearImmediate
346 );
347 (0, _jestUtil().setGlobal)(
348 global,
349 'clearInterval',
350 this._timerAPIs.clearInterval
351 );
352 (0, _jestUtil().setGlobal)(
353 global,
354 'clearTimeout',
355 this._timerAPIs.clearTimeout
356 );
357 (0, _jestUtil().setGlobal)(
358 global,
359 'setImmediate',
360 this._timerAPIs.setImmediate
361 );
362 (0, _jestUtil().setGlobal)(
363 global,
364 'setInterval',
365 this._timerAPIs.setInterval
366 );
367 (0, _jestUtil().setGlobal)(
368 global,
369 'setTimeout',
370 this._timerAPIs.setTimeout
371 );
372 global.process.nextTick = this._timerAPIs.nextTick;
373 }
374
375 useFakeTimers() {
376 this._createMocks();
377
378 const global = this._global;
379 (0, _jestUtil().setGlobal)(
380 global,
381 'clearImmediate',
382 this._fakeTimerAPIs.clearImmediate
383 );
384 (0, _jestUtil().setGlobal)(
385 global,
386 'clearInterval',
387 this._fakeTimerAPIs.clearInterval
388 );
389 (0, _jestUtil().setGlobal)(
390 global,
391 'clearTimeout',
392 this._fakeTimerAPIs.clearTimeout
393 );
394 (0, _jestUtil().setGlobal)(
395 global,
396 'setImmediate',
397 this._fakeTimerAPIs.setImmediate
398 );
399 (0, _jestUtil().setGlobal)(
400 global,
401 'setInterval',
402 this._fakeTimerAPIs.setInterval
403 );
404 (0, _jestUtil().setGlobal)(
405 global,
406 'setTimeout',
407 this._fakeTimerAPIs.setTimeout
408 );
409 global.process.nextTick = this._fakeTimerAPIs.nextTick;
410 }
411
412 getTimerCount() {
413 this._checkFakeTimers();
414
415 return this._timers.size + this._immediates.length + this._ticks.length;
416 }
417
418 _checkFakeTimers() {
419 if (this._global.setTimeout !== this._fakeTimerAPIs.setTimeout) {
420 this._global.console.warn(
421 `A function to advance timers was called but the timers API is not ` +
422 `mocked with fake timers. Call \`jest.useFakeTimers()\` in this ` +
423 `test or enable fake timers globally by setting ` +
424 `\`"timers": "fake"\` in ` +
425 `the configuration file. This warning is likely a result of a ` +
426 `default configuration change in Jest 15.\n\n` +
427 `Release Blog Post: https://jestjs.io/blog/2016/09/01/jest-15.html\n` +
428 `Stack Trace:\n` +
429 (0, _jestMessageUtil().formatStackTrace)(
430 new Error().stack,
431 this._config,
432 {
433 noStackTrace: false
434 }
435 )
436 );
437 }
438 }
439
440 _createMocks() {
441 const fn = (
442 impl // @ts-ignore TODO: figure out better typings here
443 ) => this._moduleMocker.fn().mockImplementation(impl);
444
445 const promisifiableFakeSetTimeout = fn(this._fakeSetTimeout.bind(this));
446
447 promisifiableFakeSetTimeout[_util().default.promisify.custom] = (
448 delay,
449 arg
450 ) =>
451 new Promise(resolve => promisifiableFakeSetTimeout(resolve, delay, arg)); // TODO: add better typings; these are mocks, but typed as regular timers
452
453 this._fakeTimerAPIs = {
454 clearImmediate: fn(this._fakeClearImmediate.bind(this)),
455 clearInterval: fn(this._fakeClearTimer.bind(this)),
456 clearTimeout: fn(this._fakeClearTimer.bind(this)),
457 nextTick: fn(this._fakeNextTick.bind(this)),
458 setImmediate: fn(this._fakeSetImmediate.bind(this)),
459 setInterval: fn(this._fakeSetInterval.bind(this)),
460 setTimeout: promisifiableFakeSetTimeout
461 };
462 }
463
464 _fakeClearTimer(timerRef) {
465 const uuid = this._timerConfig.refToId(timerRef);
466
467 if (uuid) {
468 this._timers.delete(String(uuid));
469 }
470 }
471
472 _fakeClearImmediate(uuid) {
473 this._immediates = this._immediates.filter(
474 immediate => immediate.uuid !== uuid
475 );
476 }
477
478 _fakeNextTick(callback, ...args) {
479 if (this._disposed) {
480 return;
481 }
482
483 const uuid = String(this._uuidCounter++);
484
485 this._ticks.push({
486 callback: () => callback.apply(null, args),
487 uuid
488 });
489
490 const cancelledTicks = this._cancelledTicks;
491
492 this._timerAPIs.nextTick(() => {
493 if (!cancelledTicks.hasOwnProperty(uuid)) {
494 // Callback may throw, so update the map prior calling.
495 cancelledTicks[uuid] = true;
496 callback.apply(null, args);
497 }
498 });
499 }
500
501 _fakeSetImmediate(callback, ...args) {
502 if (this._disposed) {
503 return null;
504 }
505
506 const uuid = String(this._uuidCounter++);
507
508 this._immediates.push({
509 callback: () => callback.apply(null, args),
510 uuid
511 });
512
513 this._timerAPIs.setImmediate(() => {
514 if (this._immediates.find(x => x.uuid === uuid)) {
515 try {
516 callback.apply(null, args);
517 } finally {
518 this._fakeClearImmediate(uuid);
519 }
520 }
521 });
522
523 return uuid;
524 }
525
526 _fakeSetInterval(callback, intervalDelay, ...args) {
527 if (this._disposed) {
528 return null;
529 }
530
531 if (intervalDelay == null) {
532 intervalDelay = 0;
533 }
534
535 const uuid = this._uuidCounter++;
536
537 this._timers.set(String(uuid), {
538 callback: () => callback.apply(null, args),
539 expiry: this._now + intervalDelay,
540 interval: intervalDelay,
541 type: 'interval'
542 });
543
544 return this._timerConfig.idToRef(uuid);
545 }
546
547 _fakeSetTimeout(callback, delay, ...args) {
548 if (this._disposed) {
549 return null;
550 } // eslint-disable-next-line no-bitwise
551
552 delay = Number(delay) | 0;
553 const uuid = this._uuidCounter++;
554
555 this._timers.set(String(uuid), {
556 callback: () => callback.apply(null, args),
557 expiry: this._now + delay,
558 interval: undefined,
559 type: 'timeout'
560 });
561
562 return this._timerConfig.idToRef(uuid);
563 }
564
565 _getNextTimerHandle() {
566 let nextTimerHandle = null;
567 let soonestTime = MS_IN_A_YEAR;
568
569 this._timers.forEach((timer, uuid) => {
570 if (timer.expiry < soonestTime) {
571 soonestTime = timer.expiry;
572 nextTimerHandle = uuid;
573 }
574 });
575
576 return nextTimerHandle;
577 }
578
579 _runTimerHandle(timerHandle) {
580 const timer = this._timers.get(timerHandle);
581
582 if (!timer) {
583 return;
584 }
585
586 switch (timer.type) {
587 case 'timeout':
588 const callback = timer.callback;
589
590 this._timers.delete(timerHandle);
591
592 callback();
593 break;
594
595 case 'interval':
596 timer.expiry = this._now + (timer.interval || 0);
597 timer.callback();
598 break;
599
600 default:
601 throw new Error('Unexpected timer type: ' + timer.type);
602 }
603 }
604}
605
606exports.default = FakeTimers;