From 65574a31210afb45f2fdba85d05f787f03418c7c Mon Sep 17 00:00:00 2001 From: Rebecca Stevens Date: Mon, 20 May 2024 15:14:45 +1200 Subject: [PATCH] feat!: allow filtering out values before merging them by default, undefined is now filtered out. fix #460 --- docs/API.md | 14 ++++++ docs/deepmergeCustom.md | 72 +++++++++++++++++++++++++++++++ src/deepmerge-into.ts | 50 +++++++++++++++------ src/deepmerge.ts | 37 ++++++++++------ src/defaults/general.ts | 25 +++++++++++ src/defaults/into.ts | 8 +--- src/defaults/meta-data-updater.ts | 11 ----- src/defaults/vanilla.ts | 7 +-- src/index.ts | 2 + src/types/defaults.ts | 19 ++++++++ src/types/merging.ts | 57 ++++++++++++++++++------ src/types/options.ts | 14 ++++++ src/types/utils.ts | 26 +++++++---- tests/deepmerge-custom.test.ts | 56 ++++++++++++++++++++++++ tests/deepmerge-into.test.ts | 12 ++++++ tests/deepmerge.test-d.ts | 7 +++ tests/deepmerge.test.ts | 12 ++++++ 17 files changed, 358 insertions(+), 71 deletions(-) create mode 100644 src/defaults/general.ts delete mode 100644 src/defaults/meta-data-updater.ts diff --git a/docs/API.md b/docs/API.md index 0b259098..8e01a2c0 100644 --- a/docs/API.md +++ b/docs/API.md @@ -59,6 +59,13 @@ If set to a function, that function will be used to merge everything else. Note: This includes merging mixed types, such as merging a map with an array. +#### `filterValues` + +Type: `false | (values: unknown[], meta: MetaData) => unknown[]` + +If `false`, no values will be filter out. If set to a function, that function will be used to filter values. +By default, `undefined` values will be filtered out (`null` values will be kept). + ### `rootMetaData` Type: `MetaData` @@ -144,6 +151,13 @@ If set to a function, that function will be used to merge everything else by mut Note: This includes merging mixed types, such as merging a map with an array. +#### `filterValues` + +Type: `false | (values: unknown[], meta: MetaData) => unknown[]` + +If `false`, no values will be filter out. If set to a function, that function will be used to filter values. +By default, `undefined` values will be filtered out (`null` values will be kept). + ### `rootMetaData` Type: `MetaData` diff --git a/docs/deepmergeCustom.md b/docs/deepmergeCustom.md index 8ac642c2..d85c69ab 100644 --- a/docs/deepmergeCustom.md +++ b/docs/deepmergeCustom.md @@ -237,6 +237,78 @@ type EveryIsDate> = Ts extends readonly [ Note: If you want to use HKTs in your own project, not related to deepmerge-ts, we recommend checking out [fp-ts](https://gcanti.github.io/fp-ts/modules/HKT.ts.html). +## Filtering Values + +You can filter the values before they are merged by using the `filterValues` option. +By default, we filter out all `undefined` values. + +If you don't want to filter out any values, you can set the `filterValues` option to `false`. +Be sure to also set the `DeepMergeFilterValuesURI` to `DeepMergeNoFilteringURI` to ensure correct return types. + +```ts +import { + type DeepMergeMergeFunctionURItoKind, + type DeepMergeMergeFunctionsURIs, + type DeepMergeNoFilteringURI, + deepmergeCustom, +} from "deepmerge-ts"; + +const customizedDeepmerge = deepmergeCustom< + unknown, + { + DeepMergeFilterValuesURI: DeepMergeNoFilteringURI; + } +>({ + filterValues: false, +}); + +const x = { key1: { subkey1: `one` } }; +const y = { key1: undefined }; +const z = { key1: { subkey2: `two` } }; + +customizedDeepmerge(x, y, z); // => { key1: { subkey2: `two` } } +``` + +Here's an example that creates a custom deepmerge function that filters out all `null` values instead of `undefined`. + + + +```ts +import { + type DeepMergeMergeFunctionURItoKind, + type DeepMergeMergeFunctionsURIs, + type FilterOut, + deepmergeCustom, +} from "deepmerge-ts"; + +const customizedDeepmerge = deepmergeCustom< + unknown, + { + DeepMergeFilterValuesURI: "FilterNullValues"; + } +>({ + filterValues(values, meta) { + return values.filter((value) => value !== null); + }, +}); + +const x = { key1: { subkey1: `one` } }; +const y = { key1: null }; +const z = { key1: { subkey2: `two` } }; + +customizedDeepmerge(x, y, z); // => { key1: { subkey1: `one`, subkey2: `two` } } + +declare module "deepmerge-ts" { + interface DeepMergeMergeFunctionURItoKind< + Ts extends Readonly>, + MF extends DeepMergeMergeFunctionsURIs, + M, + > { + readonly FilterNullValues: FilterOut; + } +} +``` + ## Meta Data We provide a simple object of meta data that states the key that the values being merged were under. diff --git a/src/deepmerge-into.ts b/src/deepmerge-into.ts index d3574d0b..b5128016 100644 --- a/src/deepmerge-into.ts +++ b/src/deepmerge-into.ts @@ -1,6 +1,9 @@ import { actionsInto as actions } from "./actions"; +import { + defaultFilterValues, + defaultMetaDataUpdater, +} from "./defaults/general"; import * as defaultMergeIntoFunctions from "./defaults/into"; -import { defaultMetaDataUpdater } from "./defaults/meta-data-updater"; import { type DeepMergeBuiltInMetaData, type DeepMergeHKT, @@ -174,6 +177,10 @@ function getIntoUtils< MM >["metaDataUpdater"], deepmergeInto: customizedDeepmergeInto, + filterValues: + options.filterValues === false + ? undefined + : options.filterValues ?? defaultFilterValues, actions, }; } @@ -196,22 +203,34 @@ export function mergeUnknownsInto< meta: M | undefined, // eslint-disable-next-line ts/no-invalid-void-type ): void | symbol { - if (values.length === 0) { + const filteredValues = utils.filterValues?.(values, meta) ?? values; + + if (filteredValues.length === 0) { return; } - if (values.length === 1) { - return void mergeOthersInto(m_target, values, utils, meta); + if (filteredValues.length === 1) { + return void mergeOthersInto( + m_target, + filteredValues, + utils, + meta, + ); } const type = getObjectType(m_target.value); if (type !== ObjectType.NOT && type !== ObjectType.OTHER) { - for (let m_index = 1; m_index < values.length; m_index++) { - if (getObjectType(values[m_index]) === type) { + for (let m_index = 1; m_index < filteredValues.length; m_index++) { + if (getObjectType(filteredValues[m_index]) === type) { continue; } - return void mergeOthersInto(m_target, values, utils, meta); + return void mergeOthersInto( + m_target, + filteredValues, + utils, + meta, + ); } } @@ -219,7 +238,7 @@ export function mergeUnknownsInto< case ObjectType.RECORD: { return void mergeRecordsInto( m_target as Reference>, - values as ReadonlyArray>>, + filteredValues as ReadonlyArray>>, utils, meta, ); @@ -228,7 +247,7 @@ export function mergeUnknownsInto< case ObjectType.ARRAY: { return void mergeArraysInto( m_target as Reference, - values as ReadonlyArray>, + filteredValues as ReadonlyArray>, utils, meta, ); @@ -237,7 +256,7 @@ export function mergeUnknownsInto< case ObjectType.SET: { return void mergeSetsInto( m_target as Reference>, - values as ReadonlyArray>>, + filteredValues as ReadonlyArray>>, utils, meta, ); @@ -246,14 +265,21 @@ export function mergeUnknownsInto< case ObjectType.MAP: { return void mergeMapsInto( m_target as Reference>, - values as ReadonlyArray>>, + filteredValues as ReadonlyArray< + Readonly> + >, utils, meta, ); } default: { - return void mergeOthersInto(m_target, values, utils, meta); + return void mergeOthersInto( + m_target, + filteredValues, + utils, + meta, + ); } } } diff --git a/src/deepmerge.ts b/src/deepmerge.ts index 1b9741f4..23b3666a 100644 --- a/src/deepmerge.ts +++ b/src/deepmerge.ts @@ -1,5 +1,8 @@ import { actions } from "./actions"; -import { defaultMetaDataUpdater } from "./defaults/meta-data-updater"; +import { + defaultFilterValues, + defaultMetaDataUpdater, +} from "./defaults/general"; import * as defaultMergeFunctions from "./defaults/vanilla"; import { type DeepMergeBuiltInMetaData, @@ -139,6 +142,10 @@ function getUtils< >["metaDataUpdater"], deepmerge: customizedDeepmerge, useImplicitDefaultMerging: options.enableImplicitDefaultMerging ?? false, + filterValues: + options.filterValues === false + ? undefined + : options.filterValues ?? defaultFilterValues, actions, }; } @@ -155,26 +162,28 @@ export function mergeUnknowns< M, MM extends DeepMergeBuiltInMetaData = DeepMergeBuiltInMetaData, >(values: Ts, utils: U, meta: M | undefined): DeepMergeHKT { - if (values.length === 0) { + const filteredValues = utils.filterValues?.(values, meta) ?? values; + + if (filteredValues.length === 0) { return undefined as DeepMergeHKT; } - if (values.length === 1) { - return mergeOthers(values, utils, meta) as DeepMergeHKT< + if (filteredValues.length === 1) { + return mergeOthers(filteredValues, utils, meta) as DeepMergeHKT< Ts, MF, M >; } - const type = getObjectType(values[0]); + const type = getObjectType(filteredValues[0]); if (type !== ObjectType.NOT && type !== ObjectType.OTHER) { - for (let m_index = 1; m_index < values.length; m_index++) { - if (getObjectType(values[m_index]) === type) { + for (let m_index = 1; m_index < filteredValues.length; m_index++) { + if (getObjectType(filteredValues[m_index]) === type) { continue; } - return mergeOthers(values, utils, meta) as DeepMergeHKT< + return mergeOthers(filteredValues, utils, meta) as DeepMergeHKT< Ts, MF, M @@ -185,7 +194,7 @@ export function mergeUnknowns< switch (type) { case ObjectType.RECORD: { return mergeRecords( - values as ReadonlyArray>>, + filteredValues as ReadonlyArray>>, utils, meta, ) as DeepMergeHKT; @@ -193,7 +202,7 @@ export function mergeUnknowns< case ObjectType.ARRAY: { return mergeArrays( - values as ReadonlyArray>>, + filteredValues as ReadonlyArray>>, utils, meta, ) as DeepMergeHKT; @@ -201,7 +210,7 @@ export function mergeUnknowns< case ObjectType.SET: { return mergeSets( - values as ReadonlyArray>>, + filteredValues as ReadonlyArray>>, utils, meta, ) as DeepMergeHKT; @@ -209,14 +218,16 @@ export function mergeUnknowns< case ObjectType.MAP: { return mergeMaps( - values as ReadonlyArray>>, + filteredValues as ReadonlyArray< + Readonly> + >, utils, meta, ) as DeepMergeHKT; } default: { - return mergeOthers(values, utils, meta) as DeepMergeHKT< + return mergeOthers(filteredValues, utils, meta) as DeepMergeHKT< Ts, MF, M diff --git a/src/defaults/general.ts b/src/defaults/general.ts new file mode 100644 index 00000000..3facc027 --- /dev/null +++ b/src/defaults/general.ts @@ -0,0 +1,25 @@ +import { type DeepMergeBuiltInMetaData } from "../types"; + +/** + * The default function to update meta data. + * + * It doesn't update the meta data. + */ +export function defaultMetaDataUpdater( + previousMeta: M, + metaMeta: DeepMergeBuiltInMetaData, +): DeepMergeBuiltInMetaData { + return metaMeta; +} + +/** + * The default function to filter values. + * + * It filters out undefined values. + */ +export function defaultFilterValues, M>( + values: Ts, + meta: M | undefined, +): unknown[] { + return values.filter((value) => value !== undefined); +} diff --git a/src/defaults/into.ts b/src/defaults/into.ts index 89cba226..c4aa2b63 100644 --- a/src/defaults/into.ts +++ b/src/defaults/into.ts @@ -121,11 +121,5 @@ export function mergeOthers>( m_target: Reference, values: Ts, ) { - for (let i = values.length - 1; i >= 0; i--) { - if (values[i] !== undefined) { - m_target.value = values[i]; - return; - } - } - m_target.value = undefined; + m_target.value = values.at(-1); } diff --git a/src/defaults/meta-data-updater.ts b/src/defaults/meta-data-updater.ts deleted file mode 100644 index 9bfd4038..00000000 --- a/src/defaults/meta-data-updater.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { type DeepMergeBuiltInMetaData } from "../types"; - -/** - * The default function to update meta data. - */ -export function defaultMetaDataUpdater( - previousMeta: M, - metaMeta: DeepMergeBuiltInMetaData, -): DeepMergeBuiltInMetaData { - return metaMeta; -} diff --git a/src/defaults/vanilla.ts b/src/defaults/vanilla.ts index 91b2d303..53a2bf77 100644 --- a/src/defaults/vanilla.ts +++ b/src/defaults/vanilla.ts @@ -122,10 +122,5 @@ export function mergeMaps< * Get the last non-undefined value in the given array. */ export function mergeOthers>(values: Ts) { - for (let i = values.length - 1; i >= 0; i--) { - if (values[i] !== undefined) { - return values[i]; - } - } - return undefined; + return values.at(-1); } diff --git a/src/index.ts b/src/index.ts index a0327db6..7dd67524 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,6 +15,7 @@ export type { DeepMergeHKT, DeepMergeLeaf, DeepMergeLeafURI, + DeepMergeNoFilteringURI, DeepMergeMapsDefaultHKT, DeepMergeMergeFunctionsDefaultURIs, DeepMergeMergeFunctionsURIs, @@ -28,3 +29,4 @@ export type { Reference as DeepMergeValueReference, GetDeepMergeMergeFunctionsURIs, } from "./types"; +export type { FilterOut } from "./types/utils"; diff --git a/src/types/defaults.ts b/src/types/defaults.ts index 0bd2caa6..aff7bc3c 100644 --- a/src/types/defaults.ts +++ b/src/types/defaults.ts @@ -5,6 +5,7 @@ import { type DeepMergeMergeFunctionsURIs, } from "./merging"; import { + type FilterOut, type FilterOutNever, type FlattenTuples, type KeyIsOptional, @@ -37,6 +38,11 @@ type DeepMergeSetsDefaultURI = "DeepMergeSetsDefaultURI"; */ type DeepMergeMapsDefaultURI = "DeepMergeMapsDefaultURI"; +/** + * The default filter values function. + */ +type DeepMergeFilterValuesDefaultURI = "DeepMergeFilterValuesDefaultURI"; + /** * The default merge functions to use when deep merging. */ @@ -46,6 +52,7 @@ export type DeepMergeMergeFunctionsDefaultURIs = Readonly<{ DeepMergeSetsURI: DeepMergeSetsDefaultURI; DeepMergeMapsURI: DeepMergeMapsDefaultURI; DeepMergeOthersURI: DeepMergeLeafURI; + DeepMergeFilterValuesURI: DeepMergeFilterValuesDefaultURI; }>; type RecordEntries> = TuplifyUnion< @@ -299,6 +306,12 @@ export type DeepMergeMapsDefaultHKT> = Map< UnionMapValues >; +/** + * Filter out undefined values. + */ +export type DeepMergeFilterValuesDefaultHKT> = + FilterOut; + /** * Get the merge functions with defaults apply from the given subset. */ @@ -334,4 +347,10 @@ export type GetDeepMergeMergeFunctionsURIs< PMF["DeepMergeOthersURI"] extends keyof DeepMergeMergeFunctionURItoKind ? PMF["DeepMergeOthersURI"] : DeepMergeLeafURI; + + // prettier-ignore + DeepMergeFilterValuesURI: + PMF["DeepMergeFilterValuesURI"] extends keyof DeepMergeMergeFunctionURItoKind + ? PMF["DeepMergeFilterValuesURI"] + : DeepMergeFilterValuesDefaultURI; }>; diff --git a/src/types/merging.ts b/src/types/merging.ts index db7838b3..a19035a6 100644 --- a/src/types/merging.ts +++ b/src/types/merging.ts @@ -1,5 +1,6 @@ import { type DeepMergeArraysDefaultHKT, + type DeepMergeFilterValuesDefaultHKT, type DeepMergeMapsDefaultHKT, type DeepMergeRecordsDefaultHKT, type DeepMergeSetsDefaultHKT, @@ -9,10 +10,8 @@ import { type EveryIsMap, type EveryIsRecord, type EveryIsSet, - type Is, type IsNever, type IsTuple, - type Or, } from "./utils"; /** @@ -29,6 +28,8 @@ export interface DeepMergeMergeFunctionURItoKind< readonly DeepMergeArraysDefaultURI: DeepMergeArraysDefaultHKT; readonly DeepMergeSetsDefaultURI: DeepMergeSetsDefaultHKT; readonly DeepMergeMapsDefaultURI: DeepMergeMapsDefaultHKT; + readonly DeepMergeFilterValuesDefaultURI: DeepMergeFilterValuesDefaultHKT; + readonly DeepMergeNoFilteringURI: Ts; } /** @@ -78,6 +79,11 @@ export type DeepMergeMergeFunctionsURIs = Readonly<{ * The merge function to merge other things with. */ DeepMergeOthersURI: DeepMergeMergeFunctionURIs; + + /** + * The function to filter values. + */ + DeepMergeFilterValuesURI: DeepMergeMergeFunctionURIs; }>; /** @@ -91,19 +97,28 @@ export type DeepMergeHKT< IsTuple extends true ? Ts extends readonly [] ? undefined - : Ts extends readonly [infer T1] - ? T1 - : EveryIsArray extends true - ? DeepMergeArraysHKT - : EveryIsMap extends true - ? DeepMergeMapsHKT - : EveryIsSet extends true - ? DeepMergeSetsHKT - : EveryIsRecord extends true - ? DeepMergeRecordsHKT - : DeepMergeOthersHKT + : DeepMergeHKTHelper, MF, M> : unknown; +type DeepMergeHKTHelper = + Ts extends ReadonlyArray + ? IsTuple extends true + ? Ts extends readonly [] + ? unknown + : Ts extends readonly [infer T1] + ? T1 + : EveryIsArray extends true + ? DeepMergeArraysHKT + : EveryIsMap extends true + ? DeepMergeMapsHKT + : EveryIsSet extends true + ? DeepMergeSetsHKT + : EveryIsRecord extends true + ? DeepMergeRecordsHKT + : DeepMergeOthersHKT + : unknown + : never; + /** * Deep merge records. */ @@ -149,11 +164,25 @@ type DeepMergeOthersHKT< M, > = DeepMergeMergeFunctionKind; +/** + * Filter values. + */ +type FilterValuesHKT< + Ts extends ReadonlyArray, + MF extends DeepMergeMergeFunctionsURIs, + M, +> = DeepMergeMergeFunctionKind; + /** * The merge function that returns a leaf. */ export type DeepMergeLeafURI = "DeepMergeLeafURI"; +/** + * Don't filter values. + */ +export type DeepMergeNoFilteringURI = "DeepMergeNoFilteringURI"; + /** * Get the leaf type from many types that can't be merged. */ @@ -163,7 +192,7 @@ export type DeepMergeLeaf> = : Ts extends readonly [infer T] ? T : Ts extends readonly [...infer Rest, infer Tail] - ? Or, Is> extends true + ? IsNever extends true ? Rest extends ReadonlyArray ? DeepMergeLeaf : never diff --git a/src/types/options.ts b/src/types/options.ts index 2cc06181..360ec0b5 100644 --- a/src/types/options.ts +++ b/src/types/options.ts @@ -38,6 +38,7 @@ type DeepMergeOptionsFull< mergeOthers: DeepMergeMergeFunctions["mergeOthers"]; metaDataUpdater: MetaDataUpdater; enableImplicitDefaultMerging: boolean; + filterValues: DeepMergeUtilityFunctions["filterValues"] | false; }>; /** @@ -53,6 +54,7 @@ type DeepMergeIntoOptionsFull< mergeSets: DeepMergeMergeIntoFunctions["mergeSets"] | false; mergeOthers: DeepMergeMergeIntoFunctions["mergeOthers"]; metaDataUpdater: MetaDataUpdater; + filterValues: DeepMergeUtilityFunctions["filterValues"] | false; }>; /** @@ -62,6 +64,16 @@ export type Reference = { value: T; }; +/** + * All the utility functions that can be overridden. + */ +type DeepMergeUtilityFunctions = Readonly<{ + filterValues: >( + values: Ts, + meta: M | undefined, + ) => unknown[]; +}>; + /** * All the merge functions that deepmerge uses. */ @@ -188,6 +200,7 @@ export type DeepMergeMergeFunctionUtils< metaDataUpdater: MetaDataUpdater; deepmerge: >(...values: Ts) => unknown; useImplicitDefaultMerging: boolean; + filterValues: DeepMergeUtilityFunctions["filterValues"] | undefined; actions: Readonly<{ defaultMerge: symbol; skip: symbol; @@ -208,6 +221,7 @@ export type DeepMergeMergeIntoFunctionUtils< target: Target, ...values: Ts ) => void; + filterValues: DeepMergeUtilityFunctions["filterValues"] | undefined; actions: Readonly<{ defaultMerge: symbol; }>; diff --git a/src/types/utils.ts b/src/types/utils.ts index 83a049da..8ec7a1d0 100644 --- a/src/types/utils.ts +++ b/src/types/utils.ts @@ -226,25 +226,35 @@ type UnionMapValuesHelper< : Acc; /** - * Filter out nevers from a tuple. + * Filter out U from a tuple. */ -export type FilterOutNever = - T extends ReadonlyArray ? FilterOutNeverHelper : never; +export type FilterOut, U> = FilterOutHelper< + T, + U, + [] +>; /** - * Tail-recursive helper type for FilterOutNever. + * Tail-recursive helper type for FilterOut. */ -type FilterOutNeverHelper< +type FilterOutHelper< T extends ReadonlyArray, + U, Acc extends ReadonlyArray, > = T extends readonly [] ? Acc : T extends readonly [infer Head, ...infer Rest] - ? IsNever extends true - ? FilterOutNeverHelper - : FilterOutNeverHelper + ? Is extends true + ? FilterOutHelper + : FilterOutHelper : T; +/** + * Filter out nevers from a tuple. + */ +export type FilterOutNever = + T extends ReadonlyArray ? FilterOut : never; + /** * Is the type a tuple? */ diff --git a/tests/deepmerge-custom.test.ts b/tests/deepmerge-custom.test.ts index 584d8c46..08311915 100644 --- a/tests/deepmerge-custom.test.ts +++ b/tests/deepmerge-custom.test.ts @@ -5,10 +5,12 @@ import { type DeepMergeLeaf, type DeepMergeLeafURI, type DeepMergeMergeFunctionsURIs, + type DeepMergeNoFilteringURI, type DeepMergeOptions, type DeepMergeRecordsDefaultHKT, deepmergeCustom, } from "../src"; +import { type FilterOut } from "../src/types/utils"; import { areAllNumbers, hasProp } from "./utils"; @@ -112,6 +114,16 @@ declare module "../src/types" { } } +declare module "../src/types" { + interface DeepMergeMergeFunctionURItoKind< + Ts extends ReadonlyArray, + MF extends DeepMergeMergeFunctionsURIs, + M, + > { + readonly CustomFilterValues1: FilterOut; + } +} + describe("deepmergeCustom", () => { it("works just like non-customized version when no options passed", () => { const v = { first: true }; @@ -760,4 +772,48 @@ describe("deepmergeCustom", () => { expect(merged).toStrictEqual(expected); }); + + it(`null can be filtered out`, () => { + const x = { key1: { subkey1: `one` } }; + const y = { key1: null }; + const z = { key1: { subkey2: `two` } }; + + const expected = { key1: { subkey1: `one`, subkey2: `two` } }; + + const customizedDeepmerge = deepmergeCustom< + unknown, + { + DeepMergeFilterValuesURI: "CustomFilterValues1"; + } + >({ + filterValues(values) { + return values.filter((value) => value !== null); + }, + }); + + const merged = customizedDeepmerge(x, y, z); + + expect(merged).toStrictEqual(expected); + }); + + it(`filtered out nothing`, () => { + const x = { key1: { subkey1: `one` } }; + const y = { key1: undefined }; + const z = { key1: { subkey2: `two` } }; + + const expected = { key1: { subkey2: `two` } }; + + const customizedDeepmerge = deepmergeCustom< + unknown, + { + DeepMergeFilterValuesURI: DeepMergeNoFilteringURI; + } + >({ + filterValues: false, + }); + + const merged = customizedDeepmerge(x, y, z); + + expect(merged).toStrictEqual(expected); + }); }); diff --git a/tests/deepmerge-into.test.ts b/tests/deepmerge-into.test.ts index e673c2fb..2314ac7a 100644 --- a/tests/deepmerge-into.test.ts +++ b/tests/deepmerge-into.test.ts @@ -277,6 +277,18 @@ describe("deepmergeInto", () => { expect(x).toStrictEqual(expected); }); + it(`undefined doesn't intefer with merging`, () => { + const x = { key1: { subkey1: `one` } }; + const y = { key1: undefined }; + const z = { key1: { subkey2: `two` } }; + + const expected = { key1: { subkey1: `one`, subkey2: `two` } }; + + deepmergeInto(x, y, z); + + expect(x).toStrictEqual(expected); + }); + it(`can merge arrays`, () => { const x = [`one`, `two`]; const y = [`one`, `three`]; diff --git a/tests/deepmerge.test-d.ts b/tests/deepmerge.test-d.ts index 5659a424..0f316e23 100644 --- a/tests/deepmerge.test-d.ts +++ b/tests/deepmerge.test-d.ts @@ -253,3 +253,10 @@ expectType<{ }; garply: number; }>(test20); + +// eslint-disable-next-line ts/no-confusing-void-expression +const test21 = deepmerge(); +expectType(test21); + +const test22 = deepmerge({} as any, {} as any); +expectType(test22); diff --git a/tests/deepmerge.test.ts b/tests/deepmerge.test.ts index 6f799a63..5415d19c 100644 --- a/tests/deepmerge.test.ts +++ b/tests/deepmerge.test.ts @@ -318,6 +318,18 @@ describe("deepmerge", () => { expect(merged).toStrictEqual(expected); }); + it(`undefined doesn't intefer with merging`, () => { + const x = { key1: { subkey1: `one` } }; + const y = { key1: undefined }; + const z = { key1: { subkey2: `two` } }; + + const expected = { key1: { subkey1: `one`, subkey2: `two` } }; + + const merged = deepmerge(x, y, z); + + expect(merged).toStrictEqual(expected); + }); + it(`can merge arrays`, () => { const x = [`one`, `two`]; const y = [`one`, `three`];