Skip to content

Commit

Permalink
(jest-fake-timers): Add now() API to get the fake clock time (#13244)
Browse files Browse the repository at this point in the history
  • Loading branch information
robhogan committed Sep 10, 2022
1 parent 27b46b6 commit bedbed1
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 15 deletions.
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());
expectError(jest.now('1995-12-17T03:24:00'));

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

Expand Down

0 comments on commit bedbed1

Please sign in to comment.