From e19cfab6f77fd4e6b72c18c0711d7ef9b2e0b9bf Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Sat, 19 Feb 2022 10:29:24 +0200 Subject: [PATCH 01/10] refactor!: rename and clean up utility types --- .../jest-mock/__typetests__/tsconfig.json | 11 ++ .../__typetests__/utility-types.test.ts | 51 ++++++++ packages/jest-mock/package.json | 12 +- packages/jest-mock/src/index.ts | 120 ++++++++---------- yarn.lock | 2 + 5 files changed, 128 insertions(+), 68 deletions(-) create mode 100644 packages/jest-mock/__typetests__/tsconfig.json create mode 100644 packages/jest-mock/__typetests__/utility-types.test.ts diff --git a/packages/jest-mock/__typetests__/tsconfig.json b/packages/jest-mock/__typetests__/tsconfig.json new file mode 100644 index 000000000000..fe8eab794254 --- /dev/null +++ b/packages/jest-mock/__typetests__/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "noUnusedLocals": false, + "noUnusedParameters": false, + "skipLibCheck": true, + + "types": [] + }, + "include": ["./**/*"] +} diff --git a/packages/jest-mock/__typetests__/utility-types.test.ts b/packages/jest-mock/__typetests__/utility-types.test.ts new file mode 100644 index 000000000000..b4bc1bff8367 --- /dev/null +++ b/packages/jest-mock/__typetests__/utility-types.test.ts @@ -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; +declare const objectProperties: PropertyKeys; + +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; +declare const classProperties: PropertyKeys; + +expectType<'methodA' | 'methodB'>(classMethods); +expectType<'propertyA' | 'propertyB' | 'propertyC'>(classProperties); diff --git a/packages/jest-mock/package.json b/packages/jest-mock/package.json index 9aa49d2d8195..f75462c22efe 100644 --- a/packages/jest-mock/package.json +++ b/packages/jest-mock/package.json @@ -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", @@ -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" } diff --git a/packages/jest-mock/src/index.ts b/packages/jest-mock/src/index.ts index bd3e3e11c809..dc62891349b4 100644 --- a/packages/jest-mock/src/index.ts +++ b/packages/jest-mock/src/index.ts @@ -20,87 +20,91 @@ export type MockFunctionMetadataType = export type MockFunctionMetadata< T, Y extends Array, - Type = MockFunctionMetadataType, + MetadataType = MockFunctionMetadataType, > = { ref?: number; members?: Record>; mockImpl?: (...args: Y) => T; name?: string; refID?: number; - type?: Type; + type?: MetadataType; value?: T; length?: number; }; -export type MockableFunction = (...args: Array) => any; -export type MethodKeysOf = { - [K in keyof T]: T[K] extends MockableFunction ? K : never; -}[keyof T]; -export type PropertyKeysOf = { - [K in keyof T]: T[K] extends MockableFunction ? never : K; +export type ClassLike = {new (...args: Array): any}; + +export type FunctionLike = (...args: Array) => any; + +export type MethodKeys = { + [K in keyof T]: T[K] extends FunctionLike ? K : never; }[keyof T]; -export type ArgumentsOf = T extends (...args: infer A) => any ? A : never; +export type PropertyKeys = { + [K in keyof T]: T[K] extends FunctionLike ? never : K; +}[keyof T]; -export type ConstructorArgumentsOf = 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 extends new (...args: infer P) => any + ? P : never; + export type MaybeMockedConstructor = T extends new ( ...args: Array ) => infer R - ? MockInstance> + ? MockInstance> : T; -export type MockedFunction = MockWithArgs & { + +export interface MockWithArgs + extends MockInstance, Parameters> { + new (...args: ConstructorParameters): T; + (...args: Parameters): ReturnType; +} + +export type MockedFunction = MockWithArgs & { [K in keyof T]: T[K]; }; -export type MockedFunctionDeep = MockWithArgs & + +export type MockedFunctionDeep = MockWithArgs & MockedObjectDeep; + export type MockedObject = MaybeMockedConstructor & { - [K in MethodKeysOf]: T[K] extends MockableFunction - ? MockedFunction - : T[K]; -} & {[K in PropertyKeysOf]: T[K]}; + [K in MethodKeys]: T[K] extends FunctionLike ? MockedFunction : T[K]; +} & {[K in PropertyKeys]: T[K]}; + export type MockedObjectDeep = MaybeMockedConstructor & { - [K in MethodKeysOf]: T[K] extends MockableFunction + [K in MethodKeys]: T[K] extends FunctionLike ? MockedFunctionDeep : T[K]; -} & {[K in PropertyKeysOf]: MaybeMockedDeep}; +} & {[K in PropertyKeys]: MaybeMockedDeep}; -export type MaybeMockedDeep = T extends MockableFunction - ? MockedFunctionDeep +export type MaybeMocked = T extends FunctionLike + ? MockedFunction : T extends object - ? MockedObjectDeep + ? MockedObject : T; -export type MaybeMocked = T extends MockableFunction - ? MockedFunction +export type MaybeMockedDeep = T extends FunctionLike + ? MockedFunctionDeep : T extends object - ? MockedObject + ? MockedObjectDeep : T; -export type ArgsType = T extends (...args: infer A) => any ? A : never; export type Mocked = { - [P in keyof T]: T[P] extends (...args: Array) => any - ? MockInstance, ArgsType> - : T[P] extends Constructable + [P in keyof T]: T[P] extends FunctionLike + ? MockInstance, Parameters> + : T[P] extends ClassLike ? MockedClass : T[P]; } & T; -export type MockedClass = MockInstance< + +export type MockedClass = MockInstance< InstanceType, T extends new (...args: infer P) => any ? P : never > & { prototype: T extends {prototype: any} ? Mocked : never; } & T; -export interface Constructable { - new (...args: Array): any; -} - -export interface MockWithArgs - extends MockInstance, ArgumentsOf> { - new (...args: ConstructorArgumentsOf): T; - (...args: ArgumentsOf): ReturnType; -} export interface Mock = Array> extends Function, @@ -109,9 +113,6 @@ export interface Mock = Array> (...args: Y): T; } -export interface SpyInstance> - extends MockInstance {} - export interface MockInstance> { _isMockFunction: true; _protoImpl: Function; @@ -129,13 +130,14 @@ export interface MockInstance> { mockReturnThis(): this; mockReturnValue(value: T): this; mockReturnValueOnce(value: T): this; - mockResolvedValue(value: Unpromisify): this; - mockResolvedValueOnce(value: Unpromisify): this; + mockResolvedValue(value: Awaited): this; + mockResolvedValueOnce(value: Awaited): this; mockRejectedValue(value: unknown): this; mockRejectedValueOnce(value: unknown): this; } -type Unpromisify = T extends Promise ? R : never; +export interface SpyInstance> + extends MockInstance {} /** * Possible types of a MockFunctionResult. @@ -183,16 +185,6 @@ type MockFunctionConfig = { specificMockImpls: Array; }; -// see https://github.com/Microsoft/TypeScript/issues/25215 -type NonFunctionPropertyNames = { - [K in keyof T]: T[K] extends (...args: Array) => any ? never : K; -}[keyof T] & - string; -type FunctionPropertyNames = { - [K in keyof T]: T[K] extends (...args: Array) => any ? K : never; -}[keyof T] & - string; - const MOCK_CONSTRUCTOR_NAME = 'mockConstructor'; const FUNCTION_NAME_RESERVED_PATTERN = /[\s!-\/:-@\[-`{-~]/; @@ -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) => + f.mockResolvedValueOnce = (value: Awaited) => f.mockImplementationOnce(() => Promise.resolve(value as T)); f.mockRejectedValueOnce = (value: unknown) => @@ -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) => + f.mockResolvedValue = (value: Awaited) => f.mockImplementation(() => Promise.resolve(value as T)); f.mockRejectedValue = (value: unknown) => @@ -1003,27 +995,27 @@ export class ModuleMocker { return fn; } - spyOn>( + spyOn>( object: T, methodName: M, accessType: 'get', ): SpyInstance; - spyOn>( + spyOn>( object: T, methodName: M, accessType: 'set', ): SpyInstance; - spyOn>( + spyOn>( object: T, methodName: M, - ): T[M] extends (...args: Array) => any + ): T[M] extends FunctionLike ? SpyInstance, Parameters> : never; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - spyOn>( + spyOn>( object: T, methodName: M, accessType?: 'get' | 'set', @@ -1094,7 +1086,7 @@ export class ModuleMocker { return object[methodName]; } - private _spyOnProperty>( + private _spyOnProperty>( obj: T, propertyName: M, accessType: 'get' | 'set' = 'get', diff --git a/yarn.lock b/yarn.lock index 8415310770e0..f2e57d528002 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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 From c4a94dda0ea04a691a05ca19644f1ec13dde7896 Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Sat, 19 Feb 2022 11:25:19 +0200 Subject: [PATCH 02/10] last touch --- packages/jest-mock/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jest-mock/src/index.ts b/packages/jest-mock/src/index.ts index dc62891349b4..41219b7dd8da 100644 --- a/packages/jest-mock/src/index.ts +++ b/packages/jest-mock/src/index.ts @@ -46,7 +46,7 @@ export type PropertyKeys = { // TODO Replace this with TS ConstructorParameters utility type // https://www.typescriptlang.org/docs/handbook/utility-types.html#constructorparameterstype -export type ConstructorParameters = T extends new (...args: infer P) => any +type ConstructorParameters = T extends new (...args: infer P) => any ? P : never; From d7aaf62d90d065b6ca93b20ecd4e65a737e2c7bc Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Sat, 19 Feb 2022 11:25:28 +0200 Subject: [PATCH 03/10] add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01f3284eb17e..dfa0584485b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) From af1038d530602e8999662f1001e23a2bfc8a8c2c Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Sat, 19 Feb 2022 15:01:29 +0200 Subject: [PATCH 04/10] more type tests --- .../__typetests__/utility-types.test.ts | 65 ++++++++++++++----- 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/packages/jest-mock/__typetests__/utility-types.test.ts b/packages/jest-mock/__typetests__/utility-types.test.ts index b4bc1bff8367..982fa3b52a81 100644 --- a/packages/jest-mock/__typetests__/utility-types.test.ts +++ b/packages/jest-mock/__typetests__/utility-types.test.ts @@ -5,21 +5,13 @@ * 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; -declare const objectProperties: PropertyKeys; - -expectType<'methodA' | 'methodB'>(objectMethods); -expectType<'propertyA' | 'propertyB'>(objectProperties); +import {expectAssignable, expectNotAssignable, expectType} from 'tsd-lite'; +import type { + ClassLike, + FunctionLike, + MethodKeys, + PropertyKeys, +} from 'jest-mock'; class SomeClass { propertyB = 123; @@ -44,8 +36,49 @@ class SomeClass { } } +interface SomeObject { + methodA(): void; + methodB(a: string): number; + propertyA: number; + propertyB: string; +} + +// ClassLike + +expectAssignable(SomeClass); +expectNotAssignable(() => {}); +expectNotAssignable(function abc() { + return; +}); +expectNotAssignable({} as SomeObject); +expectNotAssignable('abc'); +expectNotAssignable(123); +expectNotAssignable(false); + +// FunctionLike + +expectAssignable(() => {}); +expectAssignable(function abc() { + return; +}); +expectNotAssignable({} as SomeObject); +expectNotAssignable(SomeClass); +expectNotAssignable('abc'); +expectNotAssignable(123); +expectNotAssignable(false); + +// MethodKeys + declare const classMethods: MethodKeys; -declare const classProperties: PropertyKeys; +declare const objectMethods: MethodKeys; expectType<'methodA' | 'methodB'>(classMethods); +expectType<'methodA' | 'methodB'>(objectMethods); + +// PropertyKeys + +declare const classProperties: PropertyKeys; +declare const objectProperties: PropertyKeys; + expectType<'propertyA' | 'propertyB' | 'propertyC'>(classProperties); +expectType<'propertyA' | 'propertyB'>(objectProperties); From 3f27f2b8246cdb71760f1fe3212b5373f11af59d Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Sat, 19 Feb 2022 15:46:43 +0200 Subject: [PATCH 05/10] improve TSDoc --- packages/jest-mock/src/index.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/jest-mock/src/index.ts b/packages/jest-mock/src/index.ts index 41219b7dd8da..b1bd60281868 100644 --- a/packages/jest-mock/src/index.ts +++ b/packages/jest-mock/src/index.ts @@ -165,15 +165,26 @@ type MockFunctionResult = { }; type MockFunctionState> = { + /** + * List of the call arguments of all calls that have been made to the mock. + */ calls: Array; + /** + * List of all the object instances that have been instantiated from the mock. + */ instances: Array; + /** + * 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; /** - * 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; }; From f004e7cd577aa1ad10dfe50f1b3f60ac3e79ae9d Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Sun, 20 Feb 2022 21:23:42 +0200 Subject: [PATCH 06/10] revert constrains --- packages/jest-mock/src/index.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/jest-mock/src/index.ts b/packages/jest-mock/src/index.ts index b1bd60281868..59ea6f5ba37a 100644 --- a/packages/jest-mock/src/index.ts +++ b/packages/jest-mock/src/index.ts @@ -1006,19 +1006,19 @@ export class ModuleMocker { return fn; } - spyOn>( + spyOn>( object: T, methodName: M, accessType: 'get', ): SpyInstance; - spyOn>( + spyOn>( object: T, methodName: M, accessType: 'set', ): SpyInstance; - spyOn>( + spyOn>( object: T, methodName: M, ): T[M] extends FunctionLike @@ -1026,7 +1026,7 @@ export class ModuleMocker { : never; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - spyOn>( + spyOn>( object: T, methodName: M, accessType?: 'get' | 'set', @@ -1097,7 +1097,7 @@ export class ModuleMocker { return object[methodName]; } - private _spyOnProperty>( + private _spyOnProperty>( obj: T, propertyName: M, accessType: 'get' | 'set' = 'get', From 061b27522769dd5ba54f096f62fe1629f1c4676b Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Wed, 23 Feb 2022 11:52:13 +0200 Subject: [PATCH 07/10] clean up utilities --- .../__typetests__/utility-types.test.ts | 79 ++++++++++++------- packages/jest-mock/src/index.ts | 62 ++++++++------- 2 files changed, 83 insertions(+), 58 deletions(-) diff --git a/packages/jest-mock/__typetests__/utility-types.test.ts b/packages/jest-mock/__typetests__/utility-types.test.ts index 982fa3b52a81..af4efdb60a49 100644 --- a/packages/jest-mock/__typetests__/utility-types.test.ts +++ b/packages/jest-mock/__typetests__/utility-types.test.ts @@ -7,10 +7,11 @@ import {expectAssignable, expectNotAssignable, expectType} from 'tsd-lite'; import type { - ClassLike, - FunctionLike, - MethodKeys, - PropertyKeys, + ConstructorLike, + ConstructorLikeKeys, + MethodLike, + MethodLikeKeys, + PropertyLikeKeys, } from 'jest-mock'; class SomeClass { @@ -36,49 +37,67 @@ class SomeClass { } } -interface SomeObject { - methodA(): void; - methodB(a: string): number; - propertyA: number; - propertyB: string; -} +const someObject = { + SomeClass, + + methodA() { + return; + }, + methodB(b: string) { + return true; + }, + methodC: (c: number) => true, + + propertyA: 123, + propertyB: 'value', + + someClassInstance: new SomeClass('value'), +}; + +type SomeObject = typeof someObject; // ClassLike -expectAssignable(SomeClass); -expectNotAssignable(() => {}); -expectNotAssignable(function abc() { +expectAssignable(SomeClass); +expectNotAssignable(() => {}); +expectNotAssignable(function abc() { return; }); -expectNotAssignable({} as SomeObject); -expectNotAssignable('abc'); -expectNotAssignable(123); -expectNotAssignable(false); +expectNotAssignable('abc'); +expectNotAssignable(123); +expectNotAssignable(false); +expectNotAssignable(someObject); // FunctionLike -expectAssignable(() => {}); -expectAssignable(function abc() { +expectAssignable(() => {}); +expectAssignable(function abc() { return; }); -expectNotAssignable({} as SomeObject); -expectNotAssignable(SomeClass); -expectNotAssignable('abc'); -expectNotAssignable(123); -expectNotAssignable(false); +expectNotAssignable('abc'); +expectNotAssignable(123); +expectNotAssignable(false); +expectNotAssignable(SomeClass); +expectNotAssignable(someObject); + +// ConstructorKeys + +declare const constructorKeys: ConstructorLikeKeys; + +expectType<'SomeClass'>(constructorKeys); // MethodKeys -declare const classMethods: MethodKeys; -declare const objectMethods: MethodKeys; +declare const classMethods: MethodLikeKeys; +declare const objectMethods: MethodLikeKeys; expectType<'methodA' | 'methodB'>(classMethods); -expectType<'methodA' | 'methodB'>(objectMethods); +expectType<'methodA' | 'methodB' | 'methodC'>(objectMethods); // PropertyKeys -declare const classProperties: PropertyKeys; -declare const objectProperties: PropertyKeys; +declare const classProperties: PropertyLikeKeys; +declare const objectProperties: PropertyLikeKeys; expectType<'propertyA' | 'propertyB' | 'propertyC'>(classProperties); -expectType<'propertyA' | 'propertyB'>(objectProperties); +expectType<'propertyA' | 'propertyB' | 'someClassInstance'>(objectProperties); diff --git a/packages/jest-mock/src/index.ts b/packages/jest-mock/src/index.ts index e235deaf4667..3e289b59bd8a 100644 --- a/packages/jest-mock/src/index.ts +++ b/packages/jest-mock/src/index.ts @@ -32,20 +32,24 @@ export type MockFunctionMetadata< length?: number; }; -export type ClassLike = {new (...args: Array): any}; +export type ConstructorLike = {new (...args: Array): any}; -export type FunctionLike = (...args: Array) => any; +export type MethodLike = (...args: Array) => any; -type ConstructorKeys = { - [K in keyof T]: T[K] extends ClassLike ? K : never; +export type ConstructorLikeKeys = { + [K in keyof T]: T[K] extends ConstructorLike ? K : never; }[keyof T]; -export type MethodKeys = { - [K in keyof T]: T[K] extends FunctionLike ? K : never; +export type MethodLikeKeys = { + [K in keyof T]: T[K] extends MethodLike ? K : never; }[keyof T]; -export type PropertyKeys = { - [K in keyof T]: T[K] extends FunctionLike ? never : K; +export type PropertyLikeKeys = { + [K in keyof T]: T[K] extends MethodLike + ? never + : T[K] extends ConstructorLike + ? never + : K; }[keyof T]; // TODO Replace this with TS ConstructorParameters utility type @@ -60,50 +64,52 @@ export type MaybeMockedConstructor = T extends new ( ? MockInstance> : T; -export interface MockWithArgs +export interface MockWithArgs extends MockInstance, Parameters> { new (...args: ConstructorParameters): T; (...args: Parameters): ReturnType; } -export type MockedFunction = MockWithArgs & { +export type MockedFunction = MockWithArgs & { [K in keyof T]: T[K]; }; -export type MockedFunctionDeep = MockWithArgs & +export type MockedFunctionDeep = MockWithArgs & MockedObjectDeep; export type MockedObject = MaybeMockedConstructor & { - [K in MethodKeys]: T[K] extends FunctionLike ? MockedFunction : T[K]; -} & {[K in PropertyKeys]: T[K]}; + [K in MethodLikeKeys]: T[K] extends MethodLike + ? MockedFunction + : T[K]; +} & {[K in PropertyLikeKeys]: T[K]}; export type MockedObjectDeep = MaybeMockedConstructor & { - [K in MethodKeys]: T[K] extends FunctionLike + [K in MethodLikeKeys]: T[K] extends MethodLike ? MockedFunctionDeep : T[K]; -} & {[K in PropertyKeys]: MaybeMockedDeep}; +} & {[K in PropertyLikeKeys]: MaybeMockedDeep}; -export type MaybeMocked = T extends FunctionLike +export type MaybeMocked = T extends MethodLike ? MockedFunction : T extends object ? MockedObject : T; -export type MaybeMockedDeep = T extends FunctionLike +export type MaybeMockedDeep = T extends MethodLike ? MockedFunctionDeep : T extends object ? MockedObjectDeep : T; export type Mocked = { - [P in keyof T]: T[P] extends FunctionLike + [P in keyof T]: T[P] extends MethodLike ? MockInstance, Parameters> - : T[P] extends ClassLike + : T[P] extends ConstructorLike ? MockedClass : T[P]; } & T; -export type MockedClass = MockInstance< +export type MockedClass = MockInstance< InstanceType, T extends new (...args: infer P) => any ? P : never > & { @@ -1028,34 +1034,34 @@ export class ModuleMocker { return fn; } - spyOn>( + spyOn>( object: T, methodName: M, accessType: 'get', ): SpyInstance; - spyOn>( + spyOn>( object: T, methodName: M, accessType: 'set', ): SpyInstance; - spyOn>( + spyOn>( object: T, methodName: M, - ): T[M] extends new (...args: Array) => any + ): T[M] extends ConstructorLike ? SpyInstance, ConstructorParameters> : never; - spyOn>( + spyOn>( object: T, methodName: M, - ): T[M] extends FunctionLike + ): T[M] extends MethodLike ? SpyInstance, Parameters> : never; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - spyOn>( + spyOn>( object: T, methodName: M, accessType?: 'get' | 'set', @@ -1126,7 +1132,7 @@ export class ModuleMocker { return object[methodName]; } - private _spyOnProperty>( + private _spyOnProperty>( obj: T, propertyName: M, accessType: 'get' | 'set' = 'get', From e409ad84619447e81550a8fc985255e97afd209e Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Wed, 23 Feb 2022 12:02:24 +0200 Subject: [PATCH 08/10] revert Unpromisify --- packages/jest-mock/src/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/jest-mock/src/index.ts b/packages/jest-mock/src/index.ts index 3e289b59bd8a..980f424ce7f0 100644 --- a/packages/jest-mock/src/index.ts +++ b/packages/jest-mock/src/index.ts @@ -123,6 +123,8 @@ export interface Mock = Array> (...args: Y): T; } +type Unpromisify = T extends Promise ? R : never; + export interface MockInstance> { _isMockFunction: true; _protoImpl: Function; @@ -142,8 +144,8 @@ export interface MockInstance> { mockReturnThis(): this; mockReturnValue(value: T): this; mockReturnValueOnce(value: T): this; - mockResolvedValue(value: Awaited): this; - mockResolvedValueOnce(value: Awaited): this; + mockResolvedValue(value: Unpromisify): this; + mockResolvedValueOnce(value: Unpromisify): this; mockRejectedValue(value: unknown): this; mockRejectedValueOnce(value: unknown): this; } @@ -751,7 +753,7 @@ export class ModuleMocker { // next function call will return this value or default return value f.mockImplementationOnce(() => value); - f.mockResolvedValueOnce = (value: Awaited) => + f.mockResolvedValueOnce = (value: Unpromisify) => f.mockImplementationOnce(() => Promise.resolve(value as T)); f.mockRejectedValueOnce = (value: unknown) => @@ -761,7 +763,7 @@ export class ModuleMocker { // next function call will return specified return value or this one f.mockImplementation(() => value); - f.mockResolvedValue = (value: Awaited) => + f.mockResolvedValue = (value: Unpromisify) => f.mockImplementation(() => Promise.resolve(value as T)); f.mockRejectedValue = (value: unknown) => From 7cd40aae48e852dc7be7902a574cb408bc386be2 Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Wed, 23 Feb 2022 12:19:26 +0200 Subject: [PATCH 09/10] tweak todo comments --- packages/jest-mock/src/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/jest-mock/src/index.ts b/packages/jest-mock/src/index.ts index 980f424ce7f0..16fee2a2addc 100644 --- a/packages/jest-mock/src/index.ts +++ b/packages/jest-mock/src/index.ts @@ -52,7 +52,7 @@ export type PropertyLikeKeys = { : K; }[keyof T]; -// TODO Replace this with TS ConstructorParameters utility type +// TODO Figure out how to replace this with TS ConstructorParameters utility type // https://www.typescriptlang.org/docs/handbook/utility-types.html#constructorparameterstype type ConstructorParameters = T extends new (...args: infer P) => any ? P @@ -123,6 +123,8 @@ export interface Mock = Array> (...args: Y): T; } +// TODO Replace with Awaited utility type when minimum supported TS version will be 4.5 or above +//https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html#the-awaited-type-and-promise-improvements type Unpromisify = T extends Promise ? R : never; export interface MockInstance> { From 3a7081b4fc52f60dbd8ed3a7928e4116fea5db87 Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Wed, 23 Feb 2022 12:49:35 +0200 Subject: [PATCH 10/10] fix change log entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80d636d8bf3f..a0a2dbd41e21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ - `[jest-environment-node]` [**BREAKING**] Second argument `context` to constructor is mandatory ([#12469](https://github.com/facebook/jest/pull/12469)) - `[@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 builtin utility types `ConstructorParameters` and `Parameters` should be used instead ([#12435](https://github.com/facebook/jest/pull/12435)) +- `[jest-mock]` [**BREAKING**] Rename exported utility types `ConstructorLike`, `MethodLike`, `ConstructorLikeKeys`, `MethodLikeKeys`, `PropertyLikeKeys`; remove exports of utility types `ArgumentsOf`, `ArgsType`, `ConstructorArgumentsOf` - TS builtin utility types `ConstructorParameters` and `Parameters` should be used instead ([#12435](https://github.com/facebook/jest/pull/12435)) - `[jest-mock]` Improve `isMockFunction` to infer types of passed function ([#12442](https://github.com/facebook/jest/pull/12442)) - `[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))