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 3 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
67 changes: 47 additions & 20 deletions packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap
Expand Up @@ -1198,84 +1198,111 @@ 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>)</>

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 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: 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: <green>A</>
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], // omit Expected constuctor
].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() {}], // omit Received constructor
[new DefinesNameProp(), RegExp], // omit Expected constructor
pedrottimark marked this conversation as resolved.
Show resolved Hide resolved
].forEach(([a, b]) => {
test(`failing ${stringify(a)} and ${stringify(b)}`, () => {
expect(() =>
Expand Down
60 changes: 34 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,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 has no prototype\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};
Expand Down