@radio-garden/ditojs-utils 2.85.0-0.5067ad799

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.
Files changed (104) hide show
  1. package/README.md +6 -0
  2. package/package.json +43 -0
  3. package/src/__snapshots__/index.test.js.snap +88 -0
  4. package/src/array/flatten.js +14 -0
  5. package/src/array/flattten.test.js +29 -0
  6. package/src/array/index.js +2 -0
  7. package/src/array/shuffle.js +11 -0
  8. package/src/array/shuffle.test.js +25 -0
  9. package/src/base/base.js +118 -0
  10. package/src/base/base.test.js +590 -0
  11. package/src/base/index.js +1 -0
  12. package/src/class/index.js +1 -0
  13. package/src/class/mixin.js +29 -0
  14. package/src/class/mixin.test.js +70 -0
  15. package/src/dataPath/getEntriesAtDataPath.js +67 -0
  16. package/src/dataPath/getEntriesAtDataPath.test.js +204 -0
  17. package/src/dataPath/getValueAtDataPath.js +45 -0
  18. package/src/dataPath/getValueAtDataPath.test.js +140 -0
  19. package/src/dataPath/index.js +6 -0
  20. package/src/dataPath/normalizeDataPath.js +27 -0
  21. package/src/dataPath/normalizeDataPath.test.js +36 -0
  22. package/src/dataPath/parseDataPath.js +16 -0
  23. package/src/dataPath/parseDataPath.test.js +67 -0
  24. package/src/dataPath/setDataPathEntries.js +8 -0
  25. package/src/dataPath/setDataPathEntries.test.js +36 -0
  26. package/src/dataPath/setValueAtDataPath.js +13 -0
  27. package/src/dataPath/setValueAtDataPath.test.js +34 -0
  28. package/src/function/deprecate.js +9 -0
  29. package/src/function/index.js +4 -0
  30. package/src/function/toAsync.js +9 -0
  31. package/src/function/toAsync.test.js +31 -0
  32. package/src/function/toCallback.js +11 -0
  33. package/src/function/toCallback.test.js +29 -0
  34. package/src/function/toPromiseCallback.js +9 -0
  35. package/src/function/toPromiseCallback.test.js +24 -0
  36. package/src/html/escapeHtml.js +11 -0
  37. package/src/html/escapeHtml.test.js +19 -0
  38. package/src/html/index.js +2 -0
  39. package/src/html/stripHtml.js +23 -0
  40. package/src/html/stripHtml.test.js +37 -0
  41. package/src/index.js +10 -0
  42. package/src/index.test.js +7 -0
  43. package/src/object/asCallback.js +9 -0
  44. package/src/object/clone.js +75 -0
  45. package/src/object/clone.test.js +131 -0
  46. package/src/object/equals.js +36 -0
  47. package/src/object/equals.test.js +269 -0
  48. package/src/object/groupBy.js +15 -0
  49. package/src/object/groupBy.test.js +70 -0
  50. package/src/object/index.js +8 -0
  51. package/src/object/mapKeys.js +7 -0
  52. package/src/object/mapKeys.test.js +16 -0
  53. package/src/object/mapValues.js +9 -0
  54. package/src/object/mapValues.test.js +38 -0
  55. package/src/object/mergeDeeply.js +47 -0
  56. package/src/object/mergeDeeply.test.js +152 -0
  57. package/src/object/pick.js +11 -0
  58. package/src/object/pick.test.js +23 -0
  59. package/src/object/pickBy.js +11 -0
  60. package/src/object/pickBy.test.js +48 -0
  61. package/src/promise/index.js +2 -0
  62. package/src/promise/mapConcurrently.js +33 -0
  63. package/src/promise/mapSequentially.js +9 -0
  64. package/src/string/camelize.js +14 -0
  65. package/src/string/camelize.test.js +37 -0
  66. package/src/string/capitalize.js +7 -0
  67. package/src/string/capitalize.test.js +33 -0
  68. package/src/string/decamelize.js +27 -0
  69. package/src/string/decamelize.test.js +83 -0
  70. package/src/string/deindent.js +69 -0
  71. package/src/string/deindent.test.js +181 -0
  72. package/src/string/escapeRegexp.js +3 -0
  73. package/src/string/format.js +109 -0
  74. package/src/string/format.test.js +196 -0
  75. package/src/string/formatDate.js +13 -0
  76. package/src/string/formatDate.test.js +28 -0
  77. package/src/string/getCommonPrefix.js +35 -0
  78. package/src/string/getCommonPrefix.test.js +23 -0
  79. package/src/string/index.js +15 -0
  80. package/src/string/isAbsoluteUrl.js +7 -0
  81. package/src/string/isAbsoluteUrl.test.js +15 -0
  82. package/src/string/isCreditCard.js +21 -0
  83. package/src/string/isCreditCard.test.js +50 -0
  84. package/src/string/isDomain.js +9 -0
  85. package/src/string/isDomain.test.js +15 -0
  86. package/src/string/isEmail.js +6 -0
  87. package/src/string/isEmail.test.js +37 -0
  88. package/src/string/isHostname.js +8 -0
  89. package/src/string/isHostname.test.js +12 -0
  90. package/src/string/isUrl.js +23 -0
  91. package/src/string/isUrl.test.js +1595 -0
  92. package/src/string/labelize.js +17 -0
  93. package/src/string/labelize.test.js +39 -0
  94. package/src/timer/debounce.js +34 -0
  95. package/src/timer/debounce.test.js +101 -0
  96. package/src/timer/debounceAsync.js +60 -0
  97. package/src/timer/debounceAsync.test.js +143 -0
  98. package/src/timer/index.js +2 -0
  99. package/types/index.d.ts +939 -0
  100. package/types/tests/base.test-d.ts +172 -0
  101. package/types/tests/datapath.test-d.ts +75 -0
  102. package/types/tests/function.test-d.ts +137 -0
  103. package/types/tests/object.test-d.ts +190 -0
  104. package/types/tests/promise.test-d.ts +66 -0
