@superhero/deep 4.0.0 → 4.2.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.
- package/README.md +95 -53
- package/assign.js +63 -66
- package/assign.test.js +15 -15
- package/clone.js +44 -5
- package/clone.test.js +53 -52
- package/freeze.js +22 -25
- package/freeze.test.js +9 -9
- package/index.js +7 -4
- package/index.test.js +23 -0
- package/merge.js +67 -70
- package/merge.test.js +13 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ import deepassign from '@superhero/deep/assign'
|
|
|
22
22
|
const target = { foo: { bar: 1 } }
|
|
23
23
|
const source = { foo: { baz: 2 } }
|
|
24
24
|
|
|
25
|
-
deepassign
|
|
25
|
+
deepassign(target, source)
|
|
26
26
|
|
|
27
27
|
console.log(target) // { foo: { bar: 1, baz: 2 } }
|
|
28
28
|
```
|
|
@@ -46,7 +46,7 @@ import deepmerge from '@superhero/deep/merge'
|
|
|
46
46
|
const obj1 = { foo: { bar: 1 }, arr: [1, 2] }
|
|
47
47
|
const obj2 = { foo: { baz: 2 }, arr: [2, 3] }
|
|
48
48
|
|
|
49
|
-
const result = deepmerge
|
|
49
|
+
const result = deepmerge(obj1, obj2)
|
|
50
50
|
|
|
51
51
|
console.log(result)
|
|
52
52
|
// { foo: { bar: 1, baz: 2 }, arr: [1, 2, 3] }
|
|
@@ -70,7 +70,7 @@ import deepfreeze from '@superhero/deep/freeze'
|
|
|
70
70
|
const obj = { foo: { bar: 'baz' } }
|
|
71
71
|
obj.foo.self = obj.foo // Circular reference
|
|
72
72
|
|
|
73
|
-
deepfreeze
|
|
73
|
+
deepfreeze(obj)
|
|
74
74
|
|
|
75
75
|
obj.foo.bar = 'new value' // TypeError: Cannot assign to read-only property
|
|
76
76
|
```
|
|
@@ -93,7 +93,7 @@ import deepclone from '@superhero/deep/clone'
|
|
|
93
93
|
|
|
94
94
|
const obj = { foo: { bar: 'baz' }, arr: [1, 2, 3] }
|
|
95
95
|
|
|
96
|
-
const clone = deepclone
|
|
96
|
+
const clone = deepclone(obj)
|
|
97
97
|
|
|
98
98
|
console.log(clone) // Deeply cloned object
|
|
99
99
|
console.log(clone === obj) // false
|
|
@@ -101,6 +101,37 @@ console.log(clone === obj) // false
|
|
|
101
101
|
|
|
102
102
|
---
|
|
103
103
|
|
|
104
|
+
## 5. **Deep**
|
|
105
|
+
|
|
106
|
+
### Purpose:
|
|
107
|
+
Makes the functions accessible through the imported default object.
|
|
108
|
+
|
|
109
|
+
### Features:
|
|
110
|
+
- Exports all the above mentioned functions.
|
|
111
|
+
- Exports a default object with all the above mentioned functions as members.
|
|
112
|
+
|
|
113
|
+
### Example:
|
|
114
|
+
```javascript
|
|
115
|
+
import deep from '@superhero/deep'
|
|
116
|
+
|
|
117
|
+
deep.assign(/* ... */)
|
|
118
|
+
deep.clone(/* ... */)
|
|
119
|
+
deep.freeze(/* ... */)
|
|
120
|
+
deep.merge(/* ... */)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Example:
|
|
124
|
+
```javascript
|
|
125
|
+
import { assign, clone, freeze, merge } from '@superhero/deep'
|
|
126
|
+
|
|
127
|
+
assign(/* ... */)
|
|
128
|
+
clone(/* ... */)
|
|
129
|
+
freeze(/* ... */)
|
|
130
|
+
merge(/* ... */)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
104
135
|
## Testing
|
|
105
136
|
Each utility class has a set of unit tests to ensure correctness across different cases.
|
|
106
137
|
|
|
@@ -114,75 +145,86 @@ npm test
|
|
|
114
145
|
|
|
115
146
|
```
|
|
116
147
|
▶ @superhero/deep/assign
|
|
117
|
-
✔
|
|
118
|
-
✔
|
|
119
|
-
✔ Overwrites non-object properties correctly (0.
|
|
120
|
-
✔ Handles undefined values correctly (0.
|
|
148
|
+
✔ Assigns arrays correctly (3.167398ms)
|
|
149
|
+
✔ Assigns objects correctly (0.763768ms)
|
|
150
|
+
✔ Overwrites non-object properties correctly (0.347727ms)
|
|
151
|
+
✔ Handles undefined values correctly (0.521744ms)
|
|
152
|
+
|
|
121
153
|
▶ Descriptor properties
|
|
122
154
|
▶ Retains
|
|
123
|
-
✔ non-writable, non-configurable and non-enumarable (0.
|
|
124
|
-
✔ writable but non-configurable and non-enumarable (0.
|
|
125
|
-
✔ writable and configurable but non-enumarable (0.
|
|
126
|
-
✔ Retains (
|
|
155
|
+
✔ non-writable, non-configurable and non-enumarable (0.474666ms)
|
|
156
|
+
✔ writable but non-configurable and non-enumarable (0.649244ms)
|
|
157
|
+
✔ writable and configurable but non-enumarable (0.298073ms)
|
|
158
|
+
✔ Retains (1.918778ms)
|
|
159
|
+
|
|
127
160
|
▶ Assigns
|
|
128
|
-
✔ non-writable, non-configurable and non-enumarable (0.
|
|
129
|
-
✔ Assigns (
|
|
130
|
-
✔ Descriptor properties (
|
|
131
|
-
|
|
132
|
-
✔ Merges nested
|
|
133
|
-
✔
|
|
134
|
-
✔
|
|
161
|
+
✔ non-writable, non-configurable and non-enumarable (0.485428ms)
|
|
162
|
+
✔ Assigns (0.889839ms)
|
|
163
|
+
✔ Descriptor properties (3.74915ms)
|
|
164
|
+
|
|
165
|
+
✔ Merges nested arrays correctly (1.898073ms)
|
|
166
|
+
✔ Merges nested objects correctly (0.761838ms)
|
|
167
|
+
✔ Does not alter objects with no conflicts (0.277694ms)
|
|
168
|
+
✔ @superhero/deep/assign (14.053485ms)
|
|
135
169
|
|
|
136
170
|
▶ @superhero/deep/clone
|
|
137
|
-
✔ Clones simple objects (
|
|
138
|
-
✔ Clones nested objects (0.
|
|
139
|
-
✔ Clones arrays (0.
|
|
140
|
-
✔ Handles circular references (0.
|
|
141
|
-
✔ Clones objects with null prototype (0.
|
|
142
|
-
✔ @superhero/deep/clone (
|
|
171
|
+
✔ Clones simple objects (7.235299ms)
|
|
172
|
+
✔ Clones nested objects (0.634553ms)
|
|
173
|
+
✔ Clones arrays (0.650244ms)
|
|
174
|
+
✔ Handles circular references (0.382144ms)
|
|
175
|
+
✔ Clones objects with null prototype (0.375334ms)
|
|
176
|
+
✔ @superhero/deep/clone (11.89498ms)
|
|
143
177
|
|
|
144
178
|
▶ @superhero/deep/freeze
|
|
145
|
-
✔ Freezes a simple object (
|
|
146
|
-
✔ Freezes nested objects recursively (0.
|
|
147
|
-
✔ Handles circular references gracefully (0.
|
|
148
|
-
✔ Freezes objects with symbols (0.
|
|
149
|
-
✔ Handles already frozen objects without error (0.
|
|
150
|
-
✔ Freezes objects with non-enumerable properties (0.
|
|
151
|
-
✔ Freezes arrays (0.
|
|
152
|
-
✔ Handles objects with null prototype (0.
|
|
153
|
-
✔ Freezes objects with multiple property types (0.
|
|
154
|
-
✔ @superhero/deep/freeze (
|
|
179
|
+
✔ Freezes a simple object (3.683567ms)
|
|
180
|
+
✔ Freezes nested objects recursively (0.406919ms)
|
|
181
|
+
✔ Handles circular references gracefully (0.309979ms)
|
|
182
|
+
✔ Freezes objects with symbols (0.405137ms)
|
|
183
|
+
✔ Handles already frozen objects without error (0.337038ms)
|
|
184
|
+
✔ Freezes objects with non-enumerable properties (0.354681ms)
|
|
185
|
+
✔ Freezes arrays (0.662049ms)
|
|
186
|
+
✔ Handles objects with null prototype (0.387428ms)
|
|
187
|
+
✔ Freezes objects with multiple property types (0.626902ms)
|
|
188
|
+
✔ @superhero/deep/freeze (10.607326ms)
|
|
189
|
+
|
|
190
|
+
▶ @superhero/deep
|
|
191
|
+
✔ All functions are accessible as a member to the default import object (1.734551ms)
|
|
192
|
+
✔ All functions are accessible to import from the default import object (0.255022ms)
|
|
193
|
+
✔ @superhero/deep (5.188429ms)
|
|
155
194
|
|
|
156
195
|
▶ @superhero/deep/merge
|
|
157
|
-
✔ Merges arrays with unique values (
|
|
158
|
-
✔ Merges arrays with order preserved (0.
|
|
159
|
-
✔ Handles empty arrays correctly (0.
|
|
160
|
-
✔ Handles arrays with duplicate values (0.
|
|
161
|
-
✔ Merges objects and prioritizes restrictive descriptors (0.
|
|
162
|
-
✔ Merges objects with non-enumerable properties (0.
|
|
163
|
-
✔ Handles nested object merging (0.
|
|
164
|
-
✔ Stops at circular references (0.
|
|
165
|
-
✔ Stops when nested and with circular references (0.
|
|
166
|
-
✔ Returns second value for non-object types (0.
|
|
167
|
-
✔ Handles multiple merges sequentially (0.
|
|
168
|
-
✔ @superhero/deep/merge (
|
|
169
|
-
|
|
170
|
-
tests
|
|
171
|
-
|
|
196
|
+
✔ Merges arrays with unique values (3.271451ms)
|
|
197
|
+
✔ Merges arrays with order preserved (0.455376ms)
|
|
198
|
+
✔ Handles empty arrays correctly (0.283857ms)
|
|
199
|
+
✔ Handles arrays with duplicate values (0.538911ms)
|
|
200
|
+
✔ Merges objects and prioritizes restrictive descriptors (0.592729ms)
|
|
201
|
+
✔ Merges objects with non-enumerable properties (0.343159ms)
|
|
202
|
+
✔ Handles nested object merging (0.359878ms)
|
|
203
|
+
✔ Stops at circular references (0.554345ms)
|
|
204
|
+
✔ Stops when nested and with circular references (0.667265ms)
|
|
205
|
+
✔ Returns second value for non-object types (0.744991ms)
|
|
206
|
+
✔ Handles multiple merges sequentially (0.327608ms)
|
|
207
|
+
✔ @superhero/deep/merge (11.803686ms)
|
|
208
|
+
|
|
209
|
+
tests 38
|
|
210
|
+
suites 8
|
|
211
|
+
pass 38
|
|
172
212
|
|
|
173
213
|
-----------------------------------------------------------------
|
|
174
214
|
file | line % | branch % | funcs % | uncovered lines
|
|
175
215
|
-----------------------------------------------------------------
|
|
176
216
|
assign.js | 100.00 | 100.00 | 100.00 |
|
|
177
217
|
assign.test.js | 100.00 | 100.00 | 100.00 |
|
|
178
|
-
clone.js |
|
|
179
|
-
clone.test.js |
|
|
218
|
+
clone.js | 95.83 | 92.86 | 100.00 | 22-23
|
|
219
|
+
clone.test.js | 100.00 | 100.00 | 100.00 |
|
|
180
220
|
freeze.js | 100.00 | 100.00 | 100.00 |
|
|
181
221
|
freeze.test.js | 100.00 | 100.00 | 100.00 |
|
|
222
|
+
index.js | 100.00 | 100.00 | 100.00 |
|
|
223
|
+
index.test.js | 100.00 | 100.00 | 100.00 |
|
|
182
224
|
merge.js | 100.00 | 100.00 | 100.00 |
|
|
183
225
|
merge.test.js | 100.00 | 100.00 | 100.00 |
|
|
184
226
|
-----------------------------------------------------------------
|
|
185
|
-
all files | 99.
|
|
227
|
+
all files | 99.78 | 99.25 | 100.00 |
|
|
186
228
|
-----------------------------------------------------------------
|
|
187
229
|
```
|
|
188
230
|
|
package/assign.js
CHANGED
|
@@ -1,89 +1,86 @@
|
|
|
1
|
-
export default
|
|
1
|
+
export default function assign(a, ...b)
|
|
2
2
|
{
|
|
3
|
-
|
|
3
|
+
b.forEach((b) => assignB2A(a, b))
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function assignB2A(a, b)
|
|
7
|
+
{
|
|
8
|
+
if(b === undefined)
|
|
4
9
|
{
|
|
5
|
-
|
|
10
|
+
return a
|
|
6
11
|
}
|
|
7
12
|
|
|
8
|
-
|
|
13
|
+
if(Object.prototype.toString.call(a) === '[object Array]'
|
|
14
|
+
&& Object.prototype.toString.call(b) === '[object Array]')
|
|
9
15
|
{
|
|
10
|
-
|
|
11
|
-
{
|
|
12
|
-
return a
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if(Object.prototype.toString.call(a) === '[object Array]'
|
|
16
|
-
&& Object.prototype.toString.call(b) === '[object Array]')
|
|
17
|
-
{
|
|
18
|
-
return this.#assignArray(a, b)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if(Object.prototype.toString.call(a) === '[object Object]'
|
|
22
|
-
&& Object.prototype.toString.call(b) === '[object Object]')
|
|
23
|
-
{
|
|
24
|
-
return this.#assignObject(a, b)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return b
|
|
16
|
+
return assignArray(a, b)
|
|
28
17
|
}
|
|
29
18
|
|
|
30
|
-
|
|
19
|
+
if(Object.prototype.toString.call(a) === '[object Object]'
|
|
20
|
+
&& Object.prototype.toString.call(b) === '[object Object]')
|
|
31
21
|
{
|
|
32
|
-
|
|
33
|
-
a.length = 0
|
|
34
|
-
a.push(...values)
|
|
35
|
-
return a
|
|
22
|
+
return assignObject(a, b)
|
|
36
23
|
}
|
|
37
24
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
25
|
+
return b
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function assignArray(a, b)
|
|
29
|
+
{
|
|
30
|
+
const values = new Set(a.concat(b))
|
|
31
|
+
a.length = 0
|
|
32
|
+
a.push(...values)
|
|
33
|
+
return a
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {Object} a
|
|
38
|
+
* @param {Object} b
|
|
39
|
+
*
|
|
40
|
+
* @throws {TypeError} If a property in "a" is also in "b",
|
|
41
|
+
* but the property in "a" is not configurable.
|
|
42
|
+
*
|
|
43
|
+
* @returns {Object} a
|
|
44
|
+
*/
|
|
45
|
+
function assignObject(a, b)
|
|
46
|
+
{
|
|
47
|
+
for (const key of Object.getOwnPropertyNames(b))
|
|
48
48
|
{
|
|
49
|
-
|
|
49
|
+
if(Object.prototype.toString.call(a[key]) === '[object Object]'
|
|
50
|
+
&& Object.prototype.toString.call(b[key]) === '[object Object]')
|
|
51
|
+
{
|
|
52
|
+
assignObject(a[key], b[key])
|
|
53
|
+
}
|
|
54
|
+
else if(Object.hasOwnProperty.call(a, key))
|
|
50
55
|
{
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
const descriptor_a = Object.getOwnPropertyDescriptor(a, key)
|
|
57
|
+
|
|
58
|
+
if(descriptor_a.configurable)
|
|
53
59
|
{
|
|
54
|
-
|
|
60
|
+
assignPropertyDescriptor(a, b, key)
|
|
55
61
|
}
|
|
56
|
-
else if(
|
|
62
|
+
else if(descriptor_a.writable)
|
|
57
63
|
{
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if(descriptor_a.configurable)
|
|
61
|
-
{
|
|
62
|
-
this.#assignPropertyDescriptor(a, b, key)
|
|
63
|
-
}
|
|
64
|
-
else if(descriptor_a.writable)
|
|
65
|
-
{
|
|
66
|
-
descriptor_a.value = b[key]
|
|
67
|
-
Object.defineProperty(a, key, descriptor_a)
|
|
68
|
-
}
|
|
69
|
-
else
|
|
70
|
-
{
|
|
71
|
-
continue
|
|
72
|
-
}
|
|
64
|
+
descriptor_a.value = b[key]
|
|
65
|
+
Object.defineProperty(a, key, descriptor_a)
|
|
73
66
|
}
|
|
74
67
|
else
|
|
75
68
|
{
|
|
76
|
-
|
|
69
|
+
continue
|
|
77
70
|
}
|
|
78
71
|
}
|
|
79
|
-
|
|
80
|
-
|
|
72
|
+
else
|
|
73
|
+
{
|
|
74
|
+
assignPropertyDescriptor(a, b, key)
|
|
75
|
+
}
|
|
81
76
|
}
|
|
82
77
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
78
|
+
return a
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function assignPropertyDescriptor(a, b, key)
|
|
82
|
+
{
|
|
83
|
+
const descriptor_b = Object.getOwnPropertyDescriptor(b, key)
|
|
84
|
+
descriptor_b.value = assignB2A(a[key], b[key])
|
|
85
|
+
Object.defineProperty(a, key, descriptor_b)
|
|
89
86
|
}
|
package/assign.test.js
CHANGED
|
@@ -4,25 +4,25 @@ import deepassign from '@superhero/deep/assign'
|
|
|
4
4
|
|
|
5
5
|
suite('@superhero/deep/assign', () =>
|
|
6
6
|
{
|
|
7
|
-
test('
|
|
7
|
+
test('Assigns arrays correctly', () =>
|
|
8
8
|
{
|
|
9
9
|
const
|
|
10
10
|
a = [1, 2, 3],
|
|
11
11
|
b = [3, 4, 5],
|
|
12
12
|
expected = [1, 2, 3, 4, 5]
|
|
13
13
|
|
|
14
|
-
deepassign
|
|
14
|
+
deepassign(a, b)
|
|
15
15
|
assert.deepStrictEqual(a, expected, 'Arrays should merge with unique values')
|
|
16
16
|
})
|
|
17
17
|
|
|
18
|
-
test('
|
|
18
|
+
test('Assigns objects correctly', () =>
|
|
19
19
|
{
|
|
20
20
|
const
|
|
21
21
|
a = { foo: 1, bar: { baz: 2 } },
|
|
22
|
-
b = { bar: { qux: 3 }, hello: 'world' },
|
|
23
|
-
expected = { foo:
|
|
22
|
+
b = { foo: 2, bar: { qux: 3 }, hello: 'world' },
|
|
23
|
+
expected = { foo: 2, bar: { baz: 2, qux: 3 }, hello: 'world' }
|
|
24
24
|
|
|
25
|
-
deepassign
|
|
25
|
+
deepassign(a, b)
|
|
26
26
|
assert.deepStrictEqual(a, expected, 'Objects should deep merge')
|
|
27
27
|
})
|
|
28
28
|
|
|
@@ -33,7 +33,7 @@ suite('@superhero/deep/assign', () =>
|
|
|
33
33
|
b = { foo: 2 },
|
|
34
34
|
expected = { foo: 2 }
|
|
35
35
|
|
|
36
|
-
deepassign
|
|
36
|
+
deepassign(a, b)
|
|
37
37
|
assert.deepStrictEqual(a, expected, 'Properties should be overwritten')
|
|
38
38
|
})
|
|
39
39
|
|
|
@@ -44,7 +44,7 @@ suite('@superhero/deep/assign', () =>
|
|
|
44
44
|
b = { foo: undefined },
|
|
45
45
|
expected = { foo: 1 }
|
|
46
46
|
|
|
47
|
-
deepassign
|
|
47
|
+
deepassign(a, b)
|
|
48
48
|
assert.deepStrictEqual(a, expected, 'Undefined values should not overwrite existing properties')
|
|
49
49
|
})
|
|
50
50
|
|
|
@@ -66,7 +66,7 @@ suite('@superhero/deep/assign', () =>
|
|
|
66
66
|
|
|
67
67
|
const b = { foo: 2 }
|
|
68
68
|
|
|
69
|
-
deepassign
|
|
69
|
+
deepassign(a, b)
|
|
70
70
|
|
|
71
71
|
const descriptor_a = Object.getOwnPropertyDescriptor(a, 'foo')
|
|
72
72
|
|
|
@@ -90,7 +90,7 @@ suite('@superhero/deep/assign', () =>
|
|
|
90
90
|
|
|
91
91
|
const b = { foo: 2 }
|
|
92
92
|
|
|
93
|
-
deepassign
|
|
93
|
+
deepassign(a, b)
|
|
94
94
|
|
|
95
95
|
const descriptor_a = Object.getOwnPropertyDescriptor(a, 'foo')
|
|
96
96
|
|
|
@@ -114,7 +114,7 @@ suite('@superhero/deep/assign', () =>
|
|
|
114
114
|
|
|
115
115
|
const b = { foo: 2 }
|
|
116
116
|
|
|
117
|
-
deepassign
|
|
117
|
+
deepassign(a, b)
|
|
118
118
|
|
|
119
119
|
const descriptor_a = Object.getOwnPropertyDescriptor(a, 'foo')
|
|
120
120
|
|
|
@@ -141,7 +141,7 @@ suite('@superhero/deep/assign', () =>
|
|
|
141
141
|
enumerable : false
|
|
142
142
|
})
|
|
143
143
|
|
|
144
|
-
deepassign
|
|
144
|
+
deepassign(a, b)
|
|
145
145
|
|
|
146
146
|
const descriptor_a = Object.getOwnPropertyDescriptor(a, 'foo')
|
|
147
147
|
|
|
@@ -160,7 +160,7 @@ suite('@superhero/deep/assign', () =>
|
|
|
160
160
|
b = { foo: [2, 3] },
|
|
161
161
|
expected = { foo: [1, 2, 3] }
|
|
162
162
|
|
|
163
|
-
deepassign
|
|
163
|
+
deepassign(a, b)
|
|
164
164
|
assert.deepStrictEqual(a, expected, 'Nested arrays should merge with unique values')
|
|
165
165
|
})
|
|
166
166
|
|
|
@@ -171,7 +171,7 @@ suite('@superhero/deep/assign', () =>
|
|
|
171
171
|
b = { foo: { bar: { qux: 2 }}},
|
|
172
172
|
expected = { foo: { bar: { baz: 1, qux: 2 }}}
|
|
173
173
|
|
|
174
|
-
deepassign
|
|
174
|
+
deepassign(a, b)
|
|
175
175
|
assert.deepStrictEqual(a, expected, 'Nested objects should deep merge')
|
|
176
176
|
})
|
|
177
177
|
|
|
@@ -182,7 +182,7 @@ suite('@superhero/deep/assign', () =>
|
|
|
182
182
|
b = { bar: 2 },
|
|
183
183
|
expected = { foo: 1, bar: 2 }
|
|
184
184
|
|
|
185
|
-
deepassign
|
|
185
|
+
deepassign(a, b)
|
|
186
186
|
assert.deepStrictEqual(a, expected, 'Objects without conflicts should merge correctly')
|
|
187
187
|
})
|
|
188
188
|
})
|
package/clone.js
CHANGED
|
@@ -1,9 +1,48 @@
|
|
|
1
|
-
export default
|
|
1
|
+
export default function clone(input)
|
|
2
2
|
{
|
|
3
|
-
|
|
3
|
+
const seen = new WeakSet()
|
|
4
|
+
return deepClone(input, seen)
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function deepClone(value, seen)
|
|
8
|
+
{
|
|
9
|
+
switch(Object.prototype.toString.call(value))
|
|
10
|
+
{
|
|
11
|
+
case '[object Array]' : return cloneArray(value, seen)
|
|
12
|
+
case '[object Object]' : return cloneObject(value, seen)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return structuredClone(value)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function cloneArray(array, seen)
|
|
19
|
+
{
|
|
20
|
+
if(seen.has(array))
|
|
21
|
+
{
|
|
22
|
+
return array
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
seen.add(array)
|
|
26
|
+
return array.map((item) => deepClone(item, seen))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function cloneObject(obj, seen)
|
|
30
|
+
{
|
|
31
|
+
if(seen.has(obj))
|
|
32
|
+
{
|
|
33
|
+
return obj
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
seen.add(obj)
|
|
37
|
+
|
|
38
|
+
const output = {}
|
|
39
|
+
|
|
40
|
+
for(const key of Object.getOwnPropertyNames(obj))
|
|
4
41
|
{
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
42
|
+
const descriptor = Object.getOwnPropertyDescriptor(obj, key)
|
|
43
|
+
Object.defineProperty(output, key,
|
|
44
|
+
{ ...descriptor, value : deepClone(descriptor.value, seen) })
|
|
8
45
|
}
|
|
46
|
+
|
|
47
|
+
return output
|
|
9
48
|
}
|
package/clone.test.js
CHANGED
|
@@ -8,75 +8,76 @@ suite('@superhero/deep/clone', () =>
|
|
|
8
8
|
{
|
|
9
9
|
const
|
|
10
10
|
obj = { foo: 'bar', baz: 42 },
|
|
11
|
-
|
|
12
|
-
legacy = deepclone.clone(obj, true)
|
|
11
|
+
cloned = deepclone(obj)
|
|
13
12
|
|
|
14
|
-
assert.deepStrictEqual(
|
|
15
|
-
assert.
|
|
16
|
-
|
|
17
|
-
assert.notStrictEqual(result, obj, 'Cloned object should not be the same reference as the original')
|
|
18
|
-
assert.notStrictEqual(legacy, obj, 'Cloned object should not be the same reference as the original (legacy mode)')
|
|
13
|
+
assert.deepStrictEqual(cloned, obj, 'Cloned object should be equal to the original')
|
|
14
|
+
assert.notStrictEqual(cloned, obj, 'Not the same reference as the original')
|
|
19
15
|
})
|
|
20
16
|
|
|
21
17
|
test('Clones nested objects', () =>
|
|
22
18
|
{
|
|
23
|
-
const obj = { foo: { bar: { baz: 'qux' } } }
|
|
24
|
-
|
|
25
19
|
const
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
obj = { foo: { bar: { baz: 'qux' } } },
|
|
21
|
+
cloned = deepclone(obj)
|
|
22
|
+
|
|
23
|
+
assert.deepStrictEqual(cloned, obj, 'Cloned nested object should be equal to the original')
|
|
24
|
+
assert.notStrictEqual(cloned.foo, obj.foo, 'Not the same reference as the original')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('Preserves descriptors', () =>
|
|
28
|
+
{
|
|
29
|
+
const origin = {}
|
|
30
|
+
|
|
31
|
+
Object.defineProperty(origin, 'foo', { value: {}, enumerable: true, writable: true, configurable: true })
|
|
32
|
+
Object.defineProperty(origin, 'bar', { value: {}, enumerable: false, writable: true, configurable: true })
|
|
33
|
+
Object.defineProperty(origin, 'baz', { value: {}, enumerable: false, writable: false, configurable: true })
|
|
34
|
+
Object.defineProperty(origin, 'qux', { value: {}, enumerable: false, writable: false, configurable: false })
|
|
35
|
+
|
|
36
|
+
const cloned = deepclone(origin)
|
|
37
|
+
|
|
38
|
+
assert.deepStrictEqual(cloned, origin, 'Cloned nested object should be equal to the original')
|
|
39
|
+
assert.notStrictEqual(cloned, origin, 'Not the same reference as the original')
|
|
40
|
+
assert.notStrictEqual(cloned.foo, origin.foo, 'Cloned nested object should not share reference with the original')
|
|
28
41
|
|
|
29
|
-
|
|
30
|
-
|
|
42
|
+
const
|
|
43
|
+
clonedDescriptors = Object.getOwnPropertyDescriptors(cloned),
|
|
44
|
+
originDescriptors = Object.getOwnPropertyDescriptors(origin)
|
|
31
45
|
|
|
32
|
-
|
|
33
|
-
|
|
46
|
+
for(const key in originDescriptors)
|
|
47
|
+
{
|
|
48
|
+
assert.equal(clonedDescriptors[key].enumerable, originDescriptors[key].enumerable)
|
|
49
|
+
assert.equal(clonedDescriptors[key].writable, originDescriptors[key].writable)
|
|
50
|
+
assert.equal(clonedDescriptors[key].configurable, originDescriptors[key].configurable)
|
|
51
|
+
}
|
|
34
52
|
})
|
|
35
53
|
|
|
36
54
|
test('Clones arrays', () =>
|
|
37
55
|
{
|
|
38
|
-
const arr = [1, 2, 3, [4, 5]]
|
|
39
|
-
|
|
40
56
|
const
|
|
41
|
-
|
|
42
|
-
|
|
57
|
+
array = [1, 2, 3, [4, 5]],
|
|
58
|
+
cloned = deepclone(array)
|
|
43
59
|
|
|
44
|
-
assert.deepStrictEqual(
|
|
45
|
-
assert.
|
|
60
|
+
assert.deepStrictEqual(cloned, array, 'Cloned array should be equal to the original')
|
|
61
|
+
assert.notStrictEqual(cloned, array, 'Not the same reference as the original')
|
|
62
|
+
assert.notStrictEqual(cloned[3], array[3], 'Nested array in clone should not share reference with the original')
|
|
63
|
+
})
|
|
46
64
|
|
|
47
|
-
|
|
48
|
-
|
|
65
|
+
test('Handles circular references', () =>
|
|
66
|
+
{
|
|
67
|
+
const obj = {}
|
|
68
|
+
obj.self = obj
|
|
49
69
|
|
|
50
|
-
|
|
51
|
-
assert.
|
|
70
|
+
const cloned = deepclone(obj)
|
|
71
|
+
assert.strictEqual(cloned.self, obj, 'Circular references should be preserved in the clone')
|
|
52
72
|
})
|
|
53
73
|
|
|
54
|
-
|
|
55
|
-
{
|
|
56
|
-
test.skip('Handles circular references (structuredClone not available)')
|
|
57
|
-
test.skip('Clones objects with null prototype (structuredClone not available)')
|
|
58
|
-
}
|
|
59
|
-
else
|
|
74
|
+
test('Clones objects with null prototype', () =>
|
|
60
75
|
{
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
assert.strictEqual(result.self, result, 'Circular references should be preserved in the clone')
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
test('Clones objects with null prototype', () =>
|
|
72
|
-
{
|
|
73
|
-
const obj = Object.create(null)
|
|
74
|
-
obj.foo = 'bar'
|
|
75
|
-
|
|
76
|
-
const result = deepclone.clone(obj)
|
|
77
|
-
|
|
78
|
-
assert.deepEqual(result, obj, 'Cloned object with null prototype should be equal to the original')
|
|
79
|
-
assert.notStrictEqual(result, obj, 'Cloned object with null prototype should not share reference with the original')
|
|
80
|
-
})
|
|
81
|
-
}
|
|
76
|
+
const obj = Object.create(null)
|
|
77
|
+
obj.foo = 'bar'
|
|
78
|
+
|
|
79
|
+
const cloned = deepclone(obj)
|
|
80
|
+
assert.deepEqual(cloned, obj, 'Cloned object with null prototype should be equal to the original')
|
|
81
|
+
assert.notStrictEqual(cloned, obj, 'Not the same reference as the original')
|
|
82
|
+
})
|
|
82
83
|
})
|
package/freeze.js
CHANGED
|
@@ -1,33 +1,30 @@
|
|
|
1
|
-
export default
|
|
1
|
+
export default function freeze(obj)
|
|
2
2
|
{
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
this.#freeze(obj, seen)
|
|
7
|
-
}
|
|
3
|
+
const seen = new WeakSet
|
|
4
|
+
freezeIterator(obj, seen)
|
|
5
|
+
}
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
function freezeIterator(obj, seen)
|
|
8
|
+
{
|
|
9
|
+
const objType = Object.prototype.toString.call(obj)
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
if('[object Array]' === objType
|
|
12
|
+
|| '[object Object]' === objType)
|
|
13
|
+
{
|
|
14
|
+
if(seen.has(obj))
|
|
15
15
|
{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
seen.add(obj)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
for(const key of [...Object.getOwnPropertyNames(obj), ...Object.getOwnPropertySymbols(obj)])
|
|
26
|
-
{
|
|
27
|
-
this.#freeze(obj[key], seen)
|
|
28
|
-
}
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
else
|
|
19
|
+
{
|
|
20
|
+
seen.add(obj)
|
|
21
|
+
}
|
|
29
22
|
|
|
30
|
-
|
|
23
|
+
for(const key of [...Object.getOwnPropertyNames(obj), ...Object.getOwnPropertySymbols(obj)])
|
|
24
|
+
{
|
|
25
|
+
freezeIterator(obj[key], seen)
|
|
31
26
|
}
|
|
27
|
+
|
|
28
|
+
Object.freeze(obj)
|
|
32
29
|
}
|
|
33
30
|
}
|
package/freeze.test.js
CHANGED
|
@@ -8,7 +8,7 @@ suite('@superhero/deep/freeze', () =>
|
|
|
8
8
|
{
|
|
9
9
|
const obj = { foo: 'bar' }
|
|
10
10
|
|
|
11
|
-
deepfreeze
|
|
11
|
+
deepfreeze(obj)
|
|
12
12
|
|
|
13
13
|
assert.throws(() => { obj.foo = 'baz' }, TypeError, 'Should not allow modifying a frozen object')
|
|
14
14
|
assert.strictEqual(Object.isFrozen(obj), true, 'Object should be frozen')
|
|
@@ -18,7 +18,7 @@ suite('@superhero/deep/freeze', () =>
|
|
|
18
18
|
{
|
|
19
19
|
const obj = { foo: { bar: { baz: 'qux' } } }
|
|
20
20
|
|
|
21
|
-
deepfreeze
|
|
21
|
+
deepfreeze(obj)
|
|
22
22
|
|
|
23
23
|
assert.throws(() => { obj.foo.bar.baz = 'changed' }, TypeError, 'Should not allow modifying nested properties')
|
|
24
24
|
assert.strictEqual(Object.isFrozen(obj.foo.bar), true, 'Nested object should be frozen')
|
|
@@ -30,7 +30,7 @@ suite('@superhero/deep/freeze', () =>
|
|
|
30
30
|
const obj = {}
|
|
31
31
|
obj.self = obj
|
|
32
32
|
|
|
33
|
-
deepfreeze
|
|
33
|
+
deepfreeze(obj)
|
|
34
34
|
|
|
35
35
|
assert.strictEqual(Object.isFrozen(obj), true, 'Object with circular reference should be frozen')
|
|
36
36
|
assert.strictEqual(Object.isFrozen(obj.self), true, 'Circular reference should also be frozen')
|
|
@@ -41,7 +41,7 @@ suite('@superhero/deep/freeze', () =>
|
|
|
41
41
|
const sym = Symbol('test')
|
|
42
42
|
const obj = { [sym]: 'value' }
|
|
43
43
|
|
|
44
|
-
deepfreeze
|
|
44
|
+
deepfreeze(obj)
|
|
45
45
|
|
|
46
46
|
assert.throws(() => { obj[sym] = 'new value' }, TypeError, 'Should not allow modifying properties with symbols')
|
|
47
47
|
assert.strictEqual(Object.isFrozen(obj), true, 'Object with symbols should be frozen')
|
|
@@ -51,7 +51,7 @@ suite('@superhero/deep/freeze', () =>
|
|
|
51
51
|
{
|
|
52
52
|
const obj = Object.freeze({ foo: 'bar' })
|
|
53
53
|
|
|
54
|
-
deepfreeze
|
|
54
|
+
deepfreeze(obj) // Should not throw an error
|
|
55
55
|
assert.strictEqual(Object.isFrozen(obj), true, 'Already frozen object should remain frozen')
|
|
56
56
|
})
|
|
57
57
|
|
|
@@ -66,7 +66,7 @@ suite('@superhero/deep/freeze', () =>
|
|
|
66
66
|
writable : true
|
|
67
67
|
})
|
|
68
68
|
|
|
69
|
-
deepfreeze
|
|
69
|
+
deepfreeze(obj)
|
|
70
70
|
|
|
71
71
|
assert.throws(() => { obj.foo = 'baz' }, TypeError, 'Should not allow modifying non-enumerable properties')
|
|
72
72
|
assert.strictEqual(Object.isFrozen(obj), true, 'Object with non-enumerable properties should be frozen')
|
|
@@ -76,7 +76,7 @@ suite('@superhero/deep/freeze', () =>
|
|
|
76
76
|
{
|
|
77
77
|
const arr = [1, 2, 3]
|
|
78
78
|
|
|
79
|
-
deepfreeze
|
|
79
|
+
deepfreeze(arr)
|
|
80
80
|
|
|
81
81
|
assert.throws(() => { arr[0] = 4 }, TypeError, 'Should not allow modifying an array')
|
|
82
82
|
assert.strictEqual(Object.isFrozen(arr), true, 'Array should be frozen')
|
|
@@ -87,7 +87,7 @@ suite('@superhero/deep/freeze', () =>
|
|
|
87
87
|
const obj = Object.create(null)
|
|
88
88
|
obj.foo = 'bar'
|
|
89
89
|
|
|
90
|
-
deepfreeze
|
|
90
|
+
deepfreeze(obj)
|
|
91
91
|
|
|
92
92
|
assert.throws(() => { obj.foo = 'baz' }, TypeError, 'Should not allow modifying properties of objects with null prototype')
|
|
93
93
|
assert.strictEqual(Object.isFrozen(obj), true, 'Object with null prototype should be frozen')
|
|
@@ -112,7 +112,7 @@ suite('@superhero/deep/freeze', () =>
|
|
|
112
112
|
writable : true
|
|
113
113
|
})
|
|
114
114
|
|
|
115
|
-
deepfreeze
|
|
115
|
+
deepfreeze(obj)
|
|
116
116
|
|
|
117
117
|
assert.throws(() => { obj.nested.baz = 'changed' }, TypeError, 'Should not allow modifying nested properties')
|
|
118
118
|
assert.throws(() => { obj[sym] = 'new value' }, TypeError, 'Should not allow modifying symbol properties')
|
package/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import assign from '@superhero/deep/assign'
|
|
2
|
+
import clone from '@superhero/deep/clone'
|
|
3
|
+
import freeze from '@superhero/deep/freeze'
|
|
4
|
+
import merge from '@superhero/deep/merge'
|
|
5
|
+
|
|
6
|
+
export { assign, clone, freeze, merge }
|
|
7
|
+
export default { assign, clone, freeze, merge }
|
package/index.test.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import { suite, test } from 'node:test'
|
|
3
|
+
import deep from '@superhero/deep'
|
|
4
|
+
import { assign, clone, freeze, merge } from '@superhero/deep'
|
|
5
|
+
|
|
6
|
+
suite('@superhero/deep', () =>
|
|
7
|
+
{
|
|
8
|
+
test('All functions are accessible as a member to the default import object', () =>
|
|
9
|
+
{
|
|
10
|
+
assert.ok('function' === typeof deep.assign, 'Shold export the assign function')
|
|
11
|
+
assert.ok('function' === typeof deep.clone, 'Shold export the clone function')
|
|
12
|
+
assert.ok('function' === typeof deep.freeze, 'Shold export the freeze function')
|
|
13
|
+
assert.ok('function' === typeof deep.merge, 'Shold export the merge function')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('All functions are accessible to import from the default import object', () =>
|
|
17
|
+
{
|
|
18
|
+
assert.ok('function' === typeof assign, 'Shold export the assign function')
|
|
19
|
+
assert.ok('function' === typeof clone, 'Shold export the clone function')
|
|
20
|
+
assert.ok('function' === typeof freeze, 'Shold export the freeze function')
|
|
21
|
+
assert.ok('function' === typeof merge, 'Shold export the merge function')
|
|
22
|
+
})
|
|
23
|
+
})
|
package/merge.js
CHANGED
|
@@ -56,97 +56,94 @@
|
|
|
56
56
|
* @example if array "a" with values [1, 1, 2, 2] is merged with array "b" with
|
|
57
57
|
* values [2, 2, 3, 3], the new array will have values [1, 2, 3].
|
|
58
58
|
*/
|
|
59
|
-
export default
|
|
59
|
+
export default function merge(a, b, ...c)
|
|
60
60
|
{
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
const
|
|
62
|
+
seen = new WeakSet,
|
|
63
|
+
output = mergeAandB(a, b, seen)
|
|
64
|
+
|
|
65
|
+
return c.length
|
|
66
|
+
? merge(output, ...c)
|
|
67
|
+
: output
|
|
68
|
+
}
|
|
66
69
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
function mergeAandB(a, b, seen)
|
|
71
|
+
{
|
|
72
|
+
if(b === undefined)
|
|
73
|
+
{
|
|
74
|
+
return a
|
|
70
75
|
}
|
|
71
76
|
|
|
72
|
-
|
|
77
|
+
const
|
|
78
|
+
aType = Object.prototype.toString.call(a),
|
|
79
|
+
bType = Object.prototype.toString.call(b)
|
|
80
|
+
|
|
81
|
+
if('[object Array]' === aType
|
|
82
|
+
&& '[object Array]' === bType)
|
|
73
83
|
{
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return a
|
|
77
|
-
}
|
|
84
|
+
return mergeArray(a, b)
|
|
85
|
+
}
|
|
78
86
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
87
|
+
if('[object Object]' === aType
|
|
88
|
+
&& '[object Object]' === bType)
|
|
89
|
+
{
|
|
90
|
+
return mergeObject(a, b, seen)
|
|
91
|
+
}
|
|
82
92
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
{
|
|
86
|
-
return this.#mergeArray(a, b)
|
|
87
|
-
}
|
|
93
|
+
return b
|
|
94
|
+
}
|
|
88
95
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
96
|
+
function mergeArray(a, b)
|
|
97
|
+
{
|
|
98
|
+
return [...new Set(a.concat(b))]
|
|
99
|
+
}
|
|
94
100
|
|
|
101
|
+
function mergeObject(a, b, seen)
|
|
102
|
+
{
|
|
103
|
+
if(seen.has(a))
|
|
104
|
+
{
|
|
95
105
|
return b
|
|
96
106
|
}
|
|
97
107
|
|
|
98
|
-
|
|
99
|
-
{
|
|
100
|
-
return [...new Set(a.concat(b))]
|
|
101
|
-
}
|
|
108
|
+
seen.add(a)
|
|
102
109
|
|
|
103
|
-
|
|
110
|
+
const output = {}
|
|
111
|
+
|
|
112
|
+
for(const key of Object.getOwnPropertyNames(a))
|
|
104
113
|
{
|
|
105
|
-
if(
|
|
114
|
+
if(key in b)
|
|
106
115
|
{
|
|
107
|
-
|
|
116
|
+
continue
|
|
108
117
|
}
|
|
109
|
-
|
|
110
|
-
seen.add(a)
|
|
111
|
-
|
|
112
|
-
const output = {}
|
|
113
|
-
|
|
114
|
-
for(const key of Object.getOwnPropertyNames(a))
|
|
118
|
+
else
|
|
115
119
|
{
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
continue
|
|
119
|
-
}
|
|
120
|
-
else
|
|
121
|
-
{
|
|
122
|
-
const descriptor = Object.getOwnPropertyDescriptor(a, key)
|
|
123
|
-
Object.defineProperty(output, key, descriptor)
|
|
124
|
-
}
|
|
120
|
+
const descriptor = Object.getOwnPropertyDescriptor(a, key)
|
|
121
|
+
Object.defineProperty(output, key, descriptor)
|
|
125
122
|
}
|
|
123
|
+
}
|
|
126
124
|
|
|
127
|
-
|
|
125
|
+
for(const key of Object.getOwnPropertyNames(b))
|
|
126
|
+
{
|
|
127
|
+
if(key in a)
|
|
128
128
|
{
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
Object.defineProperty(output, key,
|
|
136
|
-
{
|
|
137
|
-
configurable : descriptor_a.configurable && descriptor_b.configurable,
|
|
138
|
-
enumerable : descriptor_a.enumerable && descriptor_b.enumerable,
|
|
139
|
-
writable : descriptor_a.writable && descriptor_b.writable,
|
|
140
|
-
value : this.#merge(a[key], b[key], seen)
|
|
141
|
-
})
|
|
142
|
-
}
|
|
143
|
-
else
|
|
129
|
+
const
|
|
130
|
+
descriptor_a = Object.getOwnPropertyDescriptor(a, key),
|
|
131
|
+
descriptor_b = Object.getOwnPropertyDescriptor(b, key)
|
|
132
|
+
|
|
133
|
+
Object.defineProperty(output, key,
|
|
144
134
|
{
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
135
|
+
configurable : descriptor_a.configurable && descriptor_b.configurable,
|
|
136
|
+
enumerable : descriptor_a.enumerable && descriptor_b.enumerable,
|
|
137
|
+
writable : descriptor_a.writable && descriptor_b.writable,
|
|
138
|
+
value : mergeAandB(a[key], b[key], seen)
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
else
|
|
142
|
+
{
|
|
143
|
+
const descriptor = Object.getOwnPropertyDescriptor(b, key)
|
|
144
|
+
Object.defineProperty(output, key, descriptor)
|
|
148
145
|
}
|
|
149
|
-
|
|
150
|
-
return output
|
|
151
146
|
}
|
|
147
|
+
|
|
148
|
+
return output
|
|
152
149
|
}
|
package/merge.test.js
CHANGED
|
@@ -11,7 +11,7 @@ suite('@superhero/deep/merge', () =>
|
|
|
11
11
|
b = [2, 3, 4],
|
|
12
12
|
expected = [1, 2, 3, 4]
|
|
13
13
|
|
|
14
|
-
const result = deepmerge
|
|
14
|
+
const result = deepmerge(a, b)
|
|
15
15
|
assert.deepStrictEqual(result, expected, 'Arrays should merge with unique values')
|
|
16
16
|
})
|
|
17
17
|
|
|
@@ -22,7 +22,7 @@ suite('@superhero/deep/merge', () =>
|
|
|
22
22
|
b = [1, 2, 3],
|
|
23
23
|
expected = [2, 3, 4, 1]
|
|
24
24
|
|
|
25
|
-
const result = deepmerge
|
|
25
|
+
const result = deepmerge(a, b)
|
|
26
26
|
assert.deepStrictEqual(result, expected, 'Order of values should be preserved')
|
|
27
27
|
})
|
|
28
28
|
|
|
@@ -33,7 +33,7 @@ suite('@superhero/deep/merge', () =>
|
|
|
33
33
|
b = [],
|
|
34
34
|
expected = [1, 2, 3]
|
|
35
35
|
|
|
36
|
-
const result = deepmerge
|
|
36
|
+
const result = deepmerge(a, b)
|
|
37
37
|
assert.deepStrictEqual(result, expected, 'Merging with empty array should not alter values')
|
|
38
38
|
})
|
|
39
39
|
|
|
@@ -44,7 +44,7 @@ suite('@superhero/deep/merge', () =>
|
|
|
44
44
|
b = [2, 2, 3, 3],
|
|
45
45
|
expected = [1, 2, 3]
|
|
46
46
|
|
|
47
|
-
const result = deepmerge
|
|
47
|
+
const result = deepmerge(a, b)
|
|
48
48
|
assert.deepStrictEqual(result, expected, 'Duplicate values should be removed')
|
|
49
49
|
})
|
|
50
50
|
|
|
@@ -71,7 +71,7 @@ suite('@superhero/deep/merge', () =>
|
|
|
71
71
|
})
|
|
72
72
|
|
|
73
73
|
const
|
|
74
|
-
result = deepmerge
|
|
74
|
+
result = deepmerge(a, b),
|
|
75
75
|
descriptor = Object.getOwnPropertyDescriptor(result, 'foo')
|
|
76
76
|
|
|
77
77
|
assert.strictEqual(descriptor.value, 2, 'Value should prioritize the second object')
|
|
@@ -103,7 +103,7 @@ suite('@superhero/deep/merge', () =>
|
|
|
103
103
|
})
|
|
104
104
|
|
|
105
105
|
const
|
|
106
|
-
result = deepmerge
|
|
106
|
+
result = deepmerge(a, b),
|
|
107
107
|
descriptor = Object.getOwnPropertyDescriptor(result, 'foo')
|
|
108
108
|
|
|
109
109
|
assert.strictEqual(descriptor.value, 2, 'Value should prioritize the second object')
|
|
@@ -119,7 +119,7 @@ suite('@superhero/deep/merge', () =>
|
|
|
119
119
|
b = { foo: { baz: 2 } },
|
|
120
120
|
expected = { foo: { bar: 1, baz: 2 } }
|
|
121
121
|
|
|
122
|
-
const result = deepmerge
|
|
122
|
+
const result = deepmerge(a, b)
|
|
123
123
|
assert.deepStrictEqual(result, expected, 'Nested objects should merge correctly')
|
|
124
124
|
})
|
|
125
125
|
|
|
@@ -132,7 +132,7 @@ suite('@superhero/deep/merge', () =>
|
|
|
132
132
|
a.self = a
|
|
133
133
|
b.self = b
|
|
134
134
|
|
|
135
|
-
const result = deepmerge
|
|
135
|
+
const result = deepmerge(a, b)
|
|
136
136
|
|
|
137
137
|
assert.strictEqual(result.self, b.self, 'Circular references should not merge further')
|
|
138
138
|
})
|
|
@@ -146,8 +146,8 @@ suite('@superhero/deep/merge', () =>
|
|
|
146
146
|
b.foo.bar = b
|
|
147
147
|
|
|
148
148
|
const
|
|
149
|
-
resultA = deepmerge
|
|
150
|
-
resultB = deepmerge
|
|
149
|
+
resultA = deepmerge(a, b),
|
|
150
|
+
resultB = deepmerge(b, a)
|
|
151
151
|
|
|
152
152
|
assert.strictEqual(resultA.foo.bar.foo.bar, b, 'Circular references should not interfare with the merged result')
|
|
153
153
|
assert.strictEqual(resultB.foo.bar.foo.bar, 'baz', 'Circular references should not interfare with the merged result')
|
|
@@ -160,7 +160,7 @@ suite('@superhero/deep/merge', () =>
|
|
|
160
160
|
b = { foo: 42 },
|
|
161
161
|
expected = { foo: 42 }
|
|
162
162
|
|
|
163
|
-
const result = deepmerge
|
|
163
|
+
const result = deepmerge(a, b)
|
|
164
164
|
assert.deepStrictEqual(result, expected, 'Non-object types should replace with the second value')
|
|
165
165
|
})
|
|
166
166
|
|
|
@@ -172,10 +172,10 @@ suite('@superhero/deep/merge', () =>
|
|
|
172
172
|
c = { baz: 3 },
|
|
173
173
|
expected = { foo: 1, bar: 2, baz: 3 }
|
|
174
174
|
|
|
175
|
-
const resultA = deepmerge
|
|
175
|
+
const resultA = deepmerge(a, b, c)
|
|
176
176
|
assert.deepStrictEqual(resultA, expected, 'Multiple objects should merge sequentially')
|
|
177
177
|
|
|
178
|
-
const resultB = deepmerge
|
|
178
|
+
const resultB = deepmerge(a, b, undefined, c)
|
|
179
179
|
assert.deepStrictEqual(resultB, expected, 'Ignore undefined attributes')
|
|
180
180
|
})
|
|
181
181
|
})
|