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

refactor(@jest/fake-timers): improve internal typings of legacy fake timers #12567

Merged
merged 2 commits into from Mar 24, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion .eslintrc.js
Expand Up @@ -95,7 +95,6 @@ module.exports = {
'packages/jest-core/src/TestScheduler.ts',
'packages/jest-core/src/collectHandles.ts',
'packages/jest-core/src/plugins/UpdateSnapshotsInteractive.ts',
'packages/jest-fake-timers/src/legacyFakeTimers.ts',
'packages/jest-haste-map/src/index.ts',
'packages/jest-haste-map/src/watchers/FSEventsWatcher.ts',
'packages/jest-jasmine2/src/jasmine/SpyStrategy.ts',
Expand Down
62 changes: 33 additions & 29 deletions packages/jest-fake-timers/src/legacyFakeTimers.ts
Expand Up @@ -5,11 +5,16 @@
* LICENSE file in the root directory of this source tree.
*/

/* eslint-disable local/ban-types-eventually, local/prefer-spread-eventually */
/* eslint-disable local/prefer-spread-eventually */

import util = require('util');
import {StackTraceConfig, formatStackTrace} from 'jest-message-util';
import type {ModuleMocker} from 'jest-mock';
import type {
FunctionLike,
Mock,
ModuleMocker,
UnknownFunction,
} from 'jest-mock';
import {setGlobal} from 'jest-util';

type Callback = (...args: Array<unknown>) => void;
Expand All @@ -29,38 +34,42 @@ type Timer = {
};

type TimerAPI = {
cancelAnimationFrame: FakeTimersGlobal['cancelAnimationFrame'];
cancelAnimationFrame: typeof globalThis.cancelAnimationFrame;
clearImmediate: typeof globalThis.clearImmediate;
clearInterval: typeof globalThis.clearInterval;
clearTimeout: typeof globalThis.clearTimeout;
nextTick: typeof process.nextTick;

requestAnimationFrame: FakeTimersGlobal['requestAnimationFrame'];
requestAnimationFrame: typeof globalThis.requestAnimationFrame;
setImmediate: typeof globalThis.setImmediate;
setInterval: typeof globalThis.setInterval;
setTimeout: typeof globalThis.setTimeout;
};

type FakeTimerAPI = {
cancelAnimationFrame: Mock<FakeTimers['_fakeClearTimer']>;
clearImmediate: Mock<FakeTimers['_fakeClearImmediate']>;
clearInterval: Mock<FakeTimers['_fakeClearTimer']>;
clearTimeout: Mock<FakeTimers['_fakeClearTimer']>;
nextTick: Mock<FakeTimers['_fakeNextTick']>;
requestAnimationFrame: Mock<FakeTimers['_fakeRequestAnimationFrame']>;
setImmediate: Mock<FakeTimers['_fakeSetImmediate']>;
setInterval: Mock<FakeTimers['_fakeSetInterval']>;
setTimeout: Mock<FakeTimers['_fakeSetTimeout']>;
};

type TimerConfig<Ref> = {
idToRef: (id: number) => Ref;
refToId: (ref: Ref) => number | void;
};

const MS_IN_A_YEAR = 31536000000;

type GlobalThis = typeof globalThis;

interface FakeTimersGlobal extends GlobalThis {
cancelAnimationFrame: (handle: number) => void;
requestAnimationFrame: (callback: (time: number) => void) => number;
}
Comment on lines -53 to -56
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be these types were copied from TS, because they do not exist on global in Node. globalThis defines these props, so now this is safe to remove, right?


export default class FakeTimers<TimerRef> {
export default class FakeTimers<TimerRef = unknown> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a default to generic helps to keep FakeTimerAPI simple and slim.

private _cancelledTicks!: Record<string, boolean>;
private _config: StackTraceConfig;
private _disposed?: boolean;
private _fakeTimerAPIs!: TimerAPI;
private _global: FakeTimersGlobal;
private _fakeTimerAPIs!: FakeTimerAPI;
private _global: typeof globalThis;
private _immediates!: Array<Tick>;
private _maxLoops: number;
private _moduleMocker: ModuleMocker;
Expand All @@ -78,7 +87,7 @@ export default class FakeTimers<TimerRef> {
config,
maxLoops,
}: {
global: FakeTimersGlobal;
global: typeof globalThis;
moduleMocker: ModuleMocker;
timerConfig: TimerConfig<TimerRef>;
config: StackTraceConfig;
Expand Down Expand Up @@ -390,6 +399,7 @@ export default class FakeTimers<TimerRef> {
}

private _checkFakeTimers() {
// @ts-expect-error: condition always returns 'true'
if (this._global.setTimeout !== this._fakeTimerAPIs?.setTimeout) {
this._global.console.warn(
'A function to advance timers was called but the timers API is not ' +
Expand All @@ -407,32 +417,26 @@ export default class FakeTimers<TimerRef> {
}

private _createMocks() {
const fn = (impl: Function) =>
// @ts-expect-error TODO: figure out better typings here
this._moduleMocker.fn().mockImplementation(impl);
const fn = <T extends FunctionLike = UnknownFunction>(implementation?: T) =>
this._moduleMocker.fn(implementation);

const promisifiableFakeSetTimeout = fn(this._fakeSetTimeout.bind(this));
// @ts-expect-error TODO: figure out better typings here
// @ts-expect-error: no index
promisifiableFakeSetTimeout[util.promisify.custom] = (
delay?: number,
arg?: unknown,
) =>
new Promise(resolve => promisifiableFakeSetTimeout(resolve, delay, arg));

// TODO: add better typings; these are mocks, but typed as regular timers
this._fakeTimerAPIs = {
cancelAnimationFrame: fn(this._fakeClearTimer.bind(this)),
clearImmediate: fn(this._fakeClearImmediate.bind(this)),
clearInterval: fn(this._fakeClearTimer.bind(this)),
clearTimeout: fn(this._fakeClearTimer.bind(this)),
nextTick: fn(this._fakeNextTick.bind(this)),
// @ts-expect-error TODO: figure out better typings here
requestAnimationFrame: fn(this._fakeRequestAnimationFrame.bind(this)),
// @ts-expect-error TODO: figure out better typings here
setImmediate: fn(this._fakeSetImmediate.bind(this)),
// @ts-expect-error TODO: figure out better typings here
setInterval: fn(this._fakeSetInterval.bind(this)),
// @ts-expect-error TODO: figure out better typings here
setTimeout: promisifiableFakeSetTimeout,
};
}
Expand All @@ -451,7 +455,7 @@ export default class FakeTimers<TimerRef> {
);
}

private _fakeNextTick(callback: Callback, ...args: Array<any>) {
private _fakeNextTick(callback: Callback, ...args: Array<unknown>) {
if (this._disposed) {
return;
}
Expand Down Expand Up @@ -480,7 +484,7 @@ export default class FakeTimers<TimerRef> {
}, 1000 / 60);
}

private _fakeSetImmediate(callback: Callback, ...args: Array<any>) {
private _fakeSetImmediate(callback: Callback, ...args: Array<unknown>) {
if (this._disposed) {
return null;
}
Expand Down Expand Up @@ -508,7 +512,7 @@ export default class FakeTimers<TimerRef> {
private _fakeSetInterval(
callback: Callback,
intervalDelay?: number,
...args: Array<any>
...args: Array<unknown>
) {
if (this._disposed) {
return null;
Expand All @@ -533,7 +537,7 @@ export default class FakeTimers<TimerRef> {
private _fakeSetTimeout(
callback: Callback,
delay?: number,
...args: Array<any>
...args: Array<unknown>
) {
if (this._disposed) {
return null;
Expand Down
2 changes: 1 addition & 1 deletion packages/jest-mock/src/index.ts
Expand Up @@ -112,7 +112,7 @@ export type MockedClass<T extends ClassLike> = MockInstance<
prototype: T extends {prototype: any} ? Mocked<T['prototype']> : never;
} & T;

type UnknownFunction = (...args: Array<unknown>) => unknown;
export type UnknownFunction = (...args: Array<unknown>) => unknown;

/**
* All what the internal typings need is to be sure that we have any-function.
Expand Down