ts-patch-mongoose 2.9.5 → 3.0.0

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.
@@ -1,9 +1,9 @@
1
1
  import type { Mock, MockInstance } from 'vitest'
2
2
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
3
3
 
4
- import ms from 'ms'
5
- import { setPatchHistoryTTL } from '../src/helpers'
4
+ import { cloneDeep, isEmpty, setPatchHistoryTTL } from '../src/helpers'
6
5
  import { HistoryModel } from '../src/model'
6
+ import { ms } from '../src/ms'
7
7
 
8
8
  vi.mock('../src/model', () => ({
9
9
  HistoryModel: {
@@ -82,3 +82,230 @@ describe('useTTL', () => {
82
82
  expect(createIndexSpy).toHaveBeenCalledWith({ createdAt: 1 }, { expireAfterSeconds, name })
83
83
  })
84
84
  })
85
+
86
+ describe('isEmpty', () => {
87
+ it('should return true for null and undefined', () => {
88
+ expect(isEmpty(null)).toBe(true)
89
+ expect(isEmpty(undefined)).toBe(true)
90
+ })
91
+
92
+ it('should return true for empty arrays and strings', () => {
93
+ expect(isEmpty([])).toBe(true)
94
+ expect(isEmpty('')).toBe(true)
95
+ })
96
+
97
+ it('should return false for non-empty arrays and strings', () => {
98
+ expect(isEmpty([1, 2])).toBe(false)
99
+ expect(isEmpty('hello')).toBe(false)
100
+ })
101
+
102
+ it('should return true for empty objects', () => {
103
+ expect(isEmpty({})).toBe(true)
104
+ })
105
+
106
+ it('should return false for non-empty objects', () => {
107
+ expect(isEmpty({ a: 1 })).toBe(false)
108
+ })
109
+
110
+ it('should return true for empty Map and Set', () => {
111
+ expect(isEmpty(new Map())).toBe(true)
112
+ expect(isEmpty(new Set())).toBe(true)
113
+ })
114
+
115
+ it('should return false for non-empty Map and Set', () => {
116
+ expect(isEmpty(new Map([['a', 1]]))).toBe(false)
117
+ expect(isEmpty(new Set([1]))).toBe(false)
118
+ })
119
+
120
+ it('should return true for numbers, booleans, and functions (matches lodash)', () => {
121
+ expect(isEmpty(0)).toBe(true)
122
+ expect(isEmpty(1)).toBe(true)
123
+ expect(isEmpty(true)).toBe(true)
124
+ expect(isEmpty(false)).toBe(true)
125
+ expect(isEmpty(() => {})).toBe(true)
126
+ })
127
+
128
+ it('should return true for Date and RegExp (matches lodash)', () => {
129
+ expect(isEmpty(new Date())).toBe(true)
130
+ expect(isEmpty(/test/)).toBe(true)
131
+ })
132
+ })
133
+
134
+ describe('cloneDeep', () => {
135
+ it('should clone primitives', () => {
136
+ expect(cloneDeep(42)).toBe(42)
137
+ expect(cloneDeep('hello')).toBe('hello')
138
+ expect(cloneDeep(null)).toBe(null)
139
+ expect(cloneDeep(undefined)).toBe(undefined)
140
+ expect(cloneDeep(true)).toBe(true)
141
+ })
142
+
143
+ it('should deep clone plain objects', () => {
144
+ const original = { a: 1, b: { c: 2 } }
145
+ const cloned = cloneDeep(original)
146
+ expect(cloned).toEqual(original)
147
+ expect(cloned).not.toBe(original)
148
+ expect(cloned.b).not.toBe(original.b)
149
+ })
150
+
151
+ it('should deep clone arrays', () => {
152
+ const original = [1, [2, 3], { a: 4 }]
153
+ const cloned = cloneDeep(original)
154
+ expect(cloned).toEqual(original)
155
+ expect(cloned).not.toBe(original)
156
+ expect(cloned[1]).not.toBe(original[1])
157
+ expect(cloned[2]).not.toBe(original[2])
158
+ })
159
+
160
+ it('should clone Date instances', () => {
161
+ const original = new Date('2026-01-01')
162
+ const cloned = cloneDeep(original)
163
+ expect(cloned).toEqual(original)
164
+ expect(cloned).not.toBe(original)
165
+ expect(cloned.getTime()).toBe(original.getTime())
166
+ })
167
+
168
+ it('should clone RegExp instances', () => {
169
+ const original = /test/gi
170
+ const cloned = cloneDeep(original)
171
+ expect(cloned).not.toBe(original)
172
+ expect(cloned.source).toBe(original.source)
173
+ expect(cloned.flags).toBe(original.flags)
174
+ })
175
+
176
+ it('should clone Map instances', () => {
177
+ const original = new Map([['a', { nested: 1 }]])
178
+ const cloned = cloneDeep(original)
179
+ expect(cloned).not.toBe(original)
180
+ expect(cloned.get('a')).toEqual({ nested: 1 })
181
+ expect(cloned.get('a')).not.toBe(original.get('a'))
182
+ })
183
+
184
+ it('should clone Set instances', () => {
185
+ const obj = { a: 1 }
186
+ const original = new Set([obj])
187
+ const cloned = cloneDeep(original)
188
+ expect(cloned).not.toBe(original)
189
+ expect(cloned.size).toBe(1)
190
+ const [clonedItem] = cloned
191
+ expect(clonedItem).toEqual(obj)
192
+ expect(clonedItem).not.toBe(obj)
193
+ })
194
+
195
+ it('should handle circular references in objects', () => {
196
+ const original: Record<string, unknown> = { a: 1 }
197
+ original.self = original
198
+ const cloned = cloneDeep(original)
199
+ expect(cloned.a).toBe(1)
200
+ expect(cloned.self).toBe(cloned)
201
+ expect(cloned).not.toBe(original)
202
+ })
203
+
204
+ it('should handle circular references in arrays', () => {
205
+ const original: unknown[] = [1, 2]
206
+ original.push(original)
207
+ const cloned = cloneDeep(original)
208
+ expect(cloned[0]).toBe(1)
209
+ expect(cloned[1]).toBe(2)
210
+ expect(cloned[2]).toBe(cloned)
211
+ expect(cloned).not.toBe(original)
212
+ })
213
+
214
+ it('should handle circular references in nested objects', () => {
215
+ const child: Record<string, unknown> = { value: 'child' }
216
+ const parent: Record<string, unknown> = { child }
217
+ child.parent = parent
218
+ const cloned = cloneDeep(parent)
219
+ expect(cloned).not.toBe(parent)
220
+ expect(cloned.child).not.toBe(child)
221
+ expect((cloned.child as Record<string, unknown>).value).toBe('child')
222
+ expect((cloned.child as Record<string, unknown>).parent).toBe(cloned)
223
+ })
224
+
225
+ it('should handle circular references in Maps', () => {
226
+ const original = new Map<string, unknown>()
227
+ original.set('self', original)
228
+ const cloned = cloneDeep(original)
229
+ expect(cloned).not.toBe(original)
230
+ expect(cloned.get('self')).toBe(cloned)
231
+ })
232
+
233
+ it('should handle circular references in Sets', () => {
234
+ const original = new Set<unknown>()
235
+ original.add(original)
236
+ const cloned = cloneDeep(original)
237
+ expect(cloned).not.toBe(original)
238
+ expect(cloned.has(cloned)).toBe(true)
239
+ expect(cloned.size).toBe(1)
240
+ })
241
+
242
+ it('should clone objects with toJSON method via JSON round-trip', () => {
243
+ const original = { value: 42, toJSON: () => ({ value: 42 }) }
244
+ const cloned = cloneDeep(original)
245
+ expect(cloned).toEqual({ value: 42 })
246
+ expect(cloned).not.toBe(original)
247
+ })
248
+
249
+ it('should clone ArrayBuffer', () => {
250
+ const original = new ArrayBuffer(8)
251
+ new Uint8Array(original).set([1, 2, 3, 4, 5, 6, 7, 8])
252
+ const cloned = cloneDeep(original)
253
+ expect(cloned).not.toBe(original)
254
+ expect(cloned.byteLength).toBe(8)
255
+ expect(new Uint8Array(cloned)).toEqual(new Uint8Array(original))
256
+ })
257
+
258
+ it('should clone DataView', () => {
259
+ const buffer = new ArrayBuffer(16)
260
+ const original = new DataView(buffer, 4, 8)
261
+ original.setInt32(0, 42)
262
+ const cloned = cloneDeep(original)
263
+ expect(cloned).not.toBe(original)
264
+ expect(cloned.buffer).not.toBe(original.buffer)
265
+ expect(cloned.byteOffset).toBe(4)
266
+ expect(cloned.byteLength).toBe(8)
267
+ expect(cloned.getInt32(0)).toBe(42)
268
+ })
269
+
270
+ it('should clone TypedArrays with offset and length', () => {
271
+ const buffer = new ArrayBuffer(16)
272
+ const original = new Uint8Array(buffer, 4, 8)
273
+ original.set([10, 20, 30, 40, 50, 60, 70, 80])
274
+ const cloned = cloneDeep(original)
275
+ expect(cloned).not.toBe(original)
276
+ expect(cloned.buffer).not.toBe(original.buffer)
277
+ expect(cloned.byteOffset).toBe(4)
278
+ expect(cloned.length).toBe(8)
279
+ expect(Array.from(cloned)).toEqual([10, 20, 30, 40, 50, 60, 70, 80])
280
+ })
281
+
282
+ it('should clone Float64Array', () => {
283
+ const original = new Float64Array([1.1, 2.2, 3.3])
284
+ const cloned = cloneDeep(original)
285
+ expect(cloned).not.toBe(original)
286
+ expect(cloned.buffer).not.toBe(original.buffer)
287
+ expect(Array.from(cloned)).toEqual([1.1, 2.2, 3.3])
288
+ })
289
+
290
+ it('should clone RegExp with lastIndex', () => {
291
+ const original = /foo/g
292
+ original.exec('foobar')
293
+ expect(original.lastIndex).toBe(3)
294
+ const cloned = cloneDeep(original)
295
+ expect(cloned).not.toBe(original)
296
+ expect(cloned.lastIndex).toBe(3)
297
+ expect(cloned.source).toBe('foo')
298
+ expect(cloned.flags).toBe('g')
299
+ })
300
+
301
+ it('should clone object without constructor', () => {
302
+ const original = Object.create(null) as Record<string, unknown>
303
+ original.a = 1
304
+ original.b = { c: 2 }
305
+ const cloned = cloneDeep(original)
306
+ expect(cloned).not.toBe(original)
307
+ expect(cloned.a).toBe(1)
308
+ expect(cloned.b).toEqual({ c: 2 })
309
+ expect(cloned.b).not.toBe(original.b)
310
+ })
311
+ })
@@ -0,0 +1,113 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { ms, UNITS } from '../src/ms'
4
+
5
+ const { s, m, h, d, w, mo, y } = UNITS
6
+
7
+ describe('ms', () => {
8
+ it('should parse milliseconds', () => {
9
+ expect(ms('100ms')).toBe(100)
10
+ expect(ms('500msecs')).toBe(500)
11
+ expect(ms('1millisecond')).toBe(1)
12
+ expect(ms('5milliseconds')).toBe(5)
13
+ })
14
+
15
+ it('should parse seconds', () => {
16
+ expect(ms('1s')).toBe(s)
17
+ expect(ms('5sec')).toBe(5 * s)
18
+ expect(ms('10secs')).toBe(10 * s)
19
+ expect(ms('1second')).toBe(s)
20
+ expect(ms('2seconds')).toBe(2 * s)
21
+ })
22
+
23
+ it('should parse minutes', () => {
24
+ expect(ms('1m')).toBe(m)
25
+ expect(ms('5min')).toBe(5 * m)
26
+ expect(ms('10mins')).toBe(10 * m)
27
+ expect(ms('1minute')).toBe(m)
28
+ expect(ms('2minutes')).toBe(2 * m)
29
+ })
30
+
31
+ it('should parse hours', () => {
32
+ expect(ms('1h')).toBe(h)
33
+ expect(ms('2hr')).toBe(2 * h)
34
+ expect(ms('3hrs')).toBe(3 * h)
35
+ expect(ms('1hour')).toBe(h)
36
+ expect(ms('2hours')).toBe(2 * h)
37
+ })
38
+
39
+ it('should parse days', () => {
40
+ expect(ms('1d')).toBe(d)
41
+ expect(ms('1day')).toBe(d)
42
+ expect(ms('2days')).toBe(2 * d)
43
+ })
44
+
45
+ it('should parse weeks', () => {
46
+ expect(ms('1w')).toBe(w)
47
+ expect(ms('1week')).toBe(w)
48
+ expect(ms('2weeks')).toBe(2 * w)
49
+ })
50
+
51
+ it('should parse months', () => {
52
+ expect(ms('1mo')).toBe(mo)
53
+ expect(ms('1month')).toBe(mo)
54
+ expect(ms('2months')).toBe(2 * mo)
55
+ expect(ms('6mo')).toBe(6 * mo)
56
+ expect(ms('0.5mo')).toBe(0.5 * mo)
57
+ })
58
+
59
+ it('should parse years', () => {
60
+ expect(ms('1y')).toBe(y)
61
+ expect(ms('1yr')).toBe(y)
62
+ expect(ms('1year')).toBe(y)
63
+ expect(ms('2years')).toBe(2 * y)
64
+ })
65
+
66
+ it('should default to milliseconds when no unit', () => {
67
+ expect(ms('500')).toBe(500)
68
+ })
69
+
70
+ it('should handle decimal values', () => {
71
+ expect(ms('1.5h')).toBe(1.5 * h)
72
+ expect(ms('0.5d')).toBe(0.5 * d)
73
+ })
74
+
75
+ it('should handle negative values', () => {
76
+ expect(ms('-1s')).toBe(-s)
77
+ expect(ms('-500ms')).toBe(-500)
78
+ })
79
+
80
+ it('should handle spaces between number and unit', () => {
81
+ expect(ms('1 hour')).toBe(h)
82
+ expect(ms('2 days')).toBe(2 * d)
83
+ expect(ms('500 ms')).toBe(500)
84
+ expect(ms('30 seconds')).toBe(30 * s)
85
+ expect(ms('5 min')).toBe(5 * m)
86
+ expect(ms('1 mo')).toBe(mo)
87
+ expect(ms('1 week')).toBe(w)
88
+ expect(ms('1 year')).toBe(y)
89
+ })
90
+
91
+ it('should be case insensitive', () => {
92
+ // @ts-expect-error runtime check
93
+ expect(ms('1H')).toBe(h)
94
+ // @ts-expect-error runtime check
95
+ expect(ms('1D')).toBe(d)
96
+ // @ts-expect-error runtime check
97
+ expect(ms('1MS')).toBe(1)
98
+ })
99
+
100
+ it('should return NaN for strings over 100 chars', () => {
101
+ // @ts-expect-error testing invalid input
102
+ expect(ms('a'.repeat(101))).toBeNaN()
103
+ })
104
+
105
+ it('should return NaN for invalid strings', () => {
106
+ // @ts-expect-error testing invalid input
107
+ expect(ms('abc')).toBeNaN()
108
+ // @ts-expect-error testing invalid input
109
+ expect(ms('')).toBeNaN()
110
+ // @ts-expect-error testing invalid input
111
+ expect(ms('hello world')).toBeNaN()
112
+ })
113
+ })
@@ -0,0 +1,220 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { omitDeep } from '../src/omit-deep'
4
+
5
+ describe('omitDeep', () => {
6
+ it('should return empty object for undefined', () => {
7
+ expect(omitDeep(undefined, 'a')).toEqual({})
8
+ })
9
+
10
+ it('should return primitives as-is', () => {
11
+ expect(omitDeep(42 as unknown, 'a')).toBe(42)
12
+ expect(omitDeep('hello' as unknown, 'a')).toBe('hello')
13
+ expect(omitDeep(null as unknown, 'a')).toBe(null)
14
+ })
15
+
16
+ it('should omit top-level keys', () => {
17
+ const obj = { a: 1, b: 2, c: 3 }
18
+ expect(omitDeep(obj, ['b'])).toEqual({ a: 1, c: 3 })
19
+ })
20
+
21
+ it('should omit with a single string key', () => {
22
+ const obj = { a: 1, b: 2 }
23
+ expect(omitDeep(obj, 'b')).toEqual({ a: 1 })
24
+ })
25
+
26
+ it('should omit nested keys recursively', () => {
27
+ const obj = { a: 1, nested: { a: 2, b: 3 } }
28
+ expect(omitDeep(obj, ['a'])).toEqual({ nested: { b: 3 } })
29
+ })
30
+
31
+ it('should handle arrays of objects', () => {
32
+ const arr = [
33
+ { a: 1, b: 2 },
34
+ { a: 3, b: 4 },
35
+ ]
36
+ expect(omitDeep(arr, ['a'])).toEqual([{ b: 2 }, { b: 4 }])
37
+ })
38
+
39
+ it('should handle deeply nested structures', () => {
40
+ const obj = { level1: { level2: { level3: { secret: 'hidden', keep: 'visible' } } } }
41
+ expect(omitDeep(obj, ['secret'])).toEqual({ level1: { level2: { level3: { keep: 'visible' } } } })
42
+ })
43
+
44
+ it('should support dot-notation paths', () => {
45
+ const obj = { address: { street: '123 Main', city: 'Springfield' }, name: 'John' }
46
+ expect(omitDeep(obj, ['address.street'])).toEqual({ address: { city: 'Springfield' }, name: 'John' })
47
+ })
48
+
49
+ it('should handle dot-notation with nested arrays', () => {
50
+ const obj = { config: { items: [{ secret: 1 }] }, meta: { secret: 2 } }
51
+ expect(omitDeep(obj, ['secret'])).toEqual({ config: { items: [{}] }, meta: {} })
52
+ })
53
+
54
+ it('should block __proto__ in dot-notation paths', () => {
55
+ const obj = { a: { b: 1 } }
56
+ omitDeep(obj, ['__proto__.polluted'])
57
+ expect(({} as Record<string, unknown>).polluted).toBeUndefined()
58
+ })
59
+
60
+ it('should block constructor in dot-notation paths', () => {
61
+ const obj = { a: 1 }
62
+ omitDeep(obj, ['constructor.something'])
63
+ expect(obj).toEqual({ a: 1 })
64
+ })
65
+
66
+ it('should block prototype in dot-notation paths', () => {
67
+ const obj = { a: 1 }
68
+ omitDeep(obj, ['prototype.something'])
69
+ expect(obj).toEqual({ a: 1 })
70
+ })
71
+
72
+ it('should return value if keys is not an array or string', () => {
73
+ const obj = { a: 1 }
74
+ expect(omitDeep(obj, 123 as unknown as string[])).toEqual({ a: 1 })
75
+ })
76
+
77
+ it('should handle non-plain objects', () => {
78
+ const date = new Date()
79
+ expect(omitDeep(date as unknown, 'a')).toBe(date)
80
+ })
81
+
82
+ it('should handle empty objects', () => {
83
+ expect(omitDeep({}, ['a'])).toEqual({})
84
+ })
85
+
86
+ it('should handle empty arrays', () => {
87
+ expect(omitDeep([], ['a'])).toEqual([])
88
+ })
89
+
90
+ it('should handle dot-notation where intermediate path is missing', () => {
91
+ const obj = { a: 1 }
92
+ expect(omitDeep(obj, ['x.y.z'])).toEqual({ a: 1 })
93
+ })
94
+
95
+ it('should handle dot-notation where intermediate is not an object', () => {
96
+ const obj = { a: 'string' }
97
+ expect(omitDeep(obj, ['a.b'])).toEqual({ a: 'string' })
98
+ })
99
+
100
+ it('should handle objects with null prototype', () => {
101
+ const obj = Object.create(null) as Record<string, unknown>
102
+ obj.a = 1
103
+ obj.b = 2
104
+ expect(omitDeep(obj, ['b'])).toEqual({ a: 1 })
105
+ })
106
+
107
+ it('should handle multiple keys at once', () => {
108
+ const obj = { a: 1, b: 2, c: 3, d: 4 }
109
+ expect(omitDeep(obj, ['a', 'c'])).toEqual({ b: 2, d: 4 })
110
+ })
111
+
112
+ it('should not omit from dot-notation when value at path has no value', () => {
113
+ const obj = { a: { b: '' } }
114
+ expect(omitDeep(obj, ['a.b'])).toEqual({ a: { b: '' } })
115
+ })
116
+
117
+ it('should omit dot-notation with nested value present', () => {
118
+ const obj = { a: { b: { c: 1 } } }
119
+ expect(omitDeep(obj, ['a.b.c'])).toEqual({ a: { b: {} } })
120
+ })
121
+
122
+ it('should handle object where nested values are all empty', () => {
123
+ const obj = { a: { b: {}, c: '' } }
124
+ expect(omitDeep(obj, ['x'])).toEqual({ a: { b: {}, c: '' } })
125
+ })
126
+
127
+ it('should handle object with RegExp values', () => {
128
+ const obj = { pattern: /test/i, name: 'hello' }
129
+ expect(omitDeep(obj, ['name'])).toEqual({ pattern: /test/i })
130
+ })
131
+
132
+ it('should handle object with Error values', () => {
133
+ const err = new Error('test error')
134
+ const obj = { error: err, name: 'hello' }
135
+ const result = omitDeep(obj, ['name'])
136
+ expect(result).toEqual({ error: err })
137
+ })
138
+
139
+ it('should handle object with Map values', () => {
140
+ const map = new Map([['key', 'value']])
141
+ const obj = { data: map, name: 'hello' } as Record<string, unknown>
142
+ const result = omitDeep(obj, ['name'])
143
+ expect(result.data).toBe(map)
144
+ })
145
+
146
+ it('should handle object with Set values', () => {
147
+ const set = new Set([1, 2, 3])
148
+ const obj = { data: set, name: 'hello' } as Record<string, unknown>
149
+ const result = omitDeep(obj, ['name'])
150
+ expect(result.data).toBe(set)
151
+ })
152
+
153
+ it('should handle object with boolean and number values at dot path', () => {
154
+ const obj = { config: { enabled: true, count: 0 } }
155
+ expect(omitDeep(obj, ['config.enabled'])).toEqual({ config: { count: 0 } })
156
+ })
157
+
158
+ it('should handle object with function values at dot path', () => {
159
+ const fn = () => 42
160
+ const obj = { handler: fn, name: 'test' } as Record<string, unknown>
161
+ expect(omitDeep(obj, ['name'])).toEqual({ handler: fn })
162
+ })
163
+
164
+ it('should handle object with array values at dot path', () => {
165
+ const obj = { tags: ['a', 'b'], name: 'test' }
166
+ expect(omitDeep(obj, ['tags'])).toEqual({ name: 'test' })
167
+ })
168
+
169
+ it('should unset non-empty RegExp at dot path', () => {
170
+ const obj = { config: { pattern: /test/i } }
171
+ expect(omitDeep(obj, ['config.pattern'])).toEqual({ config: {} })
172
+ })
173
+
174
+ it('should unset Error with message at dot path', () => {
175
+ const obj = { config: { err: new Error('oops') } }
176
+ expect(omitDeep(obj, ['config.err'])).toEqual({ config: {} })
177
+ })
178
+
179
+ it('should unset non-empty Map at dot path', () => {
180
+ const obj = { config: { data: new Map([['a', 1]]) } } as Record<string, unknown>
181
+ expect(omitDeep(obj, ['config.data'])).toEqual({ config: {} })
182
+ })
183
+
184
+ it('should not unset empty Map at dot path (hasValue is false)', () => {
185
+ const obj = { config: { data: new Map() } } as Record<string, unknown>
186
+ const result = omitDeep(obj, ['config.data'])
187
+ expect(result.config).toHaveProperty('data')
188
+ })
189
+
190
+ it('should unset non-empty Set at dot path', () => {
191
+ const obj = { config: { data: new Set([1]) } } as Record<string, unknown>
192
+ expect(omitDeep(obj, ['config.data'])).toEqual({ config: {} })
193
+ })
194
+
195
+ it('should not unset empty Set at dot path (hasValue is false)', () => {
196
+ const obj = { config: { data: new Set() } } as Record<string, unknown>
197
+ const result = omitDeep(obj, ['config.data'])
198
+ expect(result.config).toHaveProperty('data')
199
+ })
200
+
201
+ it('should handle null value at dot-notation intermediate', () => {
202
+ const obj = { a: null } as Record<string, unknown>
203
+ expect(omitDeep(obj, ['a.b'])).toEqual({ a: null })
204
+ })
205
+
206
+ it('should handle mixed arrays with primitives and objects', () => {
207
+ const arr = [1, 'two', { a: 1, b: 2 }, null]
208
+ expect(omitDeep(arr, ['a'])).toEqual([1, 'two', { b: 2 }, null])
209
+ })
210
+
211
+ it('should unset non-empty array at dot path', () => {
212
+ const obj = { config: { items: [1, 2] } }
213
+ expect(omitDeep(obj, ['config.items'])).toEqual({ config: {} })
214
+ })
215
+
216
+ it('should not unset empty array at dot path (hasValue is false)', () => {
217
+ const obj = { config: { items: [] } }
218
+ expect(omitDeep(obj, ['config.items'])).toEqual({ config: { items: [] } })
219
+ })
220
+ })