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

feat(jest-runtime): expose @sinonjs/fake-timers async APIs #13981

Merged
merged 14 commits into from
Mar 6, 2023
Merged
Prev Previous commit
Next Next commit
fixed documentation, added advanceTimersToNextTimerAsync steps parameter
  • Loading branch information
lpizzinidev committed Mar 6, 2023
commit eb4c655018e1ee7bb67f04afa84339ee82107241
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
- `[jest-snapshot]` Add support to `cts` and `mts` TypeScript files to inline snapshots ([#13975](https://github.com/facebook/jest/pull/13975))
- `[jest-worker]` Add `start` method to worker farms ([#13937](https://github.com/facebook/jest/pull/13937))
- `[jest-worker]` Support passing a URL as path to worker ([#13982](https://github.com/facebook/jest/pull/13982))
- `[jest-runtime]` Expose `@sinonjs/fake-timers` async APIs functions `advanceTimersByTimeAsync(msToRun)` (`tickAsync(msToRun)`), `advanceTimersToNextTimerAsync` (`nextAsync`), `runAllTimersAsync` (`runAllAsync`), and `runOnlyPendingTimersAsync` (`runToLastAsync`) ([#13981](https://github.com/facebook/jest/pull/13981))
- `[jest-runtime]` Expose `@sinonjs/fake-timers` async APIs functions `advanceTimersByTimeAsync(msToRun)` (`tickAsync(msToRun)`), `advanceTimersToNextTimerAsync(steps)` (`nextAsync`), `runAllTimersAsync` (`runAllAsync`), and `runOnlyPendingTimersAsync` (`runToLastAsync`) ([#13981](https://github.com/facebook/jest/pull/13981))

### Fixes

Expand Down
18 changes: 5 additions & 13 deletions docs/JestObjectAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -923,9 +923,7 @@ This is often useful for synchronously executing setTimeouts during a test in or

### `jest.runAllTimersAsync()`

Runs all pending timers until there are none remaining.

Also breaks the event loop, allowing any scheduled promise callbacks to execute _before_ running the timers.
Asynchronous equivalent of `jest.runAllTimers()`.
SimenB marked this conversation as resolved.
Show resolved Hide resolved

:::info

Expand All @@ -951,9 +949,7 @@ When this API is called, all timers are advanced by `msToRun` milliseconds. All

### `jest.advanceTimersByTimeAsync(msToRun)`

Advance the clock, firing callbacks if necessary.

Also breaks the event loop, allowing any scheduled promise callbacks to execute _before_ running the timers.
Asynchronous equivalent of `jest.advanceTimersByTime(msToRun)`.

:::info

Expand All @@ -969,9 +965,7 @@ This is useful for scenarios such as one where the module being tested schedules

### `jest.runOnlyPendingTimersAsync()`

Takes note of the last scheduled timer when it is run, and advances the clock to that time firing callbacks as necessary.

Also breaks the event loop, allowing any scheduled promise callbacks to execute _before_ running the timers.
Asynchronous equivalent of `jest.runOnlyPendingTimers()`.

:::info

Expand All @@ -985,11 +979,9 @@ Advances all timers by the needed milliseconds so that only the next timeouts/in

Optionally, you can provide `steps`, so it will run `steps` amount of next timeouts/intervals.

### `jest.advanceTimersToNextTimerAsync()`

Advances the clock to the the moment of the first scheduled timer.
### `jest.advanceTimersToNextTimerAsync(steps)`

Also breaks the event loop, allowing any scheduled promise callbacks to execute _before_ running the timers.
Asynchronous equivalent of `jest.advanceTimersToNextTimer(steps)`.

:::info

Expand Down
16 changes: 11 additions & 5 deletions packages/jest-environment/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ export interface Jest {
/**
* Advances all timers by `msToRun` milliseconds, firing callbacks if necessary.
*
* Also breaks the event loop, allowing any scheduled promise callbacks to execute _before_ running the timers.
* Also breaks the event loop, allowing any scheduled promise callbacks
* to execute _before_ running the timers.
*
* @remarks
* Not available when using legacy fake timers implementation.
Expand All @@ -77,13 +78,16 @@ export interface Jest {
advanceTimersToNextTimer(steps?: number): void;
/**
* Advances the clock to the the moment of the first scheduled timer, firing it.
* Optionally, you can provide steps, so it will run steps amount of
* next timeouts/intervals.
*
* Also breaks the event loop, allowing any scheduled promise callbacks to execute _before_ running the timers.
* Also breaks the event loop, allowing any scheduled promise callbacks
* to execute _before_ running the timers.
*
* @remarks
* Not available when using legacy fake timers implementation.
*/
advanceTimersToNextTimerAsync(): Promise<void>;
advanceTimersToNextTimerAsync(steps?: number): Promise<void>;
/**
* Disables automatic mocking in the module loader.
*/
Expand Down Expand Up @@ -320,7 +324,8 @@ export interface Jest {
* Exhausts the macro-task queue (i.e., all tasks queued by `setTimeout()`
* and `setInterval()`).
*
* Also breaks the event loop, allowing any scheduled promise callbacks to execute _before_ running the timers.
* Also breaks the event loop, allowing any scheduled promise callbacks
* to execute _before_ running the timers.
*
* @remarks
* If new timers are added while it is executing they will be run as well.
Expand All @@ -341,7 +346,8 @@ export interface Jest {
* point). If any of the currently pending macro-tasks schedule new
* macro-tasks, those new tasks will not be executed by this call.
*
* Also breaks the event loop, allowing any scheduled promise callbacks to execute _before_ running the timers.
* Also breaks the event loop, allowing any scheduled promise callbacks
* to execute _before_ running the timers.
*
* @remarks
* Not available when using legacy fake timers implementation.
Expand Down
24 changes: 24 additions & 0 deletions packages/jest-fake-timers/src/__tests__/modernFakeTimers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,30 @@ describe('FakeTimers', () => {
expect(timers.now()).toBe(200);
expect(spy).toHaveBeenCalled();
});

it('should advance the clock at the moment of the n-th scheduled timer', async () => {
const global = {
Date,
Promise,
clearTimeout,
process,
setTimeout,
} as unknown as typeof globalThis;
const timers = new FakeTimers({config: makeProjectConfig(), global});
timers.useFakeTimers();
timers.setSystemTime(0);

const spy = jest.fn();
global.setTimeout(async () => {
await Promise.resolve();
global.setTimeout(spy, 100);
}, 100);

await timers.advanceTimersToNextTimerAsync(2);

expect(timers.now()).toBe(200);
expect(spy).toHaveBeenCalled();
});
});

describe('runAllTimersAsync', () => {
Expand Down
12 changes: 10 additions & 2 deletions packages/jest-fake-timers/src/modernFakeTimers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,17 @@ export default class FakeTimers {
}
}

async advanceTimersToNextTimerAsync(): Promise<void> {
async advanceTimersToNextTimerAsync(steps = 1): Promise<void> {
if (this._checkFakeTimers()) {
await this._clock.nextAsync();
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);

if (this._clock.countTimers() === 0) {
break;
}
}
}
}

Expand Down
8 changes: 6 additions & 2 deletions packages/jest-runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2236,6 +2236,7 @@ export default class Runtime {

if (
fakeTimers === this._environment.fakeTimersModern &&
// TODO: remove this check in Jest 30
typeof fakeTimers.advanceTimersByTimeAsync === 'function'
SimenB marked this conversation as resolved.
Show resolved Hide resolved
) {
await fakeTimers.advanceTimersByTimeAsync(msToRun);
Expand All @@ -2247,14 +2248,15 @@ export default class Runtime {
},
advanceTimersToNextTimer: (steps?: number) =>
_getFakeTimers().advanceTimersToNextTimer(steps),
advanceTimersToNextTimerAsync: async (): Promise<void> => {
advanceTimersToNextTimerAsync: async (steps?: number): Promise<void> => {
const fakeTimers = _getFakeTimers();

if (
fakeTimers === this._environment.fakeTimersModern &&
// TODO: remove this check in Jest 30
typeof fakeTimers.advanceTimersToNextTimerAsync === 'function'
) {
await fakeTimers.advanceTimersToNextTimerAsync();
await fakeTimers.advanceTimersToNextTimerAsync(steps);
} else {
throw new TypeError(
'`jest.advanceTimersToNextTimerAsync()` is not available when using legacy fake timers.',
Expand Down Expand Up @@ -2328,6 +2330,7 @@ export default class Runtime {

if (
fakeTimers === this._environment.fakeTimersModern &&
// TODO: remove this check in Jest 30
typeof fakeTimers.runAllTimersAsync === 'function'
) {
await fakeTimers.runAllTimersAsync();
Expand All @@ -2343,6 +2346,7 @@ export default class Runtime {

if (
fakeTimers === this._environment.fakeTimersModern &&
// TODO: remove this check in Jest 30
typeof fakeTimers.runOnlyPendingTimersAsync === 'function'
) {
await fakeTimers.runOnlyPendingTimersAsync();
Expand Down
1 change: 1 addition & 0 deletions packages/jest-types/__typetests__/jest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ expectType<void>(jest.advanceTimersToNextTimer(2));
expectError(jest.advanceTimersToNextTimer('2'));

expectType<Promise<void>>(jest.advanceTimersToNextTimerAsync());
expectType<Promise<void>>(jest.advanceTimersToNextTimerAsync(2));
expectError(jest.advanceTimersToNextTimerAsync('2'));

expectType<void>(jest.clearAllTimers());
Expand Down