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

(jest-fake-timers): Add now() API to get the fake clock time #13244

Merged
merged 7 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: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,8 @@

### Features

- `[@jest/fake-timers]` Add `jest.now()` to return the current fake clock time ([#13244](https://github.com/facebook/jest/pull/13244))

### Fixes

### Chore & Maintenance
Expand Down
4 changes: 4 additions & 0 deletions docs/JestObjectAPI.md
Expand Up @@ -799,6 +799,10 @@ This means, if any timers have been scheduled (but have not yet executed), they

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.

### `jest.setSystemTime(now?: number | Date)`

Set the current system time used by fake timers. Simulates a user changing the system clock while your program is running. It affects the current time but it does not in itself cause e.g. timers to fire; they will fire exactly as they would have done without the call to `jest.setSystemTime()`.
Expand Down
4 changes: 4 additions & 0 deletions packages/jest-environment/src/index.ts
Expand Up @@ -152,6 +152,10 @@ export interface Jest {
* Returns the number of fake timers still left to run.
*/
getTimerCount(): number;
/**
* Returns the current time in ms of the fake timer clock.
*/
now(): number;
/**
* Determines if the given function is a mocked function.
*/
Expand Down
41 changes: 41 additions & 0 deletions packages/jest-fake-timers/src/__tests__/legacyFakeTimers.test.ts
Expand Up @@ -1589,4 +1589,45 @@ describe('FakeTimers', () => {
expect(timers.getTimerCount()).toEqual(0);
});
});

describe('now', () => {
it('returns the current clock', () => {
const timers = new FakeTimers({
config,
global: globalThis,
moduleMocker,
timerConfig,
});

timers.useFakeTimers();
globalThis.setTimeout(() => {}, 2);
globalThis.setTimeout(() => {}, 100);

expect(timers.now()).toEqual(0);

// This should run the 2ms timer, and then advance _now by 3ms
timers.advanceTimersByTime(5);
expect(timers.now()).toEqual(5);

// Advance _now even though there are no timers to run
timers.advanceTimersByTime(5);
expect(timers.now()).toEqual(10);

// Run up to the 100ms timer
timers.runAllTimers();
expect(timers.now()).toEqual(100);

// Verify that runOnlyPendingTimers advances now only up to the first
// recursive timer
globalThis.setTimeout(function infinitelyRecursingCallback() {
globalThis.setTimeout(infinitelyRecursingCallback, 20);
}, 10);
timers.runOnlyPendingTimers();
expect(timers.now()).toEqual(110);

// Reset should set now back to 0
timers.reset();
expect(timers.now()).toEqual(0);
});
});
});
40 changes: 40 additions & 0 deletions packages/jest-fake-timers/src/__tests__/modernFakeTimers.test.ts
Expand Up @@ -948,4 +948,44 @@ describe('FakeTimers', () => {
expect(timers.getTimerCount()).toEqual(0);
});
});

describe('now', () => {
it('returns the current clock', () => {
const timers = new FakeTimers({
config: makeProjectConfig(),
global: globalThis,
});

timers.useFakeTimers();
timers.setSystemTime(0);
globalThis.setTimeout(() => {}, 2);
globalThis.setTimeout(() => {}, 100);

expect(timers.now()).toEqual(0);

// This should run the 2ms timer, and then advance _now by 3ms
timers.advanceTimersByTime(5);
expect(timers.now()).toEqual(5);

// Advance _now even though there are no timers to run
timers.advanceTimersByTime(5);
expect(timers.now()).toEqual(10);

// Run up to the 100ms timer
timers.runAllTimers();
expect(timers.now()).toEqual(100);

// Verify that runOnlyPendingTimers advances now only up to the first
// recursive timer
globalThis.setTimeout(function infinitelyRecursingCallback() {
globalThis.setTimeout(infinitelyRecursingCallback, 20);
}, 10);
timers.runOnlyPendingTimers();
expect(timers.now()).toEqual(110);

// For modern timers, reset() explicitly preserves the clock time
timers.reset();
expect(timers.now()).toEqual(110);
});
});
});
43 changes: 28 additions & 15 deletions packages/jest-fake-timers/src/legacyFakeTimers.ts
Expand Up @@ -134,6 +134,10 @@ export default class FakeTimers<TimerRef = unknown> {
this._timers = new Map();
}

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

