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-mock)!: rename and clean up utility types #12435

Merged
merged 12 commits into from
Feb 23, 2022
11 changes: 11 additions & 0 deletions packages/jest-mock/__typetests__/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"noUnusedLocals": false,
"noUnusedParameters": false,
"skipLibCheck": true,

"types": []
},
"include": ["./**/*"]
}
51 changes: 51 additions & 0 deletions packages/jest-mock/__typetests__/utility-types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import {expectType} from 'tsd-lite';
import type {MethodKeys, PropertyKeys} from 'jest-mock';

interface SomeObject {
methodA(): void;
methodB(a: string): number;
propertyA: number;
propertyB: string;
}

declare const objectMethods: MethodKeys<SomeObject>;
declare const objectProperties: PropertyKeys<SomeObject>;

expectType<'methodA' | 'methodB'>(objectMethods);
expectType<'propertyA' | 'propertyB'>(objectProperties);

class SomeClass {
propertyB = 123;
private _propertyC: undefined;
#propertyD = 'abc';

constructor(public propertyA: string) {}

methodA(): void {
return;
}

methodB(b: string): string {
return b;
}

get propertyC() {
return this._propertyC;
}
set propertyC(value) {
this._propertyC = value;
}
}

declare const classMethods: MethodKeys<SomeClass>;
declare const classProperties: PropertyKeys<SomeClass>;

expectType<'methodA' | 'methodB'>(classMethods);
expectType<'propertyA' | 'propertyB' | 'propertyC'>(classProperties);
12 changes: 8 additions & 4 deletions packages/jest-mock/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@
"engines": {
"node": "^12.13.0 || ^14.15.0 || ^16.13.0 || >=17.0.0"
},
"dependencies": {
"@jest/types": "^28.0.0-alpha.3",
"@types/node": "*"
},
"license": "MIT",
"main": "./build/index.js",
"types": "./build/index.d.ts",
Expand All @@ -23,6 +19,14 @@
},
"./package.json": "./package.json"
},
"dependencies": {
"@jest/types": "^28.0.0-alpha.3",
"@types/node": "*"
},
"devDependencies": {
"@tsd/typescript": "~4.5.5",
"tsd-lite": "^0.5.1"
},
"publishConfig": {
"access": "public"
}
Expand Down
120 changes: 56 additions & 64 deletions packages/jest-mock/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,87 +20,91 @@ export type MockFunctionMetadataType =
export type MockFunctionMetadata<
T,
Y extends Array<unknown>,
Type = MockFunctionMetadataType,
MetadataType = MockFunctionMetadataType,
> = {
ref?: number;
members?: Record<string, MockFunctionMetadata<T, Y>>;
mockImpl?: (...args: Y) => T;
name?: string;
refID?: number;
type?: Type;
type?: MetadataType;
value?: T;
length?: number;
};

