From 6e27f0807caf7bc0c835803bdf49f7358399a475 Mon Sep 17 00:00:00 2001 From: "Fawzi E. Abdulfattah" Date: Sat, 27 Nov 2021 13:11:41 +0200 Subject: [PATCH 1/8] Adding pathAsArray utility and update the getPath to support array selection --- packages/expect/src/utils.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/expect/src/utils.ts b/packages/expect/src/utils.ts index eab02cd36204..7b83bc7b1152 100644 --- a/packages/expect/src/utils.ts +++ b/packages/expect/src/utils.ts @@ -45,7 +45,7 @@ export const getPath = ( propertyPath: string | Array, ): GetPath => { if (!Array.isArray(propertyPath)) { - propertyPath = (propertyPath as string).split('.'); + propertyPath = pathAsArray(propertyPath); } if (propertyPath.length) { @@ -372,6 +372,24 @@ export const partition = ( return result; }; +export const pathAsArray = (propertyPath: string): Array => { + // will match everything that's not a dot or a bracket, and "" for consecutive dots. + const pattern = RegExp('[^.[\\]]+|(?=(?:\\.)(?:\\.|$))', 'g'); + const properties: Array = []; + + // because the regex won't match the first dot, 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)) { From 8b7087b67cc096a68658012104490b38a9036115 Mon Sep 17 00:00:00 2001 From: "Fawzi E. Abdulfattah" Date: Sat, 27 Nov 2021 13:11:53 +0200 Subject: [PATCH 2/8] Enhance the toHaveProperty matcher to support array selection --- packages/expect/src/matchers.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/expect/src/matchers.ts b/packages/expect/src/matchers.ts index 5254722aee10..02e4c59813d0 100644 --- a/packages/expect/src/matchers.ts +++ b/packages/expect/src/matchers.ts @@ -44,6 +44,7 @@ import { getObjectSubset, getPath, iterableEquality, + pathAsArray, sparseArrayEquality, subsetEquality, typeEquality, @@ -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) { From 1122b3a547d93d0ca56abe22cbf78882a3bee236 Mon Sep 17 00:00:00 2001 From: "Fawzi E. Abdulfattah" Date: Sat, 27 Nov 2021 13:33:37 +0200 Subject: [PATCH 3/8] Adding test cases to test the array selection with toHaveProperty matcher --- .../__snapshots__/matchers.test.js.snap | 24 +++++++++++++++++++ .../expect/src/__tests__/matchers.test.js | 3 +++ 2 files changed, 27 insertions(+) diff --git a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap index a66503240bab..e62054485551 100644 --- a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap @@ -3551,6 +3551,30 @@ Expected path: "memo" Expected value: not [] `; +exports[`.toHaveProperty() {pass: true} expect({"a": {"b": [[{"c": [{"d": 1}]}]]}}).toHaveProperty('a.b[0][0].c[0].d', 1) 1`] = ` +expect(received).not.toHaveProperty(path, value) + +Expected path: "a.b[0][0].c[0].d" + +Expected value: not 1 +`; + +exports[`.toHaveProperty() {pass: true} expect({"a": {"b": [{"c": [{"d": 1}]}]}}).toHaveProperty('a.b[0].c[0].d', 1) 1`] = ` +expect(received).not.toHaveProperty(path, value) + +Expected path: "a.b[0].c[0].d" + +Expected value: not 1 +`; + +exports[`.toHaveProperty() {pass: true} expect({"a": {"b": [{"c": {"d": [{"e": 1}, {"f": 2}]}}]}}).toHaveProperty('a.b[0].c.d[1].f', 2) 1`] = ` +expect(received).not.toHaveProperty(path, value) + +Expected path: "a.b[0].c.d[1].f" + +Expected value: not 2 +`; + exports[`.toHaveProperty() {pass: true} expect({"a": {"b": [1, 2, 3]}}).toHaveProperty('a,b,1') 1`] = ` expect(received).not.toHaveProperty(path) diff --git a/packages/expect/src/__tests__/matchers.test.js b/packages/expect/src/__tests__/matchers.test.js index 6599df72b2b2..a92f99a36fbe 100644 --- a/packages/expect/src/__tests__/matchers.test.js +++ b/packages/expect/src/__tests__/matchers.test.js @@ -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'], From 2ad062c00ddca6dc672e924d918cb168d82b6370 Mon Sep 17 00:00:00 2001 From: "Fawzi E. Abdulfattah" Date: Sat, 27 Nov 2021 13:34:36 +0200 Subject: [PATCH 4/8] Update the changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc4241aefb77..9f908113a891 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 ([#12090](https://github.com/facebook/jest/pull/12090)) ### Fixes From 9f7d35a314fadb621f495ca3a28eb96b2d01feb8 Mon Sep 17 00:00:00 2001 From: "Fawzi E. Abdulfattah" Date: Sat, 27 Nov 2021 15:13:08 +0200 Subject: [PATCH 5/8] Fixing the changelog entry link --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f908113a891..657cbe73bcc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +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 ([#12090](https://github.com/facebook/jest/pull/12090)) +- `[expect]` Enhancing the `toHaveProperty` matcher to support array selection ([#12092](https://github.com/facebook/jest/pull/12092)) ### Fixes From 3ed1577246cf8e98d8dfc335555099c75625d6f8 Mon Sep 17 00:00:00 2001 From: "Fawzi E. Abdulfattah" Date: Sun, 28 Nov 2021 03:16:08 +0200 Subject: [PATCH 6/8] Adding a negative test case to test a dot in the beginning of the path --- .../src/__tests__/__snapshots__/matchers.test.js.snap | 9 +++++++++ packages/expect/src/__tests__/matchers.test.js | 1 + packages/expect/src/utils.ts | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap index e62054485551..16f85cccd725 100644 --- a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap @@ -3334,6 +3334,15 @@ Expected value: 1 Received value: {"c": {"d": 1}} `; +exports[`.toHaveProperty() {pass: false} expect({"a": {"b": {"c": {}}}}).toHaveProperty('.a.b.c') 1`] = ` +expect(received).toHaveProperty(path) + +Expected path: ".a.b.c" +Received path: [] + +Received value: {"a": {"b": {"c": {}}}} +`; + exports[`.toHaveProperty() {pass: false} expect({"a": {"b": {"c": {}}}}).toHaveProperty('a.b.c.d') 1`] = ` expect(received).toHaveProperty(path) diff --git a/packages/expect/src/__tests__/matchers.test.js b/packages/expect/src/__tests__/matchers.test.js index a92f99a36fbe..20b48ca6053a 100644 --- a/packages/expect/src/__tests__/matchers.test.js +++ b/packages/expect/src/__tests__/matchers.test.js @@ -1958,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'], diff --git a/packages/expect/src/utils.ts b/packages/expect/src/utils.ts index 7b83bc7b1152..725289ebb3a4 100644 --- a/packages/expect/src/utils.ts +++ b/packages/expect/src/utils.ts @@ -377,7 +377,7 @@ export const pathAsArray = (propertyPath: string): Array => { const pattern = RegExp('[^.[\\]]+|(?=(?:\\.)(?:\\.|$))', 'g'); const properties: Array = []; - // because the regex won't match the first dot, if present. + // Because the regex won't match a dot in the beginning of the path, if present. if (propertyPath[0] === '.') { properties.push(''); } From 794c62d432e07f631fa30cb6611575907b7d06c9 Mon Sep 17 00:00:00 2001 From: "Fawzi E. Abdulfattah" Date: Sun, 28 Nov 2021 13:35:03 +0200 Subject: [PATCH 7/8] Updating the docs to show array selection with toHaveProperty Matcher --- docs/ExpectAPI.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index eddf1bec562e..e240efa40e8e 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -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, }; @@ -922,6 +932,7 @@ 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']); From 165fbce60a64296d3d478bf5fd7e6e248c2c5b07 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 29 Nov 2021 13:56:06 +0100 Subject: [PATCH 8/8] chore: prettier --- docs/ExpectAPI.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index e240efa40e8e..bef99cce8ab5 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -898,12 +898,12 @@ const houseForSale = { livingroom: { amenities: [ { - 'couch': [ - ['large', { dimensions: [20,20] }], - ['small', { dimensions: [10,10] }], - ] + couch: [ + ['large', {dimensions: [20, 20]}], + ['small', {dimensions: [10, 10]}], + ], }, - ] + ], }, 'ceiling.height': 2, }; @@ -932,7 +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( + 'livingroom.amenities[0].couch[0][1].dimensions[0]', + 20, + ); expect(houseForSale).toHaveProperty(['kitchen', 'nice.oven']); expect(houseForSale).not.toHaveProperty(['kitchen', 'open']);