diff --git a/CHANGELOG.md b/CHANGELOG.md index 619a67e91df4..a5abaceaa795 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Features +- `[expect, @jest/expect]` support type inference for function parameters in `CalledWith` assertions ([#13268](https://github.com/facebook/jest/pull/13268)) - `[@jest/environment, jest-runtime]` Allow `jest.requireActual` and `jest.requireMock` to take a type argument ([#13253](https://github.com/facebook/jest/pull/13253)) - `[@jest/environment]` Allow `jest.mock` and `jest.doMock` to take a type argument ([#13254](https://github.com/facebook/jest/pull/13254)) - `[@jest/fake-timers]` Add `jest.now()` to return the current fake clock time ([#13244](https://github.com/facebook/jest/pull/13244), [13246](https://github.com/facebook/jest/pull/13246)) diff --git a/packages/expect/src/types.ts b/packages/expect/src/types.ts index 24c063f4022d..0e00d20bddcb 100644 --- a/packages/expect/src/types.ts +++ b/packages/expect/src/types.ts @@ -94,9 +94,9 @@ export interface BaseExpect { } export type Expect = { - (actual: T): Matchers & - Inverse> & - PromiseMatchers; + (actual: T): Matchers & + Inverse> & + PromiseMatchers; } & BaseExpect & AsymmetricMatchers & Inverse>; @@ -118,24 +118,26 @@ export interface AsymmetricMatchers { stringMatching(sample: string | RegExp): AsymmetricMatcher; } -type PromiseMatchers = { +type PromiseMatchers = { /** * Unwraps the reason of a rejected promise so any other matcher can be chained. * If the promise is fulfilled the assertion fails. */ - rejects: Matchers> & Inverse>>; + rejects: Matchers> & Inverse, T>>; /** * Unwraps the value of a fulfilled promise so any other matcher can be chained. * If the promise is rejected the assertion fails. */ - resolves: Matchers> & Inverse>>; + resolves: Matchers> & Inverse, T>>; }; -export interface Matchers> { +type EnsureFunctionLike = T extends (...args: any) => any ? T : never; + +export interface Matchers, T = unknown> { /** * Ensures the last call to a mock function was provided specific args. */ - lastCalledWith(...expected: Array): R; + lastCalledWith(...expected: Parameters>): R; /** * Ensure that the last call to a mock function has returned a specified value. */ @@ -143,7 +145,7 @@ export interface Matchers> { /** * Ensure that a mock function is called with specific arguments on an Nth call. */ - nthCalledWith(nth: number, ...expected: Array): R; + nthCalledWith(nth: number, ...expected: Parameters>): R; /** * Ensure that the nth call to a mock function has returned a specified value. */ @@ -164,7 +166,7 @@ export interface Matchers> { /** * Ensure that a mock function is called with specific arguments. */ - toBeCalledWith(...expected: Array): R; + toBeCalledWith(...expected: Parameters>): R; /** * Using exact equality with floating point numbers is a bad idea. * Rounding means that intuitive things fail. @@ -247,16 +249,19 @@ export interface Matchers> { /** * Ensure that a mock function is called with specific arguments. */ - toHaveBeenCalledWith(...expected: Array): R; + toHaveBeenCalledWith(...expected: Parameters>): R; /** * Ensure that a mock function is called with specific arguments on an Nth call. */ - toHaveBeenNthCalledWith(nth: number, ...expected: Array): R; + toHaveBeenNthCalledWith( + nth: number, + ...expected: Parameters> + ): R; /** * If you have a mock function, you can use `.toHaveBeenLastCalledWith` * to test what arguments it was last called with. */ - toHaveBeenLastCalledWith(...expected: Array): R; + toHaveBeenLastCalledWith(...expected: Parameters>): R; /** * Use to test the specific value that a mock function last returned. * If the last call to the mock function threw an error, then this matcher will fail diff --git a/packages/jest-expect/src/types.ts b/packages/jest-expect/src/types.ts index 10739da8481e..d1ba64a7f66f 100644 --- a/packages/jest-expect/src/types.ts +++ b/packages/jest-expect/src/types.ts @@ -29,7 +29,7 @@ type Inverse = { not: Matchers; }; -type JestMatchers, T> = Matchers & +type JestMatchers, T> = Matchers & SnapshotMatchers; type PromiseMatchers = { diff --git a/packages/jest-types/__typetests__/expect.test.ts b/packages/jest-types/__typetests__/expect.test.ts index 945dbd380451..aaefe2d9c752 100644 --- a/packages/jest-types/__typetests__/expect.test.ts +++ b/packages/jest-types/__typetests__/expect.test.ts @@ -218,6 +218,37 @@ expectType(expect(jest.fn()).toHaveBeenCalledWith()); expectType(expect(jest.fn()).toHaveBeenCalledWith(123)); expectType(expect(jest.fn()).toHaveBeenCalledWith(123, 'value')); +/** + * type inference for "CalledWith" matchers parameters + */ +expectError(expect(jest.fn<(a: string) => void>()).toHaveBeenCalledWith(123)); +expectError( + expect(jest.fn<(a: string) => void>()).toHaveBeenNthCalledWith(1, 123), +); +expectError( + expect(jest.fn<(a: string) => void>()).toHaveBeenLastCalledWith(123), +); +expectType( + expect( + jest.fn<(a: string, b: number, c?: boolean) => void>(), + ).toHaveBeenCalledWith('value', 123), +); +expectType( + expect( + jest.fn<(a: string, b: number, c?: boolean) => void>(), + ).toHaveBeenCalledWith('value', 123, true), +); +expectError( + expect( + jest.fn<(a: string, b: number, c?: boolean) => void>(), + ).toHaveBeenCalledWith(123, 'value'), +); +expectError( + expect( + jest.fn<(a: string, b: number, c?: boolean) => void>(), + ).toHaveBeenCalledWith('value', 123, 'not a boolean'), +); + expectType(expect(jest.fn()).lastCalledWith()); expectType(expect(jest.fn()).lastCalledWith('value')); expectType(expect(jest.fn()).lastCalledWith('value', 123));