Skip to content

Commit ae1f04b

Browse files
authored
Add type tests for all expect matchers (#11949)
1 parent 46c9c13 commit ae1f04b

7 files changed

Lines changed: 409 additions & 104 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
### Fixes
66

7-
- `[jest-runtime]` Ensure absolute paths can be resolved within test modules ([11943](https://github.com/facebook/jest/pull/11943))
7+
- `[expect]` Tweak and improve types ([#11949](https://github.com/facebook/jest/pull/11949))
8+
- `[jest-runtime]` Ensure absolute paths can be resolved within test modules ([#11943](https://github.com/facebook/jest/pull/11943))
89
- `[jest-runtime]` Fix `instanceof` for `ModernFakeTimers` and `LegacyFakeTimers` methods ([#11946](https://github.com/facebook/jest/pull/11946))
910

1011
### Chore & Maintenance

packages/expect/src/__tests__/assertionCounts.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ describe('.hasAssertions()', () => {
3737

3838
it('throws if expected is not undefined', () => {
3939
jestExpect(() => {
40+
// @ts-expect-error
4041
jestExpect.hasAssertions(2);
4142
}).toThrowErrorMatchingSnapshot();
4243
});

packages/expect/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ const expectExport = expect as Expect;
431431

432432
declare namespace expectExport {
433433
export type MatcherState = JestMatcherState;
434-
export interface Matchers<R> extends MatcherInterface<R> {}
434+
export interface Matchers<R, T> extends MatcherInterface<R, T> {}
435435
}
436436

437437
export = expectExport;

packages/expect/src/jestMatchersObject.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,14 @@ export const setMatchers = <State extends MatcherState = MatcherState>(
9898
}
9999
}
100100

101-
expect[key] = (...sample: [unknown, ...Array<unknown>]) =>
102-
new CustomMatcher(false, ...sample);
103-
if (!expect.not) {
104-
throw new Error(
105-
'`expect.not` is not defined - please report this bug to https://github.com/facebook/jest',
106-
);
107-
}
108-
expect.not[key] = (...sample: [unknown, ...Array<unknown>]) =>
109-
new CustomMatcher(true, ...sample);
101+
Object.defineProperty(expect, key, {
102+
value: (...sample: [unknown, ...Array<unknown>]) =>
103+
new CustomMatcher(false, ...sample),
104+
});
105+
Object.defineProperty(expect.not, key, {
106+
value: (...sample: [unknown, ...Array<unknown>]) =>
107+
new CustomMatcher(true, ...sample),
108+
});
110109
}
111110
});
112111

packages/expect/src/types.ts

Lines changed: 49 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
*
77
*/
88

9-
/* eslint-disable local/ban-types-eventually */
10-
119
import type {Config} from '@jest/types';
1210
import type * as jestMatcherUtils from 'jest-matcher-utils';
1311
import {INTERNAL_MATCHER_FLAG} from './jestMatchersObject';
@@ -72,26 +70,17 @@ export type ExpectedAssertionsErrors = Array<{
7270
expected: string;
7371
}>;
7472

75-
interface InverseAsymmetricMatchers {
73+
interface AsymmetricMatchers {
74+
any(sample: unknown): AsymmetricMatcher;
75+
anything(): AsymmetricMatcher;
7676
arrayContaining(sample: Array<unknown>): AsymmetricMatcher;
7777
objectContaining(sample: Record<string, unknown>): AsymmetricMatcher;
78-
stringContaining(expected: string): AsymmetricMatcher;
79-
stringMatching(expected: string | RegExp): AsymmetricMatcher;
80-
}
81-
82-
interface AsymmetricMatchers extends InverseAsymmetricMatchers {
83-
any(expectedObject: unknown): AsymmetricMatcher;
84-
anything(): AsymmetricMatcher;
85-
}
86-
87-
// Should use interface merging somehow
88-
interface ExtraAsymmetricMatchers {
89-
// at least one argument is needed - that's probably wrong. Should allow `expect.toBeDivisibleBy2()` like `expect.anything()`
90-
[id: string]: (...sample: [unknown, ...Array<unknown>]) => AsymmetricMatcher;
78+
stringContaining(sample: string): AsymmetricMatcher;
79+
stringMatching(sample: string | RegExp): AsymmetricMatcher;
9180
}
9281

9382
export type Expect<State extends MatcherState = MatcherState> = {
94-
<T = unknown>(actual: T): Matchers<void>;
83+
<T = unknown>(actual: T): Matchers<void, T>;
9584
// TODO: this is added by test runners, not `expect` itself
9685
addSnapshotSerializer(serializer: unknown): void;
9786
assertions(numberOfAssertions: number): void;
@@ -101,43 +90,42 @@ export type Expect<State extends MatcherState = MatcherState> = {
10190
getState(): State;
10291
hasAssertions(): void;
10392
setState(state: Partial<State>): void;
104-
} & AsymmetricMatchers &
105-
ExtraAsymmetricMatchers & {
106-
not: InverseAsymmetricMatchers & ExtraAsymmetricMatchers;
93+
} & AsymmetricMatchers & {
94+
not: Omit<AsymmetricMatchers, 'any' | 'anything'>;
10795
};
10896

10997
// This is a copy from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/de6730f4463cba69904698035fafd906a72b9664/types/jest/index.d.ts#L570-L817
110-
export interface Matchers<R> {
98+
export interface Matchers<R, T = unknown> {
11199
/**
112100
* Ensures the last call to a mock function was provided specific args.
113101
*/
114-
lastCalledWith(...args: Array<unknown>): R;
102+
lastCalledWith(...expected: [unknown, ...Array<unknown>]): R;
115103
/**
116104
* Ensure that the last call to a mock function has returned a specified value.
117105
*/
118-
lastReturnedWith(value: unknown): R;
106+
lastReturnedWith(expected: unknown): R;
119107
/**
120108
* If you know how to test something, `.not` lets you test its opposite.
121109
*/
122-
not: Matchers<R>;
110+
not: Matchers<R, T>;
123111
/**
124112
* Ensure that a mock function is called with specific arguments on an Nth call.
125113
*/
126-
nthCalledWith(nthCall: number, ...args: Array<unknown>): R;
114+
nthCalledWith(nth: number, ...expected: [unknown, ...Array<unknown>]): R;
127115
/**
128116
* Ensure that the nth call to a mock function has returned a specified value.
129117
*/
130-
nthReturnedWith(n: number, value: unknown): R;
118+
nthReturnedWith(nth: number, expected: unknown): R;
131119
/**
132120
* Use resolves to unwrap the value of a fulfilled promise so any other
133121
* matcher can be chained. If the promise is rejected the assertion fails.
134122
*/
135-
resolves: Matchers<Promise<R>>;
123+
resolves: Matchers<Promise<R>, T>;
136124
/**
137125
* Unwraps the reason of a rejected promise so any other matcher can be chained.
138126
* If the promise is fulfilled the assertion fails.
139127
*/
140-
rejects: Matchers<Promise<R>>;
128+
rejects: Matchers<Promise<R>, T>;
141129
/**
142130
* Checks that a value is what you expect. It uses `===` to check strict equality.
143131
* Don't use `toBe` with floating-point numbers.
@@ -154,13 +142,13 @@ export interface Matchers<R> {
154142
/**
155143
* Ensure that a mock function is called with specific arguments.
156144
*/
157-
toBeCalledWith(...args: Array<unknown>): R;
145+
toBeCalledWith(...expected: [unknown, ...Array<unknown>]): R;
158146
/**
159147
* Using exact equality with floating point numbers is a bad idea.
160148
* Rounding means that intuitive things fail.
161-
* The default for numDigits is 2.
149+
* The default for `precision` is 2.
162150
*/
163-
toBeCloseTo(expected: number, numDigits?: number): R;
151+
toBeCloseTo(expected: number, precision?: number): R;
164152
/**
165153
* Ensure that a variable is not undefined.
166154
*/
@@ -182,7 +170,7 @@ export interface Matchers<R> {
182170
* Ensure that an object is an instance of a class.
183171
* This matcher uses `instanceof` underneath.
184172
*/
185-
toBeInstanceOf(expected: Function): R;
173+
toBeInstanceOf(expected: unknown): R;
186174
/**
187175
* For comparing floating point numbers.
188176
*/
@@ -237,16 +225,19 @@ export interface Matchers<R> {
237225
/**
238226
* Ensure that a mock function is called with specific arguments.
239227
*/
240-
toHaveBeenCalledWith(...args: Array<unknown>): R;
228+
toHaveBeenCalledWith(...expected: [unknown, ...Array<unknown>]): R;
241229
/**
242230
* Ensure that a mock function is called with specific arguments on an Nth call.
243231
*/
244-
toHaveBeenNthCalledWith(nthCall: number, ...args: Array<unknown>): R;
232+
toHaveBeenNthCalledWith(
233+
nth: number,
234+
...expected: [unknown, ...Array<unknown>]
235+
): R;
245236
/**
246237
* If you have a mock function, you can use `.toHaveBeenLastCalledWith`
247238
* to test what arguments it was last called with.
248239
*/
249-
toHaveBeenLastCalledWith(...args: Array<unknown>): R;
240+
toHaveBeenLastCalledWith(...expected: [unknown, ...Array<unknown>]): R;
250241
/**
251242
* Use to test the specific value that a mock function last returned.
252243
* If the last call to the mock function threw an error, then this matcher will fail
@@ -263,7 +254,7 @@ export interface Matchers<R> {
263254
* If the nth call to the mock function threw an error, then this matcher will fail
264255
* no matter what value you provided as the expected return value.
265256
*/
266-
toHaveNthReturnedWith(nthCall: number, expected: unknown): R;
257+
toHaveNthReturnedWith(nth: number, expected: unknown): R;
267258
/**
268259
* Use to check if property at provided reference keyPath exists for an object.
269260
* For checking deeply nested properties in an object you may use dot notation or an array containing
@@ -277,7 +268,10 @@ export interface Matchers<R> {
277268
*
278269
* expect(houseForSale).toHaveProperty('kitchen.area', 20);
279270
*/
280-
toHaveProperty(keyPath: string | Array<string>, value?: unknown): R;
271+
toHaveProperty(
272+
expectedPath: string | Array<string>,
273+
expectedValue?: unknown,
274+
): R;
281275
/**
282276
* Use to test that the mock function successfully returned (i.e., did not throw an error) at least one time
283277
*/
@@ -298,65 +292,67 @@ export interface Matchers<R> {
298292
/**
299293
* Used to check that a JavaScript object matches a subset of the properties of an object
300294
*/
301-
toMatchObject(expected: Record<string, unknown> | Array<unknown>): R;
295+
toMatchObject(
296+
expected: Record<string, unknown> | Array<Record<string, unknown>>,
297+
): R;
302298
/**
303299
* Ensure that a mock function has returned (as opposed to thrown) at least once.
304300
*/
305301
toReturn(): R;
306302
/**
307303
* Ensure that a mock function has returned (as opposed to thrown) a specified number of times.
308304
*/
309-
toReturnTimes(count: number): R;
305+
toReturnTimes(expected: number): R;
310306
/**
311307
* Ensure that a mock function has returned a specified value at least once.
312308
*/
313-
toReturnWith(value: unknown): R;
309+
toReturnWith(expected: unknown): R;
314310
/**
315311
* Use to test that objects have the same types as well as structure.
316312
*/
317313
toStrictEqual(expected: unknown): R;
318314
/**
319315
* Used to test that a function throws when it is called.
320316
*/
321-
toThrow(error?: unknown): R;
317+
toThrow(expected?: unknown): R;
322318
/**
323319
* If you want to test that a specific error is thrown inside a function.
324320
*/
325-
toThrowError(error?: unknown): R;
321+
toThrowError(expected?: unknown): R;
326322

327323
/* TODO: START snapshot matchers are not from `expect`, the types should not be here */
328324
/**
329325
* This ensures that a value matches the most recent snapshot with property matchers.
330326
* Check out [the Snapshot Testing guide](https://jestjs.io/docs/snapshot-testing) for more information.
331327
*/
332-
toMatchSnapshot<T extends {[P in keyof R]: unknown}>(
333-
propertyMatchers: Partial<T>,
334-
snapshotName?: string,
335-
): R;
328+
toMatchSnapshot(hint?: string): R;
336329
/**
337330
* This ensures that a value matches the most recent snapshot.
338331
* Check out [the Snapshot Testing guide](https://jestjs.io/docs/snapshot-testing) for more information.
339332
*/
340-
toMatchSnapshot(snapshotName?: string): R;
333+
toMatchSnapshot<U extends Record<keyof T, unknown>>(
334+
propertyMatchers: Partial<U>,
335+
hint?: string,
336+
): R;
341337
/**
342338
* This ensures that a value matches the most recent snapshot with property matchers.
343339
* Instead of writing the snapshot value to a .snap file, it will be written into the source code automatically.
344340
* Check out [the Snapshot Testing guide](https://jestjs.io/docs/snapshot-testing) for more information.
345341
*/
346-
toMatchInlineSnapshot<T extends {[P in keyof R]: unknown}>(
347-
propertyMatchers: Partial<T>,
348-
snapshot?: string,
349-
): R;
342+
toMatchInlineSnapshot(snapshot?: string): R;
350343
/**
351344
* This ensures that a value matches the most recent snapshot with property matchers.
352345
* Instead of writing the snapshot value to a .snap file, it will be written into the source code automatically.
353346
* Check out [the Snapshot Testing guide](https://jestjs.io/docs/snapshot-testing) for more information.
354347
*/
355-
toMatchInlineSnapshot(snapshot?: string): R;
348+
toMatchInlineSnapshot<U extends Record<keyof T, unknown>>(
349+
propertyMatchers: Partial<U>,
350+
snapshot?: string,
351+
): R;
356352
/**
357353
* Used to test that a function throws a error matching the most recent snapshot when it is called.
358354
*/
359-
toThrowErrorMatchingSnapshot(): R;
355+
toThrowErrorMatchingSnapshot(hint?: string): R;
360356
/**
361357
* Used to test that a function throws a error matching the most recent snapshot when it is called.
362358
* Instead of writing the snapshot value to a .snap file, it will be written into the source code automatically.

0 commit comments

Comments
 (0)