export type MockableFunction = (...args: Array<any>) => any;
export type MethodKeysOf<T> = {
[K in keyof T]: T[K] extends MockableFunction ? K : never;
}[keyof T];
export type PropertyKeysOf<T> = {
[K in keyof T]: T[K] extends MockableFunction ? never : K;
export type ClassLike = {new (...args: Array<any>): any};

export type FunctionLike = (...args: Array<any>) => any;
mrazauskas marked this conversation as resolved.
Show resolved Hide resolved

export type MethodKeys<T> = {
[K in keyof T]: T[K] extends FunctionLike ? K : never;
}[keyof T];

export type ArgumentsOf<T> = T extends (...args: infer A) => any ? A : never;
export type PropertyKeys<T> = {
[K in keyof T]: T[K] extends FunctionLike ? never : K;
}[keyof T];

export type ConstructorArgumentsOf<T> = T extends new (...args: infer A) => any
? A
// TODO Replace this with TS ConstructorParameters utility type
// https://www.typescriptlang.org/docs/handbook/utility-types.html#constructorparameterstype
export type ConstructorParameters<T> = T extends new (...args: infer P) => any
? P
: never;

export type MaybeMockedConstructor<T> = T extends new (
...args: Array<any>
) => infer R
? MockInstance<R, ConstructorArgumentsOf<T>>
? MockInstance<R, ConstructorParameters<T>>
: T;
export type MockedFunction<T extends MockableFunction> = MockWithArgs<T> & {

export interface MockWithArgs<T extends FunctionLike>
extends MockInstance<ReturnType<T>, Parameters<T>> {
new (...args: ConstructorParameters<T>): T;
(...args: Parameters<T>): ReturnType<T>;
}

export type MockedFunction<T extends FunctionLike> = MockWithArgs<T> & {
[K in keyof T]: T[K];
};
export type MockedFunctionDeep<T extends MockableFunction> = MockWithArgs<T> &

export type MockedFunctionDeep<T extends FunctionLike> = MockWithArgs<T> &
MockedObjectDeep<T>;

export type MockedObject<T> = MaybeMockedConstructor<T> & {
[K in MethodKeysOf<T>]: T[K] extends MockableFunction
? MockedFunction<T[K]>
: T[K];
} & {[K in PropertyKeysOf<T>]: T[K]};
[K in MethodKeys<T>]: T[K] extends FunctionLike ? MockedFunction<T[K]> : T[K];
} & {[K in PropertyKeys<T>]: T[K]};

export type MockedObjectDeep<T> = MaybeMockedConstructor<T> & {
[K in MethodKeysOf<T>]: T[K] extends MockableFunction
[K in MethodKeys<T>]: T[K] extends FunctionLike
? MockedFunctionDeep<T[K]>
: T[K];
} & {[K in PropertyKeysOf<T>]: MaybeMockedDeep<T[K]>};
} & {[K in PropertyKeys<T>]: MaybeMockedDeep<T[K]>};

export type MaybeMockedDeep<T> = T extends MockableFunction
? MockedFunctionDeep<T>
export type MaybeMocked<T> = T extends FunctionLike
? MockedFunction<T>
: T extends object
? MockedObjectDeep<T>
? MockedObject<T>
: T;

export type MaybeMocked<T> = T extends MockableFunction
? MockedFunction<T>
export type MaybeMockedDeep<T> = T extends FunctionLike
? MockedFunctionDeep<T>
: T extends object
? MockedObject<T>
? MockedObjectDeep<T>
: T;

export type ArgsType<T> = T extends (...args: infer A) => any ? A : never;
export type Mocked<T> = {
[P in keyof T]: T[P] extends (...args: Array<any>) => any
? MockInstance<ReturnType<T[P]>, ArgsType<T[P]>>
: T[P] extends Constructable
[P in keyof T]: T[P] extends FunctionLike
? MockInstance<ReturnType<T[P]>, Parameters<T[P]>>
: T[P] extends ClassLike
? MockedClass<T[P]>
: T[P];
} & T;
export type MockedClass<T extends Constructable> = MockInstance<

export type MockedClass<T extends ClassLike> = MockInstance<
InstanceType<T>,
T extends new (...args: infer P) => any ? P : never
> & {
prototype: T extends {prototype: any} ? Mocked<T['prototype']> : never;
} & T;
export interface Constructable {
new (...args: Array<any>): any;
}

export interface MockWithArgs<T extends MockableFunction>
extends MockInstance<ReturnType<T>, ArgumentsOf<T>> {
new (...args: ConstructorArgumentsOf<T>): T;
(...args: ArgumentsOf<T>): ReturnType<T>;
}

export interface Mock<T, Y extends Array<unknown> = Array<unknown>>
extends Function,
Expand All @@ -109,9 +113,6 @@ export interface Mock<T, Y extends Array<unknown> = Array<unknown>>
(...args: Y): T;
}

export interface SpyInstance<T, Y extends Array<unknown>>
extends MockInstance<T, Y> {}

