From 9bbf44e9417d3a5d654efa87b5455f9846cddce1 Mon Sep 17 00:00:00 2001 From: Marius Bakas Date: Thu, 23 Mar 2023 09:49:16 +0200 Subject: [PATCH 1/4] fix(snapshot): bring back support for array property matchers --- .../__snapshots__/printSnapshot.test.ts.snap | 18 ---- .../src/__tests__/printSnapshot.test.ts | 85 +++++++++++++------ packages/jest-snapshot/src/index.ts | 7 +- 3 files changed, 61 insertions(+), 49 deletions(-) diff --git a/packages/jest-snapshot/src/__tests__/__snapshots__/printSnapshot.test.ts.snap b/packages/jest-snapshot/src/__tests__/__snapshots__/printSnapshot.test.ts.snap index b1d923f8275d..1b1aea2e1561 100644 --- a/packages/jest-snapshot/src/__tests__/__snapshots__/printSnapshot.test.ts.snap +++ b/packages/jest-snapshot/src/__tests__/__snapshots__/printSnapshot.test.ts.snap @@ -1,14 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`matcher error toMatchInlineSnapshot Expected properties must be an object (array) with snapshot 1`] = ` -expect(received).toMatchInlineSnapshot(properties, snapshot) - -Matcher error: Expected properties must be an object - -Expected properties has type: array -Expected properties has value: [] -`; - exports[`matcher error toMatchInlineSnapshot Expected properties must be an object (non-null) without snapshot 1`] = ` expect(received).toMatchInlineSnapshot(properties) @@ -41,15 +32,6 @@ exports[`matcher error toMatchInlineSnapshot Snapshot matchers cannot be used wi Matcher error: Snapshot matchers cannot be used with not `; -exports[`matcher error toMatchSnapshot Expected properties must be an object (array) 1`] = ` -expect(received).toMatchSnapshot(properties) - -Matcher error: Expected properties must be an object - -Expected properties has type: array -Expected properties has value: [] -`; - exports[`matcher error toMatchSnapshot Expected properties must be an object (non-null) 1`] = ` expect(received).toMatchSnapshot(properties) diff --git a/packages/jest-snapshot/src/__tests__/printSnapshot.test.ts b/packages/jest-snapshot/src/__tests__/printSnapshot.test.ts index 7e6955895e73..43edcd13029b 100644 --- a/packages/jest-snapshot/src/__tests__/printSnapshot.test.ts +++ b/packages/jest-snapshot/src/__tests__/printSnapshot.test.ts @@ -246,19 +246,6 @@ describe('matcher error', () => { }).toThrowErrorMatchingSnapshot(); }); - test('Expected properties must be an object (array) with snapshot', () => { - const context = { - isNot: false, - promise: '', - } as Context; - const properties: Array = []; - const snapshot = ''; - - expect(() => { - toMatchInlineSnapshot.call(context, received, properties, snapshot); - }).toThrowErrorMatchingSnapshot(); - }); - test('Inline snapshot must be a string', () => { const context = { isNot: false, @@ -333,18 +320,6 @@ describe('matcher error', () => { }).toThrowErrorMatchingSnapshot(); }); - test('Expected properties must be an object (array)', () => { - const context = { - isNot: false, - promise: '', - } as Context; - const properties: Array = []; - - expect(() => { - toMatchSnapshot.call(context, received, properties); - }).toThrowErrorMatchingSnapshot(); - }); - describe('received value must be an object', () => { const context = { currentTestName: '', @@ -785,6 +760,66 @@ describe('pass true', () => { ) as SyncExpectationResult; expect(pass).toBe(true); }); + + test('array', () => { + const context = { + equals: () => true, + isNot: false, + promise: '', + snapshotState: { + match() { + return { + expected: [], + pass: true, + }; + }, + }, + utils: { + iterableEquality: () => [], + subsetEquality: () => [], + }, + } as unknown as Context; + const received: Array = []; + const properties: Array = []; + + const {pass} = toMatchSnapshot.call( + context, + received, + properties, + ) as SyncExpectationResult; + expect(pass).toBe(true); + }); + }); + + describe('toMatchInlineSnapshot', () => { + test('array', () => { + const context = { + equals: () => true, + isNot: false, + promise: '', + snapshotState: { + match() { + return { + expected: [], + pass: true, + }; + }, + }, + utils: { + iterableEquality: () => [], + subsetEquality: () => [], + }, + } as unknown as Context; + const received: Array = []; + const properties: Array = []; + + const {pass} = toMatchInlineSnapshot.call( + context, + received, + properties, + ) as SyncExpectationResult; + expect(pass).toBe(true); + }); }); }); diff --git a/packages/jest-snapshot/src/index.ts b/packages/jest-snapshot/src/index.ts index 13e22a9f6605..dd310c0773af 100644 --- a/packages/jest-snapshot/src/index.ts +++ b/packages/jest-snapshot/src/index.ts @@ -163,11 +163,7 @@ export const toMatchSnapshot: MatcherFunctionWithContext< if (length === 2 && typeof propertiesOrHint === 'string') { hint = propertiesOrHint; } else if (length >= 2) { - if ( - Array.isArray(propertiesOrHint) || - typeof propertiesOrHint !== 'object' || - propertiesOrHint === null - ) { + if (typeof propertiesOrHint !== 'object' || propertiesOrHint === null) { const options: MatcherHintOptions = { isNot: this.isNot, promise: this.promise, @@ -234,7 +230,6 @@ export const toMatchInlineSnapshot: MatcherFunctionWithContext< } if ( - Array.isArray(propertiesOrSnapshot) || typeof propertiesOrSnapshot !== 'object' || propertiesOrSnapshot === null ) { From 742075b9b9bf7262b2fa40dd40dadb0bfb66391b Mon Sep 17 00:00:00 2001 From: Marius Bakas Date: Fri, 7 Apr 2023 20:16:51 +0300 Subject: [PATCH 2/4] fix(snapshot): expect.any() and expect.anything() doesn't work in toMatchSnapshot() --- .../jest-snapshot/src/__tests__/utils.test.ts | 90 +++++++++++++++---- packages/jest-snapshot/src/utils.ts | 7 +- 2 files changed, 77 insertions(+), 20 deletions(-) diff --git a/packages/jest-snapshot/src/__tests__/utils.test.ts b/packages/jest-snapshot/src/__tests__/utils.test.ts index 9bf0a57c196f..36f4d73dfdf2 100644 --- a/packages/jest-snapshot/src/__tests__/utils.test.ts +++ b/packages/jest-snapshot/src/__tests__/utils.test.ts @@ -303,7 +303,12 @@ describe('removeLinesBeforeExternalMatcherTrap', () => { }); describe('DeepMerge with property matchers', () => { - const matcher = expect.any(String); + const matcherString = expect.any(String); + const matcherNumber = expect.any(Number); + const matcherObject = expect.any(Object); + const matcherArray = expect.any(Array); + const matcherBoolean = expect.any(Boolean); + const matcherAnything = expect.anything(); it.each( /* eslint-disable sort-keys */ @@ -321,14 +326,14 @@ describe('DeepMerge with property matchers', () => { // Matchers { data: { - two: matcher, + two: matcherString, }, }, // Expected { data: { one: 'one', - two: matcher, + two: matcherString, }, }, ], @@ -358,15 +363,15 @@ describe('DeepMerge with property matchers', () => { data: { one: [ { - two: matcher, + two: matcherString, }, ], six: [ - {seven: matcher}, + {seven: matcherString}, // Include an array element not present in the target - {eight: matcher}, + {eight: matcherString}, ], - nine: [[{ten: matcher}]], + nine: [[{ten: matcherString}]], }, }, // Expected @@ -374,7 +379,7 @@ describe('DeepMerge with property matchers', () => { data: { one: [ { - two: matcher, + two: matcherString, three: 'three', }, { @@ -382,8 +387,8 @@ describe('DeepMerge with property matchers', () => { five: 'five', }, ], - six: [{seven: matcher}, {eight: matcher}], - nine: [[{ten: matcher}]], + six: [{seven: matcherString}, {eight: matcherString}], + nine: [[{ten: matcherString}]], }, }, ], @@ -402,18 +407,18 @@ describe('DeepMerge with property matchers', () => { // Matchers { data: { - one: [matcher], + one: [matcherString], two: ['two'], - three: [matcher], + three: [matcherString], five: 'five', }, }, // Expected { data: { - one: [matcher], + one: [matcherString], two: ['two'], - three: [matcher, 'four'], + three: [matcherString, 'four'], five: 'five', }, }, @@ -424,9 +429,58 @@ describe('DeepMerge with property matchers', () => { // Target [{name: 'one'}, {name: 'two'}, {name: 'three'}], // Matchers - [{name: 'one'}, {name: matcher}, {name: matcher}], + [{name: 'one'}, {name: matcherString}, {name: matcherString}], // Expected - [{name: 'one'}, {name: matcher}, {name: matcher}], + [{name: 'one'}, {name: matcherString}, {name: matcherString}], + ], + + [ + 'an array of different types', + // Target + [ + 5, + 'some words', + [], + {}, + true, + false, + 5, + 'some words', + [], + {}, + true, + false, + ], + // Matchers + [ + matcherNumber, + matcherString, + matcherArray, + matcherObject, + matcherBoolean, + matcherBoolean, + matcherAnything, + matcherAnything, + matcherAnything, + matcherAnything, + matcherAnything, + matcherAnything, + ], + // Expected + [ + matcherNumber, + matcherString, + matcherArray, + matcherObject, + matcherBoolean, + matcherBoolean, + matcherAnything, + matcherAnything, + matcherAnything, + matcherAnything, + matcherAnything, + matcherAnything, + ], ], [ @@ -434,9 +488,9 @@ describe('DeepMerge with property matchers', () => { // Target [['one'], ['two'], ['three']], // Matchers - [['one'], [matcher], [matcher]], + [['one'], [matcherString], [matcherString]], // Expected - [['one'], [matcher], [matcher]], + [['one'], [matcherString], [matcherString]], ], ], /* eslint-enable sort-keys */ diff --git a/packages/jest-snapshot/src/utils.ts b/packages/jest-snapshot/src/utils.ts index ddafe4bd94ce..e36823bee34a 100644 --- a/packages/jest-snapshot/src/utils.ts +++ b/packages/jest-snapshot/src/utils.ts @@ -220,15 +220,18 @@ export const saveSnapshotFile = ( ); }; +const isAnyOrAnything = (input: any) => + ['Any', 'Anything'].includes(input.constructor.name); + const deepMergeArray = (target: Array, source: Array) => { const mergedOutput = Array.from(target); source.forEach((sourceElement, index) => { const targetElement = mergedOutput[index]; - if (Array.isArray(target[index])) { + if (Array.isArray(target[index]) && Array.isArray(sourceElement)) { mergedOutput[index] = deepMergeArray(target[index], sourceElement); - } else if (isObject(targetElement)) { + } else if (isObject(targetElement) && !isAnyOrAnything(sourceElement)) { mergedOutput[index] = deepMerge(target[index], sourceElement); } else { // Source does not exist in target or target is primitive and cannot be deep merged From 3e10da8a99dee8c63c029f470ed96dbfb2c62107 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Sun, 9 Apr 2023 10:49:10 +0200 Subject: [PATCH 3/4] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64fc2fc659d3..5ed1dc07ff03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Features +- `[jest-snapshot]` Support arrays as property matchers ([#14025](https://github.com/facebook/jest/pull/14025)) + ### Fixes - `[jest-environment-jsdom, jest-environment-node]` Fix assignment of `customExportConditions` via `testEnvironmentOptions` when custom env subclass defines a default value ([#13989](https://github.com/facebook/jest/pull/13989)) From eeeec4480c28f910cb0933b770c84dd1deda3657 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Sun, 9 Apr 2023 10:50:00 +0200 Subject: [PATCH 4/4] Update packages/jest-snapshot/src/utils.ts Co-authored-by: Tom Mrazauskas --- packages/jest-snapshot/src/utils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/jest-snapshot/src/utils.ts b/packages/jest-snapshot/src/utils.ts index e36823bee34a..84d317ca95a3 100644 --- a/packages/jest-snapshot/src/utils.ts +++ b/packages/jest-snapshot/src/utils.ts @@ -220,7 +220,9 @@ export const saveSnapshotFile = ( ); }; -const isAnyOrAnything = (input: any) => +const isAnyOrAnything = (input: object) => + '$$typeof' in input && + input.$$typeof === Symbol.for('jest.asymmetricMatcher') && ['Any', 'Anything'].includes(input.constructor.name); const deepMergeArray = (target: Array, source: Array) => {