Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

expect: Improve report when matcher fails, part 13 #8077

Merged
merged 6 commits into from Mar 20, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,8 @@

### Features

- `[expect]`: Improve report when matcher fails, part 13 ([#8077](https://github.com/facebook/jest/pull/8077))

### Fixes

### Chore & Maintenance
Expand Down
70 changes: 50 additions & 20 deletions packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap
Expand Up @@ -1198,84 +1198,114 @@ Received: <red>34</>"
`;

exports[`.toBeInstanceOf() failing "a" and [Function String] 1`] = `
"<dim>expect(</><red>value</><dim>).toBeInstanceOf(</><green>constructor</><dim>)</>
"<dim>expect(</><red>received</><dim>).</>toBeInstanceOf<dim>(</><green>expected</><dim>)</>

Expected constructor: <green>String</>
Received constructor: <red>String</>
Received value has no prototype
Received value: <red>\\"a\\"</>"
`;

exports[`.toBeInstanceOf() failing /\\w+/ and [Function anonymous] 1`] = `
"<dim>expect(</><red>received</><dim>).</>toBeInstanceOf<dim>(</><green>expected</><dim>)</>

Expected constructor name is an empty string
Received constructor: <red>RegExp</>
Received value: <red>/\\\\w+/</>"
`;

exports[`.toBeInstanceOf() failing {} and [Function A] 1`] = `
"<dim>expect(</><red>value</><dim>).toBeInstanceOf(</><green>constructor</><dim>)</>
"<dim>expect(</><red>received</><dim>).</>toBeInstanceOf<dim>(</><green>expected</><dim>)</>

Expected constructor: <green>A</>
Received constructor: <red>undefined</>
Received value has no prototype
Received value: <red>{}</>"
`;

exports[`.toBeInstanceOf() failing {} and [Function B] 1`] = `
"<dim>expect(</><red>value</><dim>).toBeInstanceOf(</><green>constructor</><dim>)</>
"<dim>expect(</><red>received</><dim>).</>toBeInstanceOf<dim>(</><green>expected</><dim>)</>

Expected constructor: <green>B</>
Received constructor: <red>A</>
Received value: <red>{}</>"
`;

exports[`.toBeInstanceOf() failing {} and [Function RegExp] 1`] = `
"<dim>expect(</><red>received</><dim>).</>toBeInstanceOf<dim>(</><green>expected</><dim>)</>

Expected constructor: <green>RegExp</>
Received constructor name is an empty string
Received value: <red>{}</>"
`;

exports[`.toBeInstanceOf() failing 1 and [Function Number] 1`] = `
"<dim>expect(</><red>value</><dim>).toBeInstanceOf(</><green>constructor</><dim>)</>
"<dim>expect(</><red>received</><dim>).</>toBeInstanceOf<dim>(</><green>expected</><dim>)</>

Expected constructor: <green>Number</>
Received constructor: <red>Number</>
Received value has no prototype
Received value: <red>1</>"
`;

exports[`.toBeInstanceOf() failing null and [Function String] 1`] = `
"<dim>expect(</><red>value</><dim>).toBeInstanceOf(</><green>constructor</><dim>)</>
"<dim>expect(</><red>received</><dim>).</>toBeInstanceOf<dim>(</><green>expected</><dim>)</>

Expected constructor: <green>String</>
Received constructor:
Received value has no prototype
Received value: <red>null</>"
`;

exports[`.toBeInstanceOf() failing true and [Function Boolean] 1`] = `
"<dim>expect(</><red>value</><dim>).toBeInstanceOf(</><green>constructor</><dim>)</>
"<dim>expect(</><red>received</><dim>).</>toBeInstanceOf<dim>(</><green>expected</><dim>)</>

Expected constructor: <green>Boolean</>
Received constructor: <red>Boolean</>
Received value has no prototype
Received value: <red>true</>"
`;

exports[`.toBeInstanceOf() failing undefined and [Function String] 1`] = `
"<dim>expect(</><red>value</><dim>).toBeInstanceOf(</><green>constructor</><dim>)</>
"<dim>expect(</><red>received</><dim>).</>toBeInstanceOf<dim>(</><green>expected</><dim>)</>

Expected constructor: <green>String</>
Received constructor:
Received value has no prototype
Received value: <red>undefined</>"
`;

exports[`.toBeInstanceOf() passing [] and [Function Array] 1`] = `
"<dim>expect(</><red>value</><dim>).</>not<dim>.toBeInstanceOf(</><green>constructor</><dim>)</>
"<dim>expect(</><red>received</><dim>).</>not<dim>.</>toBeInstanceOf<dim>(</><green>expected</><dim>)</>

Expected constructor: <green>Array</>
Expected constructor: not <green>Array</>
Received value: <red>[]</>"
`;

exports[`.toBeInstanceOf() passing {} and [Function A] 1`] = `
"<dim>expect(</><red>value</><dim>).</>not<dim>.toBeInstanceOf(</><green>constructor</><dim>)</>
"<dim>expect(</><red>received</><dim>).</>not<dim>.</>toBeInstanceOf<dim>(</><green>expected</><dim>)</>

Expected constructor: <green>A</>
Expected constructor: not <green>A</>
Received value: <red>{}</>"
`;

exports[`.toBeInstanceOf() passing {} and [Function B] 1`] = `
"<dim>expect(</><red>received</><dim>).</>not<dim>.</>toBeInstanceOf<dim>(</><green>expected</><dim>)</>

Expected constructor: not <green>B</>
Received value: <red>{}</>"
`;

exports[`.toBeInstanceOf() passing {} and [Function name() {}] 1`] = `
"<dim>expect(</><red>received</><dim>).</>not<dim>.</>toBeInstanceOf<dim>(</><green>expected</><dim>)</>

Expected constructor name is not a string
Received value: <red>{}</>"
`;

exports[`.toBeInstanceOf() passing Map {} and [Function Map] 1`] = `
"<dim>expect(</><red>value</><dim>).</>not<dim>.toBeInstanceOf(</><green>constructor</><dim>)</>
"<dim>expect(</><red>received</><dim>).</>not<dim>.</>toBeInstanceOf<dim>(</><green>expected</><dim>)</>

Expected constructor: <green>Map</>
Expected constructor: not <green>Map</>
Received value: <red>Map {}</>"
`;

exports[`.toBeInstanceOf() throws if constructor is not a function 1`] = `
"<dim>expect(</><red>received</><dim>).toBeInstanceOf(</><green>expected</><dim>)</>
"<dim>expect(</><red>received</><dim>).</>toBeInstanceOf<dim>(</><green>expected</><dim>)</>

<bold>Matcher error</>: <green>expected</> value must be a function

Expand Down
24 changes: 23 additions & 1 deletion packages/expect/src/__tests__/matchers.test.js
Expand Up @@ -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],
].forEach(([a, b]) => {
test(`passing ${stringify(a)} and ${stringify(b)}`, () => {
expect(() =>
jestExpect(a).not.toBeInstanceOf(b),
Expand All @@ -703,6 +723,8 @@ describe('.toBeInstanceOf()', () => {
[Object.create(null), A],
[undefined, String],
[null, String],
[/\w+/, function() {}],
[new DefinesNameProp(), RegExp],
].forEach(([a, b]) => {
test(`failing ${stringify(a)} and ${stringify(b)}`, () => {
expect(() =>
Expand Down
69 changes: 43 additions & 26 deletions packages/expect/src/matchers.ts
Expand Up @@ -6,7 +6,7 @@
*
*/

import getType from 'jest-get-type';
import getType, {isPrimitive} from 'jest-get-type';
import {
EXPECTED_COLOR,
RECEIVED_COLOR,
Expand Down Expand Up @@ -220,45 +220,62 @@ 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 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', '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 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', '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 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_IS_NOT_STRING
: received.constructor.name.length === 0
? 'Received constructor' + NAME_IS_EMPTY_STRING
: `Received constructor: ${RECEIVED_COLOR(
received.constructor.name,
)}\n`) +
`Received value: ${printReceived(received)}`;

return {message, pass};
Expand Down