UNPKG

@novo-learning/novo-sdk

Version:

SDK for the Novolanguage Speech Analysis API

151 lines (145 loc) 3.7 kB
import { RecursivePartial } from '../type-helpers'; import { mergeDeep } from './merge-deep'; const mergeSource = Object.freeze({ level1: true, sibling: 10, inner: { level2: true, sibling: 100, }, }); type MergeSource = { level1: boolean; sibling: number; extra?: boolean; inner: { level2: boolean; sibling: number; extra?: boolean }; }; describe('Merge deep merges properties', () => { const cases: [{ source: MergeSource; changes: RecursivePartial<MergeSource>[] }, MergeSource][] = [ [{ source: mergeSource, changes: [] }, mergeSource], [{ source: mergeSource, changes: [{}] }, mergeSource], [ { source: mergeSource, changes: [{ sibling: 20 }] }, { level1: true, sibling: 20, inner: { level2: true, sibling: 100, }, }, ], [ { source: mergeSource, changes: [{ inner: { sibling: 200 } }] }, { level1: true, sibling: 10, inner: { level2: true, sibling: 200, }, }, ], [ { source: mergeSource, changes: [{ inner: { sibling: 200 }, sibling: 20 }] }, { level1: true, sibling: 20, inner: { level2: true, sibling: 200, }, }, ], [ { source: mergeSource, changes: [{ inner: { sibling: 300 } }, { sibling: 30 }] }, { level1: true, sibling: 30, inner: { level2: true, sibling: 300, }, }, ], [ { source: mergeSource, changes: [{ extra: true }] }, { ...mergeSource, extra: true, }, ], [ { source: mergeSource, changes: [{ inner: { extra: true } }] }, { ...mergeSource, inner: { ...mergeSource.inner, extra: true }, }, ], ]; test.each(cases)('merge deep', ({ source, changes }, expectedResult) => { const target = structuredClone(source); const result = mergeDeep(target, ...(changes as Partial<MergeSource>[])); expect(result).toEqual(expectedResult); expect(target).toEqual(expectedResult); }); // Tests against prototype pollution // eslint-disable-next-line @typescript-eslint/ban-types const casesPrototype: [{ source: MergeSource; changes: unknown[] }, MergeSource][] = [ [ { source: mergeSource, changes: [ { constructor: (): void => { console.error(''); }, }, ], }, mergeSource, ], [{ source: mergeSource, changes: [{ __proto__: { test: true } }] }, mergeSource], [{ source: mergeSource, changes: [{ prototype: { test: true } }] }, mergeSource], [ { source: mergeSource, changes: [{ sibling: 20, __proto__: { test: true } }] }, { level1: true, sibling: 20, inner: { level2: true, sibling: 100, }, }, ], [ { source: mergeSource, changes: [ { sibling: 20, inner: { constructor: (): void => { console.error(''); }, }, }, ], }, { level1: true, sibling: 20, inner: { level2: true, sibling: 100, }, }, ], ]; test.each(casesPrototype)('merge deep does not pollute prototype', ({ source, changes }, expectedResult) => { const target = structuredClone(source); const result = mergeDeep(target, ...(changes as Partial<MergeSource>[])); expect(result).toEqual(expectedResult); expect(target).toEqual(expectedResult); }); });