@tim-code/my-util 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tim-code/my-util",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "author": "",
package/src/object.js CHANGED
@@ -55,3 +55,37 @@ export function like(template) {
55
55
  return true
56
56
  }
57
57
  }
58
+
59
+ /**
60
+ * Deeply merges one or more source objects into a target object.
61
+ * If the property values are objects, they are merged recursively.
62
+ * Non-object properties (including arrays) are directly assigned to the target.
63
+ * @param {Object} target The target object that will receive the merged properties.
64
+ * @param {...Object} sources The source objects whose properties will be merged into the target.
65
+ * @returns {Object} The target object with the merged properties from all source objects.
66
+ */
67
+ export function deepMerge(target, ...sources) {
68
+ for (const source of sources) {
69
+ const keys = Object.keys(source)
70
+ for (const key of keys) {
71
+ if (!Object.hasOwn(source, key)) {
72
+ continue
73
+ }
74
+ const targetValue = target[key]
75
+ const sourceValue = source[key]
76
+ if (Array.isArray(sourceValue)) {
77
+ target[key] = sourceValue
78
+ } else if (
79
+ targetValue &&
80
+ typeof targetValue === "object" &&
81
+ sourceValue &&
82
+ typeof sourceValue === "object"
83
+ ) {
84
+ deepMerge(targetValue, sourceValue)
85
+ } else {
86
+ target[key] = sourceValue
87
+ }
88
+ }
89
+ }
90
+ return target
91
+ }
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable no-restricted-syntax */
2
2
  import { jest } from "@jest/globals"
3
- import { deleteUndefinedValues, like, mutateValues, via } from "./object.js"
3
+ import { deepMerge, deleteUndefinedValues, like, mutateValues, via } from "./object.js"
4
4
 
5
5
  describe("mutateValues", () => {
6
6
  it("mutates values in the object using the callback", () => {
@@ -159,3 +159,101 @@ describe("contains", () => {
159
159
  expect(fn(obj)).toBe(false)
160
160
  })
161
161
  })
162
+
163
+ describe("deepMerge", () => {
164
+ it("merges flat objects", () => {
165
+ const target = { a: 1, b: 2 }
166
+ const source = { b: 3, c: 4 }
167
+ expect(deepMerge(target, source)).toEqual({ a: 1, b: 3, c: 4 })
168
+ expect(target).toEqual({ a: 1, b: 3, c: 4 })
169
+ })
170
+
171
+ it("deeply merges nested objects", () => {
172
+ const target = { a: { x: 1, y: 2 }, b: 2 }
173
+ const source = { a: { y: 3, z: 4 }, b: 5 }
174
+ expect(deepMerge(target, source)).toEqual({ a: { x: 1, y: 3, z: 4 }, b: 5 })
175
+ expect(target).toEqual({ a: { x: 1, y: 3, z: 4 }, b: 5 })
176
+ })
177
+
178
+ it("overwrites arrays instead of merging them", () => {
179
+ const target = { arr: [1, 2], a: 1 }
180
+ const source = { arr: [3, 4] }
181
+ expect(deepMerge(target, source)).toEqual({ arr: [3, 4], a: 1 })
182
+ })
183
+
184
+ it("merges multiple sources left-to-right", () => {
185
+ const target = { a: 1 }
186
+ const s1 = { b: 2 }
187
+ const s2 = { a: 3, c: 4 }
188
+ expect(deepMerge(target, s1, s2)).toEqual({ a: 3, b: 2, c: 4 })
189
+ })
190
+
191
+ it("does not merge non-object values, just assigns", () => {
192
+ const target = { a: { x: 1 } }
193
+ const source = { a: 2 }
194
+ expect(deepMerge(target, source)).toEqual({ a: 2 })
195
+ })
196
+
197
+ it("handles empty sources", () => {
198
+ const target = { a: 1 }
199
+ expect(deepMerge(target)).toEqual({ a: 1 })
200
+ expect(deepMerge(target, {})).toEqual({ a: 1 })
201
+ })
202
+
203
+ it("handles empty target", () => {
204
+ const target = {}
205
+ const source = { a: 1 }
206
+ expect(deepMerge(target, source)).toEqual({ a: 1 })
207
+ })
208
+
209
+ it("does not merge inherited properties from sources", () => {
210
+ const proto = { x: 1 }
211
+ const source = Object.create(proto)
212
+ source.a = 2
213
+ const target = {}
214
+ expect(deepMerge(target, source)).toEqual({ a: 2 })
215
+ expect("x" in target).toBe(false)
216
+ })
217
+
218
+ it("merges deeply with multiple sources", () => {
219
+ const target = { a: { x: 1 } }
220
+ const s1 = { a: { y: 2 } }
221
+ const s2 = { a: { z: 3 } }
222
+ expect(deepMerge(target, s1, s2)).toEqual({ a: { x: 1, y: 2, z: 3 } })
223
+ })
224
+
225
+ it("does not merge if source value is null", () => {
226
+ const target = { a: { x: 1 } }
227
+ const source = { a: null }
228
+ expect(deepMerge(target, source)).toEqual({ a: null })
229
+ })
230
+
231
+ it("does not merge if target value is null", () => {
232
+ const target = { a: null }
233
+ const source = { a: { x: 1 } }
234
+ expect(deepMerge(target, source)).toEqual({ a: { x: 1 } })
235
+ })
236
+
237
+ it("does not merge arrays deeply", () => {
238
+ const target = { a: [1, 2, 3] }
239
+ const source = { a: [4, 5] }
240
+ expect(deepMerge(target, source)).toEqual({ a: [4, 5] })
241
+ })
242
+
243
+ it("does not merge non-enumerable properties from source", () => {
244
+ const target = {}
245
+ const source = {}
246
+ Object.defineProperty(source, "hidden", {
247
+ value: 123,
248
+ enumerable: false,
249
+ })
250
+ expect(deepMerge(target, source)).toEqual({})
251
+ expect("hidden" in target).toBe(false)
252
+ })
253
+
254
+ it("returns the target object", () => {
255
+ const target = { a: 1 }
256
+ const result = deepMerge(target, { b: 2 })
257
+ expect(result).toBe(target)
258
+ })
259
+ })