runAllTicks(): void {
this._checkFakeTimers();
// Only run a generous number of ticks and then bail.
Expand Down Expand Up @@ -200,13 +204,15 @@ export default class FakeTimers<TimerRef = unknown> {
// This is just to help avoid recursive loops
let i;
for (i = 0; i < this._maxLoops; i++) {
const nextTimerHandle = this._getNextTimerHandle();
const nextTimerHandleAndExpiry = this._getNextTimerHandleAndExpiry();

// If there are no more timer handles, stop!
if (nextTimerHandle === null) {
if (nextTimerHandleAndExpiry === null) {
break;
}

const [nextTimerHandle, expiry] = nextTimerHandleAndExpiry;
this._now = expiry;
this._runTimerHandle(nextTimerHandle);

// Some of the immediate calls could be enqueued
Expand Down Expand Up @@ -239,7 +245,10 @@ export default class FakeTimers<TimerRef = unknown> {

timerEntries
.sort(([, left], [, right]) => left.expiry - right.expiry)
.forEach(([timerHandle]) => this._runTimerHandle(timerHandle));
.forEach(([timerHandle, timer]) => {
this._now = timer.expiry;
this._runTimerHandle(timerHandle);
});
}

advanceTimersToNextTimer(steps = 1): void {
Expand All @@ -265,21 +274,16 @@ export default class FakeTimers<TimerRef = unknown> {
// This is just to help avoid recursive loops
let i;
for (i = 0; i < this._maxLoops; i++) {
const timerHandle = this._getNextTimerHandle();
const timerHandleAndExpiry = this._getNextTimerHandleAndExpiry();

// If there are no more timer handles, stop!
if (timerHandle === null) {
break;
}
const timerValue = this._timers.get(timerHandle);
if (timerValue === undefined) {
if (timerHandleAndExpiry === null) {
break;
}
const nextTimerExpiry = timerValue.expiry;
const [timerHandle, nextTimerExpiry] = timerHandleAndExpiry;

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
this._now += msToRun;
// There are no timers between now and the target we're running to
break;
} else {
msToRun -= nextTimerExpiry - this._now;
Expand All @@ -288,6 +292,9 @@ export default class FakeTimers<TimerRef = unknown> {
}
}

// Advance the clock by whatever time we still have left to run
this._now += msToRun;

if (i === this._maxLoops) {
throw new Error(
`Ran ${this._maxLoops} timers, and there are still more! ` +
Expand Down Expand Up @@ -557,7 +564,7 @@ export default class FakeTimers<TimerRef = unknown> {
return this._timerConfig.idToRef(uuid);
}

private _getNextTimerHandle() {
private _getNextTimerHandleAndExpiry(): [string, number] | null {
let nextTimerHandle = null;
let soonestTime = MS_IN_A_YEAR;

Expand All @@ -568,13 +575,19 @@ export default class FakeTimers<TimerRef = unknown> {
}
});

return nextTimerHandle;
if (nextTimerHandle === null) {
return null;
}

return [nextTimerHandle, soonestTime];
}

private _runTimerHandle(timerHandle: TimerID) {
const timer = this._timers.get(timerHandle);

if (!timer) {
// Timer has been cleared - we'll hit this when a timer is cleared within
// another timer in runOnlyPendingTimers
return;
}

Expand Down
4 changes: 4 additions & 0 deletions packages/jest-fake-timers/src/modernFakeTimers.ts
Expand Up @@ -122,6 +122,10 @@ export default class FakeTimers {
return Date.now();
}

now(): number {
return this._clock.now;
}

getTimerCount(): number {
if (this._checkFakeTimers()) {
return this._clock.countTimers();
Expand Down
1 change: 1 addition & 0 deletions packages/jest-runtime/src/index.ts
Expand Up @@ -2128,6 +2128,7 @@ export default class Runtime {
isolateModules,
mock,
mocked,
now: () => _getFakeTimers().now(),
requireActual: this.requireActual.bind(this, from),
requireMock: this.requireMock.bind(this, from),
resetAllMocks,
Expand Down
3 changes: 3 additions & 0 deletions packages/jest-types/__typetests__/jest.test.ts
Expand Up @@ -413,6 +413,9 @@ expectError(jest.clearAllTimers(false));
expectType<number>(jest.getTimerCount());
expectError(jest.getTimerCount(true));

expectType<number>(jest.now());
robhogan marked this conversation as resolved.
Show resolved Hide resolved
expectError(jest.now('1995-12-17T03:24:00'));

expectType<number>(jest.getRealSystemTime());
expectError(jest.getRealSystemTime(true));

Expand Down