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

fix(jest-mock): add index signature support for spyOn types #13013

Merged
merged 3 commits into from Jul 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -8,6 +8,7 @@

- `[jest-changed-files]` Fix a lock-up after repeated invocations ([#12757](https://github.com/facebook/jest/issues/12757))
- `[@jest/expect-utils]` Fix deep equality of ImmutableJS OrderedSets ([#12977](https://github.com/facebook/jest/pull/12977))
- `[jest-mock]` Add index signature support for `spyOn` types ([#13013](https://github.com/facebook/jest/pull/13013))
- `[jest-snapshot]` Fix indentation of awaited inline snapshots ([#12986](https://github.com/facebook/jest/pull/12986))

### Chore & Maintenance
Expand Down
55 changes: 55 additions & 0 deletions packages/jest-mock/__typetests__/mock-functions.test.ts
Expand Up @@ -279,6 +279,34 @@ const spiedObject = {
},
};

type IndexSpiedObject = {
[key: string]: Record<string, any>;

methodA(): boolean;
methodB(a: string, b: number): void;
methodC: (c: number) => boolean;
methodE: (e: any) => never;

propertyA: {a: string};
};

const indexSpiedObject: IndexSpiedObject = {
methodA() {
return true;
},
methodB(a: string, b: number) {
return;
},
methodC(c: number) {
return true;
},
methodE(e: any) {
throw new Error();
},

propertyA: {a: 'abc'},
};

const spy = spyOn(spiedObject, 'methodA');

expectNotAssignable<Function>(spy); // eslint-disable-line @typescript-eslint/ban-types
Expand Down Expand Up @@ -330,3 +358,30 @@ expectType<SpyInstance<(value: string | number | Date) => Date>>(
spyOn(globalThis, 'Date'),
);
expectType<SpyInstance<() => number>>(spyOn(Date, 'now'));

// object with index signatures

expectType<SpyInstance<typeof indexSpiedObject.methodA>>(
spyOn(indexSpiedObject, 'methodA'),
);
expectType<SpyInstance<typeof indexSpiedObject.methodB>>(
spyOn(indexSpiedObject, 'methodB'),
);
expectType<SpyInstance<typeof indexSpiedObject.methodC>>(
spyOn(indexSpiedObject, 'methodC'),
);
expectType<SpyInstance<typeof indexSpiedObject.methodE>>(
spyOn(indexSpiedObject, 'methodE'),
);

expectError(spyOn(indexSpiedObject, 'propertyA'));

expectType<SpyInstance<() => {a: string}>>(
spyOn(indexSpiedObject, 'propertyA', 'get'),
);
expectType<SpyInstance<(value: {a: string}) => void>>(
spyOn(indexSpiedObject, 'propertyA', 'set'),
);
expectError(spyOn(indexSpiedObject, 'propertyA'));

expectError(spyOn(indexSpiedObject, 'notThere'));
44 changes: 44 additions & 0 deletions packages/jest-mock/__typetests__/utility-types.test.ts
Expand Up @@ -37,6 +37,31 @@ class SomeClass {
}
}

class IndexClass {
[key: string]: Record<string, any>;

propertyB = {b: 123};
private _propertyC = {c: undefined};
#propertyD = 'abc';

constructor(public propertyA: {a: string}) {}

methodA(): void {
return;
}

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

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

const someObject = {
SomeClass,

Expand All @@ -56,6 +81,17 @@ const someObject = {

type SomeObject = typeof someObject;

type IndexObject = {
[key: string]: Record<string, any>;

methodA(): void;
methodB(b: string): boolean;
methodC: (c: number) => true;

propertyA: {a: 123};
propertyB: {b: 'value'};
};

// ClassLike

expectAssignable<ClassLike>(SomeClass);
Expand Down Expand Up @@ -89,15 +125,23 @@ expectType<'SomeClass'>(constructorKeys);
// MethodKeys

declare const classMethods: MethodLikeKeys<SomeClass>;
declare const indexClassMethods: MethodLikeKeys<IndexClass>;
declare const objectMethods: MethodLikeKeys<SomeObject>;
declare const indexObjectMethods: MethodLikeKeys<IndexObject>;

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

// PropertyKeys

declare const classProperties: PropertyLikeKeys<SomeClass>;
declare const indexClassProperties: PropertyLikeKeys<IndexClass>;
declare const objectProperties: PropertyLikeKeys<SomeObject>;
declare const indexObjectProperties: PropertyLikeKeys<IndexObject>;

expectType<'propertyA' | 'propertyB' | 'propertyC'>(classProperties);
expectType<string>(indexClassProperties);
expectType<'propertyA' | 'propertyB' | 'someClassInstance'>(objectProperties);
expectType<string>(indexObjectProperties);
12 changes: 6 additions & 6 deletions packages/jest-mock/src/index.ts
Expand Up @@ -34,13 +34,13 @@ export type MockFunctionMetadata<
export type ClassLike = {new (...args: any): any};
export type FunctionLike = (...args: any) => any;

export type ConstructorLikeKeys<T> = {
[K in keyof T]: T[K] extends ClassLike ? K : never;
}[keyof T];
export type ConstructorLikeKeys<T> = keyof {
[K in keyof T as T[K] extends ClassLike ? K : never]: T[K];
};

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

export type PropertyLikeKeys<T> = {
[K in keyof T]: T[K] extends FunctionLike
Expand Down