Skip to content

Commit

Permalink
Document .toHaveProperty matcher in Expect API Doc (#3023)
Browse files Browse the repository at this point in the history
* disable js formatting for flow to take over

* add description for `.toHaveProperty`

* add description for toHaveProperty example

* refactoring .toHaveProperty documentation

* minor grammer changes to `toHaveProperty` doc

* modify `propPath` to `refKey`

* add semicolons

* fixed breaking changes in ExpectAPI

* change propPath arg to keyPath in `toHaveProperty`

* change propPath to keyPath for `toHaveProperty`
  • Loading branch information
abdulhannanali authored and aaronabramov committed Mar 2, 2017
1 parent 602a211 commit d85989d
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 25 deletions.
38 changes: 38 additions & 0 deletions docs/ExpectAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,44 @@ test('the house has my desired features', () => {
});
```

### `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 [dot notation](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors) for deep references.

Optionally, you can provide a value to check if it's strictly equal to the `value` present
at `keyPath` on the target object.

The following example contains a `houseForSale` object with nested properties. We are using `toHaveProperty`
to check for the existence and values of various properties in the object.

```js
// Object containing house features to be tested
const houseForSale = {
bath: true,
kitchen: {
amenities: ['oven', 'stove', 'washer'],
area: 20,
wallColor: 'white'
},
bedrooms: 4,
};

test('this house has my desired features', () => {
// Simple Referencing
expect(houseForSale).toHaveProperty('bath');
expect(houseForSale).toHaveProperty('bedrooms', 4);

expect(houseForSale).not.toHaveProperty('pool');

// Deep referencing using dot notation
expect(houseForSale).toHaveProperty('kitchen.area', 20);
expect(houseForSale).toHaveProperty('kitchen.amenities', ['oven', 'stove', 'washer']);

expect(hosueForSale).not.toHaveProperty('kitchen.open');
});
```

### `.toMatchSnapshot(optionalString)`

This ensures that a value matches the most recent snapshot. Check out [the Snapshot Testing guide](/jest/docs/snapshot-testing.html) for more information.
Expand Down
38 changes: 19 additions & 19 deletions packages/jest-matchers/src/__tests__/matchers-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -605,12 +605,12 @@ describe('.toHaveProperty()', () => {
[{a: 0}, 'a', 0],
[{a: {b: undefined}}, 'a.b', undefined],
[{a: {b: {c: 5}}}, 'a.b', {c: 5}],
].forEach(([obj, path, value]) => {
].forEach(([obj, keyPath, value]) => {
test(
`{pass: true} expect(${stringify(obj)}).toHaveProperty('${path}', ${stringify(value)})`,
`{pass: true} expect(${stringify(obj)}).toHaveProperty('${keyPath}', ${stringify(value)})`,
() => {
jestExpect(obj).toHaveProperty(path, value);
expect(() => jestExpect(obj).not.toHaveProperty(path, value))
jestExpect(obj).toHaveProperty(keyPath, value);
expect(() => jestExpect(obj).not.toHaveProperty(keyPath, value))
.toThrowErrorMatchingSnapshot();
},
);
Expand All @@ -626,13 +626,13 @@ describe('.toHaveProperty()', () => {
[1, 'a.b.c', 'test'],
['abc', 'a.b.c', {a: 5}],
[{a: {b: {c: 5}}}, 'a.b', {c: 4}],
].forEach(([obj, path, value]) => {
].forEach(([obj, keyPath, value]) => {
test(
`{pass: false} expect(${stringify(obj)}).toHaveProperty('${path}', ${stringify(value)})`,
`{pass: false} expect(${stringify(obj)}).toHaveProperty('${keyPath}', ${stringify(value)})`,
() => {
expect(() => jestExpect(obj).toHaveProperty(path, value))
expect(() => jestExpect(obj).toHaveProperty(keyPath, value))
.toThrowErrorMatchingSnapshot();
jestExpect(obj).not.toHaveProperty(path, value);
jestExpect(obj).not.toHaveProperty(keyPath, value);
},
);
});
Expand All @@ -641,12 +641,12 @@ describe('.toHaveProperty()', () => {
[{a: {b: {c: {d: 1}}}}, 'a.b.c.d'],
[{a: 0}, 'a'],
[{a: {b: undefined}}, 'a.b'],
].forEach(([obj, path]) => {
].forEach(([obj, keyPath]) => {
test(
`{pass: true} expect(${stringify(obj)}).toHaveProperty('${path}')'`,
`{pass: true} expect(${stringify(obj)}).toHaveProperty('${keyPath}')'`,
() => {
jestExpect(obj).toHaveProperty(path);
expect(() => jestExpect(obj).not.toHaveProperty(path))
jestExpect(obj).toHaveProperty(keyPath);
expect(() => jestExpect(obj).not.toHaveProperty(keyPath))
.toThrowErrorMatchingSnapshot();
},
);
Expand All @@ -658,13 +658,13 @@ describe('.toHaveProperty()', () => {
[{}, 'a'],
[1, 'a.b.c'],
['abc', 'a.b.c'],
].forEach(([obj, path]) => {
].forEach(([obj, keyPath]) => {
test(
`{pass: false} expect(${stringify(obj)}).toHaveProperty('${path}')`,
`{pass: false} expect(${stringify(obj)}).toHaveProperty('${keyPath}')`,
() => {
expect(() => jestExpect(obj).toHaveProperty(path))
expect(() => jestExpect(obj).toHaveProperty(keyPath))
.toThrowErrorMatchingSnapshot();
jestExpect(obj).not.toHaveProperty(path);
jestExpect(obj).not.toHaveProperty(keyPath);
},
);
});
Expand All @@ -675,11 +675,11 @@ describe('.toHaveProperty()', () => {
[{a: {b: {}}}, undefined],
[{a: {b: {}}}, null],
[{a: {b: {}}}, 1],
].forEach(([obj, path]) => {
].forEach(([obj, keyPath]) => {
test(
`{error} expect(${stringify(obj)}).toHaveProperty('${path}')`,
`{error} expect(${stringify(obj)}).toHaveProperty('${keyPath}')`,
() => {
expect(() => jestExpect(obj).toHaveProperty(path))
expect(() => jestExpect(obj).toHaveProperty(keyPath))
.toThrowErrorMatchingSnapshot();
},
);
Expand Down
12 changes: 6 additions & 6 deletions packages/jest-matchers/src/matchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ const matchers: MatchersObject = {
return {message, pass};
},

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

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

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

const result = getPath(object, propPath);
const result = getPath(object, keyPath);
const {lastTraversedObject, hasEndProp} = result;

let diffString;
Expand All @@ -499,13 +499,13 @@ const matchers: MatchersObject = {
`Expected the object:\n` +
` ${printReceived(object)}\n` +
`Not to have a nested property:\n` +
` ${printExpected(propPath)}\n` +
` ${printExpected(keyPath)}\n` +
(valuePassed ? `With a value of:\n ${printExpected(value)}\n` : '')
: matcherHint('.toHaveProperty', 'object', 'path', {secondArgument: (valuePassed ? 'value' : null)}) + '\n\n' +
`Expected the object:\n` +
` ${printReceived(object)}\n` +
`To have a nested property:\n` +
` ${printExpected(propPath)}\n` +
` ${printExpected(keyPath)}\n` +
(valuePassed ? `With a value of:\n ${printExpected(value)}\n` : '') +
(traversedPath ? `Received:\n ${RECEIVED_COLOR('object')}.${traversedPath}: ${printReceived(lastTraversedObject)}` : '') +
(diffString ? `\nDifference:\n\n${diffString}` : '');
Expand Down

0 comments on commit d85989d

Please sign in to comment.