You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
673 lines
16 KiB
673 lines
16 KiB
'use strict'; |
|
|
|
Object.defineProperty(exports, '__esModule', { |
|
value: true |
|
}); |
|
exports.default = void 0; |
|
|
|
function _util() { |
|
const data = _interopRequireDefault(require('util')); |
|
|
|
_util = function () { |
|
return data; |
|
}; |
|
|
|
return data; |
|
} |
|
|
|
function _jestMessageUtil() { |
|
const data = require('jest-message-util'); |
|
|
|
_jestMessageUtil = function () { |
|
return data; |
|
}; |
|
|
|
return data; |
|
} |
|
|
|
function _jestUtil() { |
|
const data = require('jest-util'); |
|
|
|
_jestUtil = function () { |
|
return data; |
|
}; |
|
|
|
return data; |
|
} |
|
|
|
function _interopRequireDefault(obj) { |
|
return obj && obj.__esModule ? obj : {default: obj}; |
|
} |
|
|
|
function _defineProperty(obj, key, value) { |
|
if (key in obj) { |
|
Object.defineProperty(obj, key, { |
|
value: value, |
|
enumerable: true, |
|
configurable: true, |
|
writable: true |
|
}); |
|
} else { |
|
obj[key] = value; |
|
} |
|
return obj; |
|
} |
|
|
|
const MS_IN_A_YEAR = 31536000000; |
|
|
|
class FakeTimers { |
|
constructor({global, moduleMocker, timerConfig, config, maxLoops}) { |
|
_defineProperty(this, '_cancelledTicks', void 0); |
|
|
|
_defineProperty(this, '_config', void 0); |
|
|
|
_defineProperty(this, '_disposed', void 0); |
|
|
|
_defineProperty(this, '_fakeTimerAPIs', void 0); |
|
|
|
_defineProperty(this, '_global', void 0); |
|
|
|
_defineProperty(this, '_immediates', void 0); |
|
|
|
_defineProperty(this, '_maxLoops', void 0); |
|
|
|
_defineProperty(this, '_moduleMocker', void 0); |
|
|
|
_defineProperty(this, '_now', void 0); |
|
|
|
_defineProperty(this, '_ticks', void 0); |
|
|
|
_defineProperty(this, '_timerAPIs', void 0); |
|
|
|
_defineProperty(this, '_timers', void 0); |
|
|
|
_defineProperty(this, '_uuidCounter', void 0); |
|
|
|
_defineProperty(this, '_timerConfig', void 0); |
|
|
|
this._global = global; |
|
this._timerConfig = timerConfig; |
|
this._config = config; |
|
this._maxLoops = maxLoops || 100000; |
|
this._uuidCounter = 1; |
|
this._moduleMocker = moduleMocker; // Store original timer APIs for future reference |
|
|
|
this._timerAPIs = { |
|
cancelAnimationFrame: global.cancelAnimationFrame, |
|
clearImmediate: global.clearImmediate, |
|
clearInterval: global.clearInterval, |
|
clearTimeout: global.clearTimeout, |
|
nextTick: global.process && global.process.nextTick, |
|
requestAnimationFrame: global.requestAnimationFrame, |
|
setImmediate: global.setImmediate, |
|
setInterval: global.setInterval, |
|
setTimeout: global.setTimeout |
|
}; |
|
this.reset(); |
|
} |
|
|
|
clearAllTimers() { |
|
this._immediates = []; |
|
|
|
this._timers.clear(); |
|
} |
|
|
|
dispose() { |
|
this._disposed = true; |
|
this.clearAllTimers(); |
|
} |
|
|
|
reset() { |
|
this._cancelledTicks = {}; |
|
this._now = 0; |
|
this._ticks = []; |
|
this._immediates = []; |
|
this._timers = new Map(); |
|
} |
|
|
|
runAllTicks() { |
|
this._checkFakeTimers(); // Only run a generous number of ticks and then bail. |
|
// This is just to help avoid recursive loops |
|
|
|
let i; |
|
|
|
for (i = 0; i < this._maxLoops; i++) { |
|
const tick = this._ticks.shift(); |
|
|
|
if (tick === undefined) { |
|
break; |
|
} |
|
|
|
if (!this._cancelledTicks.hasOwnProperty(tick.uuid)) { |
|
// Callback may throw, so update the map prior calling. |
|
this._cancelledTicks[tick.uuid] = true; |
|
tick.callback(); |
|
} |
|
} |
|
|
|
if (i === this._maxLoops) { |
|
throw new Error( |
|
'Ran ' + |
|
this._maxLoops + |
|
' ticks, and there are still more! ' + |
|
"Assuming we've hit an infinite recursion and bailing out..." |
|
); |
|
} |
|
} |
|
|
|
runAllImmediates() { |
|
this._checkFakeTimers(); // Only run a generous number of immediates and then bail. |
|
|
|
let i; |
|
|
|
for (i = 0; i < this._maxLoops; i++) { |
|
const immediate = this._immediates.shift(); |
|
|
|
if (immediate === undefined) { |
|
break; |
|
} |
|
|
|
this._runImmediate(immediate); |
|
} |
|
|
|
if (i === this._maxLoops) { |
|
throw new Error( |
|
'Ran ' + |
|
this._maxLoops + |
|
' immediates, and there are still more! Assuming ' + |
|
"we've hit an infinite recursion and bailing out..." |
|
); |
|
} |
|
} |
|
|
|
_runImmediate(immediate) { |
|
try { |
|
immediate.callback(); |
|
} finally { |
|
this._fakeClearImmediate(immediate.uuid); |
|
} |
|
} |
|
|
|
runAllTimers() { |
|
this._checkFakeTimers(); |
|
|
|
this.runAllTicks(); |
|
this.runAllImmediates(); // Only run a generous number of timers and then bail. |
|
// This is just to help avoid recursive loops |
|
|
|
let i; |
|
|
|
for (i = 0; i < this._maxLoops; i++) { |
|
const nextTimerHandle = this._getNextTimerHandle(); // If there are no more timer handles, stop! |
|
|
|
if (nextTimerHandle === null) { |
|
break; |
|
} |
|
|
|
this._runTimerHandle(nextTimerHandle); // Some of the immediate calls could be enqueued |
|
// during the previous handling of the timers, we should |
|
// run them as well. |
|
|
|
if (this._immediates.length) { |
|
this.runAllImmediates(); |
|
} |
|
|
|
if (this._ticks.length) { |
|
this.runAllTicks(); |
|
} |
|
} |
|
|
|
if (i === this._maxLoops) { |
|
throw new Error( |
|
'Ran ' + |
|
this._maxLoops + |
|
' timers, and there are still more! ' + |
|
"Assuming we've hit an infinite recursion and bailing out..." |
|
); |
|
} |
|
} |
|
|
|
runOnlyPendingTimers() { |
|
// We need to hold the current shape of `this._timers` because existing |
|
// timers can add new ones to the map and hence would run more than necessary. |
|
// See https://github.com/facebook/jest/pull/4608 for details |
|
const timerEntries = Array.from(this._timers.entries()); |
|
|
|
this._checkFakeTimers(); |
|
|
|
this._immediates.forEach(this._runImmediate, this); |
|
|
|
timerEntries |
|
.sort(([, left], [, right]) => left.expiry - right.expiry) |
|
.forEach(([timerHandle]) => this._runTimerHandle(timerHandle)); |
|
} |
|
|
|
advanceTimersToNextTimer(steps = 1) { |
|
if (steps < 1) { |
|
return; |
|
} |
|
|
|
const nextExpiry = Array.from(this._timers.values()).reduce( |
|
(minExpiry, timer) => { |
|
if (minExpiry === null || timer.expiry < minExpiry) return timer.expiry; |
|
return minExpiry; |
|
}, |
|
null |
|
); |
|
|
|
if (nextExpiry !== null) { |
|
this.advanceTimersByTime(nextExpiry - this._now); |
|
this.advanceTimersToNextTimer(steps - 1); |
|
} |
|
} |
|
|
|
advanceTimersByTime(msToRun) { |
|
this._checkFakeTimers(); // Only run a generous number of timers and then bail. |
|
// This is just to help avoid recursive loops |
|
|
|
let i; |
|
|
|
for (i = 0; i < this._maxLoops; i++) { |
|
const timerHandle = this._getNextTimerHandle(); // If there are no more timer handles, stop! |
|
|
|
if (timerHandle === null) { |
|
break; |
|
} |
|
|
|
const timerValue = this._timers.get(timerHandle); |
|
|
|
if (timerValue === undefined) { |
|
break; |
|
} |
|
|
|
const nextTimerExpiry = timerValue.expiry; |
|
|
|
if (this._now + msToRun < nextTimerExpiry) { |
|
// There are no timers between now and the target we're running to, so |
|
// adjust our time cursor and quit |
|
this._now += msToRun; |
|
break; |
|
} else { |
|
msToRun -= nextTimerExpiry - this._now; |
|
this._now = nextTimerExpiry; |
|
|
|
this._runTimerHandle(timerHandle); |
|
} |
|
} |
|
|
|
if (i === this._maxLoops) { |
|
throw new Error( |
|
'Ran ' + |
|
this._maxLoops + |
|
' timers, and there are still more! ' + |
|
"Assuming we've hit an infinite recursion and bailing out..." |
|
); |
|
} |
|
} |
|
|
|
runWithRealTimers(cb) { |
|
const prevClearImmediate = this._global.clearImmediate; |
|
const prevClearInterval = this._global.clearInterval; |
|
const prevClearTimeout = this._global.clearTimeout; |
|
const prevNextTick = this._global.process.nextTick; |
|
const prevSetImmediate = this._global.setImmediate; |
|
const prevSetInterval = this._global.setInterval; |
|
const prevSetTimeout = this._global.setTimeout; |
|
this.useRealTimers(); |
|
let cbErr = null; |
|
let errThrown = false; |
|
|
|
try { |
|
cb(); |
|
} catch (e) { |
|
errThrown = true; |
|
cbErr = e; |
|
} |
|
|
|
this._global.clearImmediate = prevClearImmediate; |
|
this._global.clearInterval = prevClearInterval; |
|
this._global.clearTimeout = prevClearTimeout; |
|
this._global.process.nextTick = prevNextTick; |
|
this._global.setImmediate = prevSetImmediate; |
|
this._global.setInterval = prevSetInterval; |
|
this._global.setTimeout = prevSetTimeout; |
|
|
|
if (errThrown) { |
|
throw cbErr; |
|
} |
|
} |
|
|
|
useRealTimers() { |
|
const global = this._global; |
|
|
|
if (typeof global.cancelAnimationFrame === 'function') { |
|
(0, _jestUtil().setGlobal)( |
|
global, |
|
'cancelAnimationFrame', |
|
this._timerAPIs.cancelAnimationFrame |
|
); |
|
} |
|
|
|
if (typeof global.clearImmediate === 'function') { |
|
(0, _jestUtil().setGlobal)( |
|
global, |
|
'clearImmediate', |
|
this._timerAPIs.clearImmediate |
|
); |
|
} |
|
|
|
(0, _jestUtil().setGlobal)( |
|
global, |
|
'clearInterval', |
|
this._timerAPIs.clearInterval |
|
); |
|
(0, _jestUtil().setGlobal)( |
|
global, |
|
'clearTimeout', |
|
this._timerAPIs.clearTimeout |
|
); |
|
|
|
if (typeof global.requestAnimationFrame === 'function') { |
|
(0, _jestUtil().setGlobal)( |
|
global, |
|
'requestAnimationFrame', |
|
this._timerAPIs.requestAnimationFrame |
|
); |
|
} |
|
|
|
if (typeof global.setImmediate === 'function') { |
|
(0, _jestUtil().setGlobal)( |
|
global, |
|
'setImmediate', |
|
this._timerAPIs.setImmediate |
|
); |
|
} |
|
|
|
(0, _jestUtil().setGlobal)( |
|
global, |
|
'setInterval', |
|
this._timerAPIs.setInterval |
|
); |
|
(0, _jestUtil().setGlobal)( |
|
global, |
|
'setTimeout', |
|
this._timerAPIs.setTimeout |
|
); |
|
global.process.nextTick = this._timerAPIs.nextTick; |
|
} |
|
|
|
useFakeTimers() { |
|
this._createMocks(); |
|
|
|
const global = this._global; |
|
|
|
if (typeof global.cancelAnimationFrame === 'function') { |
|
(0, _jestUtil().setGlobal)( |
|
global, |
|
'cancelAnimationFrame', |
|
this._fakeTimerAPIs.cancelAnimationFrame |
|
); |
|
} |
|
|
|
if (typeof global.clearImmediate === 'function') { |
|
(0, _jestUtil().setGlobal)( |
|
global, |
|
'clearImmediate', |
|
this._fakeTimerAPIs.clearImmediate |
|
); |
|
} |
|
|
|
(0, _jestUtil().setGlobal)( |
|
global, |
|
'clearInterval', |
|
this._fakeTimerAPIs.clearInterval |
|
); |
|
(0, _jestUtil().setGlobal)( |
|
global, |
|
'clearTimeout', |
|
this._fakeTimerAPIs.clearTimeout |
|
); |
|
|
|
if (typeof global.requestAnimationFrame === 'function') { |
|
(0, _jestUtil().setGlobal)( |
|
global, |
|
'requestAnimationFrame', |
|
this._fakeTimerAPIs.requestAnimationFrame |
|
); |
|
} |
|
|
|
if (typeof global.setImmediate === 'function') { |
|
(0, _jestUtil().setGlobal)( |
|
global, |
|
'setImmediate', |
|
this._fakeTimerAPIs.setImmediate |
|
); |
|
} |
|
|
|
(0, _jestUtil().setGlobal)( |
|
global, |
|
'setInterval', |
|
this._fakeTimerAPIs.setInterval |
|
); |
|
(0, _jestUtil().setGlobal)( |
|
global, |
|
'setTimeout', |
|
this._fakeTimerAPIs.setTimeout |
|
); |
|
global.process.nextTick = this._fakeTimerAPIs.nextTick; |
|
} |
|
|
|
getTimerCount() { |
|
this._checkFakeTimers(); |
|
|
|
return this._timers.size + this._immediates.length + this._ticks.length; |
|
} |
|
|
|
_checkFakeTimers() { |
|
var _this$_fakeTimerAPIs; |
|
|
|
if ( |
|
this._global.setTimeout !== |
|
((_this$_fakeTimerAPIs = this._fakeTimerAPIs) === null || |
|
_this$_fakeTimerAPIs === void 0 |
|
? void 0 |
|
: _this$_fakeTimerAPIs.setTimeout) |
|
) { |
|
this._global.console.warn( |
|
'A function to advance timers was called but the timers API is not ' + |
|
'mocked with fake timers. Call `jest.useFakeTimers()` in this ' + |
|
'test or enable fake timers globally by setting ' + |
|
'`"timers": "fake"` in ' + |
|
'the configuration file. This warning is likely a result of a ' + |
|
'default configuration change in Jest 15.\n\n' + |
|
'Release Blog Post: https://jestjs.io/blog/2016/09/01/jest-15\n' + |
|
'Stack Trace:\n' + |
|
(0, _jestMessageUtil().formatStackTrace)( |
|
new Error().stack, |
|
this._config, |
|
{ |
|
noStackTrace: false |
|
} |
|
) |
|
); |
|
} |
|
} |
|
|
|
_createMocks() { |
|
const fn = ( |
|
impl // @ts-expect-error TODO: figure out better typings here |
|
) => this._moduleMocker.fn().mockImplementation(impl); |
|
|
|
const promisifiableFakeSetTimeout = fn(this._fakeSetTimeout.bind(this)); // @ts-expect-error TODO: figure out better typings here |
|
|
|
promisifiableFakeSetTimeout[_util().default.promisify.custom] = ( |
|
delay, |
|
arg |
|
) => |
|
new Promise(resolve => promisifiableFakeSetTimeout(resolve, delay, arg)); // TODO: add better typings; these are mocks, but typed as regular timers |
|
|
|
this._fakeTimerAPIs = { |
|
cancelAnimationFrame: fn(this._fakeClearTimer.bind(this)), |
|
clearImmediate: fn(this._fakeClearImmediate.bind(this)), |
|
clearInterval: fn(this._fakeClearTimer.bind(this)), |
|
clearTimeout: fn(this._fakeClearTimer.bind(this)), |
|
nextTick: fn(this._fakeNextTick.bind(this)), |
|
// @ts-expect-error TODO: figure out better typings here |
|
requestAnimationFrame: fn(this._fakeRequestAnimationFrame.bind(this)), |
|
// @ts-expect-error TODO: figure out better typings here |
|
setImmediate: fn(this._fakeSetImmediate.bind(this)), |
|
// @ts-expect-error TODO: figure out better typings here |
|
setInterval: fn(this._fakeSetInterval.bind(this)), |
|
// @ts-expect-error TODO: figure out better typings here |
|
setTimeout: promisifiableFakeSetTimeout |
|
}; |
|
} |
|
|
|
_fakeClearTimer(timerRef) { |
|
const uuid = this._timerConfig.refToId(timerRef); |
|
|
|
if (uuid) { |
|
this._timers.delete(String(uuid)); |
|
} |
|
} |
|
|
|
_fakeClearImmediate(uuid) { |
|
this._immediates = this._immediates.filter( |
|
immediate => immediate.uuid !== uuid |
|
); |
|
} |
|
|
|
_fakeNextTick(callback, ...args) { |
|
if (this._disposed) { |
|
return; |
|
} |
|
|
|
const uuid = String(this._uuidCounter++); |
|
|
|
this._ticks.push({ |
|
callback: () => callback.apply(null, args), |
|
uuid |
|
}); |
|
|
|
const cancelledTicks = this._cancelledTicks; |
|
|
|
this._timerAPIs.nextTick(() => { |
|
if (!cancelledTicks.hasOwnProperty(uuid)) { |
|
// Callback may throw, so update the map prior calling. |
|
cancelledTicks[uuid] = true; |
|
callback.apply(null, args); |
|
} |
|
}); |
|
} |
|
|
|
_fakeRequestAnimationFrame(callback) { |
|
return this._fakeSetTimeout(() => { |
|
// TODO: Use performance.now() once it's mocked |
|
callback(this._now); |
|
}, 1000 / 60); |
|
} |
|
|
|
_fakeSetImmediate(callback, ...args) { |
|
if (this._disposed) { |
|
return null; |
|
} |
|
|
|
const uuid = String(this._uuidCounter++); |
|
|
|
this._immediates.push({ |
|
callback: () => callback.apply(null, args), |
|
uuid |
|
}); |
|
|
|
this._timerAPIs.setImmediate(() => { |
|
if (this._immediates.find(x => x.uuid === uuid)) { |
|
try { |
|
callback.apply(null, args); |
|
} finally { |
|
this._fakeClearImmediate(uuid); |
|
} |
|
} |
|
}); |
|
|
|
return uuid; |
|
} |
|
|
|
_fakeSetInterval(callback, intervalDelay, ...args) { |
|
if (this._disposed) { |
|
return null; |
|
} |
|
|
|
if (intervalDelay == null) { |
|
intervalDelay = 0; |
|
} |
|
|
|
const uuid = this._uuidCounter++; |
|
|
|
this._timers.set(String(uuid), { |
|
callback: () => callback.apply(null, args), |
|
expiry: this._now + intervalDelay, |
|
interval: intervalDelay, |
|
type: 'interval' |
|
}); |
|
|
|
return this._timerConfig.idToRef(uuid); |
|
} |
|
|
|
_fakeSetTimeout(callback, delay, ...args) { |
|
if (this._disposed) { |
|
return null; |
|
} // eslint-disable-next-line no-bitwise |
|
|
|
delay = Number(delay) | 0; |
|
const uuid = this._uuidCounter++; |
|
|
|
this._timers.set(String(uuid), { |
|
callback: () => callback.apply(null, args), |
|
expiry: this._now + delay, |
|
interval: undefined, |
|
type: 'timeout' |
|
}); |
|
|
|
return this._timerConfig.idToRef(uuid); |
|
} |
|
|
|
_getNextTimerHandle() { |
|
let nextTimerHandle = null; |
|
let soonestTime = MS_IN_A_YEAR; |
|
|
|
this._timers.forEach((timer, uuid) => { |
|
if (timer.expiry < soonestTime) { |
|
soonestTime = timer.expiry; |
|
nextTimerHandle = uuid; |
|
} |
|
}); |
|
|
|
return nextTimerHandle; |
|
} |
|
|
|
_runTimerHandle(timerHandle) { |
|
const timer = this._timers.get(timerHandle); |
|
|
|
if (!timer) { |
|
return; |
|
} |
|
|
|
switch (timer.type) { |
|
case 'timeout': |
|
this._timers.delete(timerHandle); |
|
|
|
timer.callback(); |
|
break; |
|
|
|
case 'interval': |
|
timer.expiry = this._now + (timer.interval || 0); |
|
timer.callback(); |
|
break; |
|
|
|
default: |
|
throw new Error('Unexpected timer type: ' + timer.type); |
|
} |
|
} |
|
} |
|
|
|
exports.default = FakeTimers;
|
|
|