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
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -12,6 +12,7 @@
- `[jest-environment-node]` [**BREAKING**] Add default `node` and `node-addon` conditions to `exportConditions` for `node` environment ([#11924](https://github.com/facebook/jest/pull/11924))
- `[@jest/expect]` New module which extends `expect` with `jest-snapshot` matchers ([#12404](https://github.com/facebook/jest/pull/12404), [#12410](https://github.com/facebook/jest/pull/12410), [#12418](https://github.com/facebook/jest/pull/12418))
- `[@jest/expect-utils]` New module exporting utils for `expect` ([#12323](https://github.com/facebook/jest/pull/12323))
- `[jest-mock]` [**BREAKING**] Rename exported utility types `ClassLike`, `FunctionLike`, `MethodKeys`, `PropertyKeys`; remove exports of utility types `ArgumentsOf`, `ArgsType`, `ConstructorArgumentsOf` - TS buildin utility types `ConstructorParameters` and `Parameters` should be used instead ([#12435](https://github.com/facebook/jest/pull/12435))
- `[jest-resolve]` [**BREAKING**] Add support for `package.json` `exports` ([#11961](https://github.com/facebook/jest/pull/11961), [#12373](https://github.com/facebook/jest/pull/12373))
- `[jest-resolve, jest-runtime]` Add support for `data:` URI import and mock ([#12392](https://github.com/facebook/jest/pull/12392))
- `[@jest/schemas]` New module for JSON schemas for Jest's config ([#12384](https://github.com/facebook/jest/pull/12384))
Expand Down
11 changes: 11 additions & 0 deletions packages/jest-mock/__typetests__/tsconfig.json
@@ -0,0 +1,11 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"noUnusedLocals": false,
"noUnusedParameters": false,
"skipLibCheck": true,

"types": []
},
"include": ["./**/*"]
}
84 changes: 84 additions & 0 deletions packages/jest-mock/__typetests__/utility-types.test.ts
@@ -0,0 +1,84 @@
/**
* 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 {expectAssignable, expectNotAssignable, expectType} from 'tsd-lite';
import type {
ClassLike,
FunctionLike,
MethodKeys,
PropertyKeys,
} from 'jest-mock';

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;
}
}

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

// ClassLike

expectAssignable<ClassLike>(SomeClass);
expectNotAssignable<ClassLike>(() => {});
expectNotAssignable<ClassLike>(function abc() {
return;
});
expectNotAssignable<ClassLike>({} as SomeObject);
expectNotAssignable<ClassLike>('abc');
expectNotAssignable<ClassLike>(123);
expectNotAssignable<ClassLike>(false);

// FunctionLike

expectAssignable<FunctionLike>(() => {});
expectAssignable<FunctionLike>(function abc() {
return;
});
expectNotAssignable<FunctionLike>({} as SomeObject);
expectNotAssignable<FunctionLike>(SomeClass);
expectNotAssignable<FunctionLike>('abc');
expectNotAssignable<FunctionLike>(123);
expectNotAssignable<FunctionLike>(false);

// MethodKeys

declare const classMethods: MethodKeys<SomeClass>;
declare const objectMethods: MethodKeys<SomeObject>;

expectType<'methodA' | 'methodB'>(classMethods);
expectType<'methodA' | 'methodB'>(objectMethods);

// PropertyKeys

declare const classProperties: PropertyKeys<SomeClass>;
declare const objectProperties: PropertyKeys<SomeObject>;

expectType<'propertyA' | 'propertyB' | 'propertyC'>(classProperties);
expectType<'propertyA' | 'propertyB'>(objectProperties);
12 changes: 8 additions & 4 deletions packages/jest-mock/package.json
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
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
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
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