Skip to content

Commit

Permalink
expect: Improve report when matcher fails, part 10 (#7960)
Browse files Browse the repository at this point in the history
  • Loading branch information
pedrottimark authored and SimenB committed Feb 22, 2019
1 parent a0834f8 commit 65b9535
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 65 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,7 @@
- `[expect]`: Improve report when matcher fails, part 7 ([#7866](https://github.com/facebook/jest/pull/7866))
- `[expect]`: Improve report when matcher fails, part 8 ([#7876](https://github.com/facebook/jest/pull/7876))
- `[expect]`: Improve report when matcher fails, part 9 ([#7940](https://github.com/facebook/jest/pull/7940))
- `[expect]`: Improve report when matcher fails, part 10 ([#7960](https://github.com/facebook/jest/pull/7960))
- `[pretty-format]` Support `React.memo` ([#7891](https://github.com/facebook/jest/pull/7891))
- `[jest-config]` Print error information on preset normalization error ([#7935](https://github.com/facebook/jest/pull/7935))

Expand Down
93 changes: 62 additions & 31 deletions packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap
Expand Up @@ -2603,87 +2603,82 @@ Received: <red>{\\"a\\": 1}</>"
`;

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

Expected length: <green>1</>
Received length: <red>0</>
Received string: <red>\\"\\"</>"
`;

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

Expected length: <green>66</>
Received length: <red>3</>
Received string: <red>\\"abc\\"</>"
`;

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

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

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

Expected length: <green>1</>
Received length: <red>0</>
Received array: <red>[]</>"
`;

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

Expected length: <green>3</>
Received length: <red>2</>
Received array: <red>[1, 2]</>"
`;

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

Expected length: <green>0</>
Received length: <red>0</>
Received string: <red>\\"\\"</>"
Expected length: not <green>0</>
Received string: <red>\\"\\"</>"
`;

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

Expected length: <green>3</>
Received length: <red>3</>
Received string: <red>\\"abc\\"</>"
Expected length: not <green>3</>
Received string: <red>\\"abc\\"</>"
`;

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

Expected length: <green>2</>
Received length: <red>2</>
Received array: <red>[\\"a\\", \\"b\\"]</>"
Expected length: not <green>2</>
Received array: <red>[\\"a\\", \\"b\\"]</>"
`;

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

Expected length: <green>0</>
Received length: <red>0</>
Received array: <red>[]</>"
Expected length: not <green>0</>
Received array: <red>[]</>"
`;

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

Expected length: <green>2</>
Received length: <red>2</>
Received array: <red>[1, 2]</>"
Expected length: not <green>2</>
Received array: <red>[1, 2]</>"
`;

exports[`.toHaveLength error cases 1`] = `
"<dim>expect(</><red>received</><dim>).toHaveLength(</><green>expected</><dim>)</>
"<dim>expect(</><red>received</><dim>).</>toHaveLength<dim>(</><green>expected</><dim>)</>

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

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

exports[`.toHaveLength error cases 2`] = `
"<dim>expect(</><red>received</><dim>).toHaveLength(</><green>expected</><dim>)</>
"<dim>expect(</><red>received</><dim>).</>toHaveLength<dim>(</><green>expected</><dim>)</>

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

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

exports[`.toHaveLength error cases 3`] = `
"<dim>expect(</><red>received</><dim>).toHaveLength(</><green>expected</><dim>)</>
"<dim>expect(</><red>received</><dim>).</>not<dim>.</>toHaveLength<dim>(</><green>expected</><dim>)</>

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

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

exports[`.toHaveLength matcher error expected length 1`] = `
"<dim>expect(</><red>received</><dim>).toHaveLength(</><green>expected</><dim>)</>
exports[`.toHaveLength matcher error expected length not number 1`] = `
"<dim>expect(</><red>received</><dim>).</>not<dim>.</>toHaveLength<dim>(</><green>expected</><dim>)</>

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

Expected has type: string
Expected has value: <green>\\"3\\"</>"
`;

exports[`.toHaveLength matcher error expected length number Infinity 1`] = `
"<dim>expect(</><red>received</><dim>).</>rejects<dim>.</>toHaveLength<dim>(</><green>expected</><dim>)</>

<bold>Matcher error</>: <green>expected</> value must be a non-negative integer

Expected has type: number
Expected has value: <green>Infinity</>"
`;

exports[`.toHaveLength matcher error expected length number NaN 1`] = `
"<dim>expect(</><red>received</><dim>).</>rejects<dim>.</>not<dim>.</>toHaveLength<dim>(</><green>expected</><dim>)</>

<bold>Matcher error</>: <green>expected</> value must be a non-negative integer

Expected has type: number
Expected has value: <green>NaN</>"
`;

exports[`.toHaveLength matcher error expected length number float 1`] = `
"<dim>expect(</><red>received</><dim>).</>resolves<dim>.</>toHaveLength<dim>(</><green>expected</><dim>)</>

<bold>Matcher error</>: <green>expected</> value must be a non-negative integer

Expected has type: number
Expected has value: <green>0.5</>"
`;

exports[`.toHaveLength matcher error expected length number negative integer 1`] = `
"<dim>expect(</><red>received</><dim>).</>resolves<dim>.</>not<dim>.</>toHaveLength<dim>(</><green>expected</><dim>)</>

<bold>Matcher error</>: <green>expected</> value must be a non-negative integer

Expected has type: number
Expected has value: <green>-3</>"
`;

exports[`.toHaveProperty() {error} expect({"a": {"b": {}}}).toHaveProperty('1') 1`] = `
"<dim>expect(</><red>received</><dim>).toHaveProperty(</><green>path</><dim>)</>

Expand Down
46 changes: 41 additions & 5 deletions packages/expect/src/__tests__/matchers.test.js
Expand Up @@ -1151,14 +1151,50 @@ describe('.toHaveLength', () => {
).toThrowErrorMatchingSnapshot();
expect(() => jestExpect(0).toHaveLength(1)).toThrowErrorMatchingSnapshot();
expect(() =>
jestExpect(undefined).toHaveLength(1),
jestExpect(undefined).not.toHaveLength(1),
).toThrowErrorMatchingSnapshot();
});

test('matcher error expected length', () => {
expect(() =>
jestExpect('abc').toHaveLength('3'),
).toThrowErrorMatchingSnapshot();
describe('matcher error expected length', () => {
test('not number', () => {
const expected = '3';
const received = 'abc';
expect(() => {
jestExpect(received).not.toHaveLength(expected);
}).toThrowErrorMatchingSnapshot();
});

test('number Infinity', () => {
const expected = Infinity;
const received = Promise.reject('abc');
return expect(
jestExpect(received).rejects.toHaveLength(expected),
).rejects.toThrowErrorMatchingSnapshot();
});

test('number NaN', () => {
const expected = NaN;
const received = Promise.reject('abc');
return expect(
jestExpect(received).rejects.not.toHaveLength(expected),
).rejects.toThrowErrorMatchingSnapshot();
});

test('number float', () => {
const expected = 0.5;
const received = Promise.resolve('abc');
return expect(
jestExpect(received).resolves.toHaveLength(expected),
).rejects.toThrowErrorMatchingSnapshot();
});

test('number negative integer', () => {
const expected = -3;
const received = Promise.resolve('abc');
return expect(
jestExpect(received).resolves.not.toHaveLength(expected),
).rejects.toThrowErrorMatchingSnapshot();
});
});
});

Expand Down
58 changes: 29 additions & 29 deletions packages/expect/src/matchers.ts
Expand Up @@ -14,6 +14,7 @@ import {
SUGGEST_TO_EQUAL,
SUGGEST_TO_CONTAIN_EQUAL,
diff,
ensureExpectedIsNonNegativeInteger,
ensureNoExpected,
ensureNumbers,
getLabelPrinter,
Expand Down Expand Up @@ -505,16 +506,20 @@ const matchers: MatchersObject = {
return {actual: received, expected, message, name: 'toEqual', pass};
},

toHaveLength(this: MatcherState, received: any, length: number) {
toHaveLength(this: MatcherState, received: any, expected: number) {
const isNot = this.isNot;
const options: MatcherHintOptions = {
isNot,
promise: this.promise,
};

if (
typeof received !== 'string' &&
(!received || typeof received.length !== 'number')
) {
throw new Error(
matcherErrorMessage(
matcherHint('.toHaveLength', undefined, undefined, {
isNot: this.isNot,
}),
matcherHint('toHaveLength', undefined, undefined, options),
`${RECEIVED_COLOR(
'received',
)} value must have a length property whose value must be a number`,
Expand All @@ -523,39 +528,34 @@ const matchers: MatchersObject = {
);
}

if (typeof length !== 'number') {
throw new Error(
matcherErrorMessage(
matcherHint('.toHaveLength', undefined, undefined, {
isNot: this.isNot,
}),
`${EXPECTED_COLOR('expected')} value must be a number`,
printWithType('Expected', length, printExpected),
),
);
}
ensureExpectedIsNonNegativeInteger(expected, 'toHaveLength', options);

const pass = received.length === expected;

const pass = received.length === length;
const message = () => {
const stringExpected = 'Expected length';
const stringReceivedLength = 'Received length';
const stringReceivedValue = `Received ${getType(received)}`;
const labelExpected = 'Expected length';
const labelReceivedLength = 'Received length';
const labelReceivedValue = `Received ${getType(received)}`;
const printLabel = getLabelPrinter(
stringExpected,
stringReceivedLength,
stringReceivedValue,
labelExpected,
labelReceivedLength,
labelReceivedValue,
);

return (
matcherHint('.toHaveLength', 'received', 'length', {
isNot: this.isNot,
}) +
matcherHint('toHaveLength', undefined, undefined, options) +
'\n\n' +
`${printLabel(stringExpected)}${printExpected(length)}\n` +
`${printLabel(stringReceivedLength)}${printReceived(
received.length,
`${printLabel(labelExpected)}${isNot ? 'not ' : ''}${printExpected(
expected,
)}\n` +
`${printLabel(stringReceivedValue)}${printReceived(received)}`
(isNot
? ''
: `${printLabel(labelReceivedLength)}${printReceived(
received.length,
)}\n`) +
`${printLabel(labelReceivedValue)}${isNot ? ' ' : ''}${printReceived(
received,
)}`
);
};

Expand Down
22 changes: 22 additions & 0 deletions packages/jest-matcher-utils/src/index.ts
Expand Up @@ -176,6 +176,28 @@ export const ensureNumbers = (
ensureExpectedIsNumber(expected, matcherName, options);
};

export const ensureExpectedIsNonNegativeInteger = (
expected: unknown,
matcherName: string,
options?: MatcherHintOptions,
) => {
if (
typeof expected !== 'number' ||
!Number.isSafeInteger(expected) ||
expected < 0
) {
// Prepend maybe not only for backward compatibility.
const matcherString = (options ? '' : '[.not]') + matcherName;
throw new Error(
matcherErrorMessage(
matcherHint(matcherString, undefined, undefined, options),
`${EXPECTED_COLOR('expected')} value must be a non-negative integer`,
printWithType('Expected', expected, printExpected),
),
);
}
};

// Sometimes, e.g. when comparing two numbers, the output from jest-diff
// does not contain more information than the `Expected:` / `Received:` already gives.
// In those cases, we do not print a diff to make the output shorter and not redundant.
Expand Down

0 comments on commit 65b9535

Please sign in to comment.