From 13ba46e1701a998ca0484ababbae7afe9d2a3898 Mon Sep 17 00:00:00 2001 From: Rob Hogan Date: Sat, 10 Sep 2022 11:50:20 +0100 Subject: [PATCH 1/5] (jest-fake-timers): Add `now()` API to get the fake clock time --- docs/JestObjectAPI.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/JestObjectAPI.md b/docs/JestObjectAPI.md index 274d7c919d7f..e534fa009502 100644 --- a/docs/JestObjectAPI.md +++ b/docs/JestObjectAPI.md @@ -801,7 +801,7 @@ Returns the number of fake timers still left to run. ### `jest.now()` -Returns the time in ms of the current fake clock. This is equivalent to `Date.now()` if `Date` has been mocked. +Returns the time in ms of the current fake clock. ### `jest.setSystemTime(now?: number | Date)` From 1234dc75502557fa28e899d12e935c189c716e7c Mon Sep 17 00:00:00 2001 From: Rob Hogan <2590098+robhogan@users.noreply.github.com> Date: Sat, 10 Sep 2022 17:24:07 +0100 Subject: [PATCH 2/5] Elaborate on equivalence to `Date.now()` in docs --- docs/JestObjectAPI.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/JestObjectAPI.md b/docs/JestObjectAPI.md index e534fa009502..274d7c919d7f 100644 --- a/docs/JestObjectAPI.md +++ b/docs/JestObjectAPI.md @@ -801,7 +801,7 @@ Returns the number of fake timers still left to run. ### `jest.now()` -Returns the time in ms of the current fake clock. +Returns the time in ms of the current fake clock. This is equivalent to `Date.now()` if `Date` has been mocked. ### `jest.setSystemTime(now?: number | Date)` From 9b91ffdff5218d7067b3005178dba0884ca4955f Mon Sep 17 00:00:00 2001 From: Rob Hogan Date: Sat, 10 Sep 2022 18:41:06 +0100 Subject: [PATCH 3/5] Return real time from `jest.now()` if fake timers are not in use --- docs/JestObjectAPI.md | 2 +- .../src/__tests__/legacyFakeTimers.test.ts | 104 +++++++++--------- .../src/__tests__/modernFakeTimers.test.ts | 55 ++++++--- .../jest-fake-timers/src/legacyFakeTimers.ts | 13 ++- .../jest-fake-timers/src/modernFakeTimers.ts | 5 +- 5 files changed, 110 insertions(+), 69 deletions(-) diff --git a/docs/JestObjectAPI.md b/docs/JestObjectAPI.md index 274d7c919d7f..5f63b7efcb77 100644 --- a/docs/JestObjectAPI.md +++ b/docs/JestObjectAPI.md @@ -801,7 +801,7 @@ Returns the number of fake timers still left to run. ### `jest.now()` -Returns the time in ms of the current fake clock. This is equivalent to `Date.now()` if `Date` has been mocked. +Returns the time in ms of the current clock. This is equivalent to `Date.now()` if real timers are in use, or if `Date` is mocked. In other cases (such as legacy timers) it may be useful for implementing custom mocks of `Date.now()`, `performance.now()`, etc. ### `jest.setSystemTime(now?: number | Date)` diff --git a/packages/jest-fake-timers/src/__tests__/legacyFakeTimers.test.ts b/packages/jest-fake-timers/src/__tests__/legacyFakeTimers.test.ts index 736cc354a00d..b7ccac600f93 100644 --- a/packages/jest-fake-timers/src/__tests__/legacyFakeTimers.test.ts +++ b/packages/jest-fake-timers/src/__tests__/legacyFakeTimers.test.ts @@ -506,23 +506,20 @@ describe('FakeTimers', () => { }); it('warns when trying to advance timers while real timers are used', () => { - const consoleWarn = jest - .spyOn(console, 'warn') - .mockImplementation(() => {}); + const mockConsole = {warn: jest.fn()}; const timers = new FakeTimers({ config: { rootDir: __dirname, testMatch: [], }, - global: globalThis, + global: {console: mockConsole} as unknown as typeof globalThis, moduleMocker, timerConfig, }); timers.runAllTimers(); expect( - consoleWarn.mock.calls[0][0].split('\nStack Trace')[0], + mockConsole.warn.mock.calls[0][0].split('\nStack Trace')[0], ).toMatchSnapshot(); - consoleWarn.mockRestore(); }); it('does nothing when no timers have been scheduled', () => { @@ -1508,19 +1505,33 @@ describe('FakeTimers', () => { }); describe('getTimerCount', () => { - it('returns the correct count', () => { - const timers = new FakeTimers({ + let timers: FakeTimers; + let fakedGlobal: typeof globalThis; + + beforeEach(() => { + fakedGlobal = { + Date, + cancelAnimationFrame: () => {}, + clearTimeout, + process, + requestAnimationFrame: () => {}, + setImmediate, + setTimeout, + } as unknown as typeof globalThis; + timers = new FakeTimers({ config, - global: globalThis, + global: fakedGlobal, moduleMocker, timerConfig, }); + }); + it('returns the correct count', () => { timers.useFakeTimers(); - globalThis.setTimeout(() => {}, 0); - globalThis.setTimeout(() => {}, 0); - globalThis.setTimeout(() => {}, 10); + fakedGlobal.setTimeout(() => {}, 0); + fakedGlobal.setTimeout(() => {}, 0); + fakedGlobal.setTimeout(() => {}, 10); expect(timers.getTimerCount()).toEqual(3); @@ -1534,33 +1545,19 @@ describe('FakeTimers', () => { }); it('includes immediates and ticks', () => { - const timers = new FakeTimers({ - config, - global: globalThis, - moduleMocker, - timerConfig, - }); - timers.useFakeTimers(); - globalThis.setTimeout(() => {}, 0); - globalThis.setImmediate(() => {}); + fakedGlobal.setTimeout(() => {}, 0); + fakedGlobal.setImmediate(() => {}); process.nextTick(() => {}); expect(timers.getTimerCount()).toEqual(3); }); it('not includes cancelled immediates', () => { - const timers = new FakeTimers({ - config, - global: globalThis, - moduleMocker, - timerConfig, - }); - timers.useFakeTimers(); - globalThis.setImmediate(() => {}); + fakedGlobal.setImmediate(() => {}); expect(timers.getTimerCount()).toEqual(1); timers.clearAllTimers(); @@ -1568,21 +1565,9 @@ describe('FakeTimers', () => { }); it('includes animation frames', () => { - const global = { - cancelAnimationFrame: () => {}, - process, - requestAnimationFrame: () => {}, - } as unknown as typeof globalThis & Window; - const timers = new FakeTimers({ - config, - global, - moduleMocker, - timerConfig, - }); - timers.useFakeTimers(); - global.requestAnimationFrame(() => {}); + fakedGlobal.requestAnimationFrame(() => {}); expect(timers.getTimerCount()).toEqual(1); timers.clearAllTimers(); @@ -1591,17 +1576,28 @@ describe('FakeTimers', () => { }); describe('now', () => { - it('returns the current clock', () => { - const timers = new FakeTimers({ + let timers: FakeTimers; + let fakedGlobal: typeof globalThis; + + beforeEach(() => { + fakedGlobal = { + Date, + clearTimeout, + process, + setTimeout, + } as unknown as typeof globalThis; + timers = new FakeTimers({ config, - global: globalThis, + global: fakedGlobal, moduleMocker, timerConfig, }); + }); + it('returns the current clock', () => { timers.useFakeTimers(); - globalThis.setTimeout(() => {}, 2); - globalThis.setTimeout(() => {}, 100); + fakedGlobal.setTimeout(() => {}, 2); + fakedGlobal.setTimeout(() => {}, 100); expect(timers.now()).toEqual(0); @@ -1619,15 +1615,23 @@ describe('FakeTimers', () => { // Verify that runOnlyPendingTimers advances now only up to the first // recursive timer - globalThis.setTimeout(function infinitelyRecursingCallback() { - globalThis.setTimeout(infinitelyRecursingCallback, 20); + fakedGlobal.setTimeout(function infinitelyRecursingCallback() { + fakedGlobal.setTimeout(infinitelyRecursingCallback, 20); }, 10); timers.runOnlyPendingTimers(); expect(timers.now()).toEqual(110); - // Reset should set now back to 0 + // For legacy timers, reset() sets the clock to 0 timers.reset(); expect(timers.now()).toEqual(0); }); + + it('returns the real time if useFakeTimers is not called', () => { + const before = Date.now(); + const now = timers.now(); + const after = Date.now(); + expect(now).toBeGreaterThanOrEqual(before); + expect(now).toBeLessThanOrEqual(after); + }); }); }); diff --git a/packages/jest-fake-timers/src/__tests__/modernFakeTimers.test.ts b/packages/jest-fake-timers/src/__tests__/modernFakeTimers.test.ts index b7e9120c41c6..ffc6bb8c8a74 100644 --- a/packages/jest-fake-timers/src/__tests__/modernFakeTimers.test.ts +++ b/packages/jest-fake-timers/src/__tests__/modernFakeTimers.test.ts @@ -903,10 +903,18 @@ describe('FakeTimers', () => { describe('getTimerCount', () => { let timers: FakeTimers; + let fakedGlobal: typeof globalThis; beforeEach(() => { + fakedGlobal = { + Date, + clearTimeout, + process, + setImmediate, + setTimeout, + } as unknown as typeof globalThis; timers = new FakeTimers({ config: makeProjectConfig(), - global: globalThis, + global: fakedGlobal, }); timers.useFakeTimers(); @@ -917,9 +925,9 @@ describe('FakeTimers', () => { }); it('returns the correct count', () => { - globalThis.setTimeout(() => {}, 0); - globalThis.setTimeout(() => {}, 0); - globalThis.setTimeout(() => {}, 10); + fakedGlobal.setTimeout(() => {}, 0); + fakedGlobal.setTimeout(() => {}, 0); + fakedGlobal.setTimeout(() => {}, 10); expect(timers.getTimerCount()).toEqual(3); @@ -933,15 +941,15 @@ describe('FakeTimers', () => { }); it('includes immediates and ticks', () => { - globalThis.setTimeout(() => {}, 0); - globalThis.setImmediate(() => {}); + fakedGlobal.setTimeout(() => {}, 0); + fakedGlobal.setImmediate(() => {}); process.nextTick(() => {}); expect(timers.getTimerCount()).toEqual(3); }); it('not includes cancelled immediates', () => { - globalThis.setImmediate(() => {}); + fakedGlobal.setImmediate(() => {}); expect(timers.getTimerCount()).toEqual(1); timers.clearAllTimers(); @@ -950,16 +958,27 @@ describe('FakeTimers', () => { }); describe('now', () => { - it('returns the current clock', () => { - const timers = new FakeTimers({ + let timers: FakeTimers; + let fakedGlobal: typeof globalThis; + + beforeEach(() => { + fakedGlobal = { + Date, + clearTimeout, + process, + setTimeout, + } as unknown as typeof globalThis; + timers = new FakeTimers({ config: makeProjectConfig(), - global: globalThis, + global: fakedGlobal, }); + }); + it('returns the current clock', () => { timers.useFakeTimers(); timers.setSystemTime(0); - globalThis.setTimeout(() => {}, 2); - globalThis.setTimeout(() => {}, 100); + fakedGlobal.setTimeout(() => {}, 2); + fakedGlobal.setTimeout(() => {}, 100); expect(timers.now()).toEqual(0); @@ -977,8 +996,8 @@ describe('FakeTimers', () => { // Verify that runOnlyPendingTimers advances now only up to the first // recursive timer - globalThis.setTimeout(function infinitelyRecursingCallback() { - globalThis.setTimeout(infinitelyRecursingCallback, 20); + fakedGlobal.setTimeout(function infinitelyRecursingCallback() { + fakedGlobal.setTimeout(infinitelyRecursingCallback, 20); }, 10); timers.runOnlyPendingTimers(); expect(timers.now()).toEqual(110); @@ -987,5 +1006,13 @@ describe('FakeTimers', () => { timers.reset(); expect(timers.now()).toEqual(110); }); + + it('returns the real time if useFakeTimers is not called', () => { + const before = Date.now(); + const now = timers.now(); + const after = Date.now(); + expect(now).toBeGreaterThanOrEqual(before); + expect(now).toBeLessThanOrEqual(after); + }); }); }); diff --git a/packages/jest-fake-timers/src/legacyFakeTimers.ts b/packages/jest-fake-timers/src/legacyFakeTimers.ts index a5b7766d0279..b7e614001528 100644 --- a/packages/jest-fake-timers/src/legacyFakeTimers.ts +++ b/packages/jest-fake-timers/src/legacyFakeTimers.ts @@ -69,6 +69,7 @@ export default class FakeTimers { private _config: StackTraceConfig; private _disposed?: boolean; private _fakeTimerAPIs!: FakeTimerAPI; + private _fakingTime = false; private _global: typeof globalThis; private _immediates!: Array; private _maxLoops: number; @@ -135,7 +136,10 @@ export default class FakeTimers { } now(): number { - return this._now; + if (this._fakingTime) { + return this._now; + } + return Date.now(); } runAllTicks(): void { @@ -365,6 +369,8 @@ export default class FakeTimers { setGlobal(global, 'setTimeout', this._timerAPIs.setTimeout); global.process.nextTick = this._timerAPIs.nextTick; + + this._fakingTime = false; } useFakeTimers(): void { @@ -397,6 +403,8 @@ export default class FakeTimers { setGlobal(global, 'setTimeout', this._fakeTimerAPIs.setTimeout); global.process.nextTick = this._fakeTimerAPIs.nextTick; + + this._fakingTime = true; } getTimerCount(): number { @@ -406,8 +414,7 @@ export default class FakeTimers { } private _checkFakeTimers() { - // @ts-expect-error: condition always returns 'true' - if (this._global.setTimeout !== this._fakeTimerAPIs?.setTimeout) { + if (!this._fakingTime) { this._global.console.warn( 'A function to advance timers was called but the timers APIs are not mocked ' + 'with fake timers. Call `jest.useFakeTimers({legacyFakeTimers: true})` ' + diff --git a/packages/jest-fake-timers/src/modernFakeTimers.ts b/packages/jest-fake-timers/src/modernFakeTimers.ts index 14b0e1809669..1a325ee662e3 100644 --- a/packages/jest-fake-timers/src/modernFakeTimers.ts +++ b/packages/jest-fake-timers/src/modernFakeTimers.ts @@ -123,7 +123,10 @@ export default class FakeTimers { } now(): number { - return this._clock.now; + if (this._fakingTime) { + return this._clock.now; + } + return Date.now(); } getTimerCount(): number { From 15a06c61552e5445d895f526b8fdb4af176262f8 Mon Sep 17 00:00:00 2001 From: Rob Hogan Date: Sat, 10 Sep 2022 19:00:41 +0100 Subject: [PATCH 4/5] Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94166694052f..6da3e71b173a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Fixes +- `[@jest/fake-timers]` `jest.now()` returns `Date.now()` if real timers are in use ([13246](https://github.com/facebook/jest/pull/13246)) + ### Chore & Maintenance ### Performance From 021be085e0a95517df58fa766625a4195d932b28 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Sat, 10 Sep 2022 20:24:24 +0200 Subject: [PATCH 5/5] Update CHANGELOG.md --- CHANGELOG.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6da3e71b173a..87393bcd6c9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,10 @@ ### Features -- `[@jest/fake-timers]` Add `jest.now()` to return the current fake clock time ([#13244](https://github.com/facebook/jest/pull/13244)) +- `[@jest/fake-timers]` Add `jest.now()` to return the current fake clock time ([#13244](https://github.com/facebook/jest/pull/13244), [13246](https://github.com/facebook/jest/pull/13246)) ### Fixes -- `[@jest/fake-timers]` `jest.now()` returns `Date.now()` if real timers are in use ([13246](https://github.com/facebook/jest/pull/13246)) - ### Chore & Maintenance ### Performance