Skip to content

Commit b6f5fcc

Browse files
committed
add .toBeJsonMatching(expectation) matcher
1 parent 5e66791 commit b6f5fcc

File tree

10 files changed

+501
-8
lines changed

10 files changed

+501
-8
lines changed

src/matchers/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export { toBeFrozen } from './toBeFrozen';
2020
export { toBeFunction } from './toBeFunction';
2121
export { toBeHexadecimal } from './toBeHexadecimal';
2222
export { toBeInteger } from './toBeInteger';
23+
export { toBeJsonMatching } from './toBeJsonMatching';
2324
export { toBeNaN } from './toBeNaN';
2425
export { toBeNegative } from './toBeNegative';
2526
export { toBeNil } from './toBeNil';

src/matchers/toBeJsonMatching.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { matchesObject, tryParseJSON } from '../utils';
2+
3+
export function toBeJsonMatching(actual, expected) {
4+
const { printExpected, printReceived, matcherHint } = this.utils;
5+
6+
const passMessage =
7+
`${matcherHint('.not.toBeJsonMatching')}\n\n` +
8+
`Expected input to not be a JSON string containing:\n ${printExpected(expected)}\n` +
9+
`Received:\n ${printReceived(typeof tryParseJSON(actual) !== 'undefined' ? tryParseJSON(actual) : actual)}`;
10+
11+
const failMessage =
12+
`${matcherHint('.toBeJsonMatching')}\n\n` +
13+
`Expected input to be a JSON string containing:\n ${printExpected(expected)}\n` +
14+
`Received:\n ${printReceived(typeof tryParseJSON(actual) !== 'undefined' ? tryParseJSON(actual) : actual)}`;
15+
16+
const pass =
17+
typeof actual === 'string' &&
18+
typeof tryParseJSON(actual) !== 'undefined' &&
19+
matchesObject(this.equals, tryParseJSON(actual), expected);
20+
21+
return { pass, message: () => (pass ? passMessage : failMessage) };
22+
}

src/matchers/toPartiallyContain.js

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
1-
import { containsEntry } from '../utils';
1+
import { partiallyContains } from '../utils';
22