@@ -0,0 +1,269 @@
1
+ import { equals } from './equals.js'
2
+
3
+ describe('equals()', () => {
4
+ const symbol1 = Symbol('a')
5
+ const symbol2 = Symbol('b')
6
+ describe.each([
7
+ [1, 1, true],
8
+ [1, Object(1), true],
9
+ [1, '1', false],
10
+ [1, 2, false],
11
+ [-0, -0, true],
12
+ [0, 0, true],
13
+ [0, Object(0), true],
14
+ [Object(0), Object(0), true],
15
+ [-0, 0, true],
16
+ [0, '0', false],
17
+ [0, null, false],
18
+
19
+ [NaN, NaN, true],
20
+ [NaN, Object(NaN), true],
21
+ [Object(NaN), Object(NaN), true],
22
+ [NaN, 'a', false],
23
+ [NaN, Infinity, false],
24
+
25
+ ['a', 'a', true],
26
+ ['a', Object('a'), true],
27
+ [Object('a'), Object('a'), true],
28
+ ['a', 'b', false],
29
+ ['a', ['a'], false],
30
+
31
+ [true, true, true],
32
+ [true, Object(true), true],
33
+ [Object(true), Object(true), true],
34
+ [true, 1, false],
35
+ [true, 'a', false],
36
+ [false, false, true],
37
+ [false, Object(false), true],
38
+ [Object(false), Object(false), true],
39
+ [false, 0, false],
40
+ [false, '', false],
41
+
42
+ [symbol1, symbol1, true],
43
+ [symbol1, Object(symbol1), true],
44
+ [Object(symbol1), Object(symbol1), true],
45
+ [symbol1, symbol2, false],
46
+
47
+ [null, null, true],
48
+ [null, undefined, false],
49
+ [null, {}, false],
50
+ [null, '', false],
51
+ [undefined, undefined, true],
52
+ [undefined, null, false],
53
+ [undefined, '', false]
54
+ ])(
55
+ 'equals(%o, %o) (should compare primitives)',
56
+ (a, b, expected) => {
57
+ it(`returns ${expected}`, () => {
58
+ expect(equals(a, b)).toBe(expected)
59
+ })
60
+ }
61
+ )
62
+
63
+ it('should compare arrays', () => {
64
+ let array1 = [true, null, 1, 'a', undefined]
65
+ let array2 = [true, null, 1, 'a', undefined]
66
+
67
+ expect(equals(array1, array2)).toBe(true)
68
+
69
+ array1 = [[1, 2, 3], new Date(2012, 4, 23), /x/, { e: 1 }]
70
+ array2 = [[1, 2, 3], new Date(2012, 4, 23), /x/, { e: 1 }]
71
+
72
+ expect(equals(array1, array2)).toBe(true)
73
+
74
+ array1 = [1]
75
+ array1[2] = 3
76
+
77
+ array2 = [1]
78
+ array2[1] = undefined
79
+ array2[2] = 3
80
+
81
+ expect(equals(array1, array2)).toBe(true)
82
+
83
+ array1 = [
84
+ Object(1),
85
+ false,
86
+ Object('a'),
87
+ /x/,
88
+ new Date(2012, 4, 23),
89
+ ['a', 'b', [Object('c')]],
90
+ { a: 1 }
91
+ ]
92
+ array2 = [
93
+ 1,
94
+ Object(false),
95
+ 'a',
96
+ /x/,
97
+ new Date(2012, 4, 23),
98
+ ['a', Object('b'), ['c']],
99
+ { a: 1 }
100
+ ]
101
+
102
+ expect(equals(array1, array2)).toBe(true)
103
+
104
+ array1 = [1, 2, 3]
105
+ array2 = [3, 2, 1]
106
+
107
+ expect(equals(array1, array2)).toBe(false)
108
+
109
+ array1 = [1, 2]
110
+ array2 = [1, 2, 3]
111
+
112
+ expect(equals(array1, array2)).toBe(false)
113
+ })
114
+
115
+ it(`should treat arrays with identical values but different non-index properties as equal`, () => {
116
+ let array1 = [1, 2, 3]
117
+ let array2 = [1, 2, 3]
118
+
119
+ array1.every =
120
+ array1.filter =
121
+ array1.forEach =
122
+ array1.indexOf =
123
+ array1.lastIndexOf =
124
+ array1.map =
125
+ array1.some =
126
+ array1.reduce =
127
+ array1.reduceRight =
128
+ null
129
+
130
+ array2.concat =
131
+ array2.join =
132
+ array2.pop =
133
+ array2.reverse =
134
+ array2.shift =
135
+ array2.slice =
136
+ array2.sort =
137
+ array2.splice =
138
+ array2.unshift =
139
+ null
140
+
141
+ expect(equals(array1, array2)).toBe(true)
142
+
143
+ array1 = [1, 2, 3]
144
+ array1.a = 1
145
+
146
+ array2 = [1, 2, 3]
147
+ array2.b = 1
148
+
149
+ expect(equals(array1, array2)).toBe(true)
150
+
151
+ array1 = /c/.exec('abcde')
152
+ array2 = ['c']
153
+
154
+ expect(equals(array1, array2)).toBe(true)
155
+ })
156
+
157
+ it('should compare sparse arrays', () => {
158
+ const array = Array(1)
159
+ expect(equals(array, Array(1))).toBe(true)
160
+ expect(equals(array, [undefined])).toBe(true)
161
+ expect(equals(array, Array(2))).toBe(false)
162
+ })
163
+
164
+ it('should compare plain objects', () => {
165
+ let object1 = { a: true, b: null, c: 1, d: 'a', e: undefined }
166
+ let object2 = { a: true, b: null, c: 1, d: 'a', e: undefined }
167
+
168
+ expect(equals(object1, object2)).toBe(true)
169
+
170
+ object1 = { a: [1, 2, 3], b: new Date(2012, 4, 23), c: /x/, d: { e: 1 } }
171
+ object2 = { a: [1, 2, 3], b: new Date(2012, 4, 23), c: /x/, d: { e: 1 } }
172
+
173
+ expect(equals(object1, object2)).toBe(true)
174
+
175
+ object1 = { a: 1, b: 2, c: 3 }
176
+ object2 = { a: 3, b: 2, c: 1 }
177
+
178
+ expect(equals(object1, object2)).toBe(false)
179
+
180
+ object1 = { a: 1, b: 2, c: 3 }
181
+ object2 = { d: 1, e: 2, f: 3 }
182
+
183
+ expect(equals(object1, object2)).toBe(false)
184
+
185
+ object1 = { a: 1, b: 2 }
186
+ object2 = { a: 1, b: 2, c: 3 }
187
+
188
+ expect(equals(object1, object2)).toBe(false)
189
+ })
190
+
191
+ it('should compare objects regardless of key order', () => {
192
+ const object1 = { a: 1, b: 2, c: 3 }
193
+ const object2 = { c: 3, a: 1, b: 2 }
194
+
195
+ expect(equals(object1, object2)).toBe(true)
196
+ })
197
+
198
+ it('should compare nested objects', () => {
199
+ function noop() {}
200
+ const object1 = {
201
+ a: [1, 2, 3],
202
+ b: true,
203
+ c: Object(1),
204
+ d: 'a',
205
+ e: {
206
+ f: ['a', Object('b'), 'c'],
207
+ g: Object(false),
208
+ h: new Date(2012, 4, 23),
209
+ i: noop,
210
+ j: 'a'
211
+ }
212
+ }
213
+
214
+ const object2 = {
215
+ a: [1, Object(2), 3],
216
+ b: Object(true),
217
+ c: 1,
218
+ d: Object('a'),
219
+ e: {
220
+ f: ['a', 'b', 'c'],
221
+ g: false,
222
+ h: new Date(2012, 4, 23),
223
+ i: noop,
224
+ j: 'a'
225
+ }
226
+ }
227
+
228
+ expect(equals(object1, object2)).toBe(true)
229
+ })
230
+
231
+ it('should compare objects with shared property values', () => {
232
+ const object1 = {
233
+ a: [1, 2]
234
+ }
235
+
236
+ const object2 = {
237
+ a: [1, 2],
238
+ b: [1, 2]
239
+ }
240
+
241
+ object1.b = object1.a
242
+
243
+ expect(equals(object1, object2)).toBe(true)
244
+ })
245
+
246
+ it('should avoid common type coercions', () => {
247
+ expect(equals(true, Object(false))).toBe(false)
248
+ expect(equals(Object(false), Object(0))).toBe(false)
249
+ expect(equals(false, Object(''))).toBe(false)
250
+ expect(equals(Object(36), Object('36'))).toBe(false)
251
+ expect(equals(0, '')).toBe(false)
252
+ expect(equals(1, true)).toBe(false)
253
+ expect(equals(1337756400000, new Date(2012, 4, 23))).toBe(false)
254
+ expect(equals('36', 36)).toBe(false)
255
+ expect(equals(36, '36')).toBe(false)
256
+ })
257
+
258
+ it('should compare functions', () => {
259
+ function a() {
260
+ return 1 + 2
261
+ }
262
+ function b() {
263
+ return 1 + 2
264
+ }
265
+
266
+ expect(equals(a, a)).toBe(true)
267
+ expect(equals(a, b)).toBe(false)
268
+ })
269
+ })
@@ -0,0 +1,15 @@
1
+ import { isArray } from '../base/index.js'
2
+ import { asCallback } from './asCallback.js'
3
+
4
+ export function groupBy(collection, callback) {
5
+ const array = isArray(collection)
6
+ ? collection
7
+ : Object.values(collection)
8
+ callback = asCallback(callback)
9
+ return array.reduce((groups, item) => {
10
+ const key = callback(item)
11
+ const group = groups[key] || (groups[key] = [])
12
+ group.push(item)
13
+ return groups
14
+ }, {})
15
+ }
@@ -0,0 +1,70 @@
1
+ import { groupBy } from './groupBy.js'
2
+
3
+ describe('groupBy()', () => {
4
+ const array = [6.1, 4.2, 6.3]
5
+
6
+ it('should group entries by the result of `callback`', () => {
7
+ const actual1 = groupBy(array, Math.floor)
8
+ expect(actual1).toStrictEqual({ 4: [4.2], 6: [6.1, 6.3] })
9
+
10
+ const actual2 = groupBy(['one', 'two', 'three'], value => value.length)
11
+ expect(actual2).toStrictEqual({ 3: ['one', 'two'], 5: ['three'] })
12
+ })
13
+
14
+ it('should group by numbers and strings', () => {
15
+ const array = [
16
+ [1, 'a'],
17
+ [2, 'a'],
18
+ [2, 'b']
19
+ ]
20
+ expect(groupBy(array, value => value[0])).toStrictEqual({
21
+ 1: [[1, 'a']],
22
+ 2: [[2, 'a'], [2, 'b']]
23
+ })
24
+ expect(groupBy(array, value => value[1])).toStrictEqual({
25
+ a: [[1, 'a'], [2, 'a']],
26
+ b: [[2, 'b']]
27
+ })
28
+ })
29
+
30
+ it('should work with an object for `collection`', () => {
31
+ const actual = groupBy({ a: 6.1, b: 4.2, c: 6.3 }, Math.floor)
32
+ expect(actual).toStrictEqual({ 4: [4.2], 6: [6.1, 6.3] })
33
+ })
34
+
35
+ it('should support string property accessor', () => {
36
+ const array = [
37
+ { category: 'fruit', name: 'apple' },
38
+ { category: 'vegetable', name: 'carrot' },
39
+ { category: 'fruit', name: 'banana' }
40
+ ]
41
+ const actual = groupBy(array, 'category')
42
+ expect(actual).toStrictEqual({
43
+ fruit: [
44
+ { category: 'fruit', name: 'apple' },
45
+ { category: 'fruit', name: 'banana' }
46
+ ],
47
+ vegetable: [
48
+ { category: 'vegetable', name: 'carrot' }
49
+ ]
50
+ })
51
+ })
52
+
53
+ it('should group by string property with numbers', () => {
54
+ const array = [
55
+ { id: 1, value: 'a' },
56
+ { id: 2, value: 'b' },
57
+ { id: 1, value: 'c' }
58
+ ]
59
+ const actual = groupBy(array, 'id')
60
+ expect(actual).toStrictEqual({
61
+ 1: [
62
+ { id: 1, value: 'a' },
63
+ { id: 1, value: 'c' }
64
+ ],
65
+ 2: [
66
+ { id: 2, value: 'b' }
67
+ ]
68
+ })
69
+ })
70
+ })
@@ -0,0 +1,8 @@
1
+ export * from './clone.js'
2
+ export * from './equals.js'
3
+ export * from './groupBy.js'
4
+ export * from './mapKeys.js'
5
+ export * from './mapValues.js'
6
+ export * from './mergeDeeply.js'
7
+ export * from './pick.js'
8
+ export * from './pickBy.js'
@@ -0,0 +1,7 @@
1
+ export function mapKeys(object, callback) {
2
+ return Object.keys(object).reduce((mapped, key) => {
3
+ const value = object[key]
4
+ mapped[callback(key, value, object)] = value
5
+ return mapped
6
+ }, {})
7
+ }
@@ -0,0 +1,16 @@
1
+ import { mapKeys } from './mapKeys.js'
2
+
3
+ describe('mapKeys()', () => {
4
+ const array = [1, 2]
5
+ const object = { a: 1, b: 2 }
6
+
7
+ it('should map keys in `object` to a new object', () => {
8
+ const actual = mapKeys(object, (key, value) => String(value))
9
+ expect(actual).toStrictEqual({ 1: 1, 2: 2 })
10
+ })
11
+
12
+ it('should treat arrays like objects', () => {
13
+ const actual = mapKeys(array, (key, value) => String(value))
14
+ expect(actual).toStrictEqual({ 1: 1, 2: 2 })
15
+ })
16
+ })
@@ -0,0 +1,9 @@
1
+ import { asCallback } from './asCallback.js'
2
+
3
+ export function mapValues(object, callback) {
4
+ callback = asCallback(callback)
5
+ return Object.keys(object).reduce((mapped, key) => {
6
+ mapped[key] = callback(object[key], key, object)
7
+ return mapped
8
+ }, {})
9
+ }
@@ -0,0 +1,38 @@
1
+ import { mapValues } from './mapValues.js'
2
+
3
+ describe('mapValues()', () => {
4
+ const array = [1, 2]
5
+ const object = { a: 1, b: 2 }
6
+
7
+ it('should map values in `object` to a new object', () => {
8
+ const actual = mapValues(object, String)
9
+ expect(actual).toStrictEqual({ a: '1', b: '2' })
10
+ })
11
+
12
+ it('should treat arrays like objects', () => {
13
+ const actual = mapValues(array, String)
14
+ expect(actual).toStrictEqual({ 0: '1', 1: '2' })
15
+ })
16
+
17
+ it('should support string property accessor', () => {
18
+ const users = {
19
+ user1: { id: 1, name: 'Alice', email: 'alice@example.com' },
20
+ user2: { id: 2, name: 'Bob', email: 'bob@example.com' }
21
+ }
22
+ const actual = mapValues(users, 'email')
23
+ expect(actual).toStrictEqual({
24
+ user1: 'alice@example.com',
25
+ user2: 'bob@example.com'
26
+ })
27
+ })
28
+
29
+ it('should extract nested properties with string accessor', () => {
30
+ const data = {
31
+ a: { value: 10 },
32
+ b: { value: 20 },
33
+ c: { value: 30 }
34
+ }
35
+ const actual = mapValues(data, 'value')
36
+ expect(actual).toStrictEqual({ a: 10, b: 20, c: 30 })
37
+ })
38
+ })
@@ -0,0 +1,47 @@
1
+ import { isArray, isPlainObject } from '../base/index.js'
2
+ import { clone } from './clone.js'
3
+
4
+ export function mergeDeeply(target, ...sources) {
5
+ for (const source of sources) {
6
+ target = mergeValues(target, source, true, true)
7
+ }
8
+ return target
9
+ }
10
+
11
+ export function assignDeeply(target, ...sources) {
12
+ for (const source of sources) {
13
+ target = mergeValues(target, source, true, false)
14
+ }
15
+ return target
16
+ }
17
+
18
+ function mergeValues(target, source, root, concat) {
19
+ if (target && source && target !== source) {
20
+ if (isArray(target) && isArray(source)) {
21
+ return mergeIteratively(target, source, root, concat, true)
22
+ } else if (isPlainObject(target) && isPlainObject(source)) {
23
+ return mergeIteratively(target, source, root, concat, false)
24
+ }
25
+ }
26
+ return root // Don't override values with nullish at root level
27
+ ? source ?? target
28
+ : source
29
+ }
30
+
31
+ function mergeIteratively(target, source, root, concat, toArray) {
32
+ const result = root
33
+ ? target
34
+ : clone(target, { shallow: true })
35
+ for (const key of Object.keys(source)) {
36
+ const to = target[key]
37
+ const from = source[key]
38
+ const value = mergeValues(to, from, false, concat)
39
+ if (value === from && toArray && concat) {
40
+ // Append non-mergeable values to the end of results array.
41
+ result.push(value)
42
+ } else {
43
+ result[key] = value
44
+ }
45
+ }
46
+ return result
47
+ }
@@ -0,0 +1,152 @@
1
+ import { mergeDeeply, assignDeeply } from './mergeDeeply.js'
2
+
3
+ describe('mergeDeeply()', () => {
4
+ it('should merge nested objects', () => {
5
+ const source1 = { a: { b: [] } }
6
+ const source2 = { a: { c: [] } }
7
+ const expected = { a: { b: [], c: [] } }
8
+ expect(mergeDeeply({}, source1, source2)).toStrictEqual(expected)
9
+ expect(mergeDeeply({}, source2, source1)).toStrictEqual(expected)
10
+ })
11
+
12
+ it('should override keys in target with different types', () => {
13
+ const source1 = { a: { b: 1 } }
14
+ const source2 = { a: { b: [] } }
15
+ expect(mergeDeeply({}, source1, source2)).toStrictEqual(source2)
16
+ expect(mergeDeeply({}, source2, source1)).toStrictEqual(source1)
17
+ })
18
+
19
+ it('should merge objects at the same indices inside arrays', () => {
20
+ const source1 = [{ a: 1 }, { b: 2 }]
21
+ const source2 = [{ c: 3 }, { d: 4 }]
22
+ const expected = [{ a: 1, c: 3 }, { b: 2, d: 4 }]
23
+ expect(mergeDeeply([], source1, source2)).toStrictEqual(expected)
24
+ })
25
+
26
+ it('should merge `source` into `object`', () => {
27
+ const names = {
28
+ characters: [
29
+ { name: 'barney' },
30
+ { name: 'fred' }
31
+ ]
32
+ }
33
+
34
+ const ages = {
35
+ characters: [
36
+ { age: 36 },
37
+ { age: 40 }
38
+ ]
39
+ }
40
+
41
+ const heights = {
42
+ characters: [
43
+ { height: '5\'4"' },
44
+ { height: '5\'5"' }
45
+ ]
46
+ }
47
+
48
+ const expected = {
49
+ characters: [
50
+ { name: 'barney', age: 36, height: '5\'4"' },
51
+ { name: 'fred', age: 40, height: '5\'5"' }
52
+ ]
53
+ }
54
+
55
+ expect(mergeDeeply(names, ages, heights)).toEqual(expected)
56
+ })
57
+
58
+ it('should handle plain values', () => {
59
+ expect(mergeDeeply(true, false)).toEqual(false)
60
+ expect(mergeDeeply(10, 0)).toEqual(0)
61
+ expect(mergeDeeply(0, 10)).toEqual(10)
62
+ expect(mergeDeeply('foo', 'bar')).toEqual('bar')
63
+ })
64
+
65
+ it('should not override values with nullish values at root level', () => {
66
+ expect(mergeDeeply({}, null)).toEqual({})
67
+ expect(mergeDeeply([], null)).toEqual([])
68
+ expect(mergeDeeply(10, null)).toEqual(10)
69
+ expect(mergeDeeply(false, null)).toEqual(false)
70
+ })
71
+
72
+ it('should work with multiple arguments', () => {
73
+ const expected = { a: 4 }
74
+ const actual = mergeDeeply({ a: 1 }, { a: 2 }, { a: 3 }, expected)
75
+
76
+ expect(actual).toStrictEqual(expected)
77
+ })
78
+
79
+ it('should not augment source objects', () => {
80
+ const source1 = { a: [{ a: 1 }] }
81
+ const source2 = { a: [{ b: 2 }] }
82
+ const actual1 = mergeDeeply({}, source1, source2)
83
+
84
+ expect(source1.a).toStrictEqual([{ a: 1 }])
85
+ expect(source2.a).toStrictEqual([{ b: 2 }])
86
+ expect(actual1.a).toStrictEqual([{ a: 1, b: 2 }])
87
+
88
+ const source3 = { a: [[1, 2, 3]] }
89
+ const source4 = { a: [[4, 5]] }
90
+ const actual2 = mergeDeeply({}, source3, source4)
91
+
92
+ expect(source3.a).toStrictEqual([[1, 2, 3]])
93
+ expect(source4.a).toStrictEqual([[4, 5]])
94
+ expect(actual2.a).toStrictEqual([[1, 2, 3, 4, 5]])
95
+ })
96
+
97
+ it('should overwrite existing values with `undefined` in objects', () => {
98
+ const result = mergeDeeply({ a: 1 }, { a: undefined, b: undefined })
99
+ expect(result).toStrictEqual({ a: undefined, b: undefined })
100
+ })
101
+
102
+ it('should not overwrite existing values with `undefined` in arrays', () => {
103
+ const result1 = mergeDeeply([4, 5, 6], [1, undefined, 3])
104
+ expect(result1).toStrictEqual([4, 5, 6, 1, undefined, 3])
105
+
106
+ // eslint-disable-next-line no-sparse-arrays
107
+ const result2 = mergeDeeply([4, 5, 6], [1, , 3])
108
+ expect(result2).toStrictEqual([4, 5, 6, 1, 3])
109
+ })
110
+
111
+ it('should merge regexps', () => {
112
+ const source1 = { a: /1/ }
113
+ const source2 = { a: /2/ }
114
+ const expected = { a: /2/ }
115
+ expect(mergeDeeply({}, source1, source2)).toStrictEqual(expected)
116
+ })
117
+
118
+ it('should merge dates', () => {
119
+ const source1 = { a: new Date(2012, 5, 9) }
120
+ const source2 = { a: new Date(2021, 5, 9) }
121
+ const expected = { a: new Date(2021, 5, 9) }
122
+ expect(mergeDeeply({}, source1, source2)).toStrictEqual(expected)
123
+ })
124
+
125
+ it('should be fine with nested promises', async () => {
126
+ const promise1 = (async () => 1)()
127
+ const promise2 = (async () => 2)()
128
+ const source1 = { nested: { promise1 } }
129
+ const source2 = { nested: { promise2 } }
130
+ const expected = { nested: { promise1, promise2 } }
131
+ const result = mergeDeeply({}, source1, source2)
132
+ expect(result).toStrictEqual(expected)
133
+ expect(await result.nested.promise1).toStrictEqual(1)
134
+ expect(await result.nested.promise2).toStrictEqual(2)
135
+ })
136
+ })
137
+
138
+ describe('assignDeeply()', () => {
139
+ it('should not concat-merge arrays', () => {
140
+ const result = assignDeeply({}, { a: [[1, 2, 3]] }, { a: [[4, 5]] })
141
+ expect(result).toStrictEqual({ a: [[4, 5, 3]] })
142
+ })
143
+
144
+ it('should overwrite existing values with `undefined` in arrays', () => {
145
+ const result1 = assignDeeply([4, 5, 6], [1, undefined, 3])
146
+ expect(result1).toStrictEqual([1, undefined, 3])
147
+
148
+ // eslint-disable-next-line no-sparse-arrays
149
+ const result2 = assignDeeply([4, 5, 6], [1, , 3])
150
+ expect(result2).toStrictEqual([1, 5, 3])
151
+ })
152
+ })
@@ -0,0 +1,11 @@
1
+ export function pick(...args) {
2
+ // Optimize for the most common case of two arguments:
3
+ if (args.length === 2) {
4
+ return args[0] !== undefined ? args[0] : args[1]
5
+ }
6
+ for (const arg of args) {
7
+ if (arg !== undefined) {
8
+ return arg
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,23 @@
1
+ import { pick } from './pick.js'
2
+
3
+ // Partially based on:
4
+ // https://github.com/lodash/lodash/blob/4.17.11/test/test.js
5
+
6
+ describe('pick()', () => {
7
+ it(`should return the first value that isn't undefined`, () => {
8
+ expect(pick(10)).toBe(10)
9
+ expect(pick(10, undefined)).toBe(10)
10
+ expect(pick(undefined, 10)).toBe(10)
11
+ expect(pick(undefined, undefined, 10)).toBe(10)
12
+ })
13
+
14
+ it('should consider null as defined', () => {
15
+ expect(pick(null)).toBeNull()
16
+ expect(pick(undefined, null)).toBeNull()
17
+ })
18
+
19
+ it('should return undefined if nothing is defined', () => {
20
+ expect(pick()).toBeUndefined()
21
+ expect(pick(undefined)).toBeUndefined()
22
+ })
23
+ })
@@ -0,0 +1,11 @@
1
+ import { asCallback } from './asCallback.js'
2
+
3
+ export function pickBy(object, callback) {
4
+ callback = asCallback(callback)
5
+ return Object.entries(object).reduce((result, [key, value]) => {
6
+ if (callback(value, key, object)) {
7
+ result[key] = value
8
+ }
9
+ return result
10
+ }, {})
11
+ }