From 9cd660c56de11fef15582362f1d079a5f35007eb Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Tue, 2 Jul 2019 16:36:06 -0500 Subject: [PATCH] fix(theme): merge optional params (#256) Allow the option to merge optional partial params on a partial `Theme`. fix #253 --- src/lib/themes/theme.ts | 2 +- src/lib/utils/commons.test.ts | 75 +++++++++++++++++++++++++++++++++++ src/lib/utils/commons.ts | 30 +++++++++----- 3 files changed, 97 insertions(+), 10 deletions(-) diff --git a/src/lib/themes/theme.ts b/src/lib/themes/theme.ts index 5eff91b020..7c5832025b 100644 --- a/src/lib/themes/theme.ts +++ b/src/lib/themes/theme.ts @@ -224,5 +224,5 @@ export function mergeWithDefaultAnnotationRect(config?: Partial { mergePartial(base, partial); expect(base).toEqual(baseClone); }); + + describe('MergeOptions', () => { + describe('mergeOptionalPartialValues', () => { + interface OptionalTestType { + value1: string; + value2?: number; + value3: string; + value4?: OptionalTestType; + } + const defaultBase: OptionalTestType = { + value1: 'foo', + value3: 'bar', + value4: { + value1: 'foo', + value3: 'bar', + }, + }; + const partial1: RecursivePartial = { value1: 'baz', value2: 10 }; + const partial2: RecursivePartial = { value1: 'baz', value4: { value2: 10 } }; + + describe('mergeOptionalPartialValues is true', () => { + test('should merge optional parameters', () => { + const merged = mergePartial(defaultBase, partial1, { mergeOptionalPartialValues: true }); + expect(merged).toEqual({ + value1: 'baz', + value2: 10, + value3: 'bar', + value4: { + value1: 'foo', + value3: 'bar', + }, + }); + }); + + test('should merge nested optional parameters', () => { + const merged = mergePartial(defaultBase, partial2, { mergeOptionalPartialValues: true }); + expect(merged).toEqual({ + value1: 'baz', + value3: 'bar', + value4: { + value1: 'foo', + value2: 10, + value3: 'bar', + }, + }); + }); + }); + + describe('mergeOptionalPartialValues is false', () => { + test('should NOT merge optional parameters', () => { + const merged = mergePartial(defaultBase, partial1, { mergeOptionalPartialValues: false }); + expect(merged).toEqual({ + value1: 'baz', + value3: 'bar', + value4: { + value1: 'foo', + value3: 'bar', + }, + }); + }); + + test('should NOT merge nested optional parameters', () => { + const merged = mergePartial(defaultBase, partial2, { mergeOptionalPartialValues: false }); + expect(merged).toEqual({ + value1: 'baz', + value3: 'bar', + value4: { + value1: 'foo', + value3: 'bar', + }, + }); + }); + }); + }); + }); }); }); diff --git a/src/lib/utils/commons.ts b/src/lib/utils/commons.ts index 0cf3618f37..cadfd824a8 100644 --- a/src/lib/utils/commons.ts +++ b/src/lib/utils/commons.ts @@ -42,6 +42,10 @@ export type RecursivePartial = { : RecursivePartial }; +export interface MergeOptions { + mergeOptionalPartialValues?: boolean; +} + /** * Merges values of a partial structure with a base structure. * @@ -50,18 +54,26 @@ export type RecursivePartial = { * * @returns new base structure with updated partial values */ -export function mergePartial(base: T, partial?: RecursivePartial): T { +export function mergePartial(base: T, partial?: RecursivePartial, options: MergeOptions = {}): T { if (Array.isArray(base)) { return partial ? (partial as T) : base; // No nested array merging } else if (typeof base === 'object') { - return Object.keys(base).reduce( - (newBase, key) => { - // @ts-ignore - newBase[key] = mergePartial(base[key], partial && partial[key]); - return newBase; - }, - { ...base }, - ); + const baseClone = { ...base }; + + if (partial && options.mergeOptionalPartialValues) { + Object.keys(partial).forEach((key) => { + if (!(key in baseClone)) { + // @ts-ignore + baseClone[key] = partial[key]; + } + }); + } + + return Object.keys(base).reduce((newBase, key) => { + // @ts-ignore + newBase[key] = mergePartial(base[key], partial && partial[key], options); + return newBase; + }, baseClone); } return partial !== undefined ? (partial as T) : base;