Skip to content

Commit

Permalink
Array keyPath for toHaveProperty (#5220)
Browse files Browse the repository at this point in the history
* Add array support for .toHaveProperty keypath argument

* Update .toHaveProperty tests to reflect change and test against arrays as keyPath arguments

* Update docs

* Update change log

* Resolve merge conflic with changelog

* Update documentation to fix .not example for array keyPath

* Update changelog, remove extra single-quote

* Include updated matchers.js snapshot
  • Loading branch information
dmmulroy authored and cpojer committed Jan 5, 2018
1 parent c12bce6 commit d8ffd68
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 13 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,17 @@
([#5166](https://github.com/facebook/jest/pull/5166))
* `[jest-config]` Allow configuration objects inside `projects` array
([#5176](https://github.com/facebook/jest/pull/5176))
* `[expect]` Add support to `.toHaveProperty` matcher to accept the keyPath
argument as an array of properties/indices.
([#5220](https://github.com/facebook/jest/pull/5220))
* `[docs]` Add documentation for .toHaveProperty matcher to accept the keyPath
argument as an array of properties/indices.
([#5220](https://github.com/facebook/jest/pull/5220))

### Chore & Maintenance

* `[docs]` Describe the order of execution of describe and test blocks, and a note on using `test.concurrent`.
* `[docs]` Describe the order of execution of describe and test blocks, and a
note on using `test.concurrent`.
([#5217](https://github.com/facebook/jest/pull/5217))

## jest 22.0.4
Expand Down
15 changes: 13 additions & 2 deletions docs/ExpectAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -903,9 +903,10 @@ describe('toMatchObject applied to arrays arrays', () => {
### `.toHaveProperty(keyPath, value)`

Use `.toHaveProperty` to check if property at provided reference `keyPath`
exists for an object. For checking deeply nested properties in an object use
exists for an object. For checking deeply nested properties in an object you may
use
[dot notation](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors)
for deep references.
or an array containing the keyPath for deep references.

Optionally, you can provide a `value` to check if it's equal to the value
present at `keyPath` on the target object. This matcher uses 'deep equality'
Expand Down Expand Up @@ -943,6 +944,16 @@ test('this house has my desired features', () => {
]);

expect(houseForSale).not.toHaveProperty('kitchen.open');

// Deep referencing using an array containing the keyPath
expect(houseForSale).toHaveProperty(['kitchen', 'area'], 20);
expect(houseForSale).toHaveProperty(
['kitchen', 'amenities'],
['oven', 'stove', 'washer'],
);
expect(houseForSale).toHaveProperty(['kitchen', 'amenities', 0], 'oven');

expect(houseForSale).not.toHaveProperty(['kitchen', 'open']);
});
```

Expand Down
116 changes: 110 additions & 6 deletions packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2562,21 +2562,21 @@ Expected value to have a 'length' property that is a number. Received:
exports[`.toHaveProperty() {error} expect({"a": {"b": {}}}).toHaveProperty('1') 1`] = `
"<dim>expect(</><red>object</><dim>)[.not].toHaveProperty(</><green>path</><dim>)</>

Expected <green>path</> to be a string. Received:
Expected <green>path</> to be a string or an array. Received:
number: <red>1</>"
`;

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

Expected <green>path</> to be a string. Received:
Expected <green>path</> to be a string or an array. Received:
null: <red>null</>"
`;

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

Expected <green>path</> to be a string. Received:
Expected <green>path</> to be a string or an array. Received:
undefined: <red>undefined</>"
`;

Expand Down Expand Up @@ -2616,6 +2616,19 @@ With a value of:
"
`;

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

Expected the object:
<red>{\\"a\\": {\\"b\\": {\\"c\\": {\\"d\\": 1}}}}</>
To have a nested property:
<green>[\\"a\\", \\"b\\", \\"c\\", \\"d\\"]</>
With a value of:
<green>2</>
Received:
<red>1</>"
`;

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

Expand Down Expand Up @@ -2730,6 +2743,31 @@ Received:
<red>object</>.a: <red>1</>"
`;

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

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

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

Expected the object:
<red>{\\"a.b.c.d\\": 1}</>
To have a nested property:
<green>[\\"a.b.c.d\\"]</>
With a value of:
<green>2</>
Received:
<red>1</>"
`;

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

Expand Down Expand Up @@ -2774,7 +2812,51 @@ With a value of:
"
`;

exports[`.toHaveProperty() {pass: true} expect({"a": {"b": {"c": {"d": 1}}}}).toHaveProperty('a.b.c.d')' 1`] = `
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>)</>

Expected the object:
<red>{\\"a\\": {\\"b\\": [1, 2, 3]}}</>
Not to have a nested property:
<green>[\\"a\\", \\"b\\", 1]</>
"
`;

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

Expected the object:
<red>{\\"a\\": {\\"b\\": [1, 2, 3]}}</>
Not to have a nested property:
<green>[\\"a\\", \\"b\\", 1]</>
With a value of:
<green>2</>
"
`;

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

Expected the object:
<red>{\\"a\\": {\\"b\\": {\\"c\\": {\\"d\\": 1}}}}</>
Not to have a nested property:
<green>[\\"a\\", \\"b\\", \\"c\\", \\"d\\"]</>
"
`;

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

Expected the object:
<red>{\\"a\\": {\\"b\\": {\\"c\\": {\\"d\\": 1}}}}</>
Not to have a nested property:
<green>[\\"a\\", \\"b\\", \\"c\\", \\"d\\"]</>
With a value of:
<green>1</>
"
`;

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

Expected the object:
Expand Down Expand Up @@ -2808,7 +2890,7 @@ With a value of:
"
`;

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

Expected the object:
Expand All @@ -2830,7 +2912,7 @@ With a value of:
"
`;

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

Expected the object:
Expand All @@ -2852,6 +2934,28 @@ With a value of:
"
`;

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

Expected the object:
<red>{\\"a.b.c.d\\": 1}</>
Not to have a nested property:
<green>[\\"a.b.c.d\\"]</>
"
`;

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

Expected the object:
<red>{\\"a.b.c.d\\": 1}</>
Not to have a nested property:
<green>[\\"a.b.c.d\\"]</>
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</>, <green>value</><dim>)</>

Expand Down
11 changes: 10 additions & 1 deletion packages/expect/src/__tests__/matchers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,9 @@ describe('.toHaveLength', () => {
describe('.toHaveProperty()', () => {
[
[{a: {b: {c: {d: 1}}}}, 'a.b.c.d', 1],
[{a: {b: {c: {d: 1}}}}, ['a', 'b', 'c', 'd'], 1],
[{'a.b.c.d': 1}, ['a.b.c.d'], 1],
[{a: {b: [1, 2, 3]}}, ['a', 'b', 1], 2],
[{a: 0}, 'a', 0],
[{a: {b: undefined}}, 'a.b', undefined],
[{a: {b: {c: 5}}}, 'a.b', {c: 5}],
Expand All @@ -769,6 +772,9 @@ describe('.toHaveProperty()', () => {
[
[{a: {b: {c: {d: 1}}}}, 'a.b.ttt.d', 1],
[{a: {b: {c: {d: 1}}}}, 'a.b.c.d', 2],
[{'a.b.c.d': 1}, 'a.b.c.d', 2],
[{'a.b.c.d': 1}, ['a.b.c.d'], 2],
[{a: {b: {c: {d: 1}}}}, ['a', 'b', 'c', 'd'], 2],
[{a: {b: {c: {}}}}, 'a.b.c.d', 1],
[{a: 1}, 'a.b.c.d', 5],
[{}, 'a', 'test'],
Expand All @@ -789,12 +795,15 @@ describe('.toHaveProperty()', () => {

[
[{a: {b: {c: {d: 1}}}}, 'a.b.c.d'],
[{a: {b: {c: {d: 1}}}}, ['a', 'b', 'c', 'd']],
[{'a.b.c.d': 1}, ['a.b.c.d']],
[{a: {b: [1, 2, 3]}}, ['a', 'b', 1]],
[{a: 0}, 'a'],
[{a: {b: undefined}}, 'a.b'],
].forEach(([obj, keyPath]) => {
test(`{pass: true} expect(${stringify(
obj,
)}).toHaveProperty('${keyPath}')'`, () => {
)}).toHaveProperty('${keyPath}')`, () => {
jestExpect(obj).toHaveProperty(keyPath);
expect(() =>
jestExpect(obj).not.toHaveProperty(keyPath),
Expand Down
10 changes: 7 additions & 3 deletions packages/expect/src/matchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ const matchers: MatchersObject = {
return {message, pass};
},

toHaveProperty(object: Object, keyPath: string, value?: any) {
toHaveProperty(object: Object, keyPath: string | Array<any>, value?: any) {
const valuePassed = arguments.length === 3;

if (!object && typeof object !== 'string' && typeof object !== 'number') {
Expand All @@ -500,13 +500,17 @@ const matchers: MatchersObject = {
);
}

if (getType(keyPath) !== 'string') {
const keyPathType = getType(keyPath);

if (keyPathType !== 'string' && keyPathType !== 'array') {
throw new Error(
matcherHint('[.not].toHaveProperty', 'object', 'path', {
secondArgument: valuePassed ? 'value' : null,
}) +
'\n\n' +
`Expected ${EXPECTED_COLOR('path')} to be a string. Received:\n` +
`Expected ${EXPECTED_COLOR(
'path',
)} to be a string or an array. Received:\n` +
` ${getType(keyPath)}: ${printReceived(keyPath)}`,
);
}
Expand Down

0 comments on commit d8ffd68

Please sign in to comment.