From 4bfbc6b31a700abb062c23cc09c6efcce109a278 Mon Sep 17 00:00:00 2001 From: Ben Kraft Date: Tue, 31 Jan 2023 14:19:08 -0800 Subject: [PATCH 1/8] feat(@jest/expect): Expose type of actual to Matchers Matchers isn't as typed as some users would like (see #13334, #13812). For users who want to customize it by extending the `Matchers` interface, it's useful to have access to the type of `actual` (the argument of `expect`) so you can do, say, ```ts interface Matchers { toTypedEqual(expected: T): R } ``` This commit exposes it. The first-party matchers still have the same types as before. --- CHANGELOG.md | 2 ++ packages/expect/src/types.ts | 2 +- packages/jest-expect/src/types.ts | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4997b655331c..5ac9e1680043 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Features +- `[@jest/expect]` provide type of `actual` as a generic argument to `Matchers` to allow better-typed extensions (#TBD) + ### Fixes ### Chore & Maintenance diff --git a/packages/expect/src/types.ts b/packages/expect/src/types.ts index f3687b5597cb..3803d1e251b4 100644 --- a/packages/expect/src/types.ts +++ b/packages/expect/src/types.ts @@ -134,7 +134,7 @@ type PromiseMatchers = { resolves: Matchers> & Inverse>>; }; -export interface Matchers> { +export interface Matchers, T = unknown> { /** * Ensures the last call to a mock function was provided specific args. */ 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 = { From b9c5c27d1001b00ef1fe3b43572e99b891c40082 Mon Sep 17 00:00:00 2001 From: Ben Kraft Date: Wed, 1 Feb 2023 10:50:04 -0800 Subject: [PATCH 2/8] review comments --- CHANGELOG.md | 4 ++-- packages/expect/__typetests__/expect.test.ts | 1 + .../expect/__typetests__/expectTyped.test.ts | 22 +++++++++++++++++++ packages/expect/src/types.ts | 14 +++++++----- .../__typetests__/jest-expect.test.ts | 13 +++++++++++ 5 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 packages/expect/__typetests__/expectTyped.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ac9e1680043..d8820ba0e9b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,10 @@ ### Features -- `[@jest/expect]` provide type of `actual` as a generic argument to `Matchers` to allow better-typed extensions (#TBD) - ### Fixes +- `[@jest/expect]` provide type of `actual` as a generic argument to `Matchers` to allow better-typed extensions (#TBD) + ### Chore & Maintenance - `[*]` make sure to exclude `.eslintcache` from published module ([#13832](https://github.com/facebook/jest/pull/13832)) diff --git a/packages/expect/__typetests__/expect.test.ts b/packages/expect/__typetests__/expect.test.ts index b9e32400f54f..db233cb4e135 100644 --- a/packages/expect/__typetests__/expect.test.ts +++ b/packages/expect/__typetests__/expect.test.ts @@ -19,6 +19,7 @@ import { import type * as jestMatcherUtils from 'jest-matcher-utils'; type M = Matchers; +type N = Matchers; expectError(() => { type E = Matchers; diff --git a/packages/expect/__typetests__/expectTyped.test.ts b/packages/expect/__typetests__/expectTyped.test.ts new file mode 100644 index 000000000000..c96e1d79ac15 --- /dev/null +++ b/packages/expect/__typetests__/expectTyped.test.ts @@ -0,0 +1,22 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {expectAssignable, expectError, expectType} from 'tsd-lite'; +import {Matchers, expect} from 'expect'; + +declare module 'expect' { + interface Matchers { + toTypedEqual(expected: T): void; + } +} + +expectType(expect(100).toTypedEqual(100)); +expectType(expect(101).not.toTypedEqual(101)); + +expectError(() => { + expect(100).toTypedEqual('three'); +}); diff --git a/packages/expect/src/types.ts b/packages/expect/src/types.ts index 3803d1e251b4..ff49fad4ee70 100644 --- a/packages/expect/src/types.ts +++ b/packages/expect/src/types.ts @@ -97,9 +97,9 @@ export interface BaseExpect { } export type Expect = { - (actual: T): Matchers & - Inverse> & - PromiseMatchers; + (actual: T): Matchers & + Inverse> & + PromiseMatchers; } & BaseExpect & AsymmetricMatchers & Inverse>; @@ -121,19 +121,21 @@ 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, T> & 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, T> & Inverse, T>>; }; +// @ts-expect-error unused variable T (can't use _T since users redeclare Matchers) +// eslint-disable-next-line @typescript-eslint/no-unused-vars export interface Matchers, T = unknown> { /** * Ensures the last call to a mock function was provided specific args. diff --git a/packages/jest-expect/__typetests__/jest-expect.test.ts b/packages/jest-expect/__typetests__/jest-expect.test.ts index ac1d0193565c..1620323fab9d 100644 --- a/packages/jest-expect/__typetests__/jest-expect.test.ts +++ b/packages/jest-expect/__typetests__/jest-expect.test.ts @@ -29,3 +29,16 @@ expectError(() => { expectAssignable(jestExpect); expectNotAssignable(expect); + +declare module 'expect' { + interface Matchers { + toTypedEqual(expected: T): void; + } +} + +expectType(jestExpect(100).toTypedEqual(100)); +expectType(jestExpect(101).not.toTypedEqual(101)); + +expectError(() => { + jestExpect(100).toTypedEqual('three'); +}); From d6459b4621929a9505e86c33ee3282cae85bf06f Mon Sep 17 00:00:00 2001 From: Ben Kraft Date: Wed, 1 Feb 2023 10:56:06 -0800 Subject: [PATCH 3/8] add PR number in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8820ba0e9b9..7336aa32a862 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Fixes -- `[@jest/expect]` provide type of `actual` as a generic argument to `Matchers` to allow better-typed extensions (#TBD) +- `[@jest/expect]` provide type of `actual` as a generic argument to `Matchers` to allow better-typed extensions (#13848) ### Chore & Maintenance From 2b7490e81c64cfb2bba64080346c1998cc747586 Mon Sep 17 00:00:00 2001 From: Ben Kraft Date: Wed, 1 Feb 2023 11:01:23 -0800 Subject: [PATCH 4/8] changelog comments Co-authored-by: Tom Mrazauskas --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7336aa32a862..1bff0a13bab5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ ### Fixes -- `[@jest/expect]` provide type of `actual` as a generic argument to `Matchers` to allow better-typed extensions (#13848) +- `[expect, @jest/expect]` Provide type of `actual` as a generic argument to `Matchers` to allow better-typed extensions ([#13848](https://github.com/facebook/jest/pull/13848)) +- ``` ### Chore & Maintenance From 2b4fa2d350cffd4209e087a5137caf4d6349c127 Mon Sep 17 00:00:00 2001 From: Ben Kraft Date: Wed, 1 Feb 2023 11:04:06 -0800 Subject: [PATCH 5/8] Update CHANGELOG.md Co-authored-by: Tom Mrazauskas --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bff0a13bab5..36585ad88d65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,6 @@ ### Fixes - `[expect, @jest/expect]` Provide type of `actual` as a generic argument to `Matchers` to allow better-typed extensions ([#13848](https://github.com/facebook/jest/pull/13848)) -- ``` ### Chore & Maintenance From fc53bed9d70d8de2d7d19d803794609203cb2df9 Mon Sep 17 00:00:00 2001 From: Ben Kraft Date: Wed, 1 Feb 2023 11:29:53 -0800 Subject: [PATCH 6/8] better way to avoid unused variable errors --- packages/expect/src/types.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/expect/src/types.ts b/packages/expect/src/types.ts index ff49fad4ee70..d1c6aa86df61 100644 --- a/packages/expect/src/types.ts +++ b/packages/expect/src/types.ts @@ -134,9 +134,16 @@ type PromiseMatchers = { resolves: Matchers, T> & Inverse, T>>; }; -// @ts-expect-error unused variable T (can't use _T since users redeclare Matchers) -// eslint-disable-next-line @typescript-eslint/no-unused-vars export interface Matchers, T = unknown> { + /** + * T is a type param for the benefit of users who extend Matchers. It's + * intentionally unused and needs to be named T, not _T, for those users. + * This makes sure Typescript agrees. + * + * @internal + */ + _unusedT(expected: T): R; + /** * Ensures the last call to a mock function was provided specific args. */ From 6230a94a41b40179c8a27e9b35eaccd8d9b1a370 Mon Sep 17 00:00:00 2001 From: Ben Kraft Date: Wed, 1 Feb 2023 13:28:51 -0800 Subject: [PATCH 7/8] review comments Co-authored-by: Tom Mrazauskas --- packages/expect/src/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/expect/src/types.ts b/packages/expect/src/types.ts index d1c6aa86df61..c00c3db71854 100644 --- a/packages/expect/src/types.ts +++ b/packages/expect/src/types.ts @@ -143,7 +143,6 @@ export interface Matchers, T = unknown> { * @internal */ _unusedT(expected: T): R; - /** * Ensures the last call to a mock function was provided specific args. */ From 12d3bcc3ea3d26a35c2baa8483f86e9df2b97dea Mon Sep 17 00:00:00 2001 From: Ben Kraft Date: Wed, 1 Feb 2023 13:30:45 -0800 Subject: [PATCH 8/8] review comments, again Co-authored-by: Tom Mrazauskas --- packages/expect/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/expect/src/types.ts b/packages/expect/src/types.ts index c00c3db71854..951a6eff5854 100644 --- a/packages/expect/src/types.ts +++ b/packages/expect/src/types.ts @@ -138,7 +138,7 @@ export interface Matchers, T = unknown> { /** * T is a type param for the benefit of users who extend Matchers. It's * intentionally unused and needs to be named T, not _T, for those users. - * This makes sure Typescript agrees. + * This makes sure TypeScript agrees. * * @internal */