From 1b0121ba266bf921c8c996867b4ff66fb0a6462d Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sun, 18 Sep 2022 16:01:18 +0300 Subject: [PATCH 1/7] feat: typed `*ReturnedWith` argument --- packages/expect/__typetests__/expect.test.ts | 3 +- packages/expect/src/types.ts | 38 ++++++++++++------- packages/jest-expect/src/types.ts | 2 +- .../jest-types/__typetests__/expect.test.ts | 27 ++++++++++++- 4 files changed, 53 insertions(+), 17 deletions(-) diff --git a/packages/expect/__typetests__/expect.test.ts b/packages/expect/__typetests__/expect.test.ts index 9bc1ccae24cb..6c5119eff56c 100644 --- a/packages/expect/__typetests__/expect.test.ts +++ b/packages/expect/__typetests__/expect.test.ts @@ -16,7 +16,8 @@ import { } from 'expect'; import type * as jestMatcherUtils from 'jest-matcher-utils'; -type M = Matchers; +type M = Matchers; +type N = Matchers; expectError(() => { type E = Matchers; diff --git a/packages/expect/src/types.ts b/packages/expect/src/types.ts index 24c063f4022d..c296d75fbda4 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,20 +118,22 @@ 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. */ @@ -139,7 +141,7 @@ export interface Matchers> { /** * Ensure that the last call to a mock function has returned a specified value. */ - lastReturnedWith(expected: unknown): R; + lastReturnedWith>(expected: ReturnType): R; /** * Ensure that a mock function is called with specific arguments on an Nth call. */ @@ -147,7 +149,10 @@ export interface Matchers> { /** * Ensure that the nth call to a mock function has returned a specified value. */ - nthReturnedWith(nth: number, expected: unknown): R; + nthReturnedWith>( + nth: number, + expected: ReturnType, + ): R; /** * Checks that a value is what you expect. It calls `Object.is` to compare values. * Don't use `toBe` with floating-point numbers. @@ -262,7 +267,9 @@ export interface Matchers> { * If the last call to the mock function threw an error, then this matcher will fail * no matter what value you provided as the expected return value. */ - toHaveLastReturnedWith(expected: unknown): R; + toHaveLastReturnedWith>( + expected: ReturnType, + ): R; /** * Used to check that an object has a `.length` property * and it is set to a certain numeric value. @@ -273,7 +280,10 @@ export interface Matchers> { * If the nth call to the mock function threw an error, then this matcher will fail * no matter what value you provided as the expected return value. */ - toHaveNthReturnedWith(nth: number, expected: unknown): R; + toHaveNthReturnedWith>( + nth: number, + expected: ReturnType, + ): R; /** * Use to check if property at provided reference keyPath exists for an object. * For checking deeply nested properties in an object you may use dot notation or an array containing @@ -303,7 +313,9 @@ export interface Matchers> { /** * Use to ensure that a mock function returned a specific value. */ - toHaveReturnedWith(expected: unknown): R; + toHaveReturnedWith>( + expected: ReturnType, + ): R; /** * Check that a string matches a regular expression. */ @@ -325,7 +337,7 @@ export interface Matchers> { /** * Ensure that a mock function has returned a specified value at least once. */ - toReturnWith(expected: unknown): R; + toReturnWith>(expected: ReturnType): R; /** * Use to test that objects have the same types as well as structure. */ 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..9c08b29e8ff1 100644 --- a/packages/jest-types/__typetests__/expect.test.ts +++ b/packages/jest-types/__typetests__/expect.test.ts @@ -247,21 +247,44 @@ expectError(expect(jest.fn()).toHaveReturnedTimes(true)); expectError(expect(jest.fn()).toHaveReturnedTimes()); expectType(expect(jest.fn()).toReturnWith('value')); +expectType(expect(jest.fn<() => string>()).toReturnWith('value')); +expectError(expect(jest.fn<() => number>()).toReturnWith('value')); +expectError(expect(123).toReturnWith('value')); expectError(expect(jest.fn()).toReturnWith()); + expectType(expect(jest.fn()).toHaveReturnedWith(123)); +expectType(expect(jest.fn<() => number>()).toHaveReturnedWith(123)); +expectError(expect(jest.fn<() => string>()).toHaveReturnedWith(123)); +expectError(expect(123).toHaveReturnedWith(123)); expectError(expect(jest.fn()).toHaveReturnedWith()); expectType(expect(jest.fn()).lastReturnedWith('value')); +expectType(expect(jest.fn<() => string>()).lastReturnedWith('value')); +expectError(expect(jest.fn<() => number>()).lastReturnedWith('value')); +expectError(expect(123).lastReturnedWith('value')); expectError(expect(jest.fn()).lastReturnedWith()); + expectType(expect(jest.fn()).toHaveLastReturnedWith(123)); +expectType(expect(jest.fn<() => number>()).toHaveLastReturnedWith(123)); +expectError(expect(jest.fn<() => string>()).toHaveLastReturnedWith(123)); +expectError(expect(123).toHaveLastReturnedWith(123)); expectError(expect(jest.fn()).toHaveLastReturnedWith()); expectType(expect(jest.fn()).nthReturnedWith(1, 'value')); +expectType(expect(jest.fn<() => string>()).nthReturnedWith(2, 'value')); +expectError(expect(jest.fn<() => number>()).nthReturnedWith(3, 'value')); +expectError(expect(123).nthReturnedWith(4, 'value')); +expectError(expect(123).nthReturnedWith(5)); expectError(expect(jest.fn()).nthReturnedWith()); -expectError(expect(jest.fn()).nthReturnedWith(2)); + expectType(expect(jest.fn()).toHaveNthReturnedWith(1, 'value')); +expectType( + expect(jest.fn<() => string>()).toHaveNthReturnedWith(2, 'value'), +); +expectError(expect(jest.fn<() => number>()).toHaveNthReturnedWith(3, 'value')); +expectError(expect(123).toHaveNthReturnedWith(4, 'value')); +expectError(expect(123).toHaveNthReturnedWith(5)); expectError(expect(jest.fn()).toHaveNthReturnedWith()); -expectError(expect(jest.fn()).toHaveNthReturnedWith(2)); // snapshot matchers From 00315ce007d29b6241134fb310ec07d6fa16a01a Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sun, 18 Sep 2022 16:24:02 +0300 Subject: [PATCH 2/7] use `unknown` --- packages/expect/src/types.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/expect/src/types.ts b/packages/expect/src/types.ts index c296d75fbda4..00b707cab82e 100644 --- a/packages/expect/src/types.ts +++ b/packages/expect/src/types.ts @@ -131,7 +131,9 @@ type PromiseMatchers = { resolves: Matchers> & Inverse, T>>; }; -type EnsureFunctionLike = T extends (args: any) => any ? T : never; +type EnsureFunctionLike = T extends (...args: Array) => unknown + ? T + : never; export interface Matchers, T = unknown> { /** From 027223840fdb8baa5463611d10ed3ef7b08f657b Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sun, 18 Sep 2022 16:32:49 +0300 Subject: [PATCH 3/7] add changelog entry --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88cfba26a809..b09af9971c5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,10 @@ ### Features -- `[feat(@jest/environment, jest-runtime)]` Allow `jest.requireActual` and `jest.requireMock` to take a type argument ([#13253](https://github.com/facebook/jest/pull/13253)) -- `[feat(@jest/environment]` Allow `jest.mock` and `jest.doMock` to take a type argument ([#13254](https://github.com/facebook/jest/pull/13254)) +- `[@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)) +- `[expect, @jest/expect]` Infer type of `*ReturnedWith` matchers argument ([#13278](https://github.com/facebook/jest/pull/13278)) ### Fixes From 96ab005bb9468b73d5aa1fe8339bd259a619b907 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sun, 18 Sep 2022 16:52:19 +0300 Subject: [PATCH 4/7] simplify --- packages/expect/src/types.ts | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/packages/expect/src/types.ts b/packages/expect/src/types.ts index 00b707cab82e..65f6bc0d5c82 100644 --- a/packages/expect/src/types.ts +++ b/packages/expect/src/types.ts @@ -143,7 +143,7 @@ export interface Matchers, T = unknown> { /** * Ensure that the last call to a mock function has returned a specified value. */ - lastReturnedWith>(expected: ReturnType): R; + lastReturnedWith(expected: ReturnType>): R; /** * Ensure that a mock function is called with specific arguments on an Nth call. */ @@ -151,10 +151,7 @@ export interface Matchers, T = unknown> { /** * Ensure that the nth call to a mock function has returned a specified value. */ - nthReturnedWith>( - nth: number, - expected: ReturnType, - ): R; + nthReturnedWith(nth: number, expected: ReturnType>): R; /** * Checks that a value is what you expect. It calls `Object.is` to compare values. * Don't use `toBe` with floating-point numbers. @@ -269,9 +266,7 @@ export interface Matchers, T = unknown> { * If the last call to the mock function threw an error, then this matcher will fail * no matter what value you provided as the expected return value. */ - toHaveLastReturnedWith>( - expected: ReturnType, - ): R; + toHaveLastReturnedWith(expected: ReturnType>): R; /** * Used to check that an object has a `.length` property * and it is set to a certain numeric value. @@ -282,9 +277,9 @@ export interface Matchers, T = unknown> { * If the nth call to the mock function threw an error, then this matcher will fail * no matter what value you provided as the expected return value. */ - toHaveNthReturnedWith>( + toHaveNthReturnedWith( nth: number, - expected: ReturnType, + expected: ReturnType>, ): R; /** * Use to check if property at provided reference keyPath exists for an object. @@ -315,9 +310,7 @@ export interface Matchers, T = unknown> { /** * Use to ensure that a mock function returned a specific value. */ - toHaveReturnedWith>( - expected: ReturnType, - ): R; + toHaveReturnedWith(expected: ReturnType>): R; /** * Check that a string matches a regular expression. */ @@ -339,7 +332,7 @@ export interface Matchers, T = unknown> { /** * Ensure that a mock function has returned a specified value at least once. */ - toReturnWith>(expected: ReturnType): R; + toReturnWith(expected: ReturnType>): R; /** * Use to test that objects have the same types as well as structure. */ From 9db869cd3bd9a06f041ca4f9f78eeec1099527db Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sun, 18 Sep 2022 17:19:55 +0300 Subject: [PATCH 5/7] back to `any`s --- packages/expect/src/types.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/expect/src/types.ts b/packages/expect/src/types.ts index 65f6bc0d5c82..27bc3714a9c5 100644 --- a/packages/expect/src/types.ts +++ b/packages/expect/src/types.ts @@ -131,9 +131,7 @@ type PromiseMatchers = { resolves: Matchers> & Inverse, T>>; }; -type EnsureFunctionLike = T extends (...args: Array) => unknown - ? T - : never; +type EnsureFunctionLike = T extends (...args: any) => any ? T : never; export interface Matchers, T = unknown> { /** From 9c166b5adb8721b1070db8bdd674a87abdbb6a8f Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Mon, 19 Sep 2022 07:11:05 +0300 Subject: [PATCH 6/7] fix order --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b09af9971c5c..3d7e5d65ab0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,8 @@ - `[@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)) - `[expect, @jest/expect]` Infer type of `*ReturnedWith` matchers argument ([#13278](https://github.com/facebook/jest/pull/13278)) +- `[@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)) ### Fixes From 90aa595edf22cee96669cb41ca2d7d14df71db59 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Tue, 20 Sep 2022 08:28:35 +0200 Subject: [PATCH 7/7] changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71981af71fa9..1a758cb98c90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,9 @@ ### Features - `[expect, @jest/expect]` support type inference for function parameters in `CalledWith` assertions ([#13268](https://github.com/facebook/jest/pull/13268)) +- `[expect, @jest/expect]` Infer type of `*ReturnedWith` matchers argument ([#13278](https://github.com/facebook/jest/pull/13278)) - `[@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)) -- `[expect, @jest/expect]` Infer type of `*ReturnedWith` matchers argument ([#13278](https://github.com/facebook/jest/pull/13278)) - `[@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)) ### Fixes