From b2ef4391d050148c5fbbb2539034ae5cca9e2a98 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Fri, 22 Feb 2019 12:54:26 -0500 Subject: [PATCH 1/2] expect: Improve report when matcher fails, part 10 --- .../__snapshots__/matchers.test.js.snap | 93 ++++++++++++------- .../expect/src/__tests__/matchers.test.js | 46 ++++++++- packages/expect/src/matchers.ts | 58 ++++++------ packages/jest-matcher-utils/src/index.ts | 22 +++++ 4 files changed, 154 insertions(+), 65 deletions(-) diff --git a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap index d1bbf22e1b1d..8e07feaf3198 100644 --- a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap @@ -2603,7 +2603,7 @@ Received: {\\"a\\": 1}" `; exports[`.toHaveLength {pass: false} expect("").toHaveLength(1) 1`] = ` -"expect(received).toHaveLength(length) +"expect(received).toHaveLength(expected) Expected length: 1 Received length: 0 @@ -2611,7 +2611,7 @@ Received string: \\"\\"" `; exports[`.toHaveLength {pass: false} expect("abc").toHaveLength(66) 1`] = ` -"expect(received).toHaveLength(length) +"expect(received).toHaveLength(expected) Expected length: 66 Received length: 3 @@ -2619,7 +2619,7 @@ Received string: \\"abc\\"" `; exports[`.toHaveLength {pass: false} expect(["a", "b"]).toHaveLength(99) 1`] = ` -"expect(received).toHaveLength(length) +"expect(received).toHaveLength(expected) Expected length: 99 Received length: 2 @@ -2627,7 +2627,7 @@ Received array: [\\"a\\", \\"b\\"]" `; exports[`.toHaveLength {pass: false} expect([]).toHaveLength(1) 1`] = ` -"expect(received).toHaveLength(length) +"expect(received).toHaveLength(expected) Expected length: 1 Received length: 0 @@ -2635,7 +2635,7 @@ Received array: []" `; exports[`.toHaveLength {pass: false} expect([1, 2]).toHaveLength(3) 1`] = ` -"expect(received).toHaveLength(length) +"expect(received).toHaveLength(expected) Expected length: 3 Received length: 2 @@ -2643,47 +2643,42 @@ Received array: [1, 2]" `; exports[`.toHaveLength {pass: true} expect("").toHaveLength(0) 1`] = ` -"expect(received).not.toHaveLength(length) +"expect(received).not.toHaveLength(expected) -Expected length: 0 -Received length: 0 -Received string: \\"\\"" +Expected length: not 0 +Received string: \\"\\"" `; exports[`.toHaveLength {pass: true} expect("abc").toHaveLength(3) 1`] = ` -"expect(received).not.toHaveLength(length) +"expect(received).not.toHaveLength(expected) -Expected length: 3 -Received length: 3 -Received string: \\"abc\\"" +Expected length: not 3 +Received string: \\"abc\\"" `; exports[`.toHaveLength {pass: true} expect(["a", "b"]).toHaveLength(2) 1`] = ` -"expect(received).not.toHaveLength(length) +"expect(received).not.toHaveLength(expected) -Expected length: 2 -Received length: 2 -Received array: [\\"a\\", \\"b\\"]" +Expected length: not 2 +Received array: [\\"a\\", \\"b\\"]" `; exports[`.toHaveLength {pass: true} expect([]).toHaveLength(0) 1`] = ` -"expect(received).not.toHaveLength(length) +"expect(received).not.toHaveLength(expected) -Expected length: 0 -Received length: 0 -Received array: []" +Expected length: not 0 +Received array: []" `; exports[`.toHaveLength {pass: true} expect([1, 2]).toHaveLength(2) 1`] = ` -"expect(received).not.toHaveLength(length) +"expect(received).not.toHaveLength(expected) -Expected length: 2 -Received length: 2 -Received array: [1, 2]" +Expected length: not 2 +Received array: [1, 2]" `; exports[`.toHaveLength error cases 1`] = ` -"expect(received).toHaveLength(expected) +"expect(received).toHaveLength(expected) Matcher error: received value must have a length property whose value must be a number @@ -2692,7 +2687,7 @@ Received has value: {\\"a\\": 9}" `; exports[`.toHaveLength error cases 2`] = ` -"expect(received).toHaveLength(expected) +"expect(received).toHaveLength(expected) Matcher error: received value must have a length property whose value must be a number @@ -2701,22 +2696,58 @@ Received has value: 0" `; exports[`.toHaveLength error cases 3`] = ` -"expect(received).toHaveLength(expected) +"expect(received).not.toHaveLength(expected) Matcher error: received value must have a length property whose value must be a number Received has value: undefined" `; -exports[`.toHaveLength matcher error expected length 1`] = ` -"expect(received).toHaveLength(expected) +exports[`.toHaveLength matcher error expected length not number 1`] = ` +"expect(received).not.toHaveLength(expected) -Matcher error: expected value must be a number +Matcher error: expected value must be a non-negative integer Expected has type: string Expected has value: \\"3\\"" `; +exports[`.toHaveLength matcher error expected length number Infinity 1`] = ` +"expect(received).rejects.toHaveLength(expected) + +Matcher error: expected value must be a non-negative integer + +Expected has type: number +Expected has value: Infinity" +`; + +exports[`.toHaveLength matcher error expected length number NaN 1`] = ` +"expect(received).rejects.not.toHaveLength(expected) + +Matcher error: expected value must be a non-negative integer + +Expected has type: number +Expected has value: NaN" +`; + +exports[`.toHaveLength matcher error expected length number float 1`] = ` +"expect(received).resolves.toHaveLength(expected) + +Matcher error: expected value must be a non-negative integer + +Expected has type: number +Expected has value: 0.5" +`; + +exports[`.toHaveLength matcher error expected length number negative integer 1`] = ` +"expect(received).resolves.not.toHaveLength(expected) + +Matcher error: expected value must be a non-negative integer + +Expected has type: number +Expected has value: -3" +`; + exports[`.toHaveProperty() {error} expect({"a": {"b": {}}}).toHaveProperty('1') 1`] = ` "expect(received).toHaveProperty(path) diff --git a/packages/expect/src/__tests__/matchers.test.js b/packages/expect/src/__tests__/matchers.test.js index fa29aba611ed..89435ff47c1d 100644 --- a/packages/expect/src/__tests__/matchers.test.js +++ b/packages/expect/src/__tests__/matchers.test.js @@ -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(); + }); }); }); diff --git a/packages/expect/src/matchers.ts b/packages/expect/src/matchers.ts index adb628dd4231..e76bd64c63c6 100644 --- a/packages/expect/src/matchers.ts +++ b/packages/expect/src/matchers.ts @@ -14,6 +14,7 @@ import { SUGGEST_TO_EQUAL, SUGGEST_TO_CONTAIN_EQUAL, diff, + ensureExpectedIsNonNegativeInteger, ensureNoExpected, ensureNumbers, getLabelPrinter, @@ -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`, @@ -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, + )}` ); }; diff --git a/packages/jest-matcher-utils/src/index.ts b/packages/jest-matcher-utils/src/index.ts index 293c93e8d344..6207960d5af7 100644 --- a/packages/jest-matcher-utils/src/index.ts +++ b/packages/jest-matcher-utils/src/index.ts @@ -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. From 12abff015b035efb6a9544b09758163d491e991a Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Fri, 22 Feb 2019 13:04:24 -0500 Subject: [PATCH 2/2] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d6ca4619aa6..7da8428efb9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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))