Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(jest-fake-timers): Return real Date.now() from jest.now() when real timers are in use #13246

Merged
merged 5 commits into from Sep 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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) {
Comment on lines -409 to +417
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

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