diff --git a/CHANGELOG.md b/CHANGELOG.md index 4997b655331c..36585ad88d65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### 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 - `[*]` 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 f3687b5597cb..951a6eff5854 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,20 +121,28 @@ 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>>; }; -export interface Matchers> { +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. */ 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'); +}); 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 = {