diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ac0a2922359..2079aab40e67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,12 @@ ## master -- `[*]` Add documentation and tests related to auto-mocking ([#8086](https://github.com/facebook/jest/pull/8099)) - ### Features ### Fixes - `[jest-each]` Fix test function type ([#8145](https://github.com/facebook/jest/pull/8145)) - `[pretty-format]` Print `BigInt` as a readable number instead of `{}` ([#8138](https://github.com/facebook/jest/pull/8138)) +- `[jest-fake-timers]` `getTimerCount` not taking immediates and ticks into account ([#8139](https://github.com/facebook/jest/pull/8139)) ### Chore & Maintenance @@ -15,6 +14,7 @@ - `[*]` Use property initializer syntax in Jest codebase ([#8117](https://github.com/facebook/jest/pull/8117)) - `[docs]` Improve description of optional arguments in ExpectAPI.md ([#8126](https://github.com/facebook/jest/pull/8126) - `[*]` Move @types/node to the root package.json [#8129](https://github.com/facebook/jest/pull/8129)) +- `[*]` Add documentation and tests related to auto-mocking ([#8086](https://github.com/facebook/jest/pull/8099)) ### Performance diff --git a/packages/jest-fake-timers/src/__tests__/jestFakeTimers.test.ts b/packages/jest-fake-timers/src/__tests__/jestFakeTimers.test.ts index ac0d7ad149d1..483d2a7ca5a5 100644 --- a/packages/jest-fake-timers/src/__tests__/jestFakeTimers.test.ts +++ b/packages/jest-fake-timers/src/__tests__/jestFakeTimers.test.ts @@ -1165,5 +1165,22 @@ describe('FakeTimers', () => { expect(timers.getTimerCount()).toEqual(0); }); + + it('includes immediates and ticks', () => { + const timers = new FakeTimers({ + config, + global, + moduleMocker, + timerConfig, + }); + + timers.useFakeTimers(); + + global.setTimeout(() => {}, 0); + global.setImmediate(() => {}); + process.nextTick(() => {}); + + expect(timers.getTimerCount()).toEqual(3); + }); }); }); diff --git a/packages/jest-fake-timers/src/jestFakeTimers.ts b/packages/jest-fake-timers/src/jestFakeTimers.ts index 9dca4a0ac1d2..a8ed4b4509e7 100644 --- a/packages/jest-fake-timers/src/jestFakeTimers.ts +++ b/packages/jest-fake-timers/src/jestFakeTimers.ts @@ -65,7 +65,7 @@ export default class FakeTimers { private _now!: number; private _ticks!: Array; private _timerAPIs: TimerAPI; - private _timers!: {[key: string]: Timer}; + private _timers!: Map; private _uuidCounter: number; private _timerConfig: TimerConfig; @@ -108,9 +108,7 @@ export default class FakeTimers { this._immediates.forEach(immediate => this._fakeClearImmediate(immediate.uuid), ); - for (const uuid in this._timers) { - delete this._timers[uuid]; - } + this._timers.clear(); } dispose() { @@ -124,7 +122,7 @@ export default class FakeTimers { this._now = 0; this._ticks = []; this._immediates = []; - this._timers = {}; + this._timers = new Map(); } runAllTicks() { @@ -227,12 +225,16 @@ export default class FakeTimers { } runOnlyPendingTimers() { - const timers = {...this._timers}; + // 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); - Object.keys(timers) - .sort((left, right) => timers[left].expiry - timers[right].expiry) - .forEach(this._runTimerHandle, this); + + timerEntries + .sort(([, left], [, right]) => left.expiry - right.expiry) + .forEach(([timerHandle]) => this._runTimerHandle(timerHandle)); } advanceTimersByTime(msToRun: number) { @@ -247,8 +249,11 @@ export default class FakeTimers { if (timerHandle === null) { break; } - - const nextTimerExpiry = this._timers[timerHandle].expiry; + 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 @@ -333,7 +338,7 @@ export default class FakeTimers { getTimerCount() { this._checkFakeTimers(); - return Object.keys(this._timers).length; + return this._timers.size + this._immediates.length + this._ticks.length; } private _checkFakeTimers() { @@ -374,8 +379,8 @@ export default class FakeTimers { private _fakeClearTimer(timerRef: TimerRef) { const uuid = this._timerConfig.refToId(timerRef); - if (uuid && this._timers.hasOwnProperty(uuid)) { - delete this._timers[String(uuid)]; + if (uuid) { + this._timers.delete(String(uuid)); } } @@ -444,12 +449,12 @@ export default class FakeTimers { const uuid = this._uuidCounter++; - this._timers[String(uuid)] = { + this._timers.set(String(uuid), { callback: () => callback.apply(null, args), expiry: this._now + intervalDelay, interval: intervalDelay, type: 'interval', - }; + }); return this._timerConfig.idToRef(uuid); } @@ -468,34 +473,32 @@ export default class FakeTimers { const uuid = this._uuidCounter++; - this._timers[String(uuid)] = { + this._timers.set(String(uuid), { callback: () => callback.apply(null, args), expiry: this._now + delay, interval: undefined, type: 'timeout', - }; + }); return this._timerConfig.idToRef(uuid); } private _getNextTimerHandle() { let nextTimerHandle = null; - let uuid; let soonestTime = MS_IN_A_YEAR; - let timer; - for (uuid in this._timers) { - timer = this._timers[uuid]; + + this._timers.forEach((timer, uuid) => { if (timer.expiry < soonestTime) { soonestTime = timer.expiry; nextTimerHandle = uuid; } - } + }); return nextTimerHandle; } private _runTimerHandle(timerHandle: TimerID) { - const timer = this._timers[timerHandle]; + const timer = this._timers.get(timerHandle); if (!timer) { return; @@ -504,7 +507,7 @@ export default class FakeTimers { switch (timer.type) { case 'timeout': const callback = timer.callback; - delete this._timers[timerHandle]; + this._timers.delete(timerHandle); callback(); break;