From 02aa6c18dc0f43102120e10049cb4bfdea511be3 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Thu, 7 Mar 2019 10:29:24 -0500 Subject: [PATCH 1/5] expect: Improve report when matcher fails, part 13 --- .../__snapshots__/matchers.test.js.snap | 67 +++++++++++++------ .../expect/src/__tests__/matchers.test.js | 24 ++++++- packages/expect/src/matchers.ts | 60 ++++++++++------- 3 files changed, 104 insertions(+), 47 deletions(-) diff --git a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap index 42237ee166b2..2efec90b83d9 100644 --- a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap @@ -1198,84 +1198,111 @@ Received: 34" `; exports[`.toBeInstanceOf() failing "a" and [Function String] 1`] = ` -"expect(value).toBeInstanceOf(constructor) +"expect(received).toBeInstanceOf(expected) Expected constructor: String -Received constructor: String +Received value does not have a prototype chain Received value: \\"a\\"" `; +exports[`.toBeInstanceOf() failing /\\w+/ and [Function anonymous] 1`] = ` +"expect(received).toBeInstanceOf(expected) + +Received constructor: RegExp +Received value: /\\\\w+/" +`; + exports[`.toBeInstanceOf() failing {} and [Function A] 1`] = ` -"expect(value).toBeInstanceOf(constructor) +"expect(received).toBeInstanceOf(expected) Expected constructor: A -Received constructor: undefined +Received value does not have a prototype chain Received value: {}" `; exports[`.toBeInstanceOf() failing {} and [Function B] 1`] = ` -"expect(value).toBeInstanceOf(constructor) +"expect(received).toBeInstanceOf(expected) Expected constructor: B Received constructor: A Received value: {}" `; +exports[`.toBeInstanceOf() failing {} and [Function RegExp] 1`] = ` +"expect(received).toBeInstanceOf(expected) + +Expected constructor: RegExp +Received value: {}" +`; + exports[`.toBeInstanceOf() failing 1 and [Function Number] 1`] = ` -"expect(value).toBeInstanceOf(constructor) +"expect(received).toBeInstanceOf(expected) Expected constructor: Number -Received constructor: Number +Received value does not have a prototype chain Received value: 1" `; exports[`.toBeInstanceOf() failing null and [Function String] 1`] = ` -"expect(value).toBeInstanceOf(constructor) +"expect(received).toBeInstanceOf(expected) Expected constructor: String -Received constructor: +Received value does not have a prototype chain Received value: null" `; exports[`.toBeInstanceOf() failing true and [Function Boolean] 1`] = ` -"expect(value).toBeInstanceOf(constructor) +"expect(received).toBeInstanceOf(expected) Expected constructor: Boolean -Received constructor: Boolean +Received value does not have a prototype chain Received value: true" `; exports[`.toBeInstanceOf() failing undefined and [Function String] 1`] = ` -"expect(value).toBeInstanceOf(constructor) +"expect(received).toBeInstanceOf(expected) Expected constructor: String -Received constructor: +Received value does not have a prototype chain Received value: undefined" `; exports[`.toBeInstanceOf() passing [] and [Function Array] 1`] = ` -"expect(value).not.toBeInstanceOf(constructor) +"expect(received).not.toBeInstanceOf(expected) -Expected constructor: Array +Expected constructor: not Array Received value: []" `; exports[`.toBeInstanceOf() passing {} and [Function A] 1`] = ` -"expect(value).not.toBeInstanceOf(constructor) +"expect(received).not.toBeInstanceOf(expected) + +Expected constructor: not A +Received value: {}" +`; + +exports[`.toBeInstanceOf() passing {} and [Function B] 1`] = ` +"expect(received).not.toBeInstanceOf(expected) + +Expected constructor: not B +Received value: {}" +`; + +exports[`.toBeInstanceOf() passing {} and [Function name() {}] 1`] = ` +"expect(received).not.toBeInstanceOf(expected) -Expected constructor: A Received value: {}" `; exports[`.toBeInstanceOf() passing Map {} and [Function Map] 1`] = ` -"expect(value).not.toBeInstanceOf(constructor) +"expect(received).not.toBeInstanceOf(expected) -Expected constructor: Map +Expected constructor: not Map Received value: Map {}" `; exports[`.toBeInstanceOf() throws if constructor is not a function 1`] = ` -"expect(received).toBeInstanceOf(expected) +"expect(received).toBeInstanceOf(expected) Matcher error: expected value must be a function diff --git a/packages/expect/src/__tests__/matchers.test.js b/packages/expect/src/__tests__/matchers.test.js index dc31c645f5d9..ff5a8d948f01 100644 --- a/packages/expect/src/__tests__/matchers.test.js +++ b/packages/expect/src/__tests__/matchers.test.js @@ -684,8 +684,28 @@ describe('.toEqual()', () => { describe('.toBeInstanceOf()', () => { class A {} class B {} + class C extends B {} - [[new Map(), Map], [[], Array], [new A(), A]].forEach(([a, b]) => { + class HasStaticNameMethod { + constructor() {} + static name() {} + } + + function DefinesNameProp() {} + Object.defineProperty(DefinesNameProp, 'name', { + configurable: true, + enumerable: false, + value: '', + writable: true, + }); + + [ + [new Map(), Map], + [[], Array], + [new A(), A], + [new C(), B], // subclass + [new HasStaticNameMethod(), HasStaticNameMethod], // omit Expected constuctor + ].forEach(([a, b]) => { test(`passing ${stringify(a)} and ${stringify(b)}`, () => { expect(() => jestExpect(a).not.toBeInstanceOf(b), @@ -703,6 +723,8 @@ describe('.toBeInstanceOf()', () => { [Object.create(null), A], [undefined, String], [null, String], + [/\w+/, function() {}], // omit Received constructor + [new DefinesNameProp(), RegExp], // omit Expected constructor ].forEach(([a, b]) => { test(`failing ${stringify(a)} and ${stringify(b)}`, () => { expect(() => diff --git a/packages/expect/src/matchers.ts b/packages/expect/src/matchers.ts index bad0f0424f36..9696cb3b7271 100644 --- a/packages/expect/src/matchers.ts +++ b/packages/expect/src/matchers.ts @@ -6,7 +6,7 @@ * */ -import getType from 'jest-get-type'; +import getType, {isPrimitive} from 'jest-get-type'; import { EXPECTED_COLOR, RECEIVED_COLOR, @@ -220,45 +220,53 @@ const matchers: MatchersObject = { return {message, pass}; }, - toBeInstanceOf(this: MatcherState, received: any, constructor: Function) { - const constType = getType(constructor); + toBeInstanceOf(this: MatcherState, received: any, expected: Function) { + const options: MatcherHintOptions = { + isNot: this.isNot, + promise: this.promise, + }; - if (constType !== 'function') { + if (typeof expected !== 'function') { throw new Error( matcherErrorMessage( - matcherHint('.toBeInstanceOf', undefined, undefined, { - isNot: this.isNot, - }), + matcherHint('toBeInstanceOf', undefined, undefined, options), `${EXPECTED_COLOR('expected')} value must be a function`, - printWithType('Expected', constructor, printExpected), + printWithType('Expected', expected, printExpected), ), ); } - const pass = received instanceof constructor; + + const pass = received instanceof expected; const message = pass ? () => - matcherHint('.toBeInstanceOf', 'value', 'constructor', { - isNot: this.isNot, - }) + + matcherHint('toBeInstanceOf', undefined, undefined, options) + '\n\n' + - `Expected constructor: ${EXPECTED_COLOR( - constructor.name || String(constructor), - )}\n` + + // A truthy test for `expected.name` property has false positive for: + // function with a defined name property + // class with a static name method + (typeof expected.name === 'string' && expected.name.length !== 0 + ? `Expected constructor: not ${EXPECTED_COLOR(expected.name)}\n` + : '') + `Received value: ${printReceived(received)}` : () => - matcherHint('.toBeInstanceOf', 'value', 'constructor', { - isNot: this.isNot, - }) + + matcherHint('toBeInstanceOf', undefined, undefined, options) + '\n\n' + - `Expected constructor: ${EXPECTED_COLOR( - constructor.name || String(constructor), - )}\n` + - `Received constructor: ${RECEIVED_COLOR( - received != null - ? received.constructor && received.constructor.name - : '', - )}\n` + + // A truthy test for `expected.name` property has false positive for: + // function with a defined name property + // class with a static name method + (typeof expected.name === 'string' && expected.name.length !== 0 + ? `Expected constructor: ${EXPECTED_COLOR(expected.name)}\n` + : '') + + (isPrimitive(received) || Object.getPrototypeOf(received) === null + ? 'Received value does not have a prototype chain\n' + : typeof received.constructor === 'function' && + typeof received.constructor.name === 'string' && + received.constructor.name.length !== 0 + ? `Received constructor: ${RECEIVED_COLOR( + received.constructor.name, + )}\n` + : '') + `Received value: ${printReceived(received)}`; return {message, pass}; From e9e2b5ecbbad1ee4339a7861fc3af37720efabbb Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Thu, 7 Mar 2019 10:42:06 -0500 Subject: [PATCH 2/5] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d054e84fa592..7d624dfd3f55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Features +- `[expect]`: Improve report when matcher fails, part 13 ([#8077](https://github.com/facebook/jest/pull/8077)) + ### Fixes ### Chore & Maintenance From 81051ad4c216524a1f3987b519f309ebad4b1ec7 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Fri, 8 Mar 2019 11:47:12 -0500 Subject: [PATCH 3/5] Received value has no prototype --- .../__tests__/__snapshots__/matchers.test.js.snap | 12 ++++++------ packages/expect/src/matchers.ts | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap index 2efec90b83d9..ebcf1a40a2c6 100644 --- a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap @@ -1201,7 +1201,7 @@ exports[`.toBeInstanceOf() failing "a" and [Function String] 1`] = ` "expect(received).toBeInstanceOf(expected) Expected constructor: String -Received value does not have a prototype chain +Received value has no prototype Received value: \\"a\\"" `; @@ -1216,7 +1216,7 @@ exports[`.toBeInstanceOf() failing {} and [Function A] 1`] = ` "expect(received).toBeInstanceOf(expected) Expected constructor: A -Received value does not have a prototype chain +Received value has no prototype Received value: {}" `; @@ -1239,7 +1239,7 @@ exports[`.toBeInstanceOf() failing 1 and [Function Number] 1`] = ` "expect(received).toBeInstanceOf(expected) Expected constructor: Number -Received value does not have a prototype chain +Received value has no prototype Received value: 1" `; @@ -1247,7 +1247,7 @@ exports[`.toBeInstanceOf() failing null and [Function String] 1`] = ` "expect(received).toBeInstanceOf(expected) Expected constructor: String -Received value does not have a prototype chain +Received value has no prototype Received value: null" `; @@ -1255,7 +1255,7 @@ exports[`.toBeInstanceOf() failing true and [Function Boolean] 1`] = ` "expect(received).toBeInstanceOf(expected) Expected constructor: Boolean -Received value does not have a prototype chain +Received value has no prototype Received value: true" `; @@ -1263,7 +1263,7 @@ exports[`.toBeInstanceOf() failing undefined and [Function String] 1`] = ` "expect(received).toBeInstanceOf(expected) Expected constructor: String -Received value does not have a prototype chain +Received value has no prototype Received value: undefined" `; diff --git a/packages/expect/src/matchers.ts b/packages/expect/src/matchers.ts index 9696cb3b7271..ff0aa8fb5251 100644 --- a/packages/expect/src/matchers.ts +++ b/packages/expect/src/matchers.ts @@ -259,7 +259,7 @@ const matchers: MatchersObject = { ? `Expected constructor: ${EXPECTED_COLOR(expected.name)}\n` : '') + (isPrimitive(received) || Object.getPrototypeOf(received) === null - ? 'Received value does not have a prototype chain\n' + ? 'Received value has no prototype\n' : typeof received.constructor === 'function' && typeof received.constructor.name === 'string' && received.constructor.name.length !== 0 From 47e5390601cfa8d06bc7e9cc3958df8f5d068288 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 13 Mar 2019 16:52:57 -0400 Subject: [PATCH 4/5] Display not string and empty string messages --- .../__snapshots__/matchers.test.js.snap | 3 ++ packages/expect/src/matchers.ts | 33 ++++++++++++------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap index ebcf1a40a2c6..34be35ea0c08 100644 --- a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap @@ -1208,6 +1208,7 @@ Received value: \\"a\\"" exports[`.toBeInstanceOf() failing /\\w+/ and [Function anonymous] 1`] = ` "expect(received).toBeInstanceOf(expected) +Expected constructor name is an empty string Received constructor: RegExp Received value: /\\\\w+/" `; @@ -1232,6 +1233,7 @@ exports[`.toBeInstanceOf() failing {} and [Function RegExp] 1`] = ` "expect(received).toBeInstanceOf(expected) Expected constructor: RegExp +Received constructor name is an empty string Received value: {}" `; @@ -1291,6 +1293,7 @@ Received value: {}" exports[`.toBeInstanceOf() passing {} and [Function name() {}] 1`] = ` "expect(received).not.toBeInstanceOf(expected) +Expected constructor name is not a string Received value: {}" `; diff --git a/packages/expect/src/matchers.ts b/packages/expect/src/matchers.ts index ff0aa8fb5251..60c53d9ec436 100644 --- a/packages/expect/src/matchers.ts +++ b/packages/expect/src/matchers.ts @@ -238,6 +238,9 @@ const matchers: MatchersObject = { const pass = received instanceof expected; + const NAME_IS_NOT_STRING = ' name is not a string\n'; + const NAME_IS_EMPTY_STRING = ' name is an empty string\n'; + const message = pass ? () => matcherHint('toBeInstanceOf', undefined, undefined, options) + @@ -245,9 +248,11 @@ const matchers: MatchersObject = { // A truthy test for `expected.name` property has false positive for: // function with a defined name property // class with a static name method - (typeof expected.name === 'string' && expected.name.length !== 0 - ? `Expected constructor: not ${EXPECTED_COLOR(expected.name)}\n` - : '') + + (typeof expected.name !== 'string' + ? 'Expected constructor' + NAME_IS_NOT_STRING + : expected.name.length === 0 + ? 'Expected constructor' + NAME_IS_EMPTY_STRING + : `Expected constructor: not ${EXPECTED_COLOR(expected.name)}\n`) + `Received value: ${printReceived(received)}` : () => matcherHint('toBeInstanceOf', undefined, undefined, options) + @@ -255,18 +260,22 @@ const matchers: MatchersObject = { // A truthy test for `expected.name` property has false positive for: // function with a defined name property // class with a static name method - (typeof expected.name === 'string' && expected.name.length !== 0 - ? `Expected constructor: ${EXPECTED_COLOR(expected.name)}\n` - : '') + + (typeof expected.name !== 'string' + ? 'Expected constructor' + NAME_IS_NOT_STRING + : expected.name.length === 0 + ? 'Expected constructor' + NAME_IS_EMPTY_STRING + : `Expected constructor: ${EXPECTED_COLOR(expected.name)}\n`) + (isPrimitive(received) || Object.getPrototypeOf(received) === null ? 'Received value has no prototype\n' - : typeof received.constructor === 'function' && - typeof received.constructor.name === 'string' && - received.constructor.name.length !== 0 - ? `Received constructor: ${RECEIVED_COLOR( + : typeof received.constructor !== 'function' + ? '' + : typeof received.constructor.name !== 'string' + ? 'Received constructor' + NAME_IS_NOT_STRING + : received.constructor.name.length === 0 + ? 'Received constructor' + NAME_IS_EMPTY_STRING + : `Received constructor: ${RECEIVED_COLOR( received.constructor.name, - )}\n` - : '') + + )}\n`) + `Received value: ${printReceived(received)}`; return {message, pass}; From 424b19f296e8db497d06e407ee3d06126bb63aab Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 13 Mar 2019 16:53:35 -0400 Subject: [PATCH 5/5] Remove 3 comments about test cases --- packages/expect/src/__tests__/matchers.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/expect/src/__tests__/matchers.test.js b/packages/expect/src/__tests__/matchers.test.js index ff5a8d948f01..953de2f2fcaf 100644 --- a/packages/expect/src/__tests__/matchers.test.js +++ b/packages/expect/src/__tests__/matchers.test.js @@ -704,7 +704,7 @@ describe('.toBeInstanceOf()', () => { [[], Array], [new A(), A], [new C(), B], // subclass - [new HasStaticNameMethod(), HasStaticNameMethod], // omit Expected constuctor + [new HasStaticNameMethod(), HasStaticNameMethod], ].forEach(([a, b]) => { test(`passing ${stringify(a)} and ${stringify(b)}`, () => { expect(() => @@ -723,8 +723,8 @@ describe('.toBeInstanceOf()', () => { [Object.create(null), A], [undefined, String], [null, String], - [/\w+/, function() {}], // omit Received constructor - [new DefinesNameProp(), RegExp], // omit Expected constructor + [/\w+/, function() {}], + [new DefinesNameProp(), RegExp], ].forEach(([a, b]) => { test(`failing ${stringify(a)} and ${stringify(b)}`, () => { expect(() =>