diff --git a/CHANGELOG.md b/CHANGELOG.md index b2528beedd39..2b63187776f8 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-config]` Handle frozen config object ([#14054](https://github.com/facebook/jest/pull/14054)) 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/__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/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 ) { diff --git a/packages/jest-snapshot/src/utils.ts b/packages/jest-snapshot/src/utils.ts index 82d80344f0ec..ddcbf14cf206 100644 --- a/packages/jest-snapshot/src/utils.ts +++ b/packages/jest-snapshot/src/utils.ts @@ -220,15 +220,20 @@ export const saveSnapshotFile = ( ); }; +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) => { 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