export interface MockInstance<T, Y extends Array<unknown>> {
_isMockFunction: true;
_protoImpl: Function;
Expand All @@ -129,13 +130,14 @@ export interface MockInstance<T, Y extends Array<unknown>> {
mockReturnThis(): this;
mockReturnValue(value: T): this;
mockReturnValueOnce(value: T): this;
mockResolvedValue(value: Unpromisify<T>): this;
mockResolvedValueOnce(value: Unpromisify<T>): this;
mockResolvedValue(value: Awaited<T>): this;
mockResolvedValueOnce(value: Awaited<T>): this;
SimenB marked this conversation as resolved.
Show resolved Hide resolved
mockRejectedValue(value: unknown): this;
mockRejectedValueOnce(value: unknown): this;
}

type Unpromisify<T> = T extends Promise<infer R> ? R : never;
export interface SpyInstance<T, Y extends Array<unknown>>
extends MockInstance<T, Y> {}

/**
* Possible types of a MockFunctionResult.
Expand Down Expand Up @@ -183,16 +185,6 @@ type MockFunctionConfig = {
specificMockImpls: Array<Function>;
};

// see https://github.com/Microsoft/TypeScript/issues/25215
mrazauskas marked this conversation as resolved.
Show resolved Hide resolved
type NonFunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends (...args: Array<any>) => any ? never : K;
}[keyof T] &
string;
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends (...args: Array<any>) => any ? K : never;
}[keyof T] &
string;

const MOCK_CONSTRUCTOR_NAME = 'mockConstructor';

const FUNCTION_NAME_RESERVED_PATTERN = /[\s!-\/:-@\[-`{-~]/;
Expand Down Expand Up @@ -730,7 +722,7 @@ export class ModuleMocker {
// next function call will return this value or default return value
f.mockImplementationOnce(() => value);

f.mockResolvedValueOnce = (value: Unpromisify<T>) =>
f.mockResolvedValueOnce = (value: Awaited<T>) =>
f.mockImplementationOnce(() => Promise.resolve(value as T));

f.mockRejectedValueOnce = (value: unknown) =>
Expand All @@ -740,7 +732,7 @@ export class ModuleMocker {
// next function call will return specified return value or this one
f.mockImplementation(() => value);

f.mockResolvedValue = (value: Unpromisify<T>) =>
f.mockResolvedValue = (value: Awaited<T>) =>
f.mockImplementation(() => Promise.resolve(value as T));

f.mockRejectedValue = (value: unknown) =>
Expand Down Expand Up @@ -1003,27 +995,27 @@ export class ModuleMocker {
return fn;
}

spyOn<T extends {}, M extends NonFunctionPropertyNames<T>>(
spyOn<T, M extends PropertyKeys<T>>(
object: T,
methodName: M,
accessType: 'get',
): SpyInstance<T[M], []>;

spyOn<T extends {}, M extends NonFunctionPropertyNames<T>>(
spyOn<T, M extends PropertyKeys<T>>(
object: T,
methodName: M,
accessType: 'set',
): SpyInstance<void, [T[M]]>;

spyOn<T extends {}, M extends FunctionPropertyNames<T>>(
spyOn<T, M extends MethodKeys<T>>(
object: T,
methodName: M,
): T[M] extends (...args: Array<any>) => any
): T[M] extends FunctionLike
? SpyInstance<ReturnType<T[M]>, Parameters<T[M]>>
: never;

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
spyOn<T extends {}, M extends NonFunctionPropertyNames<T>>(
spyOn<T, M extends PropertyKeys<T>>(
object: T,
methodName: M,
accessType?: 'get' | 'set',
Expand Down Expand Up @@ -1094,7 +1086,7 @@ export class ModuleMocker {
return object[methodName];
}

private _spyOnProperty<T extends {}, M extends NonFunctionPropertyNames<T>>(
private _spyOnProperty<T, M extends PropertyKeys<T>>(
obj: T,
propertyName: M,
accessType: 'get' | 'set' = 'get',
Expand Down
2 changes: 2 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -13210,7 +13210,9 @@ __metadata:
resolution: "jest-mock@workspace:packages/jest-mock"
dependencies:
"@jest/types": ^28.0.0-alpha.3
"@tsd/typescript": ~4.5.5
"@types/node": "*"
tsd-lite: ^0.5.1
languageName: unknown
linkType: soft

Expand Down