Skip to content

Commit aece656

Browse files
authored
add matchers to expect type (#8093)
* add matchers to expect type * changelog * PR feedback
1 parent 445e6cb commit aece656

File tree

3 files changed

+269
-27
lines changed

3 files changed

+269
-27
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
### Chore & Maintenance
2525

2626
- `[*]` Make sure to include `d.ts` files in the tarball when building ([#8086](https://github.com/facebook/jest/pull/8086))
27+
- `[expect]` Export `Matchers` interface from `expect` ([#8093](https://github.com/facebook/jest/pull/8093))
2728

2829
## 24.3.1
2930

packages/expect/src/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ import {
1111
AsyncExpectationResult,
1212
SyncExpectationResult,
1313
ExpectationResult,
14+
Matchers as MatcherInterface,
1415
MatcherState as JestMatcherState,
1516
MatchersObject,
1617
RawMatcherFn,
1718
ThrowingMatcherFn,
1819
PromiseMatcherFn,
19-
ExpectationObject,
2020
Expect,
2121
} from './types';
2222

@@ -82,7 +82,7 @@ const getPromiseMatcher = (name: string, matcher: any) => {
8282
return null;
8383
};
8484

85-
const expect: any = (actual: any, ...rest: Array<any>): ExpectationObject => {
85+
const expect: any = (actual: any, ...rest: Array<any>) => {
8686
if (rest.length !== 0) {
8787
throw new Error('Expect takes at most one argument.');
8888
}
@@ -412,6 +412,7 @@ const expectExport = expect as Expect;
412412
// eslint-disable-next-line no-redeclare
413413
namespace expectExport {
414414
export type MatcherState = JestMatcherState;
415+
export interface Matchers<R> extends MatcherInterface<R> {}
415416
}
416417

417418
export = expectExport;

packages/expect/src/types.ts

Lines changed: 265 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ export type AsyncExpectationResult = Promise<SyncExpectationResult>;
1818
export type ExpectationResult = SyncExpectationResult | AsyncExpectationResult;
1919

2020
export type RawMatcherFn = (
21+
received: any,
2122
expected: any,
22-
actual: any,
2323
options?: any,
2424
) => ExpectationResult;
2525

@@ -55,7 +55,8 @@ export type MatcherState = {
5555
export type AsymmetricMatcher = Record<string, any>;
5656
export type MatchersObject = {[id: string]: RawMatcherFn};
5757
export type Expect = {
58-
(expected: any): ExpectationObject;
58+
<T = unknown>(actual: T): Matchers<T>;
59+
// TODO: this is added by test runners, not `expect` itself
5960
addSnapshotSerializer(arg0: any): void;
6061
assertions(arg0: number): void;
6162
extend(arg0: any): void;
@@ -70,34 +71,273 @@ export type Expect = {
7071

7172
any(expectedObject: any): AsymmetricMatcher;
7273
anything(): AsymmetricMatcher;
73-
arrayContaining(sample: Array<any>): AsymmetricMatcher;
74-
objectContaining(sample: Record<string, any>): AsymmetricMatcher;
74+
arrayContaining(sample: Array<unknown>): AsymmetricMatcher;
75+
objectContaining(sample: Record<string, unknown>): AsymmetricMatcher;
7576
stringContaining(expected: string): AsymmetricMatcher;
7677
stringMatching(expected: string | RegExp): AsymmetricMatcher;
7778
[id: string]: AsymmetricMatcher;
7879
not: {[id: string]: AsymmetricMatcher};
7980
};
8081

81-
type resolvesFn = {
82-
[id: string]: PromiseMatcherFn;
83-
} & {
84-
not: {[id: string]: PromiseMatcherFn};
85-
};
82+
interface Constructable {
83+
new (...args: Array<unknown>): unknown;
84+
}
8685

87-
type rejectsFn = {
88-
[id: string]: PromiseMatcherFn;
89-
} & {
90-
not: {[id: string]: PromiseMatcherFn};
91-
};
86+
// This is a copy from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/de6730f4463cba69904698035fafd906a72b9664/types/jest/index.d.ts#L570-L817
87+
export interface Matchers<R> {
88+
/**
89+
* Ensures the last call to a mock function was provided specific args.
90+
*/
91+
lastCalledWith(...args: Array<unknown>): R;
92+
/**
93+
* Ensure that the last call to a mock function has returned a specified value.
94+
*/
95+
lastReturnedWith(value: unknown): R;
96+
/**
97+
* If you know how to test something, `.not` lets you test its opposite.
98+
*/
99+
not: Matchers<R>;
100+
/**
101+
* Ensure that a mock function is called with specific arguments on an Nth call.
102+
*/
103+
nthCalledWith(nthCall: number, ...args: Array<unknown>): R;
104+
/**
105+
* Ensure that the nth call to a mock function has returned a specified value.
106+
*/
107+
nthReturnedWith(n: number, value: unknown): R;
108+
/**
109+
* Use resolves to unwrap the value of a fulfilled promise so any other
110+
* matcher can be chained. If the promise is rejected the assertion fails.
111+
*/
112+
resolves: Matchers<Promise<R>>;
113+
/**
114+
* Unwraps the reason of a rejected promise so any other matcher can be chained.
115+
* If the promise is fulfilled the assertion fails.
116+
*/
117+
rejects: Matchers<Promise<R>>;
118+
/**
119+
* Checks that a value is what you expect. It uses `===` to check strict equality.
120+
* Don't use `toBe` with floating-point numbers.
121+
*/
122+
toBe(expected: unknown): R;
123+
/**
124+
* Ensures that a mock function is called.
125+
*/
126+
toBeCalled(): R;
127+
/**
128+
* Ensures that a mock function is called an exact number of times.
129+
*/
130+
toBeCalledTimes(expected: number): R;
131+
/**
132+
* Ensure that a mock function is called with specific arguments.
133+
*/
134+
toBeCalledWith(...args: Array<unknown>): R;
135+
/**
136+
* Using exact equality with floating point numbers is a bad idea.
137+
* Rounding means that intuitive things fail.
138+
* The default for numDigits is 2.
139+
*/
140+
toBeCloseTo(expected: number, numDigits?: number): R;
141+
/**
142+
* Ensure that a variable is not undefined.
143+
*/
144+
toBeDefined(): R;
145+
/**
146+
* When you don't care what a value is, you just want to
147+
* ensure a value is false in a boolean context.
148+
*/
149+
toBeFalsy(): R;
150+
/**
151+
* For comparing floating point numbers.
152+
*/
153+
toBeGreaterThan(expected: number): R;
154+
/**
155+
* For comparing floating point numbers.
156+
*/
157+
toBeGreaterThanOrEqual(expected: number): R;
158+
/**
159+
* Ensure that an object is an instance of a class.
160+
* This matcher uses `instanceof` underneath.
161+
*/
162+
toBeInstanceOf(expected: Function): R;
163+
/**
164+
* For comparing floating point numbers.
165+
*/
166+
toBeLessThan(expected: number): R;
167+
/**
168+
* For comparing floating point numbers.
169+
*/
170+
toBeLessThanOrEqual(expected: number): R;
171+
/**
172+
* This is the same as `.toBe(null)` but the error messages are a bit nicer.
173+
* So use `.toBeNull()` when you want to check that something is null.
174+
*/
175+
toBeNull(): R;
176+
/**
177+
* Use when you don't care what a value is, you just want to ensure a value
178+
* is true in a boolean context. In JavaScript, there are six falsy values:
179+
* `false`, `0`, `''`, `null`, `undefined`, and `NaN`. Everything else is truthy.
180+
*/
181+
toBeTruthy(): R;
182+
/**
183+
* Used to check that a variable is undefined.
184+
*/
185+
toBeUndefined(): R;
186+
/**
187+
* Used to check that a variable is NaN.
188+
*/
189+
toBeNaN(): R;
190+
/**
191+
* Used when you want to check that an item is in a list.
192+
* For testing the items in the list, this uses `===`, a strict equality check.
193+
*/
194+
toContain(expected: unknown): R;
195+
/**
196+
* Used when you want to check that an item is in a list.
197+
* For testing the items in the list, this matcher recursively checks the
198+
* equality of all fields, rather than checking for object identity.
199+
*/
200+
toContainEqual(expected: unknown): R;
201+
/**
202+
* Used when you want to check that two objects have the same value.
203+
* This matcher recursively checks the equality of all fields, rather than checking for object identity.
204+
*/
205+
toEqual(expected: unknown): R;
206+
/**
207+
* Ensures that a mock function is called.
208+
*/
209+
toHaveBeenCalled(): R;
210+
/**
211+
* Ensures that a mock function is called an exact number of times.
212+
*/
213+
toHaveBeenCalledTimes(expected: number): R;
214+
/**
215+
* Ensure that a mock function is called with specific arguments.
216+
*/
217+
toHaveBeenCalledWith(...args: Array<unknown>): R;
218+
/**
219+
* Ensure that a mock function is called with specific arguments on an Nth call.
220+
*/
221+
toHaveBeenNthCalledWith(nthCall: number, ...args: Array<unknown>): R;
222+
/**
223+
* If you have a mock function, you can use `.toHaveBeenLastCalledWith`
224+
* to test what arguments it was last called with.
225+
*/
226+
toHaveBeenLastCalledWith(...args: Array<unknown>): R;
227+
/**
228+
* Use to test the specific value that a mock function last returned.
229+
* If the last call to the mock function threw an error, then this matcher will fail
230+
* no matter what value you provided as the expected return value.
231+
*/
232+
toHaveLastReturnedWith(expected: unknown): R;
233+
/**
234+
* Used to check that an object has a `.length` property
235+
* and it is set to a certain numeric value.
236+
*/
237+
toHaveLength(expected: number): R;
238+
/**
239+
* Use to test the specific value that a mock function returned for the nth call.
240+
* If the nth call to the mock function threw an error, then this matcher will fail
241+
* no matter what value you provided as the expected return value.
242+
*/
243+
toHaveNthReturnedWith(nthCall: number, expected: unknown): R;
244+
/**
245+
* Use to check if property at provided reference keyPath exists for an object.
246+
* For checking deeply nested properties in an object you may use dot notation or an array containing
247+
* the keyPath for deep references.
248+
*
249+
* Optionally, you can provide a value to check if it's equal to the value present at keyPath
250+
* on the target object. This matcher uses 'deep equality' (like `toEqual()`) and recursively checks
251+
* the equality of all fields.
252+
*
253+
* @example
254+
*
255+
* expect(houseForSale).toHaveProperty('kitchen.area', 20);
256+
*/
257+
toHaveProperty(keyPath: string | Array<string>, value?: unknown): R;
258+
/**
259+
* Use to test that the mock function successfully returned (i.e., did not throw an error) at least one time
260+
*/
261+
toHaveReturned(): R;
262+
/**
263+
* Use to ensure that a mock function returned successfully (i.e., did not throw an error) an exact number of times.
264+
* Any calls to the mock function that throw an error are not counted toward the number of times the function returned.
265+
*/
266+
toHaveReturnedTimes(expected: number): R;
267+
/**
268+
* Use to ensure that a mock function returned a specific value.
269+
*/
270+
toHaveReturnedWith(expected: unknown): R;
271+
/**
272+
* Check that a string matches a regular expression.
273+
*/
274+
toMatch(expected: string | RegExp): R;
275+
/**
276+
* Used to check that a JavaScript object matches a subset of the properties of an object
277+
*/
278+
toMatchObject(expected: Record<string, unknown> | Array<unknown>): R;
279+
/**
280+
* Ensure that a mock function has returned (as opposed to thrown) at least once.
281+
*/
282+
toReturn(): R;
283+
/**
284+
* Ensure that a mock function has returned (as opposed to thrown) a specified number of times.
285+
*/
286+
toReturnTimes(count: number): R;
287+
/**
288+
* Ensure that a mock function has returned a specified value at least once.
289+
*/
290+
toReturnWith(value: unknown): R;
291+
/**
292+
* Use to test that objects have the same types as well as structure.
293+
*/
294+
toStrictEqual(expected: unknown): R;
295+
/**
296+
* Used to test that a function throws when it is called.
297+
*/
298+
toThrow(error?: string | Constructable | RegExp | Error): R;
299+
/**
300+
* If you want to test that a specific error is thrown inside a function.
301+
*/
302+
toThrowError(error?: string | Constructable | RegExp | Error): R;
92303

93-
type notFn = {
94-
[id: string]: ThrowingMatcherFn;
95-
};
96-
97-
export type ExpectationObject = {
98-
[id: string]: ThrowingMatcherFn;
99-
} & {
100-
resolves: resolvesFn;
101-
rejects: rejectsFn;
102-
not: notFn;
103-
};
304+
/* TODO: START snapshot matchers are not from `expect`, the types should not be here */
305+
/**
306+
* This ensures that a value matches the most recent snapshot with property matchers.
307+
* Check out [the Snapshot Testing guide](https://jestjs.io/docs/en/snapshot-testing) for more information.
308+
*/
309+
toMatchSnapshot<T extends {[P in keyof R]: unknown}>(
310+
propertyMatchers: Partial<T>,
311+
snapshotName?: string,
312+
): R;
313+
/**
314+
* This ensures that a value matches the most recent snapshot.
315+
* Check out [the Snapshot Testing guide](https://jestjs.io/docs/en/snapshot-testing) for more information.
316+
*/
317+
toMatchSnapshot(snapshotName?: string): R;
318+
/**
319+
* This ensures that a value matches the most recent snapshot with property matchers.
320+
* Instead of writing the snapshot value to a .snap file, it will be written into the source code automatically.
321+
* Check out [the Snapshot Testing guide](https://jestjs.io/docs/en/snapshot-testing) for more information.
322+
*/
323+
toMatchInlineSnapshot<T extends {[P in keyof R]: unknown}>(
324+
propertyMatchers: Partial<T>,
325+
snapshot?: string,
326+
): R;
327+
/**
328+
* This ensures that a value matches the most recent snapshot with property matchers.
329+
* Instead of writing the snapshot value to a .snap file, it will be written into the source code automatically.
330+
* Check out [the Snapshot Testing guide](https://jestjs.io/docs/en/snapshot-testing) for more information.
331+
*/
332+
toMatchInlineSnapshot(snapshot?: string): R;
333+
/**
334+
* Used to test that a function throws a error matching the most recent snapshot when it is called.
335+
*/
336+
toThrowErrorMatchingSnapshot(): R;
337+
/**
338+
* Used to test that a function throws a error matching the most recent snapshot when it is called.
339+
* Instead of writing the snapshot value to a .snap file, it will be written into the source code automatically.
340+
*/
341+
toThrowErrorMatchingInlineSnapshot(snapshot?: string): R;
342+
/* TODO: END snapshot matchers are not from `expect`, the types should not be here */
343+
}

0 commit comments

Comments
 (0)