diff --git a/CHANGELOG.md b/CHANGELOG.md index 6689292c5053..414babc426d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ - `[jest-validate]` Add syntax to validate multiple permitted types ([#7207](https://github.com/facebook/jest/pull/7207)) - `[jest-config]` Accept an array as as well as a string for `testRegex`([#7209]https://github.com/facebook/jest/pull/7209)) - `[expect/jest-matcher-utils]` Improve report when assertion fails, part 4 ([#7241](https://github.com/facebook/jest/pull/7241)) +- `[expect/jest-matcher-utils]` Improve report when assertion fails, part 5 ([#7557](https://github.com/facebook/jest/pull/7557)) - `[expect]` Check constructor equality in .toStrictEqual() ([#7005](https://github.com/facebook/jest/pull/7005)) - `[jest-util]` Add `jest.getTimerCount()` to get the count of scheduled fake timers ([#7285](https://github.com/facebook/jest/pull/7285)) - `[jest-config]` Add `dependencyExtractor` option to use a custom module to extract dependencies from files ([#7313](https://github.com/facebook/jest/pull/7313), [#7349](https://github.com/facebook/jest/pull/7349), [#7350](https://github.com/facebook/jest/pull/7350)) diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index c362734a8f8f..f9946e51c2b6 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -117,7 +117,15 @@ These helper functions and properties can be found on `this` inside a custom mat #### `this.isNot` -A boolean to let you know this matcher was called with the negated `.not` modifier allowing you to flip your assertion. +A boolean to let you know this matcher was called with the negated `.not` modifier allowing you to flip your assertion and display a clear and correct matcher hint (see example code). + +#### `this.promise` + +A string allowing you to display a clear and correct matcher hint: + +- `'rejects'` if matcher was called with the promise `.rejects` modifier +- `'resolves'` if matcher was called with the promise `.resolves` modifier +- `''` if matcher was not called with a promise modifier #### `this.equals(a, b)` @@ -137,28 +145,31 @@ The most useful ones are `matcherHint`, `printExpected` and `printReceived` to f const diff = require('jest-diff'); expect.extend({ toBe(received, expected) { + const options = { + comment: 'Object.is equality', + isNot: this.isNot, + promise: this.promise, + }; + const pass = Object.is(received, expected); const message = pass ? () => - this.utils.matcherHint('.not.toBe') + + this.utils.matcherHint('toBe', undefined, undefined, options) + '\n\n' + - `Expected value to not be (using Object.is):\n` + - ` ${this.utils.printExpected(expected)}\n` + - `Received:\n` + - ` ${this.utils.printReceived(received)}` + `Expected: ${this.utils.printExpected(expected)}\n` + + `Received: ${this.utils.printReceived(received)}` : () => { - const diffString = diff(expected, received, { + const difference = diff(expected, received, { expand: this.expand, }); return ( - this.utils.matcherHint('.toBe') + + this.utils.matcherHint('toBe', undefined, undefined, options) + '\n\n' + - `Expected value to be (using Object.is):\n` + - ` ${this.utils.printExpected(expected)}\n` + - `Received:\n` + - ` ${this.utils.printReceived(received)}` + - (diffString ? `\n\nDifference:\n\n${diffString}` : '') + (difference && difference.includes('- Expect') + ? `Difference:\n\n${diffString}` + : `Expected: ${this.utils.printExpected(expected)}\n` + + `Received: ${this.utils.printReceived(received)}`) ); }; diff --git a/e2e/__tests__/__snapshots__/failures.test.js.snap b/e2e/__tests__/__snapshots__/failures.test.js.snap index b202ca4340d4..08a7abceff5d 100644 --- a/e2e/__tests__/__snapshots__/failures.test.js.snap +++ b/e2e/__tests__/__snapshots__/failures.test.js.snap @@ -360,8 +360,8 @@ exports[`works with async failures 1`] = ` expect(received).rejects.toEqual() - Expected received Promise to reject, instead it resolved to value - {\\"foo\\": \\"bar\\"} + Received promise resolved instead of rejected + Resolved to value: {\\"foo\\": \\"bar\\"} 16 | 17 | test('expect reject', () => @@ -377,8 +377,8 @@ exports[`works with async failures 1`] = ` expect(received).resolves.toEqual() - Expected received Promise to resolve, instead it rejected to value - {\\"foo\\": \\"bar\\"} + Received promise rejected instead of resolved + Rejected to value: {\\"foo\\": \\"bar\\"} 19 | 20 | test('expect resolve', () => diff --git a/packages/expect/src/__tests__/__snapshots__/assertionCounts.test.js.snap b/packages/expect/src/__tests__/__snapshots__/assertionCounts.test.js.snap index c7474a6ec766..8f2f6f1f5eb0 100644 --- a/packages/expect/src/__tests__/__snapshots__/assertionCounts.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/assertionCounts.test.js.snap @@ -1,9 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`.hasAssertions() throws if expected is not undefined 1`] = ` -"expect(received)[.not].hasAssertions() +"expect(received)[.not].hasAssertions() -Matcher error: expected value must be omitted or undefined +Matcher error: this matcher must not have an expected argument Expected has type: number Expected has value: 2" diff --git a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap index 19fe16e74e51..2fd4578e636e 100644 --- a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap @@ -1,225 +1,225 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`.rejects fails for promise that resolves 1`] = ` -"expect(received).rejects.toBe() +"expect(received).rejects.toBe() -Expected received Promise to reject, instead it resolved to value - 4" +Received promise resolved instead of rejected +Resolved to value: 4" `; exports[`.rejects fails non-promise value "a" 1`] = ` -"expect(received).rejects.toBeDefined() +"expect(received).rejects.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has type: string Received has value: \\"a\\"" `; exports[`.rejects fails non-promise value [1] 1`] = ` -"expect(received).rejects.toBeDefined() +"expect(received).rejects.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has type: array Received has value: [1]" `; exports[`.rejects fails non-promise value [Function anonymous] 1`] = ` -"expect(received).rejects.toBeDefined() +"expect(received).rejects.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has type: function Received has value: [Function anonymous]" `; exports[`.rejects fails non-promise value {"a": 1} 1`] = ` -"expect(received).rejects.toBeDefined() +"expect(received).rejects.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has type: object Received has value: {\\"a\\": 1}" `; exports[`.rejects fails non-promise value 4 1`] = ` -"expect(received).rejects.toBeDefined() +"expect(received).rejects.not.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has type: number Received has value: 4" `; exports[`.rejects fails non-promise value null 1`] = ` -"expect(received).rejects.toBeDefined() +"expect(received).rejects.not.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has value: null" `; exports[`.rejects fails non-promise value true 1`] = ` -"expect(received).rejects.toBeDefined() +"expect(received).rejects.not.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has type: boolean Received has value: true" `; exports[`.rejects fails non-promise value undefined 1`] = ` -"expect(received).rejects.toBeDefined() +"expect(received).rejects.not.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has value: undefined" `; exports[`.resolves fails for promise that rejects 1`] = ` -"expect(received).resolves.toBe() +"expect(received).resolves.toBe() -Expected received Promise to resolve, instead it rejected to value - 4" +Received promise rejected instead of resolved +Rejected to value: 4" `; exports[`.resolves fails non-promise value "a" 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has type: string Received has value: \\"a\\"" `; exports[`.resolves fails non-promise value "a" synchronously 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has type: string Received has value: \\"a\\"" `; exports[`.resolves fails non-promise value [1] 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has type: array Received has value: [1]" `; exports[`.resolves fails non-promise value [1] synchronously 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has type: array Received has value: [1]" `; exports[`.resolves fails non-promise value [Function anonymous] 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has type: function Received has value: [Function anonymous]" `; exports[`.resolves fails non-promise value [Function anonymous] synchronously 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has type: function Received has value: [Function anonymous]" `; exports[`.resolves fails non-promise value {"a": 1} 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has type: object Received has value: {\\"a\\": 1}" `; exports[`.resolves fails non-promise value {"a": 1} synchronously 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has type: object Received has value: {\\"a\\": 1}" `; exports[`.resolves fails non-promise value 4 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.not.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has type: number Received has value: 4" `; exports[`.resolves fails non-promise value 4 synchronously 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.not.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has type: number Received has value: 4" `; exports[`.resolves fails non-promise value null 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.not.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has value: null" `; exports[`.resolves fails non-promise value null synchronously 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.not.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has value: null" `; exports[`.resolves fails non-promise value true 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.not.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has type: boolean Received has value: true" `; exports[`.resolves fails non-promise value true synchronously 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.not.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has type: boolean Received has value: true" `; exports[`.resolves fails non-promise value undefined 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.not.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has value: undefined" `; exports[`.resolves fails non-promise value undefined synchronously 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.not.toBeDefined() -Matcher error: received value must be a Promise +Matcher error: received value must be a promise Received has value: undefined" `; @@ -529,121 +529,121 @@ Received: 1.23" `; exports[`.toBeDefined(), .toBeUndefined() '"a"' is defined 1`] = ` -"expect(received).not.toBeDefined() +"expect(received).not.toBeDefined() Received: \\"a\\"" `; exports[`.toBeDefined(), .toBeUndefined() '"a"' is defined 2`] = ` -"expect(received).toBeUndefined() +"expect(received).toBeUndefined() Received: \\"a\\"" `; exports[`.toBeDefined(), .toBeUndefined() '[]' is defined 1`] = ` -"expect(received).not.toBeDefined() +"expect(received).not.toBeDefined() Received: []" `; exports[`.toBeDefined(), .toBeUndefined() '[]' is defined 2`] = ` -"expect(received).toBeUndefined() +"expect(received).toBeUndefined() Received: []" `; exports[`.toBeDefined(), .toBeUndefined() '[Function anonymous]' is defined 1`] = ` -"expect(received).not.toBeDefined() +"expect(received).not.toBeDefined() Received: [Function anonymous]" `; exports[`.toBeDefined(), .toBeUndefined() '[Function anonymous]' is defined 2`] = ` -"expect(received).toBeUndefined() +"expect(received).toBeUndefined() Received: [Function anonymous]" `; exports[`.toBeDefined(), .toBeUndefined() '{}' is defined 1`] = ` -"expect(received).not.toBeDefined() +"expect(received).not.toBeDefined() Received: {}" `; exports[`.toBeDefined(), .toBeUndefined() '{}' is defined 2`] = ` -"expect(received).toBeUndefined() +"expect(received).toBeUndefined() Received: {}" `; exports[`.toBeDefined(), .toBeUndefined() '0.5' is defined 1`] = ` -"expect(received).not.toBeDefined() +"expect(received).not.toBeDefined() Received: 0.5" `; exports[`.toBeDefined(), .toBeUndefined() '0.5' is defined 2`] = ` -"expect(received).toBeUndefined() +"expect(received).toBeUndefined() Received: 0.5" `; exports[`.toBeDefined(), .toBeUndefined() '1' is defined 1`] = ` -"expect(received).not.toBeDefined() +"expect(received).not.toBeDefined() Received: 1" `; exports[`.toBeDefined(), .toBeUndefined() '1' is defined 2`] = ` -"expect(received).toBeUndefined() +"expect(received).toBeUndefined() Received: 1" `; exports[`.toBeDefined(), .toBeUndefined() 'Infinity' is defined 1`] = ` -"expect(received).not.toBeDefined() +"expect(received).not.toBeDefined() Received: Infinity" `; exports[`.toBeDefined(), .toBeUndefined() 'Infinity' is defined 2`] = ` -"expect(received).toBeUndefined() +"expect(received).toBeUndefined() Received: Infinity" `; exports[`.toBeDefined(), .toBeUndefined() 'Map {}' is defined 1`] = ` -"expect(received).not.toBeDefined() +"expect(received).not.toBeDefined() Received: Map {}" `; exports[`.toBeDefined(), .toBeUndefined() 'Map {}' is defined 2`] = ` -"expect(received).toBeUndefined() +"expect(received).toBeUndefined() Received: Map {}" `; exports[`.toBeDefined(), .toBeUndefined() 'true' is defined 1`] = ` -"expect(received).not.toBeDefined() +"expect(received).not.toBeDefined() Received: true" `; exports[`.toBeDefined(), .toBeUndefined() 'true' is defined 2`] = ` -"expect(received).toBeUndefined() +"expect(received).toBeUndefined() Received: true" `; exports[`.toBeDefined(), .toBeUndefined() undefined is undefined 1`] = ` -"expect(received).toBeDefined() +"expect(received).toBeDefined() Received: undefined" `; exports[`.toBeDefined(), .toBeUndefined() undefined is undefined 2`] = ` -"expect(received).not.toBeUndefined() +"expect(received).not.toBeUndefined() Received: undefined" `; @@ -1197,341 +1197,341 @@ Expected has value: 4" `; exports[`.toBeNaN() {pass: true} expect(NaN).toBeNaN() 1`] = ` -"expect(received).not.toBeNaN() +"expect(received).not.toBeNaN() Received: NaN" `; exports[`.toBeNaN() {pass: true} expect(NaN).toBeNaN() 2`] = ` -"expect(received).not.toBeNaN() +"expect(received).not.toBeNaN() Received: NaN" `; exports[`.toBeNaN() {pass: true} expect(NaN).toBeNaN() 3`] = ` -"expect(received).not.toBeNaN() +"expect(received).not.toBeNaN() Received: NaN" `; exports[`.toBeNaN() {pass: true} expect(NaN).toBeNaN() 4`] = ` -"expect(received).not.toBeNaN() +"expect(received).not.toBeNaN() Received: NaN" `; exports[`.toBeNaN() throws 1`] = ` -"expect(received).toBeNaN() +"expect(received).toBeNaN() Received: 1" `; exports[`.toBeNaN() throws 2`] = ` -"expect(received).toBeNaN() +"expect(received).toBeNaN() Received: \\"\\"" `; exports[`.toBeNaN() throws 3`] = ` -"expect(received).toBeNaN() +"expect(received).toBeNaN() Received: null" `; exports[`.toBeNaN() throws 4`] = ` -"expect(received).toBeNaN() +"expect(received).toBeNaN() Received: undefined" `; exports[`.toBeNaN() throws 5`] = ` -"expect(received).toBeNaN() +"expect(received).toBeNaN() Received: {}" `; exports[`.toBeNaN() throws 6`] = ` -"expect(received).toBeNaN() +"expect(received).toBeNaN() Received: []" `; exports[`.toBeNaN() throws 7`] = ` -"expect(received).toBeNaN() +"expect(received).toBeNaN() Received: 0.2" `; exports[`.toBeNaN() throws 8`] = ` -"expect(received).toBeNaN() +"expect(received).toBeNaN() Received: 0" `; exports[`.toBeNaN() throws 9`] = ` -"expect(received).toBeNaN() +"expect(received).toBeNaN() Received: Infinity" `; exports[`.toBeNaN() throws 10`] = ` -"expect(received).toBeNaN() +"expect(received).toBeNaN() Received: -Infinity" `; -exports[`.toBeNull() fails for '"a"' with .not 1`] = ` -"expect(received).toBeNull() +exports[`.toBeNull() fails for '"a"' 1`] = ` +"expect(received).toBeNull() Received: \\"a\\"" `; -exports[`.toBeNull() fails for '[]' with .not 1`] = ` -"expect(received).toBeNull() +exports[`.toBeNull() fails for '[]' 1`] = ` +"expect(received).toBeNull() Received: []" `; -exports[`.toBeNull() fails for '[Function anonymous]' with .not 1`] = ` -"expect(received).toBeNull() +exports[`.toBeNull() fails for '[Function anonymous]' 1`] = ` +"expect(received).toBeNull() Received: [Function anonymous]" `; -exports[`.toBeNull() fails for '{}' with .not 1`] = ` -"expect(received).toBeNull() +exports[`.toBeNull() fails for '{}' 1`] = ` +"expect(received).toBeNull() Received: {}" `; -exports[`.toBeNull() fails for '0.5' with .not 1`] = ` -"expect(received).toBeNull() +exports[`.toBeNull() fails for '0.5' 1`] = ` +"expect(received).toBeNull() Received: 0.5" `; -exports[`.toBeNull() fails for '1' with .not 1`] = ` -"expect(received).toBeNull() +exports[`.toBeNull() fails for '1' 1`] = ` +"expect(received).toBeNull() Received: 1" `; -exports[`.toBeNull() fails for 'Infinity' with .not 1`] = ` -"expect(received).toBeNull() +exports[`.toBeNull() fails for 'Infinity' 1`] = ` +"expect(received).toBeNull() Received: Infinity" `; -exports[`.toBeNull() fails for 'Map {}' with .not 1`] = ` -"expect(received).toBeNull() +exports[`.toBeNull() fails for 'Map {}' 1`] = ` +"expect(received).toBeNull() Received: Map {}" `; -exports[`.toBeNull() fails for 'true' with .not 1`] = ` -"expect(received).toBeNull() +exports[`.toBeNull() fails for 'true' 1`] = ` +"expect(received).toBeNull() Received: true" `; -exports[`.toBeNull() pass for null 1`] = ` -"expect(received).not.toBeNull() +exports[`.toBeNull() fails for null with .not 1`] = ` +"expect(received).not.toBeNull() Received: null" `; exports[`.toBeTruthy(), .toBeFalsy() '""' is falsy 1`] = ` -"expect(received).toBeTruthy() +"expect(received).toBeTruthy() Received: \\"\\"" `; exports[`.toBeTruthy(), .toBeFalsy() '""' is falsy 2`] = ` -"expect(received).not.toBeFalsy() +"expect(received).not.toBeFalsy() Received: \\"\\"" `; exports[`.toBeTruthy(), .toBeFalsy() '"a"' is truthy 1`] = ` -"expect(received).not.toBeTruthy() +"expect(received).not.toBeTruthy() Received: \\"a\\"" `; exports[`.toBeTruthy(), .toBeFalsy() '"a"' is truthy 2`] = ` -"expect(received).toBeFalsy() +"expect(received).toBeFalsy() Received: \\"a\\"" `; exports[`.toBeTruthy(), .toBeFalsy() '[]' is truthy 1`] = ` -"expect(received).not.toBeTruthy() +"expect(received).not.toBeTruthy() Received: []" `; exports[`.toBeTruthy(), .toBeFalsy() '[]' is truthy 2`] = ` -"expect(received).toBeFalsy() +"expect(received).toBeFalsy() Received: []" `; exports[`.toBeTruthy(), .toBeFalsy() '[Function anonymous]' is truthy 1`] = ` -"expect(received).not.toBeTruthy() +"expect(received).not.toBeTruthy() Received: [Function anonymous]" `; exports[`.toBeTruthy(), .toBeFalsy() '[Function anonymous]' is truthy 2`] = ` -"expect(received).toBeFalsy() +"expect(received).toBeFalsy() Received: [Function anonymous]" `; exports[`.toBeTruthy(), .toBeFalsy() '{}' is truthy 1`] = ` -"expect(received).not.toBeTruthy() +"expect(received).not.toBeTruthy() Received: {}" `; exports[`.toBeTruthy(), .toBeFalsy() '{}' is truthy 2`] = ` -"expect(received).toBeFalsy() +"expect(received).toBeFalsy() Received: {}" `; exports[`.toBeTruthy(), .toBeFalsy() '0' is falsy 1`] = ` -"expect(received).toBeTruthy() +"expect(received).toBeTruthy() Received: 0" `; exports[`.toBeTruthy(), .toBeFalsy() '0' is falsy 2`] = ` -"expect(received).not.toBeFalsy() +"expect(received).not.toBeFalsy() Received: 0" `; exports[`.toBeTruthy(), .toBeFalsy() '0.5' is truthy 1`] = ` -"expect(received).not.toBeTruthy() +"expect(received).not.toBeTruthy() Received: 0.5" `; exports[`.toBeTruthy(), .toBeFalsy() '0.5' is truthy 2`] = ` -"expect(received).toBeFalsy() +"expect(received).toBeFalsy() Received: 0.5" `; exports[`.toBeTruthy(), .toBeFalsy() '1' is truthy 1`] = ` -"expect(received).not.toBeTruthy() +"expect(received).not.toBeTruthy() Received: 1" `; exports[`.toBeTruthy(), .toBeFalsy() '1' is truthy 2`] = ` -"expect(received).toBeFalsy() +"expect(received).toBeFalsy() Received: 1" `; exports[`.toBeTruthy(), .toBeFalsy() 'Infinity' is truthy 1`] = ` -"expect(received).not.toBeTruthy() +"expect(received).not.toBeTruthy() Received: Infinity" `; exports[`.toBeTruthy(), .toBeFalsy() 'Infinity' is truthy 2`] = ` -"expect(received).toBeFalsy() +"expect(received).toBeFalsy() Received: Infinity" `; exports[`.toBeTruthy(), .toBeFalsy() 'Map {}' is truthy 1`] = ` -"expect(received).not.toBeTruthy() +"expect(received).not.toBeTruthy() Received: Map {}" `; exports[`.toBeTruthy(), .toBeFalsy() 'Map {}' is truthy 2`] = ` -"expect(received).toBeFalsy() +"expect(received).toBeFalsy() Received: Map {}" `; exports[`.toBeTruthy(), .toBeFalsy() 'NaN' is falsy 1`] = ` -"expect(received).toBeTruthy() +"expect(received).toBeTruthy() Received: NaN" `; exports[`.toBeTruthy(), .toBeFalsy() 'NaN' is falsy 2`] = ` -"expect(received).not.toBeFalsy() +"expect(received).not.toBeFalsy() Received: NaN" `; exports[`.toBeTruthy(), .toBeFalsy() 'false' is falsy 1`] = ` -"expect(received).toBeTruthy() +"expect(received).toBeTruthy() Received: false" `; exports[`.toBeTruthy(), .toBeFalsy() 'false' is falsy 2`] = ` -"expect(received).not.toBeFalsy() +"expect(received).not.toBeFalsy() Received: false" `; exports[`.toBeTruthy(), .toBeFalsy() 'null' is falsy 1`] = ` -"expect(received).toBeTruthy() +"expect(received).toBeTruthy() Received: null" `; exports[`.toBeTruthy(), .toBeFalsy() 'null' is falsy 2`] = ` -"expect(received).not.toBeFalsy() +"expect(received).not.toBeFalsy() Received: null" `; exports[`.toBeTruthy(), .toBeFalsy() 'true' is truthy 1`] = ` -"expect(received).not.toBeTruthy() +"expect(received).not.toBeTruthy() Received: true" `; exports[`.toBeTruthy(), .toBeFalsy() 'true' is truthy 2`] = ` -"expect(received).toBeFalsy() +"expect(received).toBeFalsy() Received: true" `; exports[`.toBeTruthy(), .toBeFalsy() 'undefined' is falsy 1`] = ` -"expect(received).toBeTruthy() +"expect(received).toBeTruthy() Received: undefined" `; exports[`.toBeTruthy(), .toBeFalsy() 'undefined' is falsy 2`] = ` -"expect(received).not.toBeFalsy() +"expect(received).not.toBeFalsy() Received: undefined" `; exports[`.toBeTruthy(), .toBeFalsy() does not accept arguments 1`] = ` -"expect(received)[.not].toBeTruthy() +"expect(received).toBeTruthy() -Matcher error: expected value must be omitted or undefined +Matcher error: this matcher must not have an expected argument Expected has value: null" `; exports[`.toBeTruthy(), .toBeFalsy() does not accept arguments 2`] = ` -"expect(received)[.not].toBeFalsy() +"expect(received).not.toBeFalsy() -Matcher error: expected value must be omitted or undefined +Matcher error: this matcher must not have an expected argument Expected has value: null" `; diff --git a/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap index 6a64d8650306..4015f9948b3b 100644 --- a/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap @@ -8,7 +8,7 @@ Expected mock function \\"named-mock\\" to not have been last called with: `; exports[`lastCalledWith works only on spies or jest.fn 1`] = ` -"expect(jest.fn())[.not].lastCalledWith() +"expect(jest.fn())[.not].lastCalledWith() Matcher error: received value must be a mock or spy function @@ -159,7 +159,7 @@ But the last call has not returned yet" `; exports[`lastReturnedWith works only on spies or jest.fn 1`] = ` -"expect(jest.fn())[.not].lastReturnedWith() +"expect(jest.fn())[.not].lastReturnedWith() Matcher error: received value must be a mock or spy function @@ -293,7 +293,7 @@ Expected mock function first call to not have been called with: `; exports[`nthCalledWith works only on spies or jest.fn 1`] = ` -"expect(jest.fn())[.not].nthCalledWith() +"expect(jest.fn())[.not].nthCalledWith() Matcher error: received value must be a mock or spy function @@ -491,7 +491,7 @@ But the first call returned exactly: `; exports[`nthReturnedWith works only on spies or jest.fn 1`] = ` -"expect(jest.fn())[.not].nthReturnedWith() +"expect(jest.fn())[.not].nthReturnedWith() Matcher error: received value must be a mock or spy function @@ -598,45 +598,45 @@ But the first call returned exactly: `; exports[`toBeCalled .not fails with any argument passed 1`] = ` -"expect(received)[.not].toBeCalled() +"expect(received)[.not].toBeCalled() -Matcher error: expected value must be omitted or undefined +Matcher error: this matcher must not have an expected argument Expected has type: number Expected has value: 555" `; exports[`toBeCalled .not passes when called 1`] = ` -"expect(jest.fn()).toBeCalled() +"expect(jest.fn()).toBeCalled() Expected mock function to have been called, but it was not called." `; exports[`toBeCalled fails with any argument passed 1`] = ` -"expect(received)[.not].toBeCalled() +"expect(received)[.not].toBeCalled() -Matcher error: expected value must be omitted or undefined +Matcher error: this matcher must not have an expected argument Expected has type: number Expected has value: 555" `; exports[`toBeCalled includes the custom mock name in the error message 1`] = ` -"expect(named-mock).not.toBeCalled() +"expect(named-mock).not.toBeCalled() Expected mock function \\"named-mock\\" not to be called but it was called with: []" `; exports[`toBeCalled passes when called 1`] = ` -"expect(jest.fn()).not.toBeCalled() +"expect(jest.fn()).not.toBeCalled() Expected mock function not to be called but it was called with: []" `; exports[`toBeCalled works only on spies or jest.fn 1`] = ` -"expect(jest.fn())[.not].toBeCalled() +"expect(jest.fn())[.not].toBeCalled() Matcher error: received value must be a mock or spy function @@ -777,7 +777,7 @@ Expected mock function not to be called two times, but it was called e `; exports[`toBeCalledTimes works only on spies or jest.fn 1`] = ` -"expect(jest.fn())[.not].toBeCalledTimes() +"expect(jest.fn())[.not].toBeCalledTimes() Matcher error: received value must be a mock or spy function @@ -793,7 +793,7 @@ Expected mock function \\"named-mock\\" not to have been called with: `; exports[`toBeCalledWith works only on spies or jest.fn 1`] = ` -"expect(jest.fn())[.not].toBeCalledWith() +"expect(jest.fn())[.not].toBeCalledWith() Matcher error: received value must be a mock or spy function @@ -920,45 +920,45 @@ Expected mock function to have been called with: `; exports[`toHaveBeenCalled .not fails with any argument passed 1`] = ` -"expect(received)[.not].toHaveBeenCalled() +"expect(received)[.not].toHaveBeenCalled() -Matcher error: expected value must be omitted or undefined +Matcher error: this matcher must not have an expected argument Expected has type: number Expected has value: 555" `; exports[`toHaveBeenCalled .not passes when called 1`] = ` -"expect(jest.fn()).toHaveBeenCalled() +"expect(jest.fn()).toHaveBeenCalled() Expected mock function to have been called, but it was not called." `; exports[`toHaveBeenCalled fails with any argument passed 1`] = ` -"expect(received)[.not].toHaveBeenCalled() +"expect(received)[.not].toHaveBeenCalled() -Matcher error: expected value must be omitted or undefined +Matcher error: this matcher must not have an expected argument Expected has type: number Expected has value: 555" `; exports[`toHaveBeenCalled includes the custom mock name in the error message 1`] = ` -"expect(named-mock).not.toHaveBeenCalled() +"expect(named-mock).not.toHaveBeenCalled() Expected mock function \\"named-mock\\" not to be called but it was called with: []" `; exports[`toHaveBeenCalled passes when called 1`] = ` -"expect(jest.fn()).not.toHaveBeenCalled() +"expect(jest.fn()).not.toHaveBeenCalled() Expected mock function not to be called but it was called with: []" `; exports[`toHaveBeenCalled works only on spies or jest.fn 1`] = ` -"expect(jest.fn())[.not].toHaveBeenCalled() +"expect(jest.fn())[.not].toHaveBeenCalled() Matcher error: received value must be a mock or spy function @@ -1099,7 +1099,7 @@ Expected mock function not to be called two times, but it was called e `; exports[`toHaveBeenCalledTimes works only on spies or jest.fn 1`] = ` -"expect(jest.fn())[.not].toHaveBeenCalledTimes() +"expect(jest.fn())[.not].toHaveBeenCalledTimes() Matcher error: received value must be a mock or spy function @@ -1115,7 +1115,7 @@ Expected mock function \\"named-mock\\" not to have been called with: `; exports[`toHaveBeenCalledWith works only on spies or jest.fn 1`] = ` -"expect(jest.fn())[.not].toHaveBeenCalledWith() +"expect(jest.fn())[.not].toHaveBeenCalledWith() Matcher error: received value must be a mock or spy function @@ -1249,7 +1249,7 @@ Expected mock function \\"named-mock\\" to not have been last called with: `; exports[`toHaveBeenLastCalledWith works only on spies or jest.fn 1`] = ` -"expect(jest.fn())[.not].toHaveBeenLastCalledWith() +"expect(jest.fn())[.not].toHaveBeenLastCalledWith() Matcher error: received value must be a mock or spy function @@ -1395,7 +1395,7 @@ Expected mock function first call to not have been called with: `; exports[`toHaveBeenNthCalledWith works only on spies or jest.fn 1`] = ` -"expect(jest.fn())[.not].toHaveBeenNthCalledWith() +"expect(jest.fn())[.not].toHaveBeenNthCalledWith() Matcher error: received value must be a mock or spy function @@ -1537,7 +1537,7 @@ But the last call has not returned yet" `; exports[`toHaveLastReturnedWith works only on spies or jest.fn 1`] = ` -"expect(jest.fn())[.not].toHaveLastReturnedWith() +"expect(jest.fn())[.not].toHaveLastReturnedWith() Matcher error: received value must be a mock or spy function @@ -1732,7 +1732,7 @@ But the first call returned exactly: `; exports[`toHaveNthReturnedWith works only on spies or jest.fn 1`] = ` -"expect(jest.fn())[.not].toHaveNthReturnedWith() +"expect(jest.fn())[.not].toHaveNthReturnedWith() Matcher error: received value must be a mock or spy function @@ -1839,56 +1839,56 @@ But the first call returned exactly: `; exports[`toHaveReturned .not fails with any argument passed 1`] = ` -"expect(received)[.not].toHaveReturned() +"expect(received)[.not].toHaveReturned() -Matcher error: expected value must be omitted or undefined +Matcher error: this matcher must not have an expected argument Expected has type: number Expected has value: 555" `; exports[`toHaveReturned .not passes when a call throws undefined 1`] = ` -"expect(jest.fn()).toHaveReturned() +"expect(jest.fn()).toHaveReturned() Expected mock function to have returned." `; exports[`toHaveReturned .not passes when all calls throw 1`] = ` -"expect(jest.fn()).toHaveReturned() +"expect(jest.fn()).toHaveReturned() Expected mock function to have returned." `; exports[`toHaveReturned .not passes when not returned 1`] = ` -"expect(jest.fn()).toHaveReturned() +"expect(jest.fn()).toHaveReturned() Expected mock function to have returned." `; exports[`toHaveReturned fails with any argument passed 1`] = ` -"expect(received)[.not].toHaveReturned() +"expect(received)[.not].toHaveReturned() -Matcher error: expected value must be omitted or undefined +Matcher error: this matcher must not have an expected argument Expected has type: number Expected has value: 555" `; exports[`toHaveReturned includes the custom mock name in the error message 1`] = ` -"expect(named-mock).not.toHaveReturned() +"expect(named-mock).not.toHaveReturned() Expected mock function \\"named-mock\\" not to have returned, but it returned: 42" `; exports[`toHaveReturned incomplete recursive calls are handled properly 1`] = ` -"expect(jest.fn()).toHaveReturned() +"expect(jest.fn()).toHaveReturned() Expected mock function to have returned." `; exports[`toHaveReturned passes when at least one call does not throw 1`] = ` -"expect(jest.fn()).not.toHaveReturned() +"expect(jest.fn()).not.toHaveReturned() Expected mock function not to have returned, but it returned: 42 @@ -1897,21 +1897,21 @@ Expected mock function not to have returned, but it returned: `; exports[`toHaveReturned passes when returned 1`] = ` -"expect(jest.fn()).not.toHaveReturned() +"expect(jest.fn()).not.toHaveReturned() Expected mock function not to have returned, but it returned: 42" `; exports[`toHaveReturned passes when undefined is returned 1`] = ` -"expect(jest.fn()).not.toHaveReturned() +"expect(jest.fn()).not.toHaveReturned() Expected mock function not to have returned, but it returned: undefined" `; exports[`toHaveReturned works only on spies or jest.fn 1`] = ` -"expect(jest.fn())[.not].toHaveReturned() +"expect(jest.fn())[.not].toHaveReturned() Matcher error: received value must be a mock or spy function @@ -2076,7 +2076,7 @@ Expected mock function not to have returned two times, but it returned `; exports[`toHaveReturnedTimes works only on spies or jest.fn 1`] = ` -"expect(jest.fn())[.not].toHaveReturnedTimes() +"expect(jest.fn())[.not].toHaveReturnedTimes() Matcher error: received value must be a mock or spy function @@ -2117,7 +2117,7 @@ But it did not return." `; exports[`toHaveReturnedWith works only on spies or jest.fn 1`] = ` -"expect(jest.fn())[.not].toHaveReturnedWith() +"expect(jest.fn())[.not].toHaveReturnedWith() Matcher error: received value must be a mock or spy function @@ -2234,56 +2234,56 @@ But it returned exactly: `; exports[`toReturn .not fails with any argument passed 1`] = ` -"expect(received)[.not].toReturn() +"expect(received)[.not].toReturn() -Matcher error: expected value must be omitted or undefined +Matcher error: this matcher must not have an expected argument Expected has type: number Expected has value: 555" `; exports[`toReturn .not passes when a call throws undefined 1`] = ` -"expect(jest.fn()).toReturn() +"expect(jest.fn()).toReturn() Expected mock function to have returned." `; exports[`toReturn .not passes when all calls throw 1`] = ` -"expect(jest.fn()).toReturn() +"expect(jest.fn()).toReturn() Expected mock function to have returned." `; exports[`toReturn .not passes when not returned 1`] = ` -"expect(jest.fn()).toReturn() +"expect(jest.fn()).toReturn() Expected mock function to have returned." `; exports[`toReturn fails with any argument passed 1`] = ` -"expect(received)[.not].toReturn() +"expect(received)[.not].toReturn() -Matcher error: expected value must be omitted or undefined +Matcher error: this matcher must not have an expected argument Expected has type: number Expected has value: 555" `; exports[`toReturn includes the custom mock name in the error message 1`] = ` -"expect(named-mock).not.toReturn() +"expect(named-mock).not.toReturn() Expected mock function \\"named-mock\\" not to have returned, but it returned: 42" `; exports[`toReturn incomplete recursive calls are handled properly 1`] = ` -"expect(jest.fn()).toReturn() +"expect(jest.fn()).toReturn() Expected mock function to have returned." `; exports[`toReturn passes when at least one call does not throw 1`] = ` -"expect(jest.fn()).not.toReturn() +"expect(jest.fn()).not.toReturn() Expected mock function not to have returned, but it returned: 42 @@ -2292,21 +2292,21 @@ Expected mock function not to have returned, but it returned: `; exports[`toReturn passes when returned 1`] = ` -"expect(jest.fn()).not.toReturn() +"expect(jest.fn()).not.toReturn() Expected mock function not to have returned, but it returned: 42" `; exports[`toReturn passes when undefined is returned 1`] = ` -"expect(jest.fn()).not.toReturn() +"expect(jest.fn()).not.toReturn() Expected mock function not to have returned, but it returned: undefined" `; exports[`toReturn works only on spies or jest.fn 1`] = ` -"expect(jest.fn())[.not].toReturn() +"expect(jest.fn())[.not].toReturn() Matcher error: received value must be a mock or spy function @@ -2471,7 +2471,7 @@ Expected mock function not to have returned two times, but it returned `; exports[`toReturnTimes works only on spies or jest.fn 1`] = ` -"expect(jest.fn())[.not].toReturnTimes() +"expect(jest.fn())[.not].toReturnTimes() Matcher error: received value must be a mock or spy function @@ -2512,7 +2512,7 @@ But it did not return." `; exports[`toReturnWith works only on spies or jest.fn 1`] = ` -"expect(jest.fn())[.not].toReturnWith() +"expect(jest.fn())[.not].toReturnWith() Matcher error: received value must be a mock or spy function diff --git a/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap index 8ce9e200485f..f02f4d14a79c 100644 --- a/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap @@ -64,7 +64,7 @@ Instead, it threw: `; exports[`.toThrow() promise/async throws if Error-like object is returned threw, but should not have 1`] = ` -[Error: expect(function).not.toThrow() +[Error: expect(function).not.toThrow() Expected the function not to throw an error. Instead, it threw: @@ -192,7 +192,7 @@ Instead, it threw: `; exports[`.toThrowError() promise/async throws if Error-like object is returned threw, but should not have 1`] = ` -[Error: expect(function).not.toThrow() +[Error: expect(function).not.toThrow() Expected the function not to throw an error. Instead, it threw: diff --git a/packages/expect/src/__tests__/matchers.test.js b/packages/expect/src/__tests__/matchers.test.js index c32b1d4856ba..faaf78e4782e 100644 --- a/packages/expect/src/__tests__/matchers.test.js +++ b/packages/expect/src/__tests__/matchers.test.js @@ -52,7 +52,7 @@ describe('.rejects', () => { await jestExpect(fn()).rejects.toThrow('some error'); }); - [4, [1], {a: 1}, 'a', true, null, undefined, () => {}].forEach(value => { + ['a', [1], () => {}, {a: 1}].forEach(value => { it(`fails non-promise value ${stringify(value)} synchronously`, () => { let error; try { @@ -62,9 +62,7 @@ describe('.rejects', () => { } expect(error).toBeDefined(); }); - }); - [4, [1], {a: 1}, 'a', true, null, undefined, () => {}].forEach(value => { it(`fails non-promise value ${stringify(value)}`, async () => { let error; try { @@ -77,6 +75,29 @@ describe('.rejects', () => { }); }); + [4, null, true, undefined].forEach(value => { + it(`fails non-promise value ${stringify(value)} synchronously`, () => { + let error; + try { + jestExpect(value).rejects.not.toBe(111); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + }); + + it(`fails non-promise value ${stringify(value)}`, async () => { + let error; + try { + await jestExpect(value).rejects.not.toBeDefined(); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + expect(error.message).toMatchSnapshot(); + }); + }); + it('fails for promise that resolves', async () => { let error; try { @@ -108,7 +129,7 @@ describe('.resolves', () => { ).resolves.toThrow(); }); - [4, [1], {a: 1}, 'a', true, null, undefined, () => {}].forEach(value => { + ['a', [1], () => {}, {a: 1}].forEach(value => { it(`fails non-promise value ${stringify(value)} synchronously`, () => { let error; try { @@ -119,9 +140,7 @@ describe('.resolves', () => { expect(error).toBeDefined(); expect(error.message).toMatchSnapshot(); }); - }); - [4, [1], {a: 1}, 'a', true, null, undefined, () => {}].forEach(value => { it(`fails non-promise value ${stringify(value)}`, async () => { let error; try { @@ -134,6 +153,30 @@ describe('.resolves', () => { }); }); + [4, null, true, undefined].forEach(value => { + it(`fails non-promise value ${stringify(value)} synchronously`, () => { + let error; + try { + jestExpect(value).resolves.not.toBeDefined(); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + expect(error.message).toMatchSnapshot(); + }); + + it(`fails non-promise value ${stringify(value)}`, async () => { + let error; + try { + await jestExpect(value).resolves.not.toBeDefined(); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + expect(error.message).toMatchSnapshot(); + }); + }); + it('fails for promise that rejects', async () => { let error; try { @@ -564,7 +607,9 @@ describe('.toBeTruthy(), .toBeFalsy()', () => { it('does not accept arguments', () => { expect(() => jestExpect(0).toBeTruthy(null)).toThrowErrorMatchingSnapshot(); - expect(() => jestExpect(0).toBeFalsy(null)).toThrowErrorMatchingSnapshot(); + expect(() => + jestExpect(0).not.toBeFalsy(null), + ).toThrowErrorMatchingSnapshot(); }); [{}, [], true, 1, 'a', 0.5, new Map(), () => {}, Infinity].forEach(v => { @@ -614,20 +659,22 @@ describe('.toBeNaN()', () => { describe('.toBeNull()', () => { [{}, [], true, 1, 'a', 0.5, new Map(), () => {}, Infinity].forEach(v => { - test(`fails for '${stringify(v)}' with .not`, () => { + test(`fails for '${stringify(v)}'`, () => { jestExpect(v).not.toBeNull(); expect(() => jestExpect(v).toBeNull()).toThrowErrorMatchingSnapshot(); }); }); - it('pass for null', () => { - jestExpect(null).toBeNull(); - + it('fails for null with .not', () => { expect(() => jestExpect(null).not.toBeNull(), ).toThrowErrorMatchingSnapshot(); }); + + it('pass for null', () => { + jestExpect(null).toBeNull(); + }); }); describe('.toBeDefined(), .toBeUndefined()', () => { diff --git a/packages/expect/src/index.js b/packages/expect/src/index.js index ac2644672c48..67e6883f7b06 100644 --- a/packages/expect/src/index.js +++ b/packages/expect/src/index.js @@ -94,8 +94,8 @@ const expect = (actual: any, ...rest: Array): ExpectationObject => { Object.keys(allMatchers).forEach(name => { const matcher = allMatchers[name]; const promiseMatcher = getPromiseMatcher(name, matcher) || matcher; - expectation[name] = makeThrowingMatcher(matcher, false, actual); - expectation.not[name] = makeThrowingMatcher(matcher, true, actual); + expectation[name] = makeThrowingMatcher(matcher, false, '', actual); + expectation.not[name] = makeThrowingMatcher(matcher, true, '', actual); expectation.resolves[name] = makeResolveMatcher( name, @@ -142,12 +142,16 @@ const makeResolveMatcher = ( actual: Promise, outerErr: JestAssertionError, ): PromiseMatcherFn => (...args) => { - const matcherStatement = `.resolves.${isNot ? 'not.' : ''}${matcherName}`; + const options = { + isNot, + promise: 'resolves', + }; + if (!isPromise(actual)) { throw new JestAssertionError( matcherUtils.matcherErrorMessage( - matcherUtils.matcherHint(matcherStatement, undefined, ''), - `${matcherUtils.RECEIVED_COLOR('received')} value must be a Promise`, + matcherUtils.matcherHint(matcherName, undefined, '', options), + `${matcherUtils.RECEIVED_COLOR('received')} value must be a promise`, matcherUtils.printWithType( 'Received', actual, @@ -161,16 +165,16 @@ const makeResolveMatcher = ( return actual.then( result => - makeThrowingMatcher(matcher, isNot, result, innerErr).apply(null, args), + makeThrowingMatcher(matcher, isNot, 'resolves', result, innerErr).apply( + null, + args, + ), reason => { outerErr.message = - matcherUtils.matcherHint(matcherStatement, 'received', '') + + matcherUtils.matcherHint(matcherName, undefined, '', options) + '\n\n' + - `Expected ${matcherUtils.RECEIVED_COLOR( - 'received', - )} Promise to resolve, ` + - 'instead it rejected to value\n' + - ` ${matcherUtils.printReceived(reason)}`; + `Received promise rejected instead of resolved\n` + + `Rejected to value: ${matcherUtils.printReceived(reason)}`; return Promise.reject(outerErr); }, ); @@ -183,12 +187,16 @@ const makeRejectMatcher = ( actual: Promise, outerErr: JestAssertionError, ): PromiseMatcherFn => (...args) => { - const matcherStatement = `.rejects.${isNot ? 'not.' : ''}${matcherName}`; + const options = { + isNot, + promise: 'rejects', + }; + if (!isPromise(actual)) { throw new JestAssertionError( matcherUtils.matcherErrorMessage( - matcherUtils.matcherHint(matcherStatement, undefined, ''), - `${matcherUtils.RECEIVED_COLOR('received')} value must be a Promise`, + matcherUtils.matcherHint(matcherName, undefined, '', options), + `${matcherUtils.RECEIVED_COLOR('received')} value must be a promise`, matcherUtils.printWithType( 'Received', actual, @@ -203,23 +211,24 @@ const makeRejectMatcher = ( return actual.then( result => { outerErr.message = - matcherUtils.matcherHint(matcherStatement, 'received', '') + + matcherUtils.matcherHint(matcherName, undefined, '', options) + '\n\n' + - `Expected ${matcherUtils.RECEIVED_COLOR( - 'received', - )} Promise to reject, ` + - 'instead it resolved to value\n' + - ` ${matcherUtils.printReceived(result)}`; + `Received promise resolved instead of rejected\n` + + `Resolved to value: ${matcherUtils.printReceived(result)}`; return Promise.reject(outerErr); }, reason => - makeThrowingMatcher(matcher, isNot, reason, innerErr).apply(null, args), + makeThrowingMatcher(matcher, isNot, 'rejects', reason, innerErr).apply( + null, + args, + ), ); }; const makeThrowingMatcher = ( matcher: RawMatcherFn, isNot: boolean, + promise: string, actual: any, err?: JestAssertionError, ): ThrowingMatcherFn => @@ -242,6 +251,7 @@ const makeThrowingMatcher = ( equals, error: err, isNot, + promise, utils, }, ); diff --git a/packages/expect/src/matchers.js b/packages/expect/src/matchers.js index 1baa47fa44af..af9faa729bc1 100644 --- a/packages/expect/src/matchers.js +++ b/packages/expect/src/matchers.js @@ -109,27 +109,37 @@ const matchers: MatchersObject = { return {message, pass}; }, - toBeDefined(actual: any, expected: void) { - ensureNoExpected(expected, '.toBeDefined'); - const pass = actual !== void 0; + toBeDefined(received: any, expected: void) { + const options = { + isNot: this.isNot, + promise: this.promise, + }; + ensureNoExpected(expected, 'toBeDefined', options); + + const pass = received !== void 0; + const message = () => - matcherHint('.toBeDefined', 'received', '', { - isNot: this.isNot, - }) + + matcherHint('toBeDefined', undefined, '', options) + '\n\n' + - `Received: ${printReceived(actual)}`; + `Received: ${printReceived(received)}`; + return {message, pass}; }, - toBeFalsy(actual: any, expected: void) { - ensureNoExpected(expected, '.toBeFalsy'); - const pass = !actual; + toBeFalsy(received: any, expected: void) { + const options = { + isNot: this.isNot, + promise: this.promise, + }; + ensureNoExpected(expected, 'toBeFalsy', options); + + const pass = !received; + const message = () => - matcherHint('.toBeFalsy', 'received', '', { - isNot: this.isNot, - }) + + matcherHint('toBeFalsy', undefined, '', options) + '\n\n' + - `Received: ${printReceived(actual)}`; + `Received: ${printReceived(received)}`; + return {message, pass}; }, @@ -229,51 +239,70 @@ const matchers: MatchersObject = { return {message, pass}; }, - toBeNaN(actual: any, expected: void) { - ensureNoExpected(expected, '.toBeNaN'); - const pass = Number.isNaN(actual); + toBeNaN(received: any, expected: void) { + const options = { + isNot: this.isNot, + promise: this.promise, + }; + ensureNoExpected(expected, 'toBeNaN', options); + + const pass = Number.isNaN(received); + const message = () => - matcherHint('.toBeNaN', 'received', '', { - isNot: this.isNot, - }) + + matcherHint('toBeNaN', undefined, '', options) + '\n\n' + - `Received: ${printReceived(actual)}`; + `Received: ${printReceived(received)}`; + return {message, pass}; }, - toBeNull(actual: any, expected: void) { - ensureNoExpected(expected, '.toBeNull'); - const pass = actual === null; + toBeNull(received: any, expected: void) { + const options = { + isNot: this.isNot, + promise: this.promise, + }; + ensureNoExpected(expected, 'toBeNull', options); + + const pass = received === null; + const message = () => - matcherHint('.toBeNull', 'received', '', { - isNot: this.isNot, - }) + + matcherHint('toBeNull', undefined, '', options) + '\n\n' + - `Received: ${printReceived(actual)}`; + `Received: ${printReceived(received)}`; + return {message, pass}; }, - toBeTruthy(actual: any, expected: void) { - ensureNoExpected(expected, '.toBeTruthy'); - const pass = !!actual; + toBeTruthy(received: any, expected: void) { + const options = { + isNot: this.isNot, + promise: this.promise, + }; + ensureNoExpected(expected, 'toBeTruthy', options); + + const pass = !!received; + const message = () => - matcherHint('.toBeTruthy', 'received', '', { - isNot: this.isNot, - }) + + matcherHint('toBeTruthy', undefined, '', options) + '\n\n' + - `Received: ${printReceived(actual)}`; + `Received: ${printReceived(received)}`; + return {message, pass}; }, - toBeUndefined(actual: any, expected: void) { - ensureNoExpected(expected, '.toBeUndefined'); - const pass = actual === void 0; + toBeUndefined(received: any, expected: void) { + const options = { + isNot: this.isNot, + promise: this.promise, + }; + ensureNoExpected(expected, 'toBeUndefined', options); + + const pass = received === void 0; + const message = () => - matcherHint('.toBeUndefined', 'received', '', { - isNot: this.isNot, - }) + + matcherHint('toBeUndefined', undefined, '', options) + '\n\n' + - `Received: ${printReceived(actual)}`; + `Received: ${printReceived(received)}`; return {message, pass}; }, diff --git a/packages/jest-matcher-utils/src/__tests__/__snapshots__/index.test.js.snap b/packages/jest-matcher-utils/src/__tests__/__snapshots__/index.test.js.snap index fb80b64d1896..32ae2ce0d013 100644 --- a/packages/jest-matcher-utils/src/__tests__/__snapshots__/index.test.js.snap +++ b/packages/jest-matcher-utils/src/__tests__/__snapshots__/index.test.js.snap @@ -1,18 +1,18 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`.ensureNoExpected() throws error when expected is not undefined 1`] = ` -"expect(received)[.not]This() +exports[`.ensureNoExpected() throws error when expected is not undefined with matcherName 1`] = ` +"expect(received)[.not].toBeDefined() -Matcher error: expected value must be omitted or undefined +Matcher error: this matcher must not have an expected argument Expected has type: object Expected has value: {\\"a\\": 1}" `; -exports[`.ensureNoExpected() throws error when expected is not undefined with matcherName 1`] = ` -"expect(received)[.not].toBeDefined() +exports[`.ensureNoExpected() throws error when expected is not undefined with matcherName and options 1`] = ` +"expect(received).not.toBeDefined() -Matcher error: expected value must be omitted or undefined +Matcher error: this matcher must not have an expected argument Expected has type: object Expected has value: {\\"a\\": 1}" diff --git a/packages/jest-matcher-utils/src/__tests__/index.test.js b/packages/jest-matcher-utils/src/__tests__/index.test.js index b6ab75a5722c..f36b931768d0 100644 --- a/packages/jest-matcher-utils/src/__tests__/index.test.js +++ b/packages/jest-matcher-utils/src/__tests__/index.test.js @@ -116,15 +116,15 @@ describe('.ensureNoExpected()', () => { }).not.toThrow(); }); - test('throws error when expected is not undefined', () => { + test('throws error when expected is not undefined with matcherName', () => { expect(() => { - ensureNoExpected({a: 1}); + ensureNoExpected({a: 1}, '.toBeDefined'); }).toThrowErrorMatchingSnapshot(); }); - test('throws error when expected is not undefined with matcherName', () => { + test('throws error when expected is not undefined with matcherName and options', () => { expect(() => { - ensureNoExpected({a: 1}, '.toBeDefined'); + ensureNoExpected({a: 1}, 'toBeDefined', {isNot: true}); }).toThrowErrorMatchingSnapshot(); }); }); diff --git a/packages/jest-matcher-utils/src/index.js b/packages/jest-matcher-utils/src/index.js index 4b0b8be351fe..629a99039bd7 100644 --- a/packages/jest-matcher-utils/src/index.js +++ b/packages/jest-matcher-utils/src/index.js @@ -7,6 +7,8 @@ * @flow */ +import type {MatcherHintOptions} from 'types/Matchers'; + import chalk from 'chalk'; import getType from 'jest-get-type'; import prettyFormat from 'pretty-format'; @@ -30,6 +32,7 @@ const PLUGINS = [ export const EXPECTED_COLOR = chalk.green; export const RECEIVED_COLOR = chalk.red; +const DIM_COLOR = chalk.dim; const NUMBERS = [ 'zero', @@ -102,13 +105,20 @@ export const printWithType = ( return hasType + hasValue; }; -export const ensureNoExpected = (expected: any, matcherName: string) => { - matcherName || (matcherName = 'This'); +export const ensureNoExpected = ( + expected: any, + matcherName: string, + options?: MatcherHintOptions, +) => { if (typeof expected !== 'undefined') { + // Prepend maybe not only for backward compatibility. + const matcherString = (options ? '' : '[.not]') + matcherName; throw new Error( matcherErrorMessage( - matcherHint('[.not]' + matcherName, undefined, ''), - `${EXPECTED_COLOR('expected')} value must be omitted or undefined`, + matcherHint(matcherString, undefined, '', options), + // Because expected is omitted in hint above, + // expected is black instead of green in message below. + 'this matcher must not have an expected argument', printWithType('Expected', expected, printExpected), ), ); @@ -174,28 +184,67 @@ export const matcherErrorMessage = ( specific: string, // incorrect value returned from call to printWithType ) => `${hint}\n\n${chalk.bold('Matcher error')}: ${generic}\n\n${specific}`; +// Display assertion for the report when a test fails. +// New format: rejects/resolves, not, and matcher name have black color +// Old format: matcher name has dim color export const matcherHint = ( matcherName: string, received: string = 'received', expected: string = 'expected', - options: { - comment?: string, - isDirectExpectCall?: boolean, - isNot?: boolean, - secondArgument?: ?string, - } = {}, + options: MatcherHintOptions = {}, ) => { - const {comment, isDirectExpectCall, isNot, secondArgument} = options; - return ( - chalk.dim('expect' + (isDirectExpectCall ? '' : '(')) + - RECEIVED_COLOR(received) + - (isNot - ? `${chalk.dim(').')}not${chalk.dim(matcherName + '(')}` - : chalk.dim((isDirectExpectCall ? '' : ')') + matcherName + '(')) + - EXPECTED_COLOR(expected) + - (secondArgument - ? `${chalk.dim(', ')}${EXPECTED_COLOR(secondArgument)}` - : '') + - chalk.dim(`)${comment ? ` // ${comment}` : ''}`) - ); + const { + comment = '', + isDirectExpectCall = false, // seems redundant with received === '' + isNot = false, + promise = '', + secondArgument = '', + } = options; + let hint = ''; + let dimString = 'expect'; // concatenate adjacent dim substrings + + if (!isDirectExpectCall && received !== '') { + hint += DIM_COLOR(dimString + '(') + RECEIVED_COLOR(received); + dimString = ')'; + } + + if (promise !== '') { + hint += DIM_COLOR(dimString + '.') + promise; + dimString = ''; + } + + if (isNot) { + hint += DIM_COLOR(dimString + '.') + 'not'; + dimString = ''; + } + + if (matcherName.includes('.')) { + // Old format: for backward compatibility, + // especially without promise or isNot options + dimString += matcherName; + } else { + // New format: omit period from matcherName arg + hint += DIM_COLOR(dimString + '.') + matcherName; + dimString = ''; + } + + if (expected === '') { + dimString += '()'; + } else { + hint += DIM_COLOR(dimString + '(') + EXPECTED_COLOR(expected); + if (secondArgument) { + hint += DIM_COLOR(', ') + EXPECTED_COLOR(secondArgument); + } + dimString = ')'; + } + + if (comment !== '') { + dimString += ' // ' + comment; + } + + if (dimString !== '') { + hint += DIM_COLOR(dimString); + } + + return hint; }; diff --git a/types/Matchers.js b/types/Matchers.js index 79e89d433fb8..768bb12c2725 100644 --- a/types/Matchers.js +++ b/types/Matchers.js @@ -36,6 +36,7 @@ export type MatcherState = { expectedAssertionsNumber: ?number, isExpectingAssertions: ?boolean, isNot: boolean, + promise: string, snapshotState: SnapshotState, suppressedErrors: Array, testPath?: Path, @@ -80,3 +81,11 @@ export type ExpectationObject = { }, not: {[id: string]: ThrowingMatcherFn}, }; + +export type MatcherHintOptions = { + comment?: string, + isDirectExpectCall?: boolean, + isNot?: boolean, + promise?: string, + secondArgument?: ?string, +};