UNPKG

16.8 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', {
4 value: true
5});
6exports.default = void 0;
7
8function _util() {
9 const data = _interopRequireDefault(require('util'));
10
11 _util = function () {
12 return data;
13 };
14
15 return data;
16}
17
18function _jestMessageUtil() {
19 const data = require('jest-message-util');
20
21 _jestMessageUtil = function () {
22 return data;
23 };
24
25 return data;
26}
27
28function _jestUtil() {
29 const data = require('jest-util');
30
31 _jestUtil = 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 cancelAnimationFrame: global.cancelAnimationFrame,
97 clearImmediate: global.clearImmediate,
98 clearInterval: global.clearInterval,
99 clearTimeout: global.clearTimeout,
100 nextTick: global.process && global.process.nextTick,
101 requestAnimationFrame: global.requestAnimationFrame,
102 setImmediate: global.setImmediate,
103 setInterval: global.setInterval,
104 setTimeout: global.setTimeout
105 };
106 this.reset();
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
343 if (typeof global.cancelAnimationFrame === 'function') {
344 (0, _jestUtil().setGlobal)(
345 global,
346 'cancelAnimationFrame',
347 this._timerAPIs.cancelAnimationFrame
348 );
349 }
350
351 if (typeof global.clearImmediate === 'function') {
352 (0, _jestUtil().setGlobal)(
353 global,
354 'clearImmediate',
355 this._timerAPIs.clearImmediate
356 );
357 }
358
359 (0, _jestUtil().setGlobal)(
360 global,
361 'clearInterval',
362 this._timerAPIs.clearInterval
363 );
364 (0, _jestUtil().setGlobal)(
365 global,
366 'clearTimeout',
367 this._timerAPIs.clearTimeout
368 );
369
370 if (typeof global.requestAnimationFrame === 'function') {
371 (0, _jestUtil().setGlobal)(
372 global,
373 'requestAnimationFrame',
374 this._timerAPIs.requestAnimationFrame
375 );
376 }
377
378 if (typeof global.setImmediate === 'function') {
379 (0, _jestUtil().setGlobal)(
380 global,
381 'setImmediate',
382 this._timerAPIs.setImmediate
383 );
384 }
385
386 (0, _jestUtil().setGlobal)(
387 global,
388 'setInterval',
389 this._timerAPIs.setInterval
390 );
391 (0, _jestUtil().setGlobal)(
392 global,
393 'setTimeout',
394 this._timerAPIs.setTimeout
395 );
396 global.process.nextTick = this._timerAPIs.nextTick;
397 }
398
399 useFakeTimers() {
400 this._createMocks();
401
402 const global = this._global;
403
404 if (typeof global.cancelAnimationFrame === 'function') {
405 (0, _jestUtil().setGlobal)(
406 global,
407 'cancelAnimationFrame',
408 this._fakeTimerAPIs.cancelAnimationFrame
409 );
410 }
411
412 if (typeof global.clearImmediate === 'function') {
413 (0, _jestUtil().setGlobal)(
414 global,
415 'clearImmediate',
416 this._fakeTimerAPIs.clearImmediate
417 );
418 }
419
420 (0, _jestUtil().setGlobal)(
421 global,
422 'clearInterval',
423 this._fakeTimerAPIs.clearInterval
424 );
425 (0, _jestUtil().setGlobal)(
426 global,
427 'clearTimeout',
428 this._fakeTimerAPIs.clearTimeout
429 );
430
431 if (typeof global.requestAnimationFrame === 'function') {
432 (0, _jestUtil().setGlobal)(
433 global,
434 'requestAnimationFrame',
435 this._fakeTimerAPIs.requestAnimationFrame
436 );
437 }
438
439 if (typeof global.setImmediate === 'function') {
440 (0, _jestUtil().setGlobal)(
441 global,
442 'setImmediate',
443 this._fakeTimerAPIs.setImmediate
444 );
445 }
446
447 (0, _jestUtil().setGlobal)(
448 global,
449 'setInterval',
450 this._fakeTimerAPIs.setInterval
451 );
452 (0, _jestUtil().setGlobal)(
453 global,
454 'setTimeout',
455 this._fakeTimerAPIs.setTimeout
456 );
457 global.process.nextTick = this._fakeTimerAPIs.nextTick;
458 }
459
460 getTimerCount() {
461 this._checkFakeTimers();
462
463 return this._timers.size + this._immediates.length + this._ticks.length;
464 }
465
466 _checkFakeTimers() {
467 var _this$_fakeTimerAPIs;
468
469 if (
470 this._global.setTimeout !==
471 ((_this$_fakeTimerAPIs = this._fakeTimerAPIs) === null ||
472 _this$_fakeTimerAPIs === void 0
473 ? void 0
474 : _this$_fakeTimerAPIs.setTimeout)
475 ) {
476 this._global.console.warn(
477 `A function to advance timers was called but the timers API is not ` +
478 `mocked with fake timers. Call \`jest.useFakeTimers()\` in this ` +
479 `test or enable fake timers globally by setting ` +
480 `\`"timers": "fake"\` in ` +
481 `the configuration file. This warning is likely a result of a ` +
482 `default configuration change in Jest 15.\n\n` +
483 `Release Blog Post: https://jestjs.io/blog/2016/09/01/jest-15\n` +
484 `Stack Trace:\n` +
485 (0, _jestMessageUtil().formatStackTrace)(
486 new Error().stack,
487 this._config,
488 {
489 noStackTrace: false
490 }
491 )
492 );
493 }
494 }
495
496 _createMocks() {
497 const fn = (
498 impl // @ts-expect-error TODO: figure out better typings here
499 ) => this._moduleMocker.fn().mockImplementation(impl);
500
501 const promisifiableFakeSetTimeout = fn(this._fakeSetTimeout.bind(this)); // @ts-expect-error TODO: figure out better typings here
502
503 promisifiableFakeSetTimeout[_util().default.promisify.custom] = (
504 delay,
505 arg
506 ) =>
507 new Promise(resolve => promisifiableFakeSetTimeout(resolve, delay, arg)); // TODO: add better typings; these are mocks, but typed as regular timers
508
509 this._fakeTimerAPIs = {
510 cancelAnimationFrame: fn(this._fakeClearTimer.bind(this)),
511 clearImmediate: fn(this._fakeClearImmediate.bind(this)),
512 clearInterval: fn(this._fakeClearTimer.bind(this)),
513 clearTimeout: fn(this._fakeClearTimer.bind(this)),
514 nextTick: fn(this._fakeNextTick.bind(this)),
515 // @ts-expect-error TODO: figure out better typings here
516 requestAnimationFrame: fn(this._fakeRequestAnimationFrame.bind(this)),
517 // @ts-expect-error TODO: figure out better typings here
518 setImmediate: fn(this._fakeSetImmediate.bind(this)),
519 // @ts-expect-error TODO: figure out better typings here
520 setInterval: fn(this._fakeSetInterval.bind(this)),
521 // @ts-expect-error TODO: figure out better typings here
522 setTimeout: promisifiableFakeSetTimeout
523 };
524 }
525
526 _fakeClearTimer(timerRef) {
527 const uuid = this._timerConfig.refToId(timerRef);
528
529 if (uuid) {
530 this._timers.delete(String(uuid));
531 }
532 }
533
534 _fakeClearImmediate(uuid) {
535 this._immediates = this._immediates.filter(
536 immediate => immediate.uuid !== uuid
537 );
538 }
539
540 _fakeNextTick(callback, ...args) {
541 if (this._disposed) {
542 return;
543 }
544
545 const uuid = String(this._uuidCounter++);
546
547 this._ticks.push({
548 callback: () => callback.apply(null, args),
549 uuid
550 });
551
552 const cancelledTicks = this._cancelledTicks;
553
554 this._timerAPIs.nextTick(() => {
555 if (!cancelledTicks.hasOwnProperty(uuid)) {
556 // Callback may throw, so update the map prior calling.
557 cancelledTicks[uuid] = true;
558 callback.apply(null, args);
559 }
560 });
561 }
562
563 _fakeRequestAnimationFrame(callback) {
564 return this._fakeSetTimeout(() => {
565 // TODO: Use performance.now() once it's mocked
566 callback(this._now);
567 }, 1000 / 60);
568 }
569
570 _fakeSetImmediate(callback, ...args) {
571 if (this._disposed) {
572 return null;
573 }
574
575 const uuid = String(this._uuidCounter++);
576
577 this._immediates.push({
578 callback: () => callback.apply(null, args),
579 uuid
580 });
581
582 this._timerAPIs.setImmediate(() => {
583 if (this._immediates.find(x => x.uuid === uuid)) {
584 try {
585 callback.apply(null, args);
586 } finally {
587 this._fakeClearImmediate(uuid);
588 }
589 }
590 });
591
592 return uuid;
593 }
594
595 _fakeSetInterval(callback, intervalDelay, ...args) {
596 if (this._disposed) {
597 return null;
598 }
599
600 if (intervalDelay == null) {
601 intervalDelay = 0;
602 }
603
604 const uuid = this._uuidCounter++;
605
606 this._timers.set(String(uuid), {
607 callback: () => callback.apply(null, args),
608 expiry: this._now + intervalDelay,
609 interval: intervalDelay,
610 type: 'interval'
611 });
612
613 return this._timerConfig.idToRef(uuid);
614 }
615
616 _fakeSetTimeout(callback, delay, ...args) {
617 if (this._disposed) {
618 return null;
619 } // eslint-disable-next-line no-bitwise
620
621 delay = Number(delay) | 0;
622 const uuid = this._uuidCounter++;
623
624 this._timers.set(String(uuid), {
625 callback: () => callback.apply(null, args),
626 expiry: this._now + delay,
627 interval: undefined,
628 type: 'timeout'
629 });
630
631 return this._timerConfig.idToRef(uuid);
632 }
633
634 _getNextTimerHandle() {
635 let nextTimerHandle = null;
636 let soonestTime = MS_IN_A_YEAR;
637
638 this._timers.forEach((timer, uuid) => {
639 if (timer.expiry < soonestTime) {
640 soonestTime = timer.expiry;
641 nextTimerHandle = uuid;
642 }
643 });
644
645 return nextTimerHandle;
646 }
647
648 _runTimerHandle(timerHandle) {
649 const timer = this._timers.get(timerHandle);
650
651 if (!timer) {
652 return;
653 }
654
655 switch (timer.type) {
656 case 'timeout':
657 this._timers.delete(timerHandle);
658
659 timer.callback();
660 break;
661
662 case 'interval':
663 timer.expiry = this._now + (timer.interval || 0);
664 timer.callback();
665 break;
666
667 default:
668 throw new Error('Unexpected timer type: ' + timer.type);
669 }
670 }
671}
672
673exports.default = FakeTimers;