Skip to content

Commit 65b9535

Browse files
pedrottimarkSimenB
authored andcommitted
expect: Improve report when matcher fails, part 10 (#7960)
1 parent a0834f8 commit 65b9535

File tree

5 files changed

+155
-65
lines changed

5 files changed

+155
-65
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- `[expect]`: Improve report when matcher fails, part 7 ([#7866](https://github.com/facebook/jest/pull/7866))
66
- `[expect]`: Improve report when matcher fails, part 8 ([#7876](https://github.com/facebook/jest/pull/7876))
77
- `[expect]`: Improve report when matcher fails, part 9 ([#7940](https://github.com/facebook/jest/pull/7940))
8+
- `[expect]`: Improve report when matcher fails, part 10 ([#7960](https://github.com/facebook/jest/pull/7960))
89
- `[pretty-format]` Support `React.memo` ([#7891](https://github.com/facebook/jest/pull/7891))
910
- `[jest-config]` Print error information on preset normalization error ([#7935](https://github.com/facebook/jest/pull/7935))
1011

packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap

Lines changed: 62 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2603,87 +2603,82 @@ Received: <red>{\\"a\\": 1}</>"
26032603
`;
26042604

26052605
exports[`.toHaveLength {pass: false} expect("").toHaveLength(1) 1`] = `
2606-
"<dim>expect(</><red>received</><dim>).toHaveLength(</><green>length</><dim>)</>
2606+
"<dim>expect(</><red>received</><dim>).</>toHaveLength<dim>(</><green>expected</><dim>)</>
26072607

26082608
Expected length: <green>1</>
26092609
Received length: <red>0</>
26102610
Received string: <red>\\"\\"</>"
26112611
`;
26122612

26132613
exports[`.toHaveLength {pass: false} expect("abc").toHaveLength(66) 1`] = `
2614-
"<dim>expect(</><red>received</><dim>).toHaveLength(</><green>length</><dim>)</>
2614+
"<dim>expect(</><red>received</><dim>).</>toHaveLength<dim>(</><green>expected</><dim>)</>
26152615

26162616
Expected length: <green>66</>
26172617
Received length: <red>3</>
26182618
Received string: <red>\\"abc\\"</>"
26192619
`;
26202620

26212621
exports[`.toHaveLength {pass: false} expect(["a", "b"]).toHaveLength(99) 1`] = `
2622-
"<dim>expect(</><red>received</><dim>).toHaveLength(</><green>length</><dim>)</>
2622+
"<dim>expect(</><red>received</><dim>).</>toHaveLength<dim>(</><green>expected</><dim>)</>
26232623

26242624
Expected length: <green>99</>
26252625
Received length: <red>2</>
26262626
Received array: <red>[\\"a\\", \\"b\\"]</>"
26272627
`;
26282628

26292629
exports[`.toHaveLength {pass: false} expect([]).toHaveLength(1) 1`] = `
2630-
"<dim>expect(</><red>received</><dim>).toHaveLength(</><green>length</><dim>)</>
2630+
"<dim>expect(</><red>received</><dim>).</>toHaveLength<dim>(</><green>expected</><dim>)</>
26312631

26322632
Expected length: <green>1</>
26332633
Received length: <red>0</>
26342634
Received array: <red>[]</>"
26352635
`;
26362636

26372637
exports[`.toHaveLength {pass: false} expect([1, 2]).toHaveLength(3) 1`] = `
2638-
"<dim>expect(</><red>received</><dim>).toHaveLength(</><green>length</><dim>)</>
2638+
"<dim>expect(</><red>received</><dim>).</>toHaveLength<dim>(</><green>expected</><dim>)</>
26392639

26402640
Expected length: <green>3</>
26412641
Received length: <red>2</>
26422642
Received array: <red>[1, 2]</>"
26432643
`;
26442644

26452645
exports[`.toHaveLength {pass: true} expect("").toHaveLength(0) 1`] = `
2646-
"<dim>expect(</><red>received</><dim>).</>not<dim>.toHaveLength(</><green>length</><dim>)</>
2646+
"<dim>expect(</><red>received</><dim>).</>not<dim>.</>toHaveLength<dim>(</><green>expected</><dim>)</>
26472647

2648-
Expected length: <green>0</>
2649-
Received length: <red>0</>
2650-
Received string: <red>\\"\\"</>"
2648+
Expected length: not <green>0</>
2649+
Received string: <red>\\"\\"</>"
26512650
`;
26522651

26532652
exports[`.toHaveLength {pass: true} expect("abc").toHaveLength(3) 1`] = `
2654-
"<dim>expect(</><red>received</><dim>).</>not<dim>.toHaveLength(</><green>length</><dim>)</>
2653+
"<dim>expect(</><red>received</><dim>).</>not<dim>.</>toHaveLength<dim>(</><green>expected</><dim>)</>
26552654

2656-
Expected length: <green>3</>
2657-
Received length: <red>3</>
2658-
Received string: <red>\\"abc\\"</>"
2655+
Expected length: not <green>3</>
2656+
Received string: <red>\\"abc\\"</>"
26592657
`;
26602658

26612659
exports[`.toHaveLength {pass: true} expect(["a", "b"]).toHaveLength(2) 1`] = `
2662-
"<dim>expect(</><red>received</><dim>).</>not<dim>.toHaveLength(</><green>length</><dim>)</>
2660+
"<dim>expect(</><red>received</><dim>).</>not<dim>.</>toHaveLength<dim>(</><green>expected</><dim>)</>
26632661

2664-
Expected length: <green>2</>
2665-
Received length: <red>2</>
2666-
Received array: <red>[\\"a\\", \\"b\\"]</>"
2662+
Expected length: not <green>2</>
2663+
Received array: <red>[\\"a\\", \\"b\\"]</>"
26672664
`;
26682665

26692666
exports[`.toHaveLength {pass: true} expect([]).toHaveLength(0) 1`] = `
2670-
"<dim>expect(</><red>received</><dim>).</>not<dim>.toHaveLength(</><green>length</><dim>)</>
2667+
"<dim>expect(</><red>received</><dim>).</>not<dim>.</>toHaveLength<dim>(</><green>expected</><dim>)</>
26712668

2672-
Expected length: <green>0</>
2673-
Received length: <red>0</>
2674-
Received array: <red>[]</>"
2669+
Expected length: not <green>0</>
2670+
Received array: <red>[]</>"
26752671
`;
26762672

26772673
exports[`.toHaveLength {pass: true} expect([1, 2]).toHaveLength(2) 1`] = `
2678-
"<dim>expect(</><red>received</><dim>).</>not<dim>.toHaveLength(</><green>length</><dim>)</>
2674+
"<dim>expect(</><red>received</><dim>).</>not<dim>.</>toHaveLength<dim>(</><green>expected</><dim>)</>
26792675

2680-
Expected length: <green>2</>
2681-
Received length: <red>2</>
2682-
Received array: <red>[1, 2]</>"
2676+
Expected length: not <green>2</>
2677+
Received array: <red>[1, 2]</>"
26832678
`;
26842679

26852680
exports[`.toHaveLength error cases 1`] = `
2686-
"<dim>expect(</><red>received</><dim>).toHaveLength(</><green>expected</><dim>)</>
2681+
"<dim>expect(</><red>received</><dim>).</>toHaveLength<dim>(</><green>expected</><dim>)</>
26872682

26882683
<bold>Matcher error</>: <red>received</> value must have a length property whose value must be a number
26892684

@@ -2692,7 +2687,7 @@ Received has value: <red>{\\"a\\": 9}</>"
26922687
`;
26932688

26942689
exports[`.toHaveLength error cases 2`] = `
2695-
"<dim>expect(</><red>received</><dim>).toHaveLength(</><green>expected</><dim>)</>
2690+
"<dim>expect(</><red>received</><dim>).</>toHaveLength<dim>(</><green>expected</><dim>)</>
26962691

26972692
<bold>Matcher error</>: <red>received</> value must have a length property whose value must be a number
26982693

@@ -2701,22 +2696,58 @@ Received has value: <red>0</>"
27012696
`;
27022697

27032698
exports[`.toHaveLength error cases 3`] = `
2704-
"<dim>expect(</><red>received</><dim>).toHaveLength(</><green>expected</><dim>)</>
2699+
"<dim>expect(</><red>received</><dim>).</>not<dim>.</>toHaveLength<dim>(</><green>expected</><dim>)</>
27052700

27062701
<bold>Matcher error</>: <red>received</> value must have a length property whose value must be a number
27072702

27082703
Received has value: <red>undefined</>"
27092704
`;
27102705

2711-
exports[`.toHaveLength matcher error expected length 1`] = `
2712-
"<dim>expect(</><red>received</><dim>).toHaveLength(</><green>expected</><dim>)</>
2706+
exports[`.toHaveLength matcher error expected length not number 1`] = `
2707+
"<dim>expect(</><red>received</><dim>).</>not<dim>.</>toHaveLength<dim>(</><green>expected</><dim>)</>
27132708

2714-
<bold>Matcher error</>: <green>expected</> value must be a number
2709+
<bold>Matcher error</>: <green>expected</> value must be a non-negative integer
27152710

27162711
Expected has type: string
27172712
Expected has value: <green>\\"3\\"</>"
27182713
`;
27192714

2715+
exports[`.toHaveLength matcher error expected length number Infinity 1`] = `
2716+
"<dim>expect(</><red>received</><dim>).</>rejects<dim>.</>toHaveLength<dim>(</><green>expected</><dim>)</>
2717+
2718+
<bold>Matcher error</>: <green>expected</> value must be a non-negative integer
2719+
2720+
Expected has type: number
2721+
Expected has value: <green>Infinity</>"
2722+
`;
2723+
2724+
exports[`.toHaveLength matcher error expected length number NaN 1`] = `
2725+
"<dim>expect(</><red>received</><dim>).</>rejects<dim>.</>not<dim>.</>toHaveLength<dim>(</><green>expected</><dim>)</>
2726+
2727+
<bold>Matcher error</>: <green>expected</> value must be a non-negative integer
2728+
2729+
Expected has type: number
2730+
Expected has value: <green>NaN</>"
2731+
`;
2732+
2733+
exports[`.toHaveLength matcher error expected length number float 1`] = `
2734+
"<dim>expect(</><red>received</><dim>).</>resolves<dim>.</>toHaveLength<dim>(</><green>expected</><dim>)</>
2735+
2736+
<bold>Matcher error</>: <green>expected</> value must be a non-negative integer
2737+
2738+
Expected has type: number
2739+
Expected has value: <green>0.5</>"
2740+
`;
2741+
2742+
exports[`.toHaveLength matcher error expected length number negative integer 1`] = `
2743+
"<dim>expect(</><red>received</><dim>).</>resolves<dim>.</>not<dim>.</>toHaveLength<dim>(</><green>expected</><dim>)</>
2744+
2745+
<bold>Matcher error</>: <green>expected</> value must be a non-negative integer
2746+
2747+
Expected has type: number
2748+
Expected has value: <green>-3</>"
2749+
`;
2750+
27202751
exports[`.toHaveProperty() {error} expect({"a": {"b": {}}}).toHaveProperty('1') 1`] = `
27212752
"<dim>expect(</><red>received</><dim>).toHaveProperty(</><green>path</><dim>)</>
27222753

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

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,14 +1151,50 @@ describe('.toHaveLength', () => {
11511151
).toThrowErrorMatchingSnapshot();
11521152
expect(() => jestExpect(0).toHaveLength(1)).toThrowErrorMatchingSnapshot();
11531153
expect(() =>
1154-
jestExpect(undefined).toHaveLength(1),
1154+
jestExpect(undefined).not.toHaveLength(1),
11551155
).toThrowErrorMatchingSnapshot();
11561156
});
11571157

1158-
test('matcher error expected length', () => {
1159-
expect(() =>
1160-
jestExpect('abc').toHaveLength('3'),
1161-
).toThrowErrorMatchingSnapshot();
1158+
describe('matcher error expected length', () => {
1159+
test('not number', () => {
1160+
const expected = '3';
1161+
const received = 'abc';
1162+
expect(() => {
1163+
jestExpect(received).not.toHaveLength(expected);
1164+
}).toThrowErrorMatchingSnapshot();
1165+
});
1166+
1167+
test('number Infinity', () => {
1168+
const expected = Infinity;
1169+
const received = Promise.reject('abc');
1170+
return expect(
1171+
jestExpect(received).rejects.toHaveLength(expected),
1172+
).rejects.toThrowErrorMatchingSnapshot();
1173+
});
1174+
1175+
test('number NaN', () => {
1176+
const expected = NaN;
1177+
const received = Promise.reject('abc');
1178+
return expect(
1179+
jestExpect(received).rejects.not.toHaveLength(expected),
1180+
).rejects.toThrowErrorMatchingSnapshot();
1181+
});
1182+
1183+
test('number float', () => {
1184+
const expected = 0.5;
1185+
const received = Promise.resolve('abc');
1186+
return expect(
1187+
jestExpect(received).resolves.toHaveLength(expected),
1188+
).rejects.toThrowErrorMatchingSnapshot();
1189+
});
1190+
1191+
test('number negative integer', () => {
1192+
const expected = -3;
1193+
const received = Promise.resolve('abc');
1194+
return expect(
1195+
jestExpect(received).resolves.not.toHaveLength(expected),
1196+
).rejects.toThrowErrorMatchingSnapshot();
1197+
});
11621198
});
11631199
});
11641200

packages/expect/src/matchers.ts

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
SUGGEST_TO_EQUAL,
1515
SUGGEST_TO_CONTAIN_EQUAL,
1616
diff,
17+
ensureExpectedIsNonNegativeInteger,
1718
ensureNoExpected,
1819
ensureNumbers,
1920
getLabelPrinter,
@@ -505,16 +506,20 @@ const matchers: MatchersObject = {
505506
return {actual: received, expected, message, name: 'toEqual', pass};
506507
},
507508

508-
toHaveLength(this: MatcherState, received: any, length: number) {
509+
toHaveLength(this: MatcherState, received: any, expected: number) {
510+
const isNot = this.isNot;
511+
const options: MatcherHintOptions = {
512+
isNot,
513+
promise: this.promise,
514+
};
515+
509516
if (
510517
typeof received !== 'string' &&
511518
(!received || typeof received.length !== 'number')
512519
) {
513520
throw new Error(
514521
matcherErrorMessage(
515-
matcherHint('.toHaveLength', undefined, undefined, {
516-
isNot: this.isNot,
517-
}),
522+
matcherHint('toHaveLength', undefined, undefined, options),
518523
`${RECEIVED_COLOR(
519524
'received',
520525
)} value must have a length property whose value must be a number`,
@@ -523,39 +528,34 @@ const matchers: MatchersObject = {
523528
);
524529
}
525530

526-
if (typeof length !== 'number') {
527-
throw new Error(
528-
matcherErrorMessage(
529-
matcherHint('.toHaveLength', undefined, undefined, {
530-
isNot: this.isNot,
531-
}),
532-
`${EXPECTED_COLOR('expected')} value must be a number`,
533-
printWithType('Expected', length, printExpected),
534-
),
535-
);
536-
}
531+
ensureExpectedIsNonNegativeInteger(expected, 'toHaveLength', options);
532+
533+
const pass = received.length === expected;
537534

538-
const pass = received.length === length;
539535
const message = () => {
540-
const stringExpected = 'Expected length';
541-
const stringReceivedLength = 'Received length';
542-
const stringReceivedValue = `Received ${getType(received)}`;
536+
const labelExpected = 'Expected length';
537+
const labelReceivedLength = 'Received length';
538+
const labelReceivedValue = `Received ${getType(received)}`;
543539
const printLabel = getLabelPrinter(
544-
stringExpected,
545-
stringReceivedLength,
546-
stringReceivedValue,
540+
labelExpected,
541+
labelReceivedLength,
542+
labelReceivedValue,
547543
);
548544

549545
return (
550-
matcherHint('.toHaveLength', 'received', 'length', {
551-
isNot: this.isNot,
552-
}) +
546+
matcherHint('toHaveLength', undefined, undefined, options) +
553547
'\n\n' +
554-
`${printLabel(stringExpected)}${printExpected(length)}\n` +
555-
`${printLabel(stringReceivedLength)}${printReceived(
556-
received.length,
548+
`${printLabel(labelExpected)}${isNot ? 'not ' : ''}${printExpected(
549+
expected,
557550
)}\n` +
558-
`${printLabel(stringReceivedValue)}${printReceived(received)}`
551+
(isNot
552+
? ''
553+
: `${printLabel(labelReceivedLength)}${printReceived(
554+
received.length,
555+
)}\n`) +
556+
`${printLabel(labelReceivedValue)}${isNot ? ' ' : ''}${printReceived(
557+
received,
558+
)}`
559559
);
560560
};
561561

packages/jest-matcher-utils/src/index.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,28 @@ export const ensureNumbers = (
176176
ensureExpectedIsNumber(expected, matcherName, options);
177177
};
178178

179+
export const ensureExpectedIsNonNegativeInteger = (
180+
expected: unknown,
181+
matcherName: string,
182+
options?: MatcherHintOptions,
183+
) => {
184+
if (
185+
typeof expected !== 'number' ||
186+
!Number.isSafeInteger(expected) ||
187+
expected < 0
188+
) {
189+
// Prepend maybe not only for backward compatibility.
190+
const matcherString = (options ? '' : '[.not]') + matcherName;
191+
throw new Error(
192+
matcherErrorMessage(
193+
matcherHint(matcherString, undefined, undefined, options),
194+
`${EXPECTED_COLOR('expected')} value must be a non-negative integer`,
195+
printWithType('Expected', expected, printExpected),
196+
),
197+
);
198+
}
199+
};
200+
179201
// Sometimes, e.g. when comparing two numbers, the output from jest-diff
180202
// does not contain more information than the `Expected:` / `Received:` already gives.
181203
// In those cases, we do not print a diff to make the output shorter and not redundant.

0 commit comments

Comments
 (0)