diff --git a/packages/jest-snapshot/src/__tests__/utils.test.ts b/packages/jest-snapshot/src/__tests__/utils.test.ts index 93193ff5977c..9283eaaf9838 100644 --- a/packages/jest-snapshot/src/__tests__/utils.test.ts +++ b/packages/jest-snapshot/src/__tests__/utils.test.ts @@ -9,6 +9,7 @@ jest.mock('fs'); import fs from 'fs'; import path from 'path'; +import assert from 'assert'; import chalk from 'chalk'; import { @@ -199,16 +200,137 @@ test('serialize handles \\r\\n', () => { expect(serializedData).toBe('\n"
\n
"\n'); }); -describe('DeepMerge', () => { - it('Correctly merges objects with property matchers', () => { - const target = {data: {bar: 'bar', foo: 'foo'}}; +describe('DeepMerge with property matchers', () => { + const matcher = expect.any(String); - const matcher = expect.any(String); - const propertyMatchers = {data: {foo: matcher}}; + /* eslint-disable sort-keys */ + // to keep keys in numerical order rather than alphabetical + const cases = [ + [ + 'a nested object', + // Target + { + data: { + one: 'one', + two: 'two', + }, + }, + // Matchers + { + data: { + two: matcher, + }, + }, + // Expected + { + data: { + one: 'one', + two: matcher, + }, + }, + ], - const mergedOutput = deepMerge(target, propertyMatchers); + [ + 'an object with an array of objects', + // Target + { + data: { + one: [ + { + two: 'two', + three: 'three', + }, + // Include an array element not present in the propertyMatchers + { + four: 'four', + five: 'five', + }, + ], + six: [{seven: 'seven'}], + nine: [[{ten: 'ten'}]], + }, + }, + // Matchers + { + data: { + one: [ + { + two: matcher, + }, + ], + six: [ + {seven: matcher}, + // Include an array element not present in the target + {eight: matcher}, + ], + nine: [[{ten: matcher}]], + }, + }, + // Expected + { + data: { + one: [ + { + two: matcher, + three: 'three', + }, + { + four: 'four', + five: 'five', + }, + ], + six: [{seven: matcher}, {eight: matcher}], + nine: [[{ten: matcher}]], + }, + }, + ], - expect(mergedOutput).toStrictEqual({data: {bar: 'bar', foo: matcher}}); - expect(target).toStrictEqual({data: {bar: 'bar', foo: 'foo'}}); - }); + [ + 'an object with an array of strings', + // Target + { + data: { + one: ['one'], + two: ['two'], + three: ['three', 'four'], + five: ['five'], + }, + }, + // Matchers + { + data: { + one: [matcher], + two: ['two'], + three: [matcher], + five: 'five', + }, + }, + // Expected + { + data: { + one: [matcher], + two: ['two'], + three: [matcher, 'four'], + five: 'five', + }, + }, + ], + ]; + /* eslint-enable sort-keys */ + + it.each(cases)( + 'Correctly merges %s', + (_case, target, propertyMatchers, expected) => { + const originalTarget = JSON.parse(JSON.stringify(target)); + const mergedOutput = deepMerge(target, propertyMatchers); + + // Use assert.deepStrictEqual() instead of expect().toStrictEqual() + // since we want to actually validate that we got the matcher + // rather than treat it specially the way that expect() does + assert.deepStrictEqual(mergedOutput, expected); + + // Ensure original target is not modified + expect(target).toStrictEqual(originalTarget); + }, + ); }); diff --git a/packages/jest-snapshot/src/utils.ts b/packages/jest-snapshot/src/utils.ts index f1f7009b1115..1a1524d2e039 100644 --- a/packages/jest-snapshot/src/utils.ts +++ b/packages/jest-snapshot/src/utils.ts @@ -178,6 +178,25 @@ export const saveSnapshotFile = ( ); }; +const deepMergeArray = (target: Array, source: Array) => { + const mergedOutput = Array.from(target); + + source.forEach((sourceElement, index) => { + const targetElement = mergedOutput[index]; + + if (Array.isArray(target[index])) { + mergedOutput[index] = deepMergeArray(target[index], sourceElement); + } else if (isObject(targetElement)) { + mergedOutput[index] = deepMerge(target[index], sourceElement); + } else { + // Source does not exist in target or target is primitive and cannot be deep merged + mergedOutput[index] = sourceElement; + } + }); + + return mergedOutput; +}; + export const deepMerge = (target: any, source: any) => { const mergedOutput = {...target}; if (isObject(target) && isObject(source)) { @@ -185,6 +204,8 @@ export const deepMerge = (target: any, source: any) => { if (isObject(source[key]) && !source[key].$$typeof) { if (!(key in target)) Object.assign(mergedOutput, {[key]: source[key]}); else mergedOutput[key] = deepMerge(target[key], source[key]); + } else if (Array.isArray(source[key])) { + mergedOutput[key] = deepMergeArray(target[key], source[key]); } else { Object.assign(mergedOutput, {[key]: source[key]}); }