Skip to content

Commit

Permalink
expect: Improve report when matcher fails, part 13 (#8077)
Browse files Browse the repository at this point in the history
  • Loading branch information
pedrottimark authored and SimenB committed Mar 20, 2019
1 parent 5bb9624 commit ed57141
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 47 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,7 @@

### Features

- `[expect]`: Improve report when matcher fails, part 13 ([#8077](https://github.com/facebook/jest/pull/8077))
- `[@jest/core]` Filter API pre-filter setup hook ([#8142](https://github.com/facebook/jest/pull/8142))
- `[jest-snapshot]` Improve report when matcher fails, part 14 ([#8132](https://github.com/facebook/jest/pull/8132))
- `[@jest/reporter]` Display todo and skip test descriptions when verbose is true ([#8038](https://github.com/facebook/jest/pull/8038))
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

0 comments on commit ed57141

Please sign in to comment.