diff --git a/packages/mui-utils/src/deepmerge.test.ts b/packages/mui-utils/src/deepmerge.test.ts index 3d7e267d9962f6..d0aceef896854a 100644 --- a/packages/mui-utils/src/deepmerge.test.ts +++ b/packages/mui-utils/src/deepmerge.test.ts @@ -45,4 +45,19 @@ describe('deepmerge', () => { bar: 'test', }); }); + + it('should deep clone source key object if target key does not exist', () => { + const foo = { foo: { baz: 'test' } }; + const bar = {}; + + const result = deepmerge(bar, foo); + + expect(result).to.deep.equal({ foo: { baz: 'test' } }); + + // @ts-ignore + result.foo.baz = 'new test'; + + expect(result).to.deep.equal({ foo: { baz: 'new test' } }); + expect(foo).to.deep.equal({ foo: { baz: 'test' } }); + }); }); diff --git a/packages/mui-utils/src/deepmerge.ts b/packages/mui-utils/src/deepmerge.ts index da53439d4da521..1d0c9a44b8a0ab 100644 --- a/packages/mui-utils/src/deepmerge.ts +++ b/packages/mui-utils/src/deepmerge.ts @@ -6,6 +6,20 @@ export interface DeepmergeOptions { clone?: boolean; } +function deepClone(source: T): T | Record { + if (!isPlainObject(source)) { + return source; + } + + const output: Record = {}; + + Object.keys(source).forEach((key) => { + output[key] = deepClone(source[key]); + }); + + return output; +} + export default function deepmerge( target: T, source: unknown, @@ -23,6 +37,10 @@ export default function deepmerge( if (isPlainObject(source[key]) && key in target && isPlainObject(target[key])) { // Since `output` is a clone of `target` and we have narrowed `target` in this block we can cast to the same type. (output as Record)[key] = deepmerge(target[key], source[key], options); + } else if (options.clone) { + (output as Record)[key] = isPlainObject(source[key]) + ? deepClone(source[key]) + : source[key]; } else { (output as Record)[key] = source[key]; }