diff --git a/CHANGELOG.md b/CHANGELOG.md index 94166694052f..87393bcd6c9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### 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 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 {