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
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
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
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": ["./**/*"]
}
84 changes: 84 additions & 0 deletions packages/jest-mock/__typetests__/utility-types.test.ts
Original file line number Diff line number Diff line change
@@ -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
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
135 changes: 69 additions & 66 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
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 @@ -163,15 +165,26 @@ type MockFunctionResult = {
};

type MockFunctionState<T, Y extends Array<unknown>> = {
/**
* List of the call arguments of all calls that have been made to the mock.
*/
calls: Array<Y>;
/**
* List of all the object instances that have been instantiated from the mock.
*/
instances: Array<T>;
/**
* List of the call order indexes of the mock. Jest is indexing the order of
* invocations of all mocks in a test file. The index is starting with `1`.
*/
invocationCallOrder: Array<number>;
/**
* Getter for retrieving the last call arguments
* List of the call arguments of the last call that was made to the mock.
* If the function was not called, it will return `undefined`.
*/
lastCall?: Y;
/**
* List of results of calls to the mock function.
* List of the results of all calls that have been made to the mock.
*/
results: Array<MockFunctionResult>;
};
Expand All @@ -183,16 +196,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 +733,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 +743,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 +1006,27 @@ export class ModuleMocker {
return fn;
}

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

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

spyOn<T extends {}, M extends FunctionPropertyNames<T>>(
spyOn<T extends {}, 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 extends {}, M extends PropertyKeys<T>>(
object: T,
methodName: M,
accessType?: 'get' | 'set',
Expand Down Expand Up @@ -1094,7 +1097,7 @@ export class ModuleMocker {
return object[methodName];
}

private _spyOnProperty<T extends {}, M extends NonFunctionPropertyNames<T>>(
private _spyOnProperty<T extends {}, M extends PropertyKeys<T>>(
obj: T,
propertyName: M,
accessType: 'get' | 'set' = 'get',
Expand Down
Loading