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: Fix non-object received value in toHaveProperty #7986

Merged
merged 6 commits into from Feb 28, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
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