Skip to content

Commit a653a6f

Browse files
authored
refactor(jest-mock)!: rename and clean up utility types (#12435)
1 parent 98341d4 commit a653a6f

File tree

3 files changed

+172
-65
lines changed

3 files changed

+172
-65
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
- `[jest-environment-node]` [**BREAKING**] Second argument `context` to constructor is mandatory ([#12469](https://github.com/facebook/jest/pull/12469))
1919
- `[@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))
2020
- `[@jest/expect-utils]` New module exporting utils for `expect` ([#12323](https://github.com/facebook/jest/pull/12323))
21+
- `[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))
2122
- `[jest-mock]` Improve `isMockFunction` to infer types of passed function ([#12442](https://github.com/facebook/jest/pull/12442))
2223
- `[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))
2324
- `[jest-resolve, jest-runtime]` Add support for `data:` URI import and mock ([#12392](https://github.com/facebook/jest/pull/12392))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import {expectAssignable, expectNotAssignable, expectType} from 'tsd-lite';
9+
import type {
10+
ConstructorLike,
11+
ConstructorLikeKeys,
12+
MethodLike,
13+
MethodLikeKeys,
14+
PropertyLikeKeys,
15+
} from 'jest-mock';
16+
17+
class SomeClass {
18+
propertyB = 123;
19+
private _propertyC: undefined;
20+
#propertyD = 'abc';
21+
22+
constructor(public propertyA: string) {}
23+
24+
methodA(): void {
25+
return;
26+
}
27+
28+
methodB(b: string): string {
29+
return b;
30+
}
31+
32+
get propertyC() {
33+
return this._propertyC;
34+
}
35+
set propertyC(value) {
36+
this._propertyC = value;
37+
}
38+
}
39+
40+
const someObject = {
41+
SomeClass,
42+
43+
methodA() {
44+
return;
45+
},
46+
methodB(b: string) {
47+
return true;
48+
},
49+
methodC: (c: number) => true,
50+
51+
propertyA: 123,
52+
propertyB: 'value',
53+
54+
someClassInstance: new SomeClass('value'),
55+
};
56+
57+
type SomeObject = typeof someObject;
58+
59+
// ClassLike
60+
61+
expectAssignable<ConstructorLike>(SomeClass);
62+
expectNotAssignable<ConstructorLike>(() => {});
63+
expectNotAssignable<ConstructorLike>(function abc() {
64+
return;
65+
});
66+
expectNotAssignable<ConstructorLike>('abc');
67+
expectNotAssignable<ConstructorLike>(123);
68+
expectNotAssignable<ConstructorLike>(false);
69+
expectNotAssignable<ConstructorLike>(someObject);
70+
71+
// FunctionLike
72+
73+
expectAssignable<MethodLike>(() => {});
74+
expectAssignable<MethodLike>(function abc() {
75+
return;
76+
});
77+
expectNotAssignable<MethodLike>('abc');
78+
expectNotAssignable<MethodLike>(123);
79+
expectNotAssignable<MethodLike>(false);
80+
expectNotAssignable<MethodLike>(SomeClass);
81+
expectNotAssignable<MethodLike>(someObject);
82+
83+
// ConstructorKeys
84+
85+
declare const constructorKeys: ConstructorLikeKeys<SomeObject>;
86+
87+
expectType<'SomeClass'>(constructorKeys);
88+
89+
// MethodKeys
90+
91+
declare const classMethods: MethodLikeKeys<SomeClass>;
92+
declare const objectMethods: MethodLikeKeys<SomeObject>;
93+
94+
expectType<'methodA' | 'methodB'>(classMethods);
95+
expectType<'methodA' | 'methodB' | 'methodC'>(objectMethods);
96+
97+
// PropertyKeys
98+
99+
declare const classProperties: PropertyLikeKeys<SomeClass>;
100+
declare const objectProperties: PropertyLikeKeys<SomeObject>;
101+
102+
expectType<'propertyA' | 'propertyB' | 'propertyC'>(classProperties);
103+
expectType<'propertyA' | 'propertyB' | 'someClassInstance'>(objectProperties);

packages/jest-mock/src/index.ts

+68-65
Original file line numberDiff line numberDiff line change
@@ -20,87 +20,101 @@ export type MockFunctionMetadataType =
2020
export type MockFunctionMetadata<
2121
T,
2222
Y extends Array<unknown>,
23-
Type = MockFunctionMetadataType,
23+
MetadataType = MockFunctionMetadataType,
2424
> = {
2525
ref?: number;
2626
members?: Record<string, MockFunctionMetadata<T, Y>>;
2727
mockImpl?: (...args: Y) => T;
2828
name?: string;
2929
refID?: number;
30-
type?: Type;
30+
type?: MetadataType;
3131
value?: T;
3232
length?: number;
3333
};
3434

35-
export type MockableFunction = (...args: Array<any>) => any;
36-
export type MethodKeysOf<T> = {
37-
[K in keyof T]: T[K] extends MockableFunction ? K : never;
35+
export type ConstructorLike = {new (...args: Array<any>): any};
36+
37+
export type MethodLike = (...args: Array<any>) => any;
38+
39+
export type ConstructorLikeKeys<T> = {
40+
[K in keyof T]: T[K] extends ConstructorLike ? K : never;
3841
}[keyof T];
39-
export type PropertyKeysOf<T> = {
40-
[K in keyof T]: T[K] extends MockableFunction ? never : K;
42+
43+
export type MethodLikeKeys<T> = {
44+
[K in keyof T]: T[K] extends MethodLike ? K : never;
4145
}[keyof T];
4246

43-
export type ArgumentsOf<T> = T extends (...args: infer A) => any ? A : never;
47+
export type PropertyLikeKeys<T> = {
48+
[K in keyof T]: T[K] extends MethodLike
49+
? never
50+
: T[K] extends ConstructorLike
51+
? never
52+
: K;
53+
}[keyof T];
4454

45-
export type ConstructorArgumentsOf<T> = T extends new (...args: infer A) => any
46-
? A
55+
// TODO Figure out how to replace this with TS ConstructorParameters utility type
56+
// https://www.typescriptlang.org/docs/handbook/utility-types.html#constructorparameterstype
57+
type ConstructorParameters<T> = T extends new (...args: infer P) => any
58+
? P
4759
: never;
60+
4861
export type MaybeMockedConstructor<T> = T extends new (
4962
...args: Array<any>
5063
) => infer R
51-
? MockInstance<R, ConstructorArgumentsOf<T>>
64+
? MockInstance<R, ConstructorParameters<T>>
5265
: T;
53-
export type MockedFunction<T extends MockableFunction> = MockWithArgs<T> & {
66+
67+
export interface MockWithArgs<T extends MethodLike>
68+
extends MockInstance<ReturnType<T>, Parameters<T>> {
69+
new (...args: ConstructorParameters<T>): T;
70+
(...args: Parameters<T>): ReturnType<T>;
71+
}
72+
73+
export type MockedFunction<T extends MethodLike> = MockWithArgs<T> & {
5474
[K in keyof T]: T[K];
5575
};
56-
export type MockedFunctionDeep<T extends MockableFunction> = MockWithArgs<T> &
76+
77+
export type MockedFunctionDeep<T extends MethodLike> = MockWithArgs<T> &
5778
MockedObjectDeep<T>;
79+
5880
export type MockedObject<T> = MaybeMockedConstructor<T> & {
59-
[K in MethodKeysOf<T>]: T[K] extends MockableFunction
81+
[K in MethodLikeKeys<T>]: T[K] extends MethodLike
6082
? MockedFunction<T[K]>
6183
: T[K];
62-
} & {[K in PropertyKeysOf<T>]: T[K]};
84+
} & {[K in PropertyLikeKeys<T>]: T[K]};
85+
6386
export type MockedObjectDeep<T> = MaybeMockedConstructor<T> & {
64-
[K in MethodKeysOf<T>]: T[K] extends MockableFunction
87+
[K in MethodLikeKeys<T>]: T[K] extends MethodLike
6588
? MockedFunctionDeep<T[K]>
6689
: T[K];
67-
} & {[K in PropertyKeysOf<T>]: MaybeMockedDeep<T[K]>};
90+
} & {[K in PropertyLikeKeys<T>]: MaybeMockedDeep<T[K]>};
6891

69-
export type MaybeMockedDeep<T> = T extends MockableFunction
70-
? MockedFunctionDeep<T>
92+
export type MaybeMocked<T> = T extends MethodLike
93+
? MockedFunction<T>
7194
: T extends object
72-
? MockedObjectDeep<T>
95+
? MockedObject<T>
7396
: T;
7497

75-
export type MaybeMocked<T> = T extends MockableFunction
76-
? MockedFunction<T>
98+
export type MaybeMockedDeep<T> = T extends MethodLike
99+
? MockedFunctionDeep<T>
77100
: T extends object
78-
? MockedObject<T>
101+
? MockedObjectDeep<T>
79102
: T;
80103

81-
export type ArgsType<T> = T extends (...args: infer A) => any ? A : never;
82104
export type Mocked<T> = {
83-
[P in keyof T]: T[P] extends (...args: Array<any>) => any
84-
? MockInstance<ReturnType<T[P]>, ArgsType<T[P]>>
85-
: T[P] extends Constructable
105+
[P in keyof T]: T[P] extends MethodLike
106+
? MockInstance<ReturnType<T[P]>, Parameters<T[P]>>
107+
: T[P] extends ConstructorLike
86108
? MockedClass<T[P]>
87109
: T[P];
88110
} & T;
89-
export type MockedClass<T extends Constructable> = MockInstance<
111+
112+
export type MockedClass<T extends ConstructorLike> = MockInstance<
90113
InstanceType<T>,
91114
T extends new (...args: infer P) => any ? P : never
92115
> & {
93116
prototype: T extends {prototype: any} ? Mocked<T['prototype']> : never;
94117
} & T;
95-
export interface Constructable {
96-
new (...args: Array<any>): any;
97-
}
98-
99-
export interface MockWithArgs<T extends MockableFunction>
100-
extends MockInstance<ReturnType<T>, ArgumentsOf<T>> {
101-
new (...args: ConstructorArgumentsOf<T>): T;
102-
(...args: ArgumentsOf<T>): ReturnType<T>;
103-
}
104118

105119
export interface Mock<T, Y extends Array<unknown> = Array<unknown>>
106120
extends Function,
@@ -109,8 +123,9 @@ export interface Mock<T, Y extends Array<unknown> = Array<unknown>>
109123
(...args: Y): T;
110124
}
111125

112-
export interface SpyInstance<T, Y extends Array<unknown>>
113-
extends MockInstance<T, Y> {}
126+
// TODO Replace with Awaited utility type when minimum supported TS version will be 4.5 or above
127+
//https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html#the-awaited-type-and-promise-improvements
128+
type Unpromisify<T> = T extends Promise<infer R> ? R : never;
114129

115130
export interface MockInstance<T, Y extends Array<unknown>> {
116131
_isMockFunction: true;
@@ -137,7 +152,8 @@ export interface MockInstance<T, Y extends Array<unknown>> {
137152
mockRejectedValueOnce(value: unknown): this;
138153
}
139154

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

142158
type MockFunctionResultIncomplete = {
143159
type: 'incomplete';
@@ -200,20 +216,6 @@ type MockFunctionConfig = {
200216
specificMockImpls: Array<Function>;
201217
};
202218

203-
// see https://github.com/Microsoft/TypeScript/issues/25215
204-
type NonFunctionPropertyNames<T> = {
205-
[K in keyof T]: T[K] extends (...args: Array<any>) => any ? never : K;
206-
}[keyof T] &
207-
string;
208-
type FunctionPropertyNames<T> = {
209-
[K in keyof T]: T[K] extends (...args: Array<any>) => any ? K : never;
210-
}[keyof T] &
211-
string;
212-
type ConstructorPropertyNames<T> = {
213-
[K in keyof T]: T[K] extends new (...args: Array<any>) => any ? K : never;
214-
}[keyof T] &
215-
string;
216-
217219
const MOCK_CONSTRUCTOR_NAME = 'mockConstructor';
218220

219221
const FUNCTION_NAME_RESERVED_PATTERN = /[\s!-\/:-@\[-`{-~]/;
@@ -1036,34 +1038,34 @@ export class ModuleMocker {
10361038
return fn;
10371039
}
10381040

1039-
spyOn<T extends object, M extends NonFunctionPropertyNames<T>>(
1041+
spyOn<T extends object, M extends PropertyLikeKeys<T>>(
10401042
object: T,
10411043
methodName: M,
10421044
accessType: 'get',
10431045
): SpyInstance<T[M], []>;
10441046

1045-
spyOn<T extends object, M extends NonFunctionPropertyNames<T>>(
1047+
spyOn<T extends object, M extends PropertyLikeKeys<T>>(
10461048
object: T,
10471049
methodName: M,
10481050
accessType: 'set',
10491051
): SpyInstance<void, [T[M]]>;
10501052

1051-
spyOn<T extends object, M extends ConstructorPropertyNames<T>>(
1053+
spyOn<T extends object, M extends ConstructorLikeKeys<T>>(
10521054
object: T,
10531055
methodName: M,
1054-
): T[M] extends new (...args: Array<any>) => any
1056+
): T[M] extends ConstructorLike
10551057
? SpyInstance<InstanceType<T[M]>, ConstructorParameters<T[M]>>
10561058
: never;
10571059

1058-
spyOn<T extends object, M extends FunctionPropertyNames<T>>(
1060+
spyOn<T extends object, M extends MethodLikeKeys<T>>(
10591061
object: T,
10601062
methodName: M,
1061-
): T[M] extends (...args: Array<any>) => any
1063+
): T[M] extends MethodLike
10621064
? SpyInstance<ReturnType<T[M]>, Parameters<T[M]>>
10631065
: never;
10641066

10651067
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
1066-
spyOn<T extends object, M extends NonFunctionPropertyNames<T>>(
1068+
spyOn<T extends object, M extends PropertyLikeKeys<T>>(
10671069
object: T,
10681070
methodName: M,
10691071
accessType?: 'get' | 'set',
@@ -1134,10 +1136,11 @@ export class ModuleMocker {
11341136
return object[methodName];
11351137
}
11361138

1137-
private _spyOnProperty<
1138-
T extends object,
1139-
M extends NonFunctionPropertyNames<T>,
1140-
>(obj: T, propertyName: M, accessType: 'get' | 'set' = 'get'): Mock<T> {
1139+
private _spyOnProperty<T extends object, M extends PropertyLikeKeys<T>>(
1140+
obj: T,
1141+
propertyName: M,
1142+
accessType: 'get' | 'set' = 'get',
1143+
): Mock<T> {
11411144
if (typeof obj !== 'object' && typeof obj !== 'function') {
11421145
throw new Error(
11431146
'Cannot spyOn on a primitive value; ' + this._typeOf(obj) + ' given',

0 commit comments

Comments
 (0)