Skip to content

Commit 664cdbb

Browse files
aymericbouzycpojer
authored andcommitted
Custom Asymmetric Matchers (#5503)
* test * define asymmetric matchers * more * correction * trying to fix flow * flow fix
1 parent 727c377 commit 664cdbb

File tree

6 files changed

+112
-8
lines changed

6 files changed

+112
-8
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,43 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`defines asymmetric matchers 1`] = `
4+
"<dim>expect(</><red>received</><dim>).toEqual(</><green>expected</><dim>)</>
5+
6+
Expected value to equal:
7+
<green>{\\"value\\": toBeDivisibleBy<2>}</>
8+
Received:
9+
<red>{\\"value\\": 3}</>
10+
11+
Difference:
12+
13+
<green>- Expected</>
14+
<red>+ Received</>
15+
16+
<dim> Object {</>
17+
<green>- \\"value\\": toBeDivisibleBy<2>,</>
18+
<red>+ \\"value\\": 3,</>
19+
<dim> }</>"
20+
`;
21+
22+
exports[`defines asymmetric matchers that can be prefixed by not 1`] = `
23+
"<dim>expect(</><red>received</><dim>).toEqual(</><green>expected</><dim>)</>
24+
25+
Expected value to equal:
26+
<green>{\\"value\\": not.toBeDivisibleBy<2>}</>
27+
Received:
28+
<red>{\\"value\\": 2}</>
29+
30+
Difference:
31+
32+
<green>- Expected</>
33+
<red>+ Received</>
34+
35+
<dim> Object {</>
36+
<green>- \\"value\\": not.toBeDivisibleBy<2>,</>
37+
<red>+ \\"value\\": 2,</>
38+
<dim> }</>"
39+
`;
40+
341
exports[`is available globally 1`] = `"expected 15 to be divisible by 2"`;
442
543
exports[`is ok if there is no message specified 1`] = `"<red>No message was specified for this matcher.</>"`;

packages/expect/src/__tests__/extend.test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,21 @@ it('exposes an equality function to custom matchers', () => {
7070

7171
expect(() => jestExpect().toBeOne()).not.toThrow();
7272
});
73+
74+
it('defines asymmetric matchers', () => {
75+
expect(() =>
76+
jestExpect({value: 2}).toEqual({value: jestExpect.toBeDivisibleBy(2)}),
77+
).not.toThrow();
78+
expect(() =>
79+
jestExpect({value: 3}).toEqual({value: jestExpect.toBeDivisibleBy(2)}),
80+
).toThrowErrorMatchingSnapshot();
81+
});
82+
83+
it('defines asymmetric matchers that can be prefixed by not', () => {
84+
expect(() =>
85+
jestExpect({value: 2}).toEqual({value: jestExpect.not.toBeDivisibleBy(2)}),
86+
).toThrowErrorMatchingSnapshot();
87+
expect(() =>
88+
jestExpect({value: 3}).toEqual({value: jestExpect.not.toBeDivisibleBy(2)}),
89+
).not.toThrow();
90+
});

packages/expect/src/asymmetric_matchers.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717

1818
import {emptyObject} from './utils';
1919

20-
class AsymmetricMatcher {
20+
export class AsymmetricMatcher {
2121
$$typeof: Symbol;
2222
inverse: boolean;
2323

packages/expect/src/index.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ const makeThrowingMatcher = (
258258
};
259259

260260
expect.extend = (matchers: MatchersObject): void =>
261-
setMatchers(matchers, false);
261+
setMatchers(matchers, false, expect);
262262

263263
expect.anything = anything;
264264
expect.any = any;
@@ -294,15 +294,15 @@ const _validateResult = result => {
294294
};
295295

296296
// add default jest matchers
297-
setMatchers(matchers, true);
298-
setMatchers(spyMatchers, true);
299-
setMatchers(toThrowMatchers, true);
297+
setMatchers(matchers, true, expect);
298+
setMatchers(spyMatchers, true, expect);
299+
setMatchers(toThrowMatchers, true, expect);
300300

301301
expect.addSnapshotSerializer = () => void 0;
302302
expect.assertions = (expected: number) => {
303303
getState().expectedAssertionsNumber = expected;
304304
};
305-
expect.hasAssertions = expected => {
305+
expect.hasAssertions = (expected: any) => {
306306
utils.ensureNoExpected(expected, '.hasAssertions');
307307
getState().isExpectingAssertions = true;
308308
};

packages/expect/src/jest_matchers_object.js

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
* @flow
88
*/
99

10-
import type {MatchersObject} from 'types/Matchers';
10+
import {AsymmetricMatcher} from './asymmetric_matchers';
11+
import type {Expect, MatchersObject} from 'types/Matchers';
1112

1213
// Global matchers object holds the list of available matchers and
1314
// the state, that can hold matcher specific values that change over time.
@@ -39,12 +40,57 @@ export const setState = (state: Object) => {
3940

4041
export const getMatchers = () => global[JEST_MATCHERS_OBJECT].matchers;
4142

42-
export const setMatchers = (matchers: MatchersObject, isInternal: boolean) => {
43+
export const setMatchers = (
44+
matchers: MatchersObject,
45+
isInternal: boolean,
46+
expect: Expect,
47+
) => {
4348
Object.keys(matchers).forEach(key => {
4449
const matcher = matchers[key];
4550
Object.defineProperty(matcher, INTERNAL_MATCHER_FLAG, {
4651
value: isInternal,
4752
});
53+
54+
if (!isInternal) {
55+
// expect is defined
56+
57+
class CustomMatcher extends AsymmetricMatcher {
58+
sample: any;
59+
60+
constructor(sample: any, inverse: boolean = false) {
61+
super();
62+
this.sample = sample;
63+
this.inverse = inverse;
64+
}
65+
66+
asymmetricMatch(other: any) {
67+
const {pass}: {message: () => string, pass: boolean} = matcher(
68+
(other: any),
69+
(this.sample: any),
70+
);
71+
72+
return this.inverse ? !pass : pass;
73+
}
74+
75+
toString() {
76+
return `${this.inverse ? 'not.' : ''}${key}`;
77+
}
78+
79+
getExpectedType() {
80+
return 'any';
81+
}
82+
83+
toAsymmetricMatcher() {
84+
return `${this.toString()}<${this.sample}>`;
85+
}
86+
}
87+
88+
expect[key] = (sample: any) => new CustomMatcher(sample);
89+
if (!expect.not) {
90+
expect.not = {};
91+
}
92+
expect.not[key] = (sample: any) => new CustomMatcher(sample, true);
93+
}
4894
});
4995

5096
Object.assign(global[JEST_MATCHERS_OBJECT].matchers, matchers);

types/Matchers.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ export type Expect = {
5959
objectContaining(sample: Object): AsymmetricMatcher,
6060
stringContaining(expected: string): AsymmetricMatcher,
6161
stringMatching(expected: string | RegExp): AsymmetricMatcher,
62+
[id: string]: AsymmetricMatcher,
63+
not: {[id: string]: AsymmetricMatcher},
6264
};
6365

6466
export type ExpectationObject = {

0 commit comments

Comments
 (0)