Skip to content

Commit

Permalink
fix(jest-fake-timers): Return real Date.now() from jest.now() whe…
Browse files Browse the repository at this point in the history
…n real timers are in use (#13246)
  • Loading branch information
robhogan committed Sep 10, 2022
1 parent bedbed1 commit 74d27a7
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 70 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/JestObjectAPI.md
Expand Up @@ -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)`

Expand Down
104 changes: 54 additions & 50 deletions packages/jest-fake-timers/src/__tests__/legacyFakeTimers.test.ts
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -1508,19 +1505,33 @@ describe('FakeTimers', () => {
});

describe('getTimerCount', () => {
it('returns the correct count', () => {
const timers = new FakeTimers({
let timers: FakeTimers<number>;
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);

Expand All @@ -1534,55 +1545,29 @@ 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();

expect(timers.getTimerCount()).toEqual(0);
});

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();

Expand All @@ -1591,17 +1576,28 @@ describe('FakeTimers', () => {
});

describe('now', () => {
it('returns the current clock', () => {
const timers = new FakeTimers({
let timers: FakeTimers<number>;
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);

Expand All @@ -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);
});
});
});
55 changes: 41 additions & 14 deletions packages/jest-fake-timers/src/__tests__/modernFakeTimers.test.ts
Expand Up @@ -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();
Expand All @@ -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);

Expand All @@ -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();

Expand All @@ -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);

Expand All @@ -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);
Expand All @@ -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);
});
});
});
13 changes: 10 additions & 3 deletions packages/jest-fake-timers/src/legacyFakeTimers.ts
Expand Up @@ -69,6 +69,7 @@ export default class FakeTimers<TimerRef = unknown> {
private _config: StackTraceConfig;
private _disposed?: boolean;
private _fakeTimerAPIs!: FakeTimerAPI;
private _fakingTime = false;
private _global: typeof globalThis;
private _immediates!: Array<Tick>;
private _maxLoops: number;
Expand Down Expand Up @@ -135,7 +136,10 @@ export default class FakeTimers<TimerRef = unknown> {
}

now(): number {
return this._now;
if (this._fakingTime) {
return this._now;
}
return Date.now();
}

runAllTicks(): void {
Expand Down Expand Up @@ -365,6 +369,8 @@ export default class FakeTimers<TimerRef = unknown> {
setGlobal(global, 'setTimeout', this._timerAPIs.setTimeout);

global.process.nextTick = this._timerAPIs.nextTick;

this._fakingTime = false;
}

useFakeTimers(): void {
Expand Down Expand Up @@ -397,6 +403,8 @@ export default class FakeTimers<TimerRef = unknown> {
setGlobal(global, 'setTimeout', this._fakeTimerAPIs.setTimeout);

global.process.nextTick = this._fakeTimerAPIs.nextTick;

this._fakingTime = true;
}

getTimerCount(): number {
Expand All @@ -406,8 +414,7 @@ export default class FakeTimers<TimerRef = unknown> {
}

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})` ' +
Expand Down
5 changes: 4 additions & 1 deletion packages/jest-fake-timers/src/modernFakeTimers.ts
Expand Up @@ -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 {
Expand Down

0 comments on commit 74d27a7

Please sign in to comment.