Skip to content

Commit

Permalink
expect: Fix non-object received value in toHaveProperty (#7986)
Browse files Browse the repository at this point in the history
  • Loading branch information
pedrottimark authored and SimenB committed Feb 28, 2019
1 parent de52b48 commit ec5e2d0
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -26,6 +26,7 @@
- `[jest-changed-files]` Fix `getChangedFilesFromRoots` to not return parts of the commit messages as if they were files, when the commit messages contained multiple paragraphs ([#7961](https://github.com/facebook/jest/pull/7961))
- `[jest-haste-map]` Enforce uniqueness in names (mocks and haste ids) ([#8002](https://github.com/facebook/jest/pull/8002))
- `[static]` Remove console log '-' on the front page
- `[expect]` Fix non-object received value in toHaveProperty ([#7986](https://github.com/facebook/jest/pull/7986))
- `[jest-jasmine2]`: Throw explicit error when errors happen after test is considered complete ([#8005](https://github.com/facebook/jest/pull/8005))
- `[jest-circus]`: Throw explicit error when errors happen after test is considered complete ([#8005](https://github.com/facebook/jest/pull/8005))

Expand Down
149 changes: 149 additions & 0 deletions packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap
Expand Up @@ -2822,6 +2822,8 @@ exports[`.toHaveProperty() {error} expect({"a": {"b": {}}}).toHaveProperty('unde
Expected has value: <green>undefined</>"
`;

exports[`.toHaveProperty() {error} expect({}).toHaveProperty('') 1`] = `"pass must be initialized"`;

exports[`.toHaveProperty() {error} expect(null).toHaveProperty('a.b') 1`] = `
"<dim>expect(</><red>received</><dim>).toHaveProperty(</><green>path</><dim>)</>

Expand All @@ -2838,6 +2840,16 @@ exports[`.toHaveProperty() {error} expect(undefined).toHaveProperty('a') 1`] = `
Received has value: <red>undefined</>"
`;

exports[`.toHaveProperty() {pass: false} expect("").toHaveProperty('key') 1`] = `
"<dim>expect(</><red>object</><dim>).toHaveProperty(</><green>path</><dim>)</>

Expected the object:
<red>\\"\\"</>
To have a nested property:
<green>\\"key\\"</>
"
`;

exports[`.toHaveProperty() {pass: false} expect("abc").toHaveProperty('a.b.c') 1`] = `
"<dim>expect(</><red>object</><dim>).toHaveProperty(</><green>path</><dim>)</>

Expand Down Expand Up @@ -2963,6 +2975,19 @@ Difference:
Comparing two different types of values. Expected <green>undefined</> but received <red>number</>."
`;

exports[`.toHaveProperty() {pass: false} expect({"a": {}}).toHaveProperty('a.b', undefined) 1`] = `
"<dim>expect(</><red>object</><dim>).toHaveProperty(</><green>path</><dim>, </><green>value</><dim>)</>

Expected the object:
<red>{\\"a\\": {}}</>
To have a nested property:
<green>\\"a.b\\"</>
With a value of:
<green>undefined</>
Received:
<red>object</>.a: <red>{}</>"
`;

exports[`.toHaveProperty() {pass: false} expect({"a": 1}).toHaveProperty('a.b.c.d') 1`] = `
"<dim>expect(</><red>object</><dim>).toHaveProperty(</><green>path</><dim>)</>

Expand Down Expand Up @@ -3012,6 +3037,16 @@ Received:
<red>1</>"
`;

exports[`.toHaveProperty() {pass: false} expect({"key": 1}).toHaveProperty('not') 1`] = `
"<dim>expect(</><red>object</><dim>).toHaveProperty(</><green>path</><dim>)</>

Expected the object:
<red>{\\"key\\": 1}</>
To have a nested property:
<green>\\"not\\"</>
"
`;

exports[`.toHaveProperty() {pass: false} expect({}).toHaveProperty('a') 1`] = `
"<dim>expect(</><red>object</><dim>).toHaveProperty(</><green>path</><dim>)</>

Expand Down Expand Up @@ -3068,6 +3103,16 @@ Difference:
Comparing two different types of values. Expected <green>undefined</> but received <red>string</>."
`;

exports[`.toHaveProperty() {pass: false} expect(0).toHaveProperty('key') 1`] = `
"<dim>expect(</><red>object</><dim>).toHaveProperty(</><green>path</><dim>)</>

Expected the object:
<red>0</>
To have a nested property:
<green>\\"key\\"</>
"
`;

exports[`.toHaveProperty() {pass: false} expect(1).toHaveProperty('a.b.c') 1`] = `
"<dim>expect(</><red>object</><dim>).toHaveProperty(</><green>path</><dim>)</>

Expand All @@ -3090,6 +3135,50 @@ With a value of:
"
`;

exports[`.toHaveProperty() {pass: false} expect(Symbol()).toHaveProperty('key') 1`] = `
"<dim>expect(</><red>object</><dim>).toHaveProperty(</><green>path</><dim>)</>

Expected the object:
<red>Symbol()</>
To have a nested property:
<green>\\"key\\"</>
"
`;

exports[`.toHaveProperty() {pass: false} expect(false).toHaveProperty('key') 1`] = `
"<dim>expect(</><red>object</><dim>).toHaveProperty(</><green>path</><dim>)</>

Expected the object:
<red>false</>
To have a nested property:
<green>\\"key\\"</>
"
`;

exports[`.toHaveProperty() {pass: true} expect("").toHaveProperty('length', 0) 1`] = `
"<dim>expect(</><red>object</><dim>).not.toHaveProperty(</><green>path</><dim>, </><green>value</><dim>)</>

Expected the object:
<red>\\"\\"</>
Not to have a nested property:
<green>\\"length\\"</>
With a value of:
<green>0</>
"
`;

exports[`.toHaveProperty() {pass: true} expect([Function memoized]).toHaveProperty('memo', []) 1`] = `
"<dim>expect(</><red>object</><dim>).not.toHaveProperty(</><green>path</><dim>, </><green>value</><dim>)</>

Expected the object:
<red>[Function memoized]</>
Not to have a nested property:
<green>\\"memo\\"</>
With a value of:
<green>[]</>
"
`;

exports[`.toHaveProperty() {pass: true} expect({"a": {"b": [1, 2, 3]}}).toHaveProperty('a,b,1') 1`] = `
"<dim>expect(</><red>object</><dim>).not.toHaveProperty(</><green>path</><dim>)</>

Expand Down Expand Up @@ -3234,6 +3323,18 @@ With a value of:
"
`;

exports[`.toHaveProperty() {pass: true} expect({"nodeName": "DIV"}).toHaveProperty('nodeType', 1) 1`] = `
"<dim>expect(</><red>object</><dim>).not.toHaveProperty(</><green>path</><dim>, </><green>value</><dim>)</>

Expected the object:
<red>{\\"nodeName\\": \\"DIV\\"}</>
Not to have a nested property:
<green>\\"nodeType\\"</>
With a value of:
<green>1</>
"
`;

exports[`.toHaveProperty() {pass: true} expect({"property": 1}).toHaveProperty('property', 1) 1`] = `
"<dim>expect(</><red>object</><dim>).not.toHaveProperty(</><green>path</><dim>, </><green>value</><dim>)</>

Expand All @@ -3246,6 +3347,42 @@ With a value of:
"
`;

exports[`.toHaveProperty() {pass: true} expect({"val": true}).toHaveProperty('a', undefined) 1`] = `
"<dim>expect(</><red>object</><dim>).not.toHaveProperty(</><green>path</><dim>, </><green>value</><dim>)</>

Expected the object:
<red>{\\"val\\": true}</>
Not to have a nested property:
<green>\\"a\\"</>
With a value of:
<green>undefined</>
"
`;

exports[`.toHaveProperty() {pass: true} expect({"val": true}).toHaveProperty('c', "c") 1`] = `
"<dim>expect(</><red>object</><dim>).not.toHaveProperty(</><green>path</><dim>, </><green>value</><dim>)</>

Expected the object:
<red>{\\"val\\": true}</>
Not to have a nested property:
<green>\\"c\\"</>
With a value of:
<green>\\"c\\"</>
"
`;

exports[`.toHaveProperty() {pass: true} expect({"val": true}).toHaveProperty('val', true) 1`] = `
"<dim>expect(</><red>object</><dim>).not.toHaveProperty(</><green>path</><dim>, </><green>value</><dim>)</>

Expected the object:
<red>{\\"val\\": true}</>
Not to have a nested property:
<green>\\"val\\"</>
With a value of:
<green>true</>
"
`;

exports[`.toHaveProperty() {pass: true} expect({}).toHaveProperty('a', undefined) 1`] = `
"<dim>expect(</><red>object</><dim>).not.toHaveProperty(</><green>path</><dim>, </><green>value</><dim>)</>

Expand All @@ -3270,6 +3407,18 @@ With a value of:
"
`;

exports[`.toHaveProperty() {pass: true} expect({}).toHaveProperty('setter', undefined) 1`] = `
"<dim>expect(</><red>object</><dim>).not.toHaveProperty(</><green>path</><dim>, </><green>value</><dim>)</>

Expected the object:
<red>{}</>
Not to have a nested property:
<green>\\"setter\\"</>
With a value of:
<green>undefined</>
"
`;

exports[`.toMatch() {pass: true} expect(Foo bar).toMatch(/^foo/i) 1`] = `
"<dim>expect(</><red>received</><dim>).not.toMatch(</><green>expected</><dim>)</>

Expand Down
33 changes: 33 additions & 0 deletions packages/expect/src/__tests__/matchers.test.js
Expand Up @@ -1270,7 +1270,26 @@ describe('.toHaveProperty()', () => {
get b() {
return 'b';
}
set setter(val) {
this.val = val;
}
}

class Foo2 extends Foo {
get c() {
return 'c';
}
}
const foo2 = new Foo2();
foo2.setter = true;

function E(nodeName) {
this.nodeName = nodeName.toUpperCase();
}
E.prototype.nodeType = 1;

const memoized = function() {};
memoized.memo = [];

[
[{a: {b: {c: {d: 1}}}}, 'a.b.c.d', 1],
Expand All @@ -1283,6 +1302,13 @@ describe('.toHaveProperty()', () => {
[Object.assign(Object.create(null), {property: 1}), 'property', 1],
[new Foo(), 'a', undefined],
[new Foo(), 'b', 'b'],
[new Foo(), 'setter', undefined],
[foo2, 'a', undefined],
[foo2, 'c', 'c'],
[foo2, 'val', true],
[new E('div'), 'nodeType', 1],
['', 'length', 0],
[memoized, 'memo', []],
].forEach(([obj, keyPath, value]) => {
test(`{pass: true} expect(${stringify(
obj,
Expand All @@ -1309,6 +1335,7 @@ describe('.toHaveProperty()', () => {
[{a: {b: {c: 5}}}, 'a.b', {c: 4}],
[new Foo(), 'a', 'a'],
[new Foo(), 'b', undefined],
[{a: {}}, 'a.b', undefined],
].forEach(([obj, keyPath, value]) => {
test(`{pass: false} expect(${stringify(
obj,
Expand Down Expand Up @@ -1344,6 +1371,11 @@ describe('.toHaveProperty()', () => {
[{}, 'a'],
[1, 'a.b.c'],
['abc', 'a.b.c'],
[false, 'key'],
[0, 'key'],
['', 'key'],
[Symbol(), 'key'],
[Object.assign(Object.create(null), {key: 1}), 'not'],
].forEach(([obj, keyPath]) => {
test(`{pass: false} expect(${stringify(
obj,
Expand All @@ -1361,6 +1393,7 @@ describe('.toHaveProperty()', () => {
[{a: {b: {}}}, undefined],
[{a: {b: {}}}, null],
[{a: {b: {}}}, 1],
[{}, []], // Residue: pass must be initialized
].forEach(([obj, keyPath]) => {
test(`{error} expect(${stringify(
obj,
Expand Down
6 changes: 3 additions & 3 deletions packages/expect/src/matchers.ts
Expand Up @@ -602,9 +602,9 @@ const matchers: MatchersObject = {
const result = getPath(object, keyPath);
const {lastTraversedObject, hasEndProp} = result;

const pass = valuePassed
? equals(result.value, value, [iterableEquality])
: hasEndProp;
const pass =
hasEndProp &&
(!valuePassed || equals(result.value, value, [iterableEquality]));

const traversedPath = result.traversedPath.join('.');

Expand Down
8 changes: 7 additions & 1 deletion packages/expect/src/utils.ts
Expand Up @@ -6,6 +6,7 @@
*
*/

import {isPrimitive} from 'jest-get-type';
import {
equals,
isA,
Expand Down Expand Up @@ -80,7 +81,12 @@ export const getPath = (
result.traversedPath.unshift(prop);

if (lastProp) {
result.hasEndProp = prop in object;
// Does object have the property with an undefined value?
// Although primitive values support bracket notation (above)
// they would throw TypeError for in operator (below).
result.hasEndProp =
newObject !== undefined || (!isPrimitive(object) && prop in object);

if (!result.hasEndProp) {
result.traversedPath.shift();
}
Expand Down

0 comments on commit ec5e2d0

Please sign in to comment.