mirror of
https://github.com/hanzoai/jest.git
synced 2026-10-30 14:46:24 +00:00
250 lines
6.1 KiB
TypeScript
250 lines
6.1 KiB
TypeScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
import {
|
|
type FakeTimers as FakeTimerWithContext,
|
|
type FakeMethod as FakeableAPI,
|
|
type Clock as InstalledClock,
|
|
type Config as SinonFakeTimersConfig,
|
|
type TemporalDuration,
|
|
type TemporalTimelike,
|
|
withGlobal,
|
|
} from '@sinonjs/fake-timers';
|
|
import type {Config} from '@jest/types';
|
|
import {formatStackTrace} from 'jest-message-util';
|
|
|
|
export default class FakeTimers {
|
|
private _clock!: InstalledClock;
|
|
private readonly _config: Config.ProjectConfig;
|
|
private _fakingTime: boolean;
|
|
private readonly _global: typeof globalThis;
|
|
private readonly _fakeTimers: FakeTimerWithContext;
|
|
|
|
constructor({
|
|
global,
|
|
config,
|
|
}: {
|
|
global: typeof globalThis;
|
|
config: Config.ProjectConfig;
|
|
}) {
|
|
this._global = global;
|
|
this._config = config;
|
|
|
|
this._fakingTime = false;
|
|
this._fakeTimers = withGlobal(global);
|
|
}
|
|
|
|
clearAllTimers(): void {
|
|
if (this._fakingTime) {
|
|
this._clock.reset();
|
|
}
|
|
}
|
|
|
|
dispose(): void {
|
|
this.useRealTimers();
|
|
}
|
|
|
|
runAllTimers(): void {
|
|
if (this._checkFakeTimers()) {
|
|
this._clock.runAll();
|
|
}
|
|
}
|
|
|
|
async runAllTimersAsync(): Promise<void> {
|
|
if (this._checkFakeTimers()) {
|
|
await this._clock.runAllAsync();
|
|
}
|
|
}
|
|
|
|
runOnlyPendingTimers(): void {
|
|
if (this._checkFakeTimers()) {
|
|
this._clock.runToLast();
|
|
}
|
|
}
|
|
|
|
async runOnlyPendingTimersAsync(): Promise<void> {
|
|
if (this._checkFakeTimers()) {
|
|
await this._clock.runToLastAsync();
|
|
}
|
|
}
|
|
|
|
advanceTimersToNextTimer(steps = 1): void {
|
|
if (this._checkFakeTimers()) {
|
|
for (let i = steps; i > 0; i--) {
|
|
this._clock.next();
|
|
// Fire all timers at this point: https://github.com/sinonjs/fake-timers/issues/250
|
|
this._clock.tick(0);
|
|
|
|
if (this._clock.countTimers() === 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
if (this._clock.countTimers() === 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
advanceTimersByTime(msToRun: number | TemporalDuration): void {
|
|
if (this._checkFakeTimers()) {
|
|
// TODO: pass msToRun directly once https://github.com/sinonjs/fake-timers/pull/574 is published
|
|
this._clock.tick(
|
|
typeof msToRun === 'number'
|
|
? msToRun
|
|
: msToRun.total({unit: 'millisecond'}),
|
|
);
|
|
}
|
|
}
|
|
|
|
async advanceTimersByTimeAsync(
|
|
msToRun: number | TemporalDuration,
|
|
): Promise<void> {
|
|
if (this._checkFakeTimers()) {
|
|
// TODO: pass msToRun directly once https://github.com/sinonjs/fake-timers/pull/574 is published
|
|
await this._clock.tickAsync(
|
|
typeof msToRun === 'number'
|
|
? msToRun
|
|
: msToRun.total({unit: 'millisecond'}),
|
|
);
|
|
}
|
|
}
|
|
|
|
advanceTimersToNextFrame(): void {
|
|
if (this._checkFakeTimers()) {
|
|
this._clock.runToFrame();
|
|
}
|
|
}
|
|
|
|
runAllTicks(): void {
|
|
if (this._checkFakeTimers()) {
|
|
this._clock.runMicrotasks();
|
|
}
|
|
}
|
|
|
|
useRealTimers(): void {
|
|
if (this._fakingTime) {
|
|
this._clock.uninstall();
|
|
this._fakingTime = false;
|
|
}
|
|
}
|
|
|
|
useFakeTimers(fakeTimersConfig?: Config.FakeTimersConfig): void {
|
|
if (this._fakingTime) {
|
|
this._clock.uninstall();
|
|
}
|
|
|
|
this._clock = this._fakeTimers.install(
|
|
this._toSinonFakeTimersConfig(fakeTimersConfig),
|
|
);
|
|
|
|
this._fakingTime = true;
|
|
}
|
|
|
|
reset(): void {
|
|
if (this._checkFakeTimers()) {
|
|
const {now} = this._clock;
|
|
this._clock.reset();
|
|
this._clock.setSystemTime(now);
|
|
}
|
|
}
|
|
|
|
setSystemTime(now?: number | Date | TemporalTimelike): void {
|
|
if (this._checkFakeTimers()) {
|
|
this._clock.setSystemTime(now);
|
|
}
|
|
}
|
|
|
|
setTimerTickMode(tickModeConfig: {
|
|
mode: 'interval' | 'manual' | 'nextAsync';
|
|
delta?: number;
|
|
}): void {
|
|
if (this._checkFakeTimers()) {
|
|
this._clock.setTickMode(tickModeConfig);
|
|
}
|
|
}
|
|
|
|
getRealSystemTime(): number {
|
|
return Date.now();
|
|
}
|
|
|
|
now(): number {
|
|
if (this._fakingTime) {
|
|
return this._clock.now;
|
|
}
|
|
return Date.now();
|
|
}
|
|
|
|
getTimerCount(): number {
|
|
if (this._checkFakeTimers()) {
|
|
return this._clock.countTimers();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
private _checkFakeTimers() {
|
|
if (!this._fakingTime) {
|
|
this._global.console.warn(
|
|
'A function to advance timers was called but the timers APIs are not replaced ' +
|
|
'with fake timers. Call `jest.useFakeTimers()` in this test file or enable ' +
|
|
"fake timers for all tests by setting 'fakeTimers': {'enableGlobally': true} " +
|
|
`in Jest configuration file.\nStack Trace:\n${formatStackTrace(
|
|
// eslint-disable-next-line unicorn/error-message
|
|
new Error().stack!,
|
|
this._config,
|
|
{noStackTrace: false},
|
|
)}`,
|
|
);
|
|
}
|
|
|
|
return this._fakingTime;
|
|
}
|
|
|
|
private _toSinonFakeTimersConfig(
|
|
fakeTimersConfig: Config.FakeTimersConfig = {},
|
|
): SinonFakeTimersConfig {
|
|
fakeTimersConfig = {
|
|
...this._config.fakeTimers,
|
|
...fakeTimersConfig,
|
|
} as Config.FakeTimersConfig;
|
|
|
|
const advanceTimeDelta =
|
|
typeof fakeTimersConfig.advanceTimers === 'number'
|
|
? fakeTimersConfig.advanceTimers
|
|
: undefined;
|
|
|
|
const toFake = new Set(
|
|
Object.keys(this._fakeTimers.timers) as Array<FakeableAPI>,
|
|
);
|
|
|
|
if (fakeTimersConfig.doNotFake)
|
|
for (const nameOfFakeableAPI of fakeTimersConfig.doNotFake) {
|
|
toFake.delete(nameOfFakeableAPI);
|
|
}
|
|
|
|
return {
|
|
advanceTimeDelta,
|
|
loopLimit: fakeTimersConfig.timerLimit || 100_000,
|
|
now: fakeTimersConfig.now ?? Date.now(),
|
|
shouldAdvanceTime: Boolean(fakeTimersConfig.advanceTimers),
|
|
shouldClearNativeTimers: true,
|
|
toFake: [...toFake],
|
|
};
|
|
}
|
|
}
|