Skip to content

Commit

Permalink
Enhancing the toHaveProperty matcher to support array selection (#1…
Browse files Browse the repository at this point in the history
  • Loading branch information
iifawzi committed Nov 29, 2021
1 parent f8c6e75 commit 5cd75f4
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,7 @@

- `[jest-core]` Add support for `testResultsProcessor` written in ESM ([#12006](https://github.com/facebook/jest/pull/12006))
- `[jest-diff, pretty-format]` Add `compareKeys` option for custom sorting of object keys ([#11992](https://github.com/facebook/jest/pull/11992))
- `[expect]` Enhancing the `toHaveProperty` matcher to support array selection ([#12092](https://github.com/facebook/jest/pull/12092))

### Fixes

Expand Down
14 changes: 14 additions & 0 deletions docs/ExpectAPI.md
Expand Up @@ -895,6 +895,16 @@ const houseForSale = {
wallColor: 'white',
'nice.oven': true,
},
livingroom: {
amenities: [
{
couch: [
['large', {dimensions: [20, 20]}],
['small', {dimensions: [10, 10]}],
],
},
],
},
'ceiling.height': 2,
};

Expand Down Expand Up @@ -922,6 +932,10 @@ test('this house has my desired features', () => {
['oven', 'stove', 'washer'],
);
expect(houseForSale).toHaveProperty(['kitchen', 'amenities', 0], 'oven');
expect(houseForSale).toHaveProperty(
'livingroom.amenities[0].couch[0][1].dimensions[0]',
20,
);
expect(houseForSale).toHaveProperty(['kitchen', 'nice.oven']);
expect(houseForSale).not.toHaveProperty(['kitchen', 'open']);

Expand Down
33 changes: 33 additions & 0 deletions packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap
Expand Up @@ -3334,6 +3334,15 @@ Expected value: <g>1</>
Received value: <r>{"c": {"d": 1}}</>
`;

exports[`.toHaveProperty() {pass: false} expect({"a": {"b": {"c": {}}}}).toHaveProperty('.a.b.c') 1`] = `
<d>expect(</><r>received</><d>).</>toHaveProperty<d>(</><g>path</><d>)</>

Expected path: <g>".a.b.c"</>
Received path: <r>[]</>

Received value: <r>{"a": {"b": {"c": {}}}}</>
`;

exports[`.toHaveProperty() {pass: false} expect({"a": {"b": {"c": {}}}}).toHaveProperty('a.b.c.d') 1`] = `
<d>expect(</><r>received</><d>).</>toHaveProperty<d>(</><g>path</><d>)</>

Expand Down Expand Up @@ -3551,6 +3560,30 @@ Expected path: <g>"memo"</>
Expected value: not <g>[]</>
`;

exports[`.toHaveProperty() {pass: true} expect({"a": {"b": [[{"c": [{"d": 1}]}]]}}).toHaveProperty('a.b[0][0].c[0].d', 1) 1`] = `
<d>expect(</><r>received</><d>).</>not<d>.</>toHaveProperty<d>(</><g>path</><d>, </><g>value</><d>)</>

Expected path: <g>"a.b[0][0].c[0].d"</>

Expected value: not <g>1</>
`;

exports[`.toHaveProperty() {pass: true} expect({"a": {"b": [{"c": [{"d": 1}]}]}}).toHaveProperty('a.b[0].c[0].d', 1) 1`] = `
<d>expect(</><r>received</><d>).</>not<d>.</>toHaveProperty<d>(</><g>path</><d>, </><g>value</><d>)</>

Expected path: <g>"a.b[0].c[0].d"</>

Expected value: not <g>1</>
`;

exports[`.toHaveProperty() {pass: true} expect({"a": {"b": [{"c": {"d": [{"e": 1}, {"f": 2}]}}]}}).toHaveProperty('a.b[0].c.d[1].f', 2) 1`] = `
<d>expect(</><r>received</><d>).</>not<d>.</>toHaveProperty<d>(</><g>path</><d>, </><g>value</><d>)</>

Expected path: <g>"a.b[0].c.d[1].f"</>

Expected value: not <g>2</>
`;

exports[`.toHaveProperty() {pass: true} expect({"a": {"b": [1, 2, 3]}}).toHaveProperty('a,b,1') 1`] = `
<d>expect(</><r>received</><d>).</>not<d>.</>toHaveProperty<d>(</><g>path</><d>)</>

Expand Down
4 changes: 4 additions & 0 deletions packages/expect/src/__tests__/matchers.test.js
Expand Up @@ -1885,6 +1885,9 @@ describe('.toHaveProperty()', () => {
[{a: {b: undefined}}, 'a.b', undefined],
[{a: {}}, 'a.b', undefined], // delete for breaking change in future major
[{a: {b: {c: 5}}}, 'a.b', {c: 5}],
[{a: {b: [{c: [{d: 1}]}]}}, 'a.b[0].c[0].d', 1],
[{a: {b: [{c: {d: [{e: 1}, {f: 2}]}}]}}, 'a.b[0].c.d[1].f', 2],
[{a: {b: [[{c: [{d: 1}]}]]}}, 'a.b[0][0].c[0].d', 1],
[Object.assign(Object.create(null), {property: 1}), 'property', 1],
[new Foo(), 'a', undefined],
[new Foo(), 'b', 'b'],
Expand Down Expand Up @@ -1955,6 +1958,7 @@ describe('.toHaveProperty()', () => {

[
[{a: {b: {c: {}}}}, 'a.b.c.d'],
[{a: {b: {c: {}}}}, '.a.b.c'],
[{a: 1}, 'a.b.c.d'],
[{}, 'a'],
[1, 'a.b.c'],
Expand Down
3 changes: 2 additions & 1 deletion packages/expect/src/matchers.ts
Expand Up @@ -44,6 +44,7 @@ import {
getObjectSubset,
getPath,
iterableEquality,
pathAsArray,
sparseArrayEquality,
subsetEquality,
typeEquality,
Expand Down Expand Up @@ -704,7 +705,7 @@ const matchers: MatchersObject = {

const expectedPathLength =
typeof expectedPath === 'string'
? expectedPath.split('.').length
? pathAsArray(expectedPath).length
: expectedPath.length;

if (expectedPathType === 'array' && expectedPathLength === 0) {
Expand Down
20 changes: 19 additions & 1 deletion packages/expect/src/utils.ts
Expand Up @@ -45,7 +45,7 @@ export const getPath = (
propertyPath: string | Array<string>,
): GetPath => {
if (!Array.isArray(propertyPath)) {
propertyPath = (propertyPath as string).split('.');
propertyPath = pathAsArray(propertyPath);
}

if (propertyPath.length) {
Expand Down Expand Up @@ -372,6 +372,24 @@ export const partition = <T>(
return result;
};

export const pathAsArray = (propertyPath: string): Array<any> => {
// will match everything that's not a dot or a bracket, and "" for consecutive dots.
const pattern = RegExp('[^.[\\]]+|(?=(?:\\.)(?:\\.|$))', 'g');
const properties: Array<string> = [];

// Because the regex won't match a dot in the beginning of the path, if present.
if (propertyPath[0] === '.') {
properties.push('');
}

propertyPath.replace(pattern, match => {
properties.push(match);
return match;
});

return properties;
};

// Copied from https://github.com/graingert/angular.js/blob/a43574052e9775cbc1d7dd8a086752c979b0f020/src/Angular.js#L685-L693
export const isError = (value: unknown): value is Error => {
switch (Object.prototype.toString.call(value)) {
Expand Down

0 comments on commit 5cd75f4

Please sign in to comment.