Skip to content

Commit bedbed1

Browse files
authored
(jest-fake-timers): Add now() API to get the fake clock time (#13244)
1 parent 27b46b6 commit bedbed1

File tree

9 files changed

+127
-15
lines changed

9 files changed

+127
-15
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
### Features
44

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

79
### Chore & Maintenance

docs/JestObjectAPI.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,10 @@ This means, if any timers have been scheduled (but have not yet executed), they
799799

800800
Returns the number of fake timers still left to run.
801801

802+
### `jest.now()`
803+
804+
Returns the time in ms of the current fake clock. This is equivalent to `Date.now()` if `Date` has been mocked.
805+
802806
### `jest.setSystemTime(now?: number | Date)`
803807

804808
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()`.

packages/jest-environment/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ export interface Jest {
152152
* Returns the number of fake timers still left to run.
153153
*/
154154
getTimerCount(): number;
155+
/**
156+
* Returns the current time in ms of the fake timer clock.
157+
*/
158+
now(): number;
155159
/**
156160
* Determines if the given function is a mocked function.
157161
*/

packages/jest-fake-timers/src/__tests__/legacyFakeTimers.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1589,4 +1589,45 @@ describe('FakeTimers', () => {
15891589
expect(timers.getTimerCount()).toEqual(0);
15901590
});
15911591
});
1592+
1593+
describe('now', () => {
1594+
it('returns the current clock', () => {
1595+
const timers = new FakeTimers({
1596+
config,
1597+
global: globalThis,
1598+
moduleMocker,
1599+
timerConfig,
1600+
});
1601+
1602+
timers.useFakeTimers();
1603+
globalThis.setTimeout(() => {}, 2);
1604+
globalThis.setTimeout(() => {}, 100);
1605+
1606+
expect(timers.now()).toEqual(0);
1607+
1608+
// This should run the 2ms timer, and then advance _now by 3ms
1609+
timers.advanceTimersByTime(5);
1610+
expect(timers.now()).toEqual(5);
1611+
1612+
// Advance _now even though there are no timers to run
1613+
timers.advanceTimersByTime(5);
1614+
expect(timers.now()).toEqual(10);
1615+
1616+
// Run up to the 100ms timer
1617+
timers.runAllTimers();
1618+
expect(timers.now()).toEqual(100);
1619+
1620+
// Verify that runOnlyPendingTimers advances now only up to the first
1621+
// recursive timer
1622+
globalThis.setTimeout(function infinitelyRecursingCallback() {
1623+
globalThis.setTimeout(infinitelyRecursingCallback, 20);
1624+
}, 10);
1625+
timers.runOnlyPendingTimers();
1626+
expect(timers.now()).toEqual(110);
1627+
1628+
// Reset should set now back to 0
1629+
timers.reset();
1630+
expect(timers.now()).toEqual(0);
1631+
});
1632+
});
15921633
});

packages/jest-fake-timers/src/__tests__/modernFakeTimers.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,4 +948,44 @@ describe('FakeTimers', () => {
948948
expect(timers.getTimerCount()).toEqual(0);
949949
});
950950
});
951+
952+
describe('now', () => {
953+
it('returns the current clock', () => {
954+
const timers = new FakeTimers({
955+
config: makeProjectConfig(),
956+
global: globalThis,
957+
});
958+
959+
timers.useFakeTimers();
960+
timers.setSystemTime(0);
961+
globalThis.setTimeout(() => {}, 2);
962+
globalThis.setTimeout(() => {}, 100);
963+
964+
expect(timers.now()).toEqual(0);
965+
966+
// This should run the 2ms timer, and then advance _now by 3ms
967+
timers.advanceTimersByTime(5);
968+
expect(timers.now()).toEqual(5);
969+
970+
// Advance _now even though there are no timers to run
971+
timers.advanceTimersByTime(5);
972+
expect(timers.now()).toEqual(10);
973+
974+
// Run up to the 100ms timer
975+
timers.runAllTimers();
976+
expect(timers.now()).toEqual(100);
977+
978+
// Verify that runOnlyPendingTimers advances now only up to the first
979+
// recursive timer
980+
globalThis.setTimeout(function infinitelyRecursingCallback() {
981+
globalThis.setTimeout(infinitelyRecursingCallback, 20);
982+
}, 10);
983+
timers.runOnlyPendingTimers();
984+
expect(timers.now()).toEqual(110);
985+
986+
// For modern timers, reset() explicitly preserves the clock time
987+
timers.reset();
988+
expect(timers.now()).toEqual(110);
989+
});
990+
});
951991
});

packages/jest-fake-timers/src/legacyFakeTimers.ts

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ export default class FakeTimers<TimerRef = unknown> {
134134
this._timers = new Map();
135135
}
136136

137+
now(): number {
138+
return this._now;
139+
}
140+
137141
runAllTicks(): void {
138142
this._checkFakeTimers();
139143
// Only run a generous number of ticks and then bail.
@@ -200,13 +204,15 @@ export default class FakeTimers<TimerRef = unknown> {
200204
// This is just to help avoid recursive loops
201205
let i;
202206
for (i = 0; i < this._maxLoops; i++) {
203-
const nextTimerHandle = this._getNextTimerHandle();
207+
const nextTimerHandleAndExpiry = this._getNextTimerHandleAndExpiry();
204208

205209
// If there are no more timer handles, stop!
206-
if (nextTimerHandle === null) {
210+
if (nextTimerHandleAndExpiry === null) {
207211
break;
208212
}
209213

214+
const [nextTimerHandle, expiry] = nextTimerHandleAndExpiry;
215+
this._now = expiry;
210216
this._runTimerHandle(nextTimerHandle);
211217

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

240246
timerEntries
241247
.sort(([, left], [, right]) => left.expiry - right.expiry)
242-
.forEach(([timerHandle]) => this._runTimerHandle(timerHandle));
248+
.forEach(([timerHandle, timer]) => {
249+
this._now = timer.expiry;
250+
this._runTimerHandle(timerHandle);
251+
});
243252
}
244253

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

270279
// If there are no more timer handles, stop!
271-
if (timerHandle === null) {
272-
break;
273-
}
274-
const timerValue = this._timers.get(timerHandle);
275-
if (timerValue === undefined) {
280+
if (timerHandleAndExpiry === null) {
276281
break;
277282
}
278-
const nextTimerExpiry = timerValue.expiry;
283+
const [timerHandle, nextTimerExpiry] = timerHandleAndExpiry;
284+
279285
if (this._now + msToRun < nextTimerExpiry) {
280-
// There are no timers between now and the target we're running to, so
281-
// adjust our time cursor and quit
282-
this._now += msToRun;
286+
// There are no timers between now and the target we're running to
283287
break;
284288
} else {
285289
msToRun -= nextTimerExpiry - this._now;
@@ -288,6 +292,9 @@ export default class FakeTimers<TimerRef = unknown> {
288292
}
289293
}
290294

295+
// Advance the clock by whatever time we still have left to run
296+
this._now += msToRun;
297+
291298
if (i === this._maxLoops) {
292299
throw new Error(
293300
`Ran ${this._maxLoops} timers, and there are still more! ` +
@@ -557,7 +564,7 @@ export default class FakeTimers<TimerRef = unknown> {
557564
return this._timerConfig.idToRef(uuid);
558565
}
559566

560-
private _getNextTimerHandle() {
567+
private _getNextTimerHandleAndExpiry(): [string, number] | null {
561568
let nextTimerHandle = null;
562569
let soonestTime = MS_IN_A_YEAR;
563570

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

571-
return nextTimerHandle;
578+
if (nextTimerHandle === null) {
579+
return null;
580+
}
581+
582+
return [nextTimerHandle, soonestTime];
572583
}
573584

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

577588
if (!timer) {
589+
// Timer has been cleared - we'll hit this when a timer is cleared within
590+
// another timer in runOnlyPendingTimers
578591
return;
579592
}
580593

packages/jest-fake-timers/src/modernFakeTimers.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ export default class FakeTimers {
122122
return Date.now();
123123
}
124124

125+
now(): number {
126+
return this._clock.now;
127+
}
128+
125129
getTimerCount(): number {
126130
if (this._checkFakeTimers()) {
127131
return this._clock.countTimers();

packages/jest-runtime/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2128,6 +2128,7 @@ export default class Runtime {
21282128
isolateModules,
21292129
mock,
21302130
mocked,
2131+
now: () => _getFakeTimers().now(),
21312132
requireActual: this.requireActual.bind(this, from),
21322133
requireMock: this.requireMock.bind(this, from),
21332134
resetAllMocks,

packages/jest-types/__typetests__/jest.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,9 @@ expectError(jest.clearAllTimers(false));
413413
expectType<number>(jest.getTimerCount());
414414
expectError(jest.getTimerCount(true));
415415

416+
expectType<number>(jest.now());
417+
expectError(jest.now('1995-12-17T03:24:00'));
418+
416419
expectType<number>(jest.getRealSystemTime());
417420
expectError(jest.getRealSystemTime(true));
418421

0 commit comments

Comments
 (0)