diff --git a/CHANGELOG.md b/CHANGELOG.md index 670867b065e9..dea223aba580 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/packages/jest-mock/__typetests__/mock-functions.test.ts b/packages/jest-mock/__typetests__/mock-functions.test.ts index 6be5a35b7b77..daf115d5bf52 100644 --- a/packages/jest-mock/__typetests__/mock-functions.test.ts +++ b/packages/jest-mock/__typetests__/mock-functions.test.ts @@ -279,6 +279,34 @@ const spiedObject = { }, }; +type IndexSpiedObject = { + [key: string]: Record; + + 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(spy); // eslint-disable-line @typescript-eslint/ban-types @@ -330,3 +358,30 @@ expectType Date>>( spyOn(globalThis, 'Date'), ); expectType number>>(spyOn(Date, 'now')); + +// object with index signatures + +expectType>( + spyOn(indexSpiedObject, 'methodA'), +); +expectType>( + spyOn(indexSpiedObject, 'methodB'), +); +expectType>( + spyOn(indexSpiedObject, 'methodC'), +); +expectType>( + spyOn(indexSpiedObject, 'methodE'), +); + +expectError(spyOn(indexSpiedObject, 'propertyA')); + +expectType {a: string}>>( + spyOn(indexSpiedObject, 'propertyA', 'get'), +); +expectType void>>( + spyOn(indexSpiedObject, 'propertyA', 'set'), +); +expectError(spyOn(indexSpiedObject, 'propertyA')); + +expectError(spyOn(indexSpiedObject, 'notThere')); diff --git a/packages/jest-mock/__typetests__/utility-types.test.ts b/packages/jest-mock/__typetests__/utility-types.test.ts index 3b866f801e49..2faa5521d4ce 100644 --- a/packages/jest-mock/__typetests__/utility-types.test.ts +++ b/packages/jest-mock/__typetests__/utility-types.test.ts @@ -37,6 +37,31 @@ class SomeClass { } } +class IndexClass { + [key: string]: Record; + + 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, @@ -56,6 +81,17 @@ const someObject = { type SomeObject = typeof someObject; +type IndexObject = { + [key: string]: Record; + + methodA(): void; + methodB(b: string): boolean; + methodC: (c: number) => true; + + propertyA: {a: 123}; + propertyB: {b: 'value'}; +}; + // ClassLike expectAssignable(SomeClass); @@ -89,15 +125,23 @@ expectType<'SomeClass'>(constructorKeys); // MethodKeys declare const classMethods: MethodLikeKeys; +declare const indexClassMethods: MethodLikeKeys; declare const objectMethods: MethodLikeKeys; +declare const indexObjectMethods: MethodLikeKeys; expectType<'methodA' | 'methodB'>(classMethods); +expectType<'methodA' | 'methodB'>(indexClassMethods); expectType<'methodA' | 'methodB' | 'methodC'>(objectMethods); +expectType<'methodA' | 'methodB' | 'methodC'>(indexObjectMethods); // PropertyKeys declare const classProperties: PropertyLikeKeys; +declare const indexClassProperties: PropertyLikeKeys; declare const objectProperties: PropertyLikeKeys; +declare const indexObjectProperties: PropertyLikeKeys; expectType<'propertyA' | 'propertyB' | 'propertyC'>(classProperties); +expectType(indexClassProperties); expectType<'propertyA' | 'propertyB' | 'someClassInstance'>(objectProperties); +expectType(indexObjectProperties); diff --git a/packages/jest-mock/src/index.ts b/packages/jest-mock/src/index.ts index 90c90be26ab8..283e06b1920d 100644 --- a/packages/jest-mock/src/index.ts +++ b/packages/jest-mock/src/index.ts @@ -34,13 +34,13 @@ export type MockFunctionMetadata< export type ClassLike = {new (...args: any): any}; export type FunctionLike = (...args: any) => any; -export type ConstructorLikeKeys = { - [K in keyof T]: T[K] extends ClassLike ? K : never; -}[keyof T]; +export type ConstructorLikeKeys = keyof { + [K in keyof T as T[K] extends ClassLike ? K : never]: T[K]; +}; -export type MethodLikeKeys = { - [K in keyof T]: T[K] extends FunctionLike ? K : never; -}[keyof T]; +export type MethodLikeKeys = keyof { + [K in keyof T as T[K] extends FunctionLike ? K : never]: T[K]; +}; export type PropertyLikeKeys = { [K in keyof T]: T[K] extends FunctionLike