33
export function toPartiallyContain(actual, expected) {
44
const { printReceived, printExpected, matcherHint } = this.utils;
55

6-
const pass =
7-
Array.isArray(actual) &&
8-
Array.isArray([expected]) &&
9-
[expected].every(partial =>
10-
actual.some(value => Object.entries(partial).every(entry => containsEntry(this.equals, value, entry))),
11-
);
6+
const pass = partiallyContains(this.equals, actual, [expected]);
127

138
return {
149
pass,

src/utils/index.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,50 @@ export const isJestMockOrSpy = value => {
1212

1313
export const containsEntry = (equals, obj, [key, value]) =>
1414
obj.hasOwnProperty && Object.prototype.hasOwnProperty.call(obj, key) && equals(obj[key], value);
15+
16+
export const partiallyContains = (equals, actual, expected) =>
17+
Array.isArray(actual) &&
18+
Array.isArray(expected) &&
19+
expected.every(partial =>
20+
actual.some(value => {
21+
if (typeof partial !== 'object' || partial === null) {
22+
return equals(value, partial);
23+
}
24+
if (Array.isArray(partial)) {
25+
return partiallyContains(equals, value, partial);
26+
}
27+
return Object.entries(partial).every(entry => containsEntry(equals, value, entry));
28+
}),
29+
);
30+
31+
export const matchesObject = (equals, actual, expected) => {
32+
if (equals(actual, expected)) {
33+
return true;
34+
}
35+
if (Array.isArray(actual) || Array.isArray(expected)) {
36+
return partiallyContains(equals, actual, expected);
37+
}
38+
if (typeof actual === 'object' && typeof expected === 'object' && expected !== null) {
39+
return Object.getOwnPropertyNames(expected).every(name => {
40+
if (equals(actual[name], expected[name])) {
41+
return true;
42+
}
43+
if (Array.isArray(actual[name]) || Array.isArray(expected[name])) {
44+
return partiallyContains(equals, actual[name], expected[name]);
45+
}
46+
if (typeof actual[name] === 'object' && typeof expected[name] === 'object' && expected[name] !== null) {
47+
return matchesObject(equals, actual[name], expected[name]);
48+
}
49+
return false;
50+
});
51+
}
52+
return false;
53+
};
54+
55+
export const tryParseJSON = input => {
56+
try {
57+
return JSON.parse(input);
58+
} catch {
59+
return undefined;
60+
}
61+
};
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`.not.toBeJsonMatching fails when given JSON string matches expectation 1`] = `
4+
"<dim>expect(</intensity><red>received</color><dim>).not.toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
5+
6+
Expected input to not be a JSON string containing:
7+
<green>{}</color>
8+
Received:
9+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
10+
`;
11+
12+
exports[`.not.toBeJsonMatching fails when given JSON string matches expectation 2`] = `
13+
"<dim>expect(</intensity><red>received</color><dim>).not.toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
14+
15+
Expected input to not be a JSON string containing:
16+
<green>{"a": 42}</color>
17+
Received:
18+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
19+
`;
20+
21+
exports[`.not.toBeJsonMatching fails when given JSON string matches expectation 3`] = `
22+
"<dim>expect(</intensity><red>received</color><dim>).not.toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
23+
24+
Expected input to not be a JSON string containing:
25+
<green>{"a": 42, "b": "foo"}</color>
26+
Received:
27+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
28+
`;
29+
30+
exports[`.not.toBeJsonMatching fails when given JSON string matches expectation 4`] = `
31+
"<dim>expect(</intensity><red>received</color><dim>).not.toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
32+
33+
Expected input to not be a JSON string containing:
34+
<green>{"a": 42, "b": "foo", "c": {}}</color>
35+
Received:
36+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
37+
`;
38+
39+
exports[`.not.toBeJsonMatching fails when given JSON string matches expectation 5`] = `
40+
"<dim>expect(</intensity><red>received</color><dim>).not.toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
41+
42+
Expected input to not be a JSON string containing:
43+
<green>{"a": 42, "b": "foo", "c": {"d": "hello"}}</color>
44+
Received:
45+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
46+
`;
47+
48+
exports[`.not.toBeJsonMatching fails when given JSON string matches expectation 6`] = `
49+
"<dim>expect(</intensity><red>received</color><dim>).not.toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
50+
51+
Expected input to not be a JSON string containing:
52+
<green>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}}</color>
53+
Received:
54+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
55+
`;
56+
57+
exports[`.not.toBeJsonMatching fails when given JSON string matches expectation 7`] = `
58+
"<dim>expect(</intensity><red>received</color><dim>).not.toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
59+
60+
Expected input to not be a JSON string containing:
61+
<green>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": []}</color>
62+
Received:
63+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
64+
`;
65+
66+
exports[`.not.toBeJsonMatching fails when given JSON string matches expectation 8`] = `
67+
"<dim>expect(</intensity><red>received</color><dim>).not.toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
68+
69+
Expected input to not be a JSON string containing:
70+
<green>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7]}</color>
71+
Received:
72+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
73+
`;
74+
75+
exports[`.not.toBeJsonMatching fails when given JSON string matches expectation 9`] = `
76+
"<dim>expect(</intensity><red>received</color><dim>).not.toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
77+
78+
Expected input to not be a JSON string containing:
79+
<green>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {}]}</color>
80+
Received:
81+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
82+
`;
83+
84+
exports[`.not.toBeJsonMatching fails when given JSON string matches expectation 10`] = `
85+
"<dim>expect(</intensity><red>received</color><dim>).not.toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
86+
87+
Expected input to not be a JSON string containing:
88+
<green>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar"}]}</color>
89+
Received:
90+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
91+
`;
92+
93+
exports[`.not.toBeJsonMatching fails when given JSON string matches expectation 11`] = `
94+
"<dim>expect(</intensity><red>received</color><dim>).not.toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
95+
96+
Expected input to not be a JSON string containing:
97+
<green>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>
98+
Received:
99+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
100+
`;
101+
102+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 1`] = `
103+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
104+
105+
Expected input to be a JSON string containing:
106+
<green>null</color>
107+
Received:
108+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
109+
`;
110+
111+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 2`] = `
112+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
113+
114+
Expected input to be a JSON string containing:
115+
<green>[]</color>
116+
Received:
117+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
118+
`;
119+
120+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 3`] = `
121+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
122+
123+
Expected input to be a JSON string containing:
124+
<green>{"a": "42"}</color>
125+
Received:
126+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
127+
`;
128+
129+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 4`] = `
130+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
131+
132+
Expected input to be a JSON string containing:
133+
<green>{"a": 41}</color>
134+
Received:
135+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
136+
`;
137+
138+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 5`] = `
139+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
140+
141+
Expected input to be a JSON string containing:
142+
<green>{"a": []}</color>
143+
Received:
144+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
145+
`;
146+
147+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 6`] = `
148+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
149+
150+
Expected input to be a JSON string containing:
151+
<green>{"a": {}}</color>
152+
Received:
153+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
154+
`;
155+
156+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 7`] = `
157+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
158+
159+
Expected input to be a JSON string containing:
160+
<green>{"a": null}</color>
161+
Received:
162+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
163+
`;
164+
165+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 8`] = `
166+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
167+
168+
Expected input to be a JSON string containing:
169+
<green>{"a": 42, "b": "bar"}</color>
170+
Received:
171+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
172+
`;
173+
174+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 9`] = `
175+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
176+
177+
Expected input to be a JSON string containing:
178+
<green>{"a": 42, "b": "foo", "c": 7}</color>
179+
Received:
180+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
181+
`;
182+
183+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 10`] = `
184+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
185+
186+
Expected input to be a JSON string containing:
187+
<green>{"a": 42, "b": "foo", "c": []}</color>
188+
Received:
189+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
190+
`;
191+
192+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 11`] = `
193+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
194+
195+
Expected input to be a JSON string containing:
196+
<green>{"a": 42, "b": "foo", "c": {"d": "bonjour"}}</color>
197+
Received:
198+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
199+
`;
200+
201+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 12`] = `
202+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
203+
204+
Expected input to be a JSON string containing:
205+
<green>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "dolly"}}</color>
206+
Received:
207+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
208+
`;
209+
210+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 13`] = `
211+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
212+
213+
Expected input to be a JSON string containing:
214+
<green>{"a": 42, "b": "foo", "c": {"d": "hello", "e": 7}}</color>
215+
Received:
216+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
217+
`;
218+
219+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 14`] = `
220+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
221+
222+
Expected input to be a JSON string containing:
223+
<green>{"a": 42, "b": "foo", "c": {"d": "hello", "e": []}}</color>
224+
Received:
225+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
226+
`;
227+
228+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 15`] = `
229+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
230+
231+
Expected input to be a JSON string containing:
232+
<green>{"a": 42, "b": "foo", "c": {"d": "hello", "e": {}}}</color>
233+
Received:
234+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
235+
`;
236+
237+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 16`] = `
238+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
239+
240+
Expected input to be a JSON string containing:
241+
<green>{"a": 42, "b": "foo", "c": {"d": "hello", "e": null}}</color>
242+
Received:
243+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
244+
`;
245+
246+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 17`] = `
247+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
248+
249+
Expected input to be a JSON string containing:
250+
<green>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": 7}</color>
251+
Received:
252+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
253+
`;
254+
255+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 18`] = `
256+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
257+
258+
Expected input to be a JSON string containing:
259+
<green>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": {}}</color>
260+
Received:
261+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
262+
`;
263+
264+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 19`] = `
265+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
266+
267+
Expected input to be a JSON string containing:
268+
<green>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [8]}</color>
269+
Received:
270+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
271+
`;
272+
273+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 20`] = `
274+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
275+
276+
Expected input to be a JSON string containing:
277+
<green>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, []]}</color>
278+
Received:
279+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
280+
`;
281+
282+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 21`] = `
283+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
284+
285+
Expected input to be a JSON string containing:
286+
<green>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "baz"}]}</color>
287+
Received:
288+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
289+
`;
290+
291+
exports[`.toBeJsonMatching fails when given JSON string does not match expectation 22`] = `
292+
"<dim>expect(</intensity><red>received</color><dim>).toBeJsonMatching(</intensity><green>expected</color><dim>)</intensity>
293+
294+
Expected input to be a JSON string containing:
295+
<green>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz", "i": "buzz"}]}</color>
296+
Received:
297+
<red>{"a": 42, "b": "foo", "c": {"d": "hello", "e": "world"}, "f": [7, {"g": "bar", "h": "baz"}]}</color>"
298+
`;

0 commit comments

Comments
 (0)