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)) diff --git a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap index fa555849d062..d1bbf22e1b1d 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, precision) + +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..0b5979ff94a2 100644 --- a/packages/expect/src/__tests__/matchers.test.js +++ b/packages/expect/src/__tests__/matchers.test.js @@ -998,6 +998,59 @@ describe('.toBeCloseTo()', () => { ).toThrowErrorMatchingSnapshot(); }); }); + + describe('throws: Matcher error', () => { + test('promise empty isNot false received', () => { + const precision = 3; + const expected = 0; + const received = ''; + expect(() => { + jestExpect(received).toBeCloseTo(expected, precision); + }).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 precision = 3; + const expected = 0; + const received = Promise.resolve(false); + return expect( + 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, precision), + ).rejects.toThrowErrorMatchingSnapshot(); + }); + }); }); describe('.toMatch()', () => { diff --git a/packages/expect/src/matchers.ts b/packages/expect/src/matchers.ts index 6154f33d7873..adb628dd4231 100644 --- a/packages/expect/src/matchers.ts +++ b/packages/expect/src/matchers.ts @@ -93,13 +93,12 @@ 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, }; - ensureNumbers(received, expected, '.toBeCloseTo'); + ensureNumbers(received, expected, 'toBeCloseTo', options); let pass = false; let expectedDiff = 0; @@ -180,7 +179,7 @@ const matchers: MatchersObject = { isNot, promise: this.promise, }; - ensureNumbers(received, expected, '.toBeGreaterThan'); + ensureNumbers(received, expected, 'toBeGreaterThan', options); const pass = received > expected; @@ -203,7 +202,7 @@ const matchers: MatchersObject = { isNot, promise: this.promise, }; - ensureNumbers(received, expected, '.toBeGreaterThanOrEqual'); + ensureNumbers(received, expected, 'toBeGreaterThanOrEqual', options); const pass = received >= expected; @@ -266,7 +265,7 @@ const matchers: MatchersObject = { isNot, promise: this.promise, }; - ensureNumbers(received, expected, '.toBeLessThan'); + ensureNumbers(received, expected, 'toBeLessThan', options); const pass = received < expected; @@ -285,7 +284,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..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 @@ -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, precision) + +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..e5f0ad94cca1 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,90 @@ 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: '', + secondArgument: 'precision', + }; + 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