Skip to content

Commit

Permalink
fixup! feat(jest-fake-timers): Add feature to enable automatically ad…
Browse files Browse the repository at this point in the history
…vancing timers
  • Loading branch information
atscott committed Sep 26, 2024
1 parent cb55046 commit 20aa0e9
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 12 deletions.
71 changes: 65 additions & 6 deletions packages/jest-fake-timers/src/__tests__/modernFakeTimers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,7 @@ describe('FakeTimers', () => {
});

afterEach(() => {
timers.clearAllTimers();
timers.dispose();
});

Expand Down Expand Up @@ -1308,21 +1309,27 @@ describe('FakeTimers', () => {

it('can turn off and on auto advancing of time', async () => {
let p2Resolved = false;
const p1 = new Promise(resolve => global.setTimeout(resolve, 50));
const p2 = new Promise(resolve => global.setTimeout(resolve, 51)).then(
() => (p2Resolved = true),
);
const p3 = new Promise(resolve => global.setTimeout(resolve, 52));
const p1 = new Promise(resolve => global.setTimeout(resolve, 1));
const p2 = new Promise<void>(resolve => global.setTimeout(() => {

Check failure on line 1313 in packages/jest-fake-timers/src/__tests__/modernFakeTimers.test.ts

View workflow job for this annotation

GitHub Actions / Lint

Insert `⏎·········`
p2Resolved = true;

Check failure on line 1314 in packages/jest-fake-timers/src/__tests__/modernFakeTimers.test.ts

View workflow job for this annotation

GitHub Actions / Lint

Insert `··`
resolve();

Check failure on line 1315 in packages/jest-fake-timers/src/__tests__/modernFakeTimers.test.ts

View workflow job for this annotation

GitHub Actions / Lint

Replace `··········` with `············`
}, 2));

Check failure on line 1316 in packages/jest-fake-timers/src/__tests__/modernFakeTimers.test.ts

View workflow job for this annotation

GitHub Actions / Lint

Replace `},·2)` with `··},·2),⏎········`
const p3 = new Promise(resolve => global.setTimeout(resolve, 3));

await expect(p1).resolves.toBeUndefined();

timers.setTickMode('manual');
// wait real, unpatched time to ensure p2 doesn't resolve on its own
await new Promise(resolve => setTimeout(resolve, 5));
expect(p2Resolved).toBe(false);

// simply updating the tick mode should not result in time immediately advancing
timers.setTickMode('nextAsync');
expect(p2Resolved).toBe(false);

// wait real, unpatched time and observe p2 and p3 resolve on their own
await new Promise(resolve => setTimeout(resolve, 5));
await expect(p2).resolves.toBe(true);
await expect(p2).resolves.toBeUndefined();
await expect(p3).resolves.toBeUndefined();
expect(p2Resolved).toBe(true);
});
Expand All @@ -1340,6 +1347,58 @@ describe('FakeTimers', () => {
).toMatchSnapshot();
consoleWarnSpy.mockRestore();
});

describe('works with manual calls to async tick functions', () => {
let timerLog: number[];

Check failure on line 1352 in packages/jest-fake-timers/src/__tests__/modernFakeTimers.test.ts

View workflow job for this annotation

GitHub Actions / Lint

Array type using 'number[]' is forbidden. Use 'Array<number>' instead
let allTimersDone: Promise<void>;
beforeEach(() => {
timerLog = [];
allTimersDone = new Promise<void>(resolve => {
global.setTimeout(() => timerLog.push(1), 1);
global.setTimeout(() => timerLog.push(2), 2);
global.setTimeout(() => timerLog.push(3), 3);
global.setTimeout(() => {
timerLog.push(4);
global.setTimeout(() => {
timerLog.push(5);
resolve();
}, 1);
}, 5);
});
});

afterEach(async () => {
await allTimersDone;
expect(timerLog).toEqual([1, 2, 3, 4, 5]);
});

it('runAllTimersAsync', async () => {
await timers.runAllTimersAsync();
expect(timerLog).toEqual([1, 2, 3, 4, 5]);
});

it('runOnlyPendingTimersAsync', async () => {
await timers.runOnlyPendingTimersAsync();
// 5 should not resolve because it wasn't queued when we called "only pending timers"
expect(timerLog).toEqual([1, 2, 3, 4]);
});

it('advanceTimersToNextTimerAsync', async () => {
await timers.advanceTimersToNextTimerAsync();
expect(timerLog).toEqual([1]);
await timers.advanceTimersToNextTimerAsync();
expect(timerLog).toEqual([1, 2]);
await timers.advanceTimersToNextTimerAsync();
expect(timerLog).toEqual([1, 2, 3]);
});

it('advanceTimersByTimeAsync', async () => {
await timers.advanceTimersByTimeAsync(2);
expect(timerLog).toEqual([1, 2]);
await timers.advanceTimersByTimeAsync(1);
expect(timerLog).toEqual([1, 2, 3]);
});
});
});
});

Expand Down
39 changes: 33 additions & 6 deletions packages/jest-fake-timers/src/modernFakeTimers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type TimerTickMode = 'manual' | 'nextAsync' | 'interval';

export default class FakeTimers {
private _clock!: InstalledClock;
private _nativeTimeout: typeof setTimeout;
private readonly _config: Config.ProjectConfig;
private _fakingTime: boolean;
private _usingSinonAdvanceTime = false;
Expand All @@ -38,6 +39,7 @@ export default class FakeTimers {
}) {
this._global = global;
this._config = config;
this._nativeTimeout = global.setTimeout;

this._fakingTime = false;
this._fakeTimers = withGlobal(global);
Expand All @@ -61,7 +63,7 @@ export default class FakeTimers {

async runAllTimersAsync(): Promise<void> {
if (this._checkFakeTimers()) {
await this._clock.runAllAsync();
await this._runWithoutNextAsyncTickMode(() => this._clock.runAllAsync());
}
}

Expand All @@ -73,7 +75,9 @@ export default class FakeTimers {

async runOnlyPendingTimersAsync(): Promise<void> {
if (this._checkFakeTimers()) {
await this._clock.runToLastAsync();
await this._runWithoutNextAsyncTickMode(() =>
this._clock.runToLastAsync(),
);
}
}

Expand All @@ -94,9 +98,11 @@ export default class FakeTimers {
async advanceTimersToNextTimerAsync(steps = 1): Promise<void> {
if (this._checkFakeTimers()) {
for (let i = steps; i > 0; i--) {
await this._clock.nextAsync();
// Fire all timers at this point: https://github.com/sinonjs/fake-timers/issues/250
await this._clock.tickAsync(0);
await this._runWithoutNextAsyncTickMode(async () => {
await this._clock.nextAsync();
// Fire all timers at this point: https://github.com/sinonjs/fake-timers/issues/250
await this._clock.tickAsync(0);
});

if (this._clock.countTimers() === 0) {
break;
Expand All @@ -113,7 +119,9 @@ export default class FakeTimers {

async advanceTimersByTimeAsync(msToRun: number): Promise<void> {
if (this._checkFakeTimers()) {
await this._clock.tickAsync(msToRun);
await this._runWithoutNextAsyncTickMode(() =>
this._clock.tickAsync(msToRun),
);
}
}

Expand Down Expand Up @@ -297,10 +305,29 @@ export default class FakeTimers {
}
const {counter} = this.tickMode;

// Wait a macrotask to prevent advancing time immediately when
await new Promise(resolve => void this._nativeTimeout(resolve));
while (this.tickMode.counter === counter && this._fakingTime) {
// nextAsync always resolves in a setTimeout, even when there are no timers.
// https://github.com/sinonjs/fake-timers/blob/710cafad25abe9465c807efd8ed9cf3a15985fb1/src/fake-timers-src.js#L1517-L1546
await this._clock.nextAsync();
}
}

/**
* Temporarily disables the `nextAsync` tick mode while the given function
* executes. Used to prevent the auto-advance from advancing while the
* user is waiting for a manually requested async tick.
*/
private async _runWithoutNextAsyncTickMode(fn: () => Promise<unknown>) {
let resetModeToNextAsync = false;
if (this.tickMode.mode === 'nextAsync') {
this.setTickMode('manual');
resetModeToNextAsync = true;
}
await fn();
if (resetModeToNextAsync) {
this.setTickMode('nextAsync');
}
}
}

0 comments on commit 20aa0e9

Please sign in to comment.