From 5b96aaf67e0c78013d785a42d25ffbfefe55c03c Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 20 Feb 2019 13:32:48 -0500 Subject: [PATCH 1/5] expect, jest-matcher-utils: Improve report when matcher fails, part 9 --- .../__snapshots__/matchers.test.js.snap | 52 ++++++++++++ .../expect/src/__tests__/matchers.test.js | 50 ++++++++++++ packages/expect/src/matchers.ts | 10 +-- .../__snapshots__/index.test.ts.snap | 60 +++++++++++++- .../src/__tests__/index.test.ts | 79 ++++++++++++++++++- packages/jest-matcher-utils/src/index.ts | 22 ++++-- 6 files changed, 253 insertions(+), 20 deletions(-) diff --git a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap index fa555849d062..52a1fbabf5cc 100644 --- a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap @@ -563,6 +563,58 @@ Expected difference: < 0.005 Received difference: 0.005000099999999952" `; +exports[`.toBeCloseTo() throws: Matcher error promise empty isNot false received 1`] = ` +"expect(received).toBeCloseTo(expected) + +Matcher error: received value must be a number + +Received has type: string +Received has value: \\"\\"" +`; + +exports[`.toBeCloseTo() throws: Matcher error promise empty isNot true expected 1`] = ` +"expect(received).not.toBeCloseTo(expected) + +Matcher error: expected value must be a number + +Expected has value: undefined" +`; + +exports[`.toBeCloseTo() throws: Matcher error promise rejects isNot false expected 1`] = ` +"expect(received).rejects.toBeCloseTo(expected) + +Matcher error: expected value must be a number + +Expected has type: string +Expected has value: \\"0\\"" +`; + +exports[`.toBeCloseTo() throws: Matcher error promise rejects isNot true received 1`] = ` +"expect(received).rejects.not.toBeCloseTo(expected) + +Matcher error: received value must be a number + +Received has type: symbol +Received has value: Symbol(0.1)" +`; + +exports[`.toBeCloseTo() throws: Matcher error promise resolves isNot false received 1`] = ` +"expect(received).resolves.toBeCloseTo(expected, precision) + +Matcher error: received value must be a number + +Received has type: boolean +Received has value: false" +`; + +exports[`.toBeCloseTo() throws: Matcher error promise resolves isNot true expected 1`] = ` +"expect(received).resolves.not.toBeCloseTo(expected, precision) + +Matcher error: expected value must be a number + +Expected has value: null" +`; + exports[`.toBeDefined(), .toBeUndefined() '"a"' is defined 1`] = ` "expect(received).not.toBeDefined() diff --git a/packages/expect/src/__tests__/matchers.test.js b/packages/expect/src/__tests__/matchers.test.js index bff61b7edc12..662775014118 100644 --- a/packages/expect/src/__tests__/matchers.test.js +++ b/packages/expect/src/__tests__/matchers.test.js @@ -998,6 +998,56 @@ describe('.toBeCloseTo()', () => { ).toThrowErrorMatchingSnapshot(); }); }); + + describe('throws: Matcher error', () => { + test('promise empty isNot false received', () => { + const expected = 0; + const received = ''; + expect(() => { + jestExpect(received).toBeCloseTo(expected); + }).toThrowErrorMatchingSnapshot(); + }); + + test('promise empty isNot true expected', () => { + const received = 0.1; + // expected is undefined + expect(() => { + jestExpect(received).not.toBeCloseTo(); + }).toThrowErrorMatchingSnapshot(); + }); + + test('promise rejects isNot false expected', () => { + const expected = '0'; + const received = Promise.reject(0.01); + return expect( + jestExpect(received).rejects.toBeCloseTo(expected), + ).rejects.toThrowErrorMatchingSnapshot(); + }); + + test('promise rejects isNot true received', () => { + const expected = 0; + const received = Promise.reject(Symbol('0.1')); + return expect( + jestExpect(received).rejects.not.toBeCloseTo(expected), + ).rejects.toThrowErrorMatchingSnapshot(); + }); + + test('promise resolves isNot false received', () => { + const expected = 0; + const received = Promise.resolve(false); + return expect( + jestExpect(received).resolves.toBeCloseTo(expected, 3), + ).rejects.toThrowErrorMatchingSnapshot(); + }); + + test('promise resolves isNot true expected', () => { + const expected = null; + const received = Promise.resolve(0.1); + expect( + jestExpect(received).resolves.not.toBeCloseTo(expected, 3), + ).rejects.toThrowErrorMatchingSnapshot(); + }); + }); }); describe('.toMatch()', () => { diff --git a/packages/expect/src/matchers.ts b/packages/expect/src/matchers.ts index 6154f33d7873..37a41e944db6 100644 --- a/packages/expect/src/matchers.ts +++ b/packages/expect/src/matchers.ts @@ -99,7 +99,7 @@ const matchers: MatchersObject = { promise: this.promise, secondArgument, }; - ensureNumbers(received, expected, '.toBeCloseTo'); + ensureNumbers(received, expected, 'toBeCloseTo', options); let pass = false; let expectedDiff = 0; @@ -180,7 +180,7 @@ const matchers: MatchersObject = { isNot, promise: this.promise, }; - ensureNumbers(received, expected, '.toBeGreaterThan'); + ensureNumbers(received, expected, 'toBeGreaterThan', options); const pass = received > expected; @@ -203,7 +203,7 @@ const matchers: MatchersObject = { isNot, promise: this.promise, }; - ensureNumbers(received, expected, '.toBeGreaterThanOrEqual'); + ensureNumbers(received, expected, 'toBeGreaterThanOrEqual', options); const pass = received >= expected; @@ -266,7 +266,7 @@ const matchers: MatchersObject = { isNot, promise: this.promise, }; - ensureNumbers(received, expected, '.toBeLessThan'); + ensureNumbers(received, expected, 'toBeLessThan', options); const pass = received < expected; @@ -285,7 +285,7 @@ const matchers: MatchersObject = { isNot, promise: this.promise, }; - ensureNumbers(received, expected, '.toBeLessThanOrEqual'); + ensureNumbers(received, expected, 'toBeLessThanOrEqual', options); const pass = received <= expected; diff --git a/packages/jest-matcher-utils/src/__tests__/__snapshots__/index.test.ts.snap b/packages/jest-matcher-utils/src/__tests__/__snapshots__/index.test.ts.snap index 6a3d31c4315c..1445078d7c74 100644 --- a/packages/jest-matcher-utils/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/jest-matcher-utils/src/__tests__/__snapshots__/index.test.ts.snap @@ -18,8 +18,8 @@ Expected has type: object Expected has value: {\\"a\\": 1}" `; -exports[`.ensureNumbers() throws error when expected is not a number 1`] = ` -"expect(received)[.not]This matcher(expected) +exports[`.ensureNumbers() throws error when expected is not a number (backward compatibility) 1`] = ` +"expect(received)[.not].toBeCloseTo(expected) Matcher error: expected value must be a number @@ -27,8 +27,8 @@ Expected has type: string Expected has value: \\"not_a_number\\"" `; -exports[`.ensureNumbers() throws error when received is not a number 1`] = ` -"expect(received)[.not]This matcher(expected) +exports[`.ensureNumbers() throws error when received is not a number (backward compatibility) 1`] = ` +"expect(received)[.not].toBeCloseTo(expected) Matcher error: received value must be a number @@ -36,6 +36,58 @@ Received has type: string Received has value: \\"not_a_number\\"" `; +exports[`.ensureNumbers() with options promise empty isNot false received 1`] = ` +"expect(received).toBeCloseTo(expected) + +Matcher error: received value must be a number + +Received has type: string +Received has value: \\"\\"" +`; + +exports[`.ensureNumbers() with options promise empty isNot true expected 1`] = ` +"expect(received).not.toBeCloseTo(expected) + +Matcher error: expected value must be a number + +Expected has value: undefined" +`; + +exports[`.ensureNumbers() with options promise rejects isNot false expected 1`] = ` +"expect(received).rejects.toBeCloseTo(expected) + +Matcher error: expected value must be a number + +Expected has type: string +Expected has value: \\"0\\"" +`; + +exports[`.ensureNumbers() with options promise rejects isNot true received 1`] = ` +"expect(received).rejects.not.toBeCloseTo(expected) + +Matcher error: received value must be a number + +Received has type: symbol +Received has value: Symbol(0.1)" +`; + +exports[`.ensureNumbers() with options promise resolves isNot false received 1`] = ` +"expect(received).resolves.toBeCloseTo(expected) + +Matcher error: received value must be a number + +Received has type: boolean +Received has value: false" +`; + +exports[`.ensureNumbers() with options promise resolves isNot true expected 1`] = ` +"expect(received).resolves.not.toBeCloseTo(expected) + +Matcher error: expected value must be a number + +Expected has value: null" +`; + exports[`.stringify() reduces maxDepth if stringifying very large objects 1`] = `"{\\"a\\": 1, \\"b\\": [Object]}"`; exports[`.stringify() reduces maxDepth if stringifying very large objects 2`] = `"{\\"a\\": 1, \\"b\\": {\\"0\\": \\"test\\", \\"1\\": \\"test\\", \\"2\\": \\"test\\", \\"3\\": \\"test\\", \\"4\\": \\"test\\", \\"5\\": \\"test\\", \\"6\\": \\"test\\", \\"7\\": \\"test\\", \\"8\\": \\"test\\", \\"9\\": \\"test\\"}}"`; diff --git a/packages/jest-matcher-utils/src/__tests__/index.test.ts b/packages/jest-matcher-utils/src/__tests__/index.test.ts index a78976c11a3c..4d6e7806fa0f 100644 --- a/packages/jest-matcher-utils/src/__tests__/index.test.ts +++ b/packages/jest-matcher-utils/src/__tests__/index.test.ts @@ -13,6 +13,7 @@ import { getLabelPrinter, pluralize, stringify, + MatcherHintOptions, } from '../'; describe('.stringify()', () => { @@ -98,19 +99,89 @@ describe('.ensureNumbers()', () => { }).not.toThrow(); }); - test('throws error when expected is not a number', () => { + test('throws error when expected is not a number (backward compatibility)', () => { expect(() => { // @ts-ignore - ensureNumbers(1, 'not_a_number'); + ensureNumbers(1, 'not_a_number', '.toBeCloseTo'); }).toThrowErrorMatchingSnapshot(); }); - test('throws error when received is not a number', () => { + test('throws error when received is not a number (backward compatibility)', () => { expect(() => { // @ts-ignore - ensureNumbers('not_a_number', 3); + ensureNumbers('not_a_number', 3, '.toBeCloseTo'); }).toThrowErrorMatchingSnapshot(); }); + + describe('with options', () => { + const matcherName = 'toBeCloseTo'; + + test('promise empty isNot false received', () => { + const options: MatcherHintOptions = { + isNot: false, + promise: '', + }; + expect(() => { + // @ts-ignore + ensureNumbers('', 0, matcherName, options); + }).toThrowErrorMatchingSnapshot(); + }); + + test('promise empty isNot true expected', () => { + const options: MatcherHintOptions = { + isNot: true, + // promise undefined is equivalent to empty string + }; + expect(() => { + // @ts-ignore + ensureNumbers(0.1, undefined, matcherName, options); + }).toThrowErrorMatchingSnapshot(); + }); + + test('promise rejects isNot false expected', () => { + const options: MatcherHintOptions = { + isNot: false, + promise: 'rejects', + }; + expect(() => { + // @ts-ignore + ensureNumbers(0.01, '0', matcherName, options); + }).toThrowErrorMatchingSnapshot(); + }); + + test('promise rejects isNot true received', () => { + const options: MatcherHintOptions = { + isNot: true, + promise: 'rejects', + }; + expect(() => { + // @ts-ignore + ensureNumbers(Symbol('0.1'), 0, matcherName, options); + }).toThrowErrorMatchingSnapshot(); + }); + + test('promise resolves isNot false received', () => { + const options: MatcherHintOptions = { + isNot: false, + promise: 'resolves', + }; + expect(() => { + // @ts-ignore + ensureNumbers(false, 0, matcherName, options); + }).toThrowErrorMatchingSnapshot(); + }); + + test('promise resolves isNot true expected', () => { + const options: MatcherHintOptions = { + isNot: true, + promise: 'resolves', + }; + expect(() => { + // @ts-ignore + ensureNumbers(0.1, null, matcherName, options); + }).toThrowErrorMatchingSnapshot(); + }); + }); }); describe('.ensureNoExpected()', () => { diff --git a/packages/jest-matcher-utils/src/index.ts b/packages/jest-matcher-utils/src/index.ts index ef5121d7210a..293c93e8d344 100644 --- a/packages/jest-matcher-utils/src/index.ts +++ b/packages/jest-matcher-utils/src/index.ts @@ -130,12 +130,17 @@ export const ensureNoExpected = ( } }; -export const ensureActualIsNumber = (actual: unknown, matcherName: string) => { - matcherName || (matcherName = 'This matcher'); +export const ensureActualIsNumber = ( + actual: unknown, + matcherName: string, + options?: MatcherHintOptions, +) => { if (typeof actual !== 'number') { + // Prepend maybe not only for backward compatibility. + const matcherString = (options ? '' : '[.not]') + matcherName; throw new Error( matcherErrorMessage( - matcherHint('[.not]' + matcherName), + matcherHint(matcherString, undefined, undefined, options), `${RECEIVED_COLOR('received')} value must be a number`, printWithType('Received', actual, printReceived), ), @@ -146,12 +151,14 @@ export const ensureActualIsNumber = (actual: unknown, matcherName: string) => { export const ensureExpectedIsNumber = ( expected: unknown, matcherName: string, + options?: MatcherHintOptions, ) => { - matcherName || (matcherName = 'This matcher'); if (typeof expected !== 'number') { + // Prepend maybe not only for backward compatibility. + const matcherString = (options ? '' : '[.not]') + matcherName; throw new Error( matcherErrorMessage( - matcherHint('[.not]' + matcherName), + matcherHint(matcherString, undefined, undefined, options), `${EXPECTED_COLOR('expected')} value must be a number`, printWithType('Expected', expected, printExpected), ), @@ -163,9 +170,10 @@ export const ensureNumbers = ( actual: unknown, expected: unknown, matcherName: string, + options?: MatcherHintOptions, ) => { - ensureActualIsNumber(actual, matcherName); - ensureExpectedIsNumber(expected, matcherName); + ensureActualIsNumber(actual, matcherName, options); + ensureExpectedIsNumber(expected, matcherName, options); }; // Sometimes, e.g. when comparing two numbers, the output from jest-diff From 17b5610c91c9a6f70638d6d2f57970f492feb0b3 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 20 Feb 2019 13:47:24 -0500 Subject: [PATCH 2/5] Test secondArgument property in options argument --- .../expect/src/__tests__/__snapshots__/matchers.test.js.snap | 2 +- packages/expect/src/__tests__/matchers.test.js | 3 ++- .../src/__tests__/__snapshots__/index.test.ts.snap | 2 +- packages/jest-matcher-utils/src/__tests__/index.test.ts | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap index 52a1fbabf5cc..d1bbf22e1b1d 100644 --- a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap @@ -564,7 +564,7 @@ Received difference: 0.005000099999999952" `; exports[`.toBeCloseTo() throws: Matcher error promise empty isNot false received 1`] = ` -"expect(received).toBeCloseTo(expected) +"expect(received).toBeCloseTo(expected, precision) Matcher error: received value must be a number diff --git a/packages/expect/src/__tests__/matchers.test.js b/packages/expect/src/__tests__/matchers.test.js index 662775014118..70e6162e898f 100644 --- a/packages/expect/src/__tests__/matchers.test.js +++ b/packages/expect/src/__tests__/matchers.test.js @@ -1001,10 +1001,11 @@ describe('.toBeCloseTo()', () => { describe('throws: Matcher error', () => { test('promise empty isNot false received', () => { + const precision = 3; const expected = 0; const received = ''; expect(() => { - jestExpect(received).toBeCloseTo(expected); + jestExpect(received).toBeCloseTo(expected, precision); }).toThrowErrorMatchingSnapshot(); }); diff --git a/packages/jest-matcher-utils/src/__tests__/__snapshots__/index.test.ts.snap b/packages/jest-matcher-utils/src/__tests__/__snapshots__/index.test.ts.snap index 1445078d7c74..208598d4c2f2 100644 --- a/packages/jest-matcher-utils/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/jest-matcher-utils/src/__tests__/__snapshots__/index.test.ts.snap @@ -37,7 +37,7 @@ Received has value: \\"not_a_number\\"" `; exports[`.ensureNumbers() with options promise empty isNot false received 1`] = ` -"expect(received).toBeCloseTo(expected) +"expect(received).toBeCloseTo(expected, precision) Matcher error: received value must be a number diff --git a/packages/jest-matcher-utils/src/__tests__/index.test.ts b/packages/jest-matcher-utils/src/__tests__/index.test.ts index 4d6e7806fa0f..e5f0ad94cca1 100644 --- a/packages/jest-matcher-utils/src/__tests__/index.test.ts +++ b/packages/jest-matcher-utils/src/__tests__/index.test.ts @@ -120,6 +120,7 @@ describe('.ensureNumbers()', () => { const options: MatcherHintOptions = { isNot: false, promise: '', + secondArgument: 'precision', }; expect(() => { // @ts-ignore From 4b44105efc665fda75a479723217fdb3daec01d5 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 20 Feb 2019 13:56:45 -0500 Subject: [PATCH 3/5] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 570249abec1d..553d66679df7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,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)) - `[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)) From 3882b1e8dcaa761fd60fcd08d3700047ee59d7a2 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 20 Feb 2019 14:05:58 -0500 Subject: [PATCH 4/5] Consistently use variable for precision to make it findable --- packages/expect/src/__tests__/matchers.test.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/expect/src/__tests__/matchers.test.js b/packages/expect/src/__tests__/matchers.test.js index 70e6162e898f..0b5979ff94a2 100644 --- a/packages/expect/src/__tests__/matchers.test.js +++ b/packages/expect/src/__tests__/matchers.test.js @@ -1034,18 +1034,20 @@ describe('.toBeCloseTo()', () => { }); test('promise resolves isNot false received', () => { + const precision = 3; const expected = 0; const received = Promise.resolve(false); return expect( - jestExpect(received).resolves.toBeCloseTo(expected, 3), + jestExpect(received).resolves.toBeCloseTo(expected, precision), ).rejects.toThrowErrorMatchingSnapshot(); }); test('promise resolves isNot true expected', () => { + const precision = 3; const expected = null; const received = Promise.resolve(0.1); expect( - jestExpect(received).resolves.not.toBeCloseTo(expected, 3), + jestExpect(received).resolves.not.toBeCloseTo(expected, precision), ).rejects.toThrowErrorMatchingSnapshot(); }); }); From befaaaaff82bb13c7f8b80ccef92d79c0f000f00 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 20 Feb 2019 16:03:51 -0500 Subject: [PATCH 5/5] Remove unneeded isNot local variable --- packages/expect/src/matchers.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/expect/src/matchers.ts b/packages/expect/src/matchers.ts index 37a41e944db6..adb628dd4231 100644 --- a/packages/expect/src/matchers.ts +++ b/packages/expect/src/matchers.ts @@ -93,9 +93,8 @@ const matchers: MatchersObject = { precision: number = 2, ) { const secondArgument = arguments.length === 3 ? 'precision' : undefined; - const isNot = this.isNot; const options: MatcherHintOptions = { - isNot, + isNot: this.isNot, promise: this.promise, secondArgument, };