@pyreon/core 0.24.5 → 0.24.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/lib/analysis/index.js.html +1 -1
- package/lib/index.js +53 -31
- package/package.json +2 -6
- package/src/compat-marker.ts +0 -79
- package/src/compat-shared.ts +0 -80
- package/src/component.ts +0 -98
- package/src/context.ts +0 -349
- package/src/defer.ts +0 -279
- package/src/dynamic.ts +0 -32
- package/src/env.d.ts +0 -6
- package/src/error-boundary.ts +0 -90
- package/src/for.ts +0 -51
- package/src/h.ts +0 -80
- package/src/index.ts +0 -80
- package/src/jsx-dev-runtime.ts +0 -2
- package/src/jsx-runtime.ts +0 -747
- package/src/lazy.ts +0 -25
- package/src/lifecycle.ts +0 -152
- package/src/manifest.ts +0 -579
- package/src/map-array.ts +0 -42
- package/src/portal.ts +0 -39
- package/src/props.ts +0 -269
- package/src/ref.ts +0 -32
- package/src/show.ts +0 -121
- package/src/style.ts +0 -102
- package/src/suspense.ts +0 -52
- package/src/telemetry.ts +0 -120
- package/src/tests/compat-marker.test.ts +0 -96
- package/src/tests/compat-shared.test.ts +0 -99
- package/src/tests/component.test.ts +0 -281
- package/src/tests/context.test.ts +0 -629
- package/src/tests/core.test.ts +0 -1290
- package/src/tests/cx.test.ts +0 -70
- package/src/tests/defer.test.ts +0 -359
- package/src/tests/dynamic.test.ts +0 -87
- package/src/tests/error-boundary.test.ts +0 -181
- package/src/tests/extract-props-overloads.types.test.ts +0 -135
- package/src/tests/for.test.ts +0 -117
- package/src/tests/h.test.ts +0 -221
- package/src/tests/jsx-compat.test.tsx +0 -86
- package/src/tests/lazy.test.ts +0 -100
- package/src/tests/lifecycle.test.ts +0 -350
- package/src/tests/manifest-snapshot.test.ts +0 -100
- package/src/tests/map-array.test.ts +0 -313
- package/src/tests/native-marker-error-boundary.test.ts +0 -12
- package/src/tests/portal.test.ts +0 -48
- package/src/tests/props-extended.test.ts +0 -157
- package/src/tests/props.test.ts +0 -250
- package/src/tests/reactive-context.test.ts +0 -69
- package/src/tests/reactive-props.test.ts +0 -157
- package/src/tests/ref.test.ts +0 -70
- package/src/tests/show.test.ts +0 -314
- package/src/tests/style.test.ts +0 -157
- package/src/tests/suspense.test.ts +0 -139
- package/src/tests/telemetry.test.ts +0 -297
- package/src/types.ts +0 -116
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
import { mapArray } from '../map-array'
|
|
2
|
-
|
|
3
|
-
describe('mapArray', () => {
|
|
4
|
-
describe('basic mapping', () => {
|
|
5
|
-
test('maps all items on first call', () => {
|
|
6
|
-
const mapped = mapArray(
|
|
7
|
-
() => [1, 2, 3],
|
|
8
|
-
(item) => item,
|
|
9
|
-
(item) => item * 10,
|
|
10
|
-
)
|
|
11
|
-
expect(mapped()).toEqual([10, 20, 30])
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
test('returns empty array for empty source', () => {
|
|
15
|
-
const mapped = mapArray(
|
|
16
|
-
() => [],
|
|
17
|
-
(item: number) => item,
|
|
18
|
-
(item) => item * 10,
|
|
19
|
-
)
|
|
20
|
-
expect(mapped()).toEqual([])
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
test('maps single item', () => {
|
|
24
|
-
const mapped = mapArray(
|
|
25
|
-
() => [42],
|
|
26
|
-
(item) => item,
|
|
27
|
-
(item) => `value-${item}`,
|
|
28
|
-
)
|
|
29
|
-
expect(mapped()).toEqual(['value-42'])
|
|
30
|
-
})
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
describe('caching behavior', () => {
|
|
34
|
-
test('caches results — map function called once per key', () => {
|
|
35
|
-
let callCount = 0
|
|
36
|
-
const items = [1, 2, 3]
|
|
37
|
-
const mapped = mapArray(
|
|
38
|
-
() => items,
|
|
39
|
-
(item) => item,
|
|
40
|
-
(item) => {
|
|
41
|
-
callCount++
|
|
42
|
-
return item * 10
|
|
43
|
-
},
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
mapped()
|
|
47
|
-
expect(callCount).toBe(3)
|
|
48
|
-
|
|
49
|
-
// Second call — all cached
|
|
50
|
-
mapped()
|
|
51
|
-
expect(callCount).toBe(3)
|
|
52
|
-
|
|
53
|
-
// Third call — still cached
|
|
54
|
-
mapped()
|
|
55
|
-
expect(callCount).toBe(3)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
test('only maps new keys when items are added', () => {
|
|
59
|
-
let callCount = 0
|
|
60
|
-
let items = [1, 2, 3]
|
|
61
|
-
const mapped = mapArray(
|
|
62
|
-
() => items,
|
|
63
|
-
(item) => item,
|
|
64
|
-
(item) => {
|
|
65
|
-
callCount++
|
|
66
|
-
return item * 10
|
|
67
|
-
},
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
mapped()
|
|
71
|
-
expect(callCount).toBe(3)
|
|
72
|
-
|
|
73
|
-
items = [1, 2, 3, 4, 5]
|
|
74
|
-
mapped()
|
|
75
|
-
expect(callCount).toBe(5) // only 4 and 5 are new
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
test('does not re-map when items are removed', () => {
|
|
79
|
-
let callCount = 0
|
|
80
|
-
let items = [1, 2, 3, 4, 5]
|
|
81
|
-
const mapped = mapArray(
|
|
82
|
-
() => items,
|
|
83
|
-
(item) => item,
|
|
84
|
-
(item) => {
|
|
85
|
-
callCount++
|
|
86
|
-
return item * 10
|
|
87
|
-
},
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
mapped()
|
|
91
|
-
expect(callCount).toBe(5)
|
|
92
|
-
|
|
93
|
-
items = [1, 3, 5] // remove 2 and 4
|
|
94
|
-
const result = mapped()
|
|
95
|
-
expect(result).toEqual([10, 30, 50])
|
|
96
|
-
expect(callCount).toBe(5) // no new calls
|
|
97
|
-
})
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
describe('key eviction', () => {
|
|
101
|
-
test('evicted keys are re-mapped when they return', () => {
|
|
102
|
-
let callCount = 0
|
|
103
|
-
let items = [1, 2, 3]
|
|
104
|
-
const mapped = mapArray(
|
|
105
|
-
() => items,
|
|
106
|
-
(item) => item,
|
|
107
|
-
(item) => {
|
|
108
|
-
callCount++
|
|
109
|
-
return item * 10
|
|
110
|
-
},
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
mapped()
|
|
114
|
-
expect(callCount).toBe(3)
|
|
115
|
-
|
|
116
|
-
// Remove key 2
|
|
117
|
-
items = [1, 3]
|
|
118
|
-
mapped()
|
|
119
|
-
expect(callCount).toBe(3) // no new mapping
|
|
120
|
-
|
|
121
|
-
// Re-add key 2 — should re-map since it was evicted
|
|
122
|
-
items = [1, 2, 3]
|
|
123
|
-
mapped()
|
|
124
|
-
expect(callCount).toBe(4) // key 2 re-mapped
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
test('evicts all keys when source becomes empty', () => {
|
|
128
|
-
let callCount = 0
|
|
129
|
-
let items: number[] = [1, 2, 3]
|
|
130
|
-
const mapped = mapArray(
|
|
131
|
-
() => items,
|
|
132
|
-
(item) => item,
|
|
133
|
-
(item) => {
|
|
134
|
-
callCount++
|
|
135
|
-
return item * 10
|
|
136
|
-
},
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
mapped()
|
|
140
|
-
expect(callCount).toBe(3)
|
|
141
|
-
|
|
142
|
-
items = []
|
|
143
|
-
mapped()
|
|
144
|
-
expect(callCount).toBe(3)
|
|
145
|
-
|
|
146
|
-
// All keys were evicted — re-adding requires re-mapping
|
|
147
|
-
items = [1, 2, 3]
|
|
148
|
-
mapped()
|
|
149
|
-
expect(callCount).toBe(6)
|
|
150
|
-
})
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
describe('reordering', () => {
|
|
154
|
-
test('reordered items use cached values (no re-mapping)', () => {
|
|
155
|
-
let callCount = 0
|
|
156
|
-
let items = [1, 2, 3]
|
|
157
|
-
const mapped = mapArray(
|
|
158
|
-
() => items,
|
|
159
|
-
(item) => item,
|
|
160
|
-
(item) => {
|
|
161
|
-
callCount++
|
|
162
|
-
return item * 10
|
|
163
|
-
},
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
mapped()
|
|
167
|
-
expect(callCount).toBe(3)
|
|
168
|
-
|
|
169
|
-
items = [3, 1, 2]
|
|
170
|
-
const result = mapped()
|
|
171
|
-
expect(result).toEqual([30, 10, 20])
|
|
172
|
-
expect(callCount).toBe(3) // no new calls
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
test('reverse order uses cached values', () => {
|
|
176
|
-
let callCount = 0
|
|
177
|
-
let items = [1, 2, 3, 4]
|
|
178
|
-
const mapped = mapArray(
|
|
179
|
-
() => items,
|
|
180
|
-
(item) => item,
|
|
181
|
-
(item) => {
|
|
182
|
-
callCount++
|
|
183
|
-
return `item-${item}`
|
|
184
|
-
},
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
mapped()
|
|
188
|
-
items = [4, 3, 2, 1]
|
|
189
|
-
const result = mapped()
|
|
190
|
-
expect(result).toEqual(['item-4', 'item-3', 'item-2', 'item-1'])
|
|
191
|
-
expect(callCount).toBe(4) // initial 4 only
|
|
192
|
-
})
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
describe('string keys', () => {
|
|
196
|
-
test('works with string keys from objects', () => {
|
|
197
|
-
interface User {
|
|
198
|
-
id: string
|
|
199
|
-
name: string
|
|
200
|
-
}
|
|
201
|
-
let callCount = 0
|
|
202
|
-
let users: User[] = [
|
|
203
|
-
{ id: 'a', name: 'Alice' },
|
|
204
|
-
{ id: 'b', name: 'Bob' },
|
|
205
|
-
]
|
|
206
|
-
const mapped = mapArray(
|
|
207
|
-
() => users,
|
|
208
|
-
(u) => u.id,
|
|
209
|
-
(u) => {
|
|
210
|
-
callCount++
|
|
211
|
-
return u.name.toUpperCase()
|
|
212
|
-
},
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
expect(mapped()).toEqual(['ALICE', 'BOB'])
|
|
216
|
-
expect(callCount).toBe(2)
|
|
217
|
-
|
|
218
|
-
// Add new user
|
|
219
|
-
users = [
|
|
220
|
-
{ id: 'a', name: 'Alice' },
|
|
221
|
-
{ id: 'b', name: 'Bob' },
|
|
222
|
-
{ id: 'c', name: 'Charlie' },
|
|
223
|
-
]
|
|
224
|
-
expect(mapped()).toEqual(['ALICE', 'BOB', 'CHARLIE'])
|
|
225
|
-
expect(callCount).toBe(3)
|
|
226
|
-
})
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
describe('mixed additions and removals', () => {
|
|
230
|
-
test('simultaneous add and remove', () => {
|
|
231
|
-
let callCount = 0
|
|
232
|
-
let items = [1, 2, 3]
|
|
233
|
-
const mapped = mapArray(
|
|
234
|
-
() => items,
|
|
235
|
-
(item) => item,
|
|
236
|
-
(item) => {
|
|
237
|
-
callCount++
|
|
238
|
-
return item * 10
|
|
239
|
-
},
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
mapped()
|
|
243
|
-
expect(callCount).toBe(3)
|
|
244
|
-
|
|
245
|
-
// Remove 2, add 4
|
|
246
|
-
items = [1, 3, 4]
|
|
247
|
-
const result = mapped()
|
|
248
|
-
expect(result).toEqual([10, 30, 40])
|
|
249
|
-
expect(callCount).toBe(4) // only key 4 is new
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
test('complete replacement of all items', () => {
|
|
253
|
-
let callCount = 0
|
|
254
|
-
let items = [1, 2, 3]
|
|
255
|
-
const mapped = mapArray(
|
|
256
|
-
() => items,
|
|
257
|
-
(item) => item,
|
|
258
|
-
(item) => {
|
|
259
|
-
callCount++
|
|
260
|
-
return item * 10
|
|
261
|
-
},
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
mapped()
|
|
265
|
-
expect(callCount).toBe(3)
|
|
266
|
-
|
|
267
|
-
items = [4, 5, 6]
|
|
268
|
-
const result = mapped()
|
|
269
|
-
expect(result).toEqual([40, 50, 60])
|
|
270
|
-
expect(callCount).toBe(6) // all new
|
|
271
|
-
})
|
|
272
|
-
})
|
|
273
|
-
|
|
274
|
-
describe('duplicate keys', () => {
|
|
275
|
-
test('duplicate keys in source share the same cached value', () => {
|
|
276
|
-
let callCount = 0
|
|
277
|
-
const mapped = mapArray(
|
|
278
|
-
() => [1, 1, 2],
|
|
279
|
-
(item) => item,
|
|
280
|
-
(item) => {
|
|
281
|
-
callCount++
|
|
282
|
-
return item * 10
|
|
283
|
-
},
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
const result = mapped()
|
|
287
|
-
// Key 1 mapped once, key 2 mapped once
|
|
288
|
-
expect(callCount).toBe(2)
|
|
289
|
-
// Both occurrences of key 1 get the same cached value
|
|
290
|
-
expect(result).toEqual([10, 10, 20])
|
|
291
|
-
})
|
|
292
|
-
})
|
|
293
|
-
|
|
294
|
-
describe('map function receives correct item', () => {
|
|
295
|
-
test('map receives the item, not the key', () => {
|
|
296
|
-
const received: Array<{ id: number; val: string }> = []
|
|
297
|
-
const items = [
|
|
298
|
-
{ id: 1, val: 'a' },
|
|
299
|
-
{ id: 2, val: 'b' },
|
|
300
|
-
]
|
|
301
|
-
const mapped = mapArray(
|
|
302
|
-
() => items,
|
|
303
|
-
(item) => item.id,
|
|
304
|
-
(item) => {
|
|
305
|
-
received.push(item)
|
|
306
|
-
return item.val
|
|
307
|
-
},
|
|
308
|
-
)
|
|
309
|
-
mapped()
|
|
310
|
-
expect(received).toEqual(items)
|
|
311
|
-
})
|
|
312
|
-
})
|
|
313
|
-
})
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { isNativeCompat } from '../compat-marker'
|
|
3
|
-
import { ErrorBoundary } from '../error-boundary'
|
|
4
|
-
|
|
5
|
-
// Marker-presence assertion (PR 3 lock-in). Bisect-verified: removing the
|
|
6
|
-
// `nativeCompat(ErrorBoundary)` call fails this test with
|
|
7
|
-
// `expected false to be true`.
|
|
8
|
-
describe('native-compat marker — @pyreon/core', () => {
|
|
9
|
-
it('ErrorBoundary is marked native', () => {
|
|
10
|
-
expect(isNativeCompat(ErrorBoundary)).toBe(true)
|
|
11
|
-
})
|
|
12
|
-
})
|
package/src/tests/portal.test.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { h } from '../h'
|
|
2
|
-
import { Portal, PortalSymbol } from '../portal'
|
|
3
|
-
import type { VNode } from '../types'
|
|
4
|
-
|
|
5
|
-
describe('Portal', () => {
|
|
6
|
-
test('returns VNode with PortalSymbol type', () => {
|
|
7
|
-
const fakeTarget = {} as Element
|
|
8
|
-
const node = Portal({ target: fakeTarget, children: h('div', null) })
|
|
9
|
-
expect(node.type).toBe(PortalSymbol)
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
test('VNode has null key', () => {
|
|
13
|
-
const node = Portal({ target: {} as Element, children: 'content' })
|
|
14
|
-
expect(node.key).toBeNull()
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
test('VNode has empty children array', () => {
|
|
18
|
-
const node = Portal({ target: {} as Element, children: 'content' })
|
|
19
|
-
expect(node.children).toEqual([])
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
test('props contain target and children', () => {
|
|
23
|
-
const fakeTarget = {} as Element
|
|
24
|
-
const child = h('span', null, 'content')
|
|
25
|
-
const node = Portal({ target: fakeTarget, children: child })
|
|
26
|
-
const props = node.props as unknown as { target: Element; children: VNode }
|
|
27
|
-
expect(props.target).toBe(fakeTarget)
|
|
28
|
-
expect(props.children).toBe(child)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
test('PortalSymbol is a unique symbol', () => {
|
|
32
|
-
expect(typeof PortalSymbol).toBe('symbol')
|
|
33
|
-
expect(PortalSymbol.toString()).toContain('pyreon.Portal')
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
test('string children are stored in props', () => {
|
|
37
|
-
const node = Portal({ target: {} as Element, children: 'text content' })
|
|
38
|
-
const props = node.props as unknown as { children: string }
|
|
39
|
-
expect(props.children).toBe('text content')
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
test('multiple VNode children via fragment', () => {
|
|
43
|
-
const children = h('div', null, h('span', null, 'a'), h('span', null, 'b'))
|
|
44
|
-
const node = Portal({ target: {} as Element, children })
|
|
45
|
-
const props = node.props as unknown as { children: VNode }
|
|
46
|
-
expect((props.children as VNode).type).toBe('div')
|
|
47
|
-
})
|
|
48
|
-
})
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { _resetIdCounter, createUniqueId, mergeProps, splitProps } from '../props'
|
|
2
|
-
|
|
3
|
-
describe('createUniqueId — extended', () => {
|
|
4
|
-
test('returns pyreon- prefixed string', () => {
|
|
5
|
-
const id = createUniqueId()
|
|
6
|
-
expect(id).toMatch(/^pyreon-\d+$/)
|
|
7
|
-
})
|
|
8
|
-
|
|
9
|
-
test('returns incrementing values', () => {
|
|
10
|
-
const id1 = createUniqueId()
|
|
11
|
-
const id2 = createUniqueId()
|
|
12
|
-
const id3 = createUniqueId()
|
|
13
|
-
const num1 = Number.parseInt(id1.replace('pyreon-', ''), 10)
|
|
14
|
-
const num2 = Number.parseInt(id2.replace('pyreon-', ''), 10)
|
|
15
|
-
const num3 = Number.parseInt(id3.replace('pyreon-', ''), 10)
|
|
16
|
-
expect(num2).toBe(num1 + 1)
|
|
17
|
-
expect(num3).toBe(num2 + 1)
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
test('all IDs are unique', () => {
|
|
21
|
-
const ids = new Set<string>()
|
|
22
|
-
for (let i = 0; i < 100; i++) {
|
|
23
|
-
ids.add(createUniqueId())
|
|
24
|
-
}
|
|
25
|
-
expect(ids.size).toBe(100)
|
|
26
|
-
})
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
describe('_resetIdCounter', () => {
|
|
30
|
-
test('resets the counter so IDs restart', () => {
|
|
31
|
-
// Generate some IDs to advance counter
|
|
32
|
-
createUniqueId()
|
|
33
|
-
createUniqueId()
|
|
34
|
-
|
|
35
|
-
_resetIdCounter()
|
|
36
|
-
|
|
37
|
-
const id = createUniqueId()
|
|
38
|
-
expect(id).toBe('pyreon-1')
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
test('subsequent calls after reset increment from 1', () => {
|
|
42
|
-
_resetIdCounter()
|
|
43
|
-
expect(createUniqueId()).toBe('pyreon-1')
|
|
44
|
-
expect(createUniqueId()).toBe('pyreon-2')
|
|
45
|
-
expect(createUniqueId()).toBe('pyreon-3')
|
|
46
|
-
})
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
describe('splitProps — extended', () => {
|
|
50
|
-
test('non-existent keys produce empty picked object', () => {
|
|
51
|
-
const props = { a: 1, b: 2 }
|
|
52
|
-
const [own, rest] = splitProps(props, ['c' as keyof typeof props])
|
|
53
|
-
expect(Object.keys(own)).toEqual([])
|
|
54
|
-
expect(rest).toEqual({ a: 1, b: 2 })
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
test('all keys in picked leaves rest empty', () => {
|
|
58
|
-
const props = { x: 10, y: 20 }
|
|
59
|
-
const [own, rest] = splitProps(props, ['x', 'y'])
|
|
60
|
-
expect(own).toEqual({ x: 10, y: 20 })
|
|
61
|
-
expect(Object.keys(rest)).toEqual([])
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
test('preserves getter on rest side', () => {
|
|
65
|
-
let count = 0
|
|
66
|
-
const props = {} as Record<string, unknown>
|
|
67
|
-
Object.defineProperty(props, 'reactive', {
|
|
68
|
-
get: () => ++count,
|
|
69
|
-
enumerable: true,
|
|
70
|
-
configurable: true,
|
|
71
|
-
})
|
|
72
|
-
Object.defineProperty(props, 'other', {
|
|
73
|
-
value: 'static',
|
|
74
|
-
enumerable: true,
|
|
75
|
-
configurable: true,
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
const [_own, rest] = splitProps(props, ['other'])
|
|
79
|
-
expect((rest as Record<string, unknown>).reactive).toBe(1)
|
|
80
|
-
expect((rest as Record<string, unknown>).reactive).toBe(2) // getter called again
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
test('handles object with undefined values', () => {
|
|
84
|
-
const props = { a: undefined, b: 'defined' }
|
|
85
|
-
const [own, rest] = splitProps(props, ['a'])
|
|
86
|
-
expect(own.a).toBeUndefined()
|
|
87
|
-
expect((rest as Record<string, unknown>).b).toBe('defined')
|
|
88
|
-
})
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
describe('mergeProps — extended', () => {
|
|
92
|
-
test('single source returns copy', () => {
|
|
93
|
-
const src = { a: 1, b: 2 }
|
|
94
|
-
const result = mergeProps(src)
|
|
95
|
-
expect(result).toEqual({ a: 1, b: 2 })
|
|
96
|
-
expect(result).not.toBe(src) // should be a new object
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
test('three sources merge correctly', () => {
|
|
100
|
-
const result = mergeProps({ a: 1 }, { b: 2 }, { c: 3 })
|
|
101
|
-
expect(result).toEqual({ a: 1, b: 2, c: 3 })
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
test('later defined value overrides earlier', () => {
|
|
105
|
-
const result = mergeProps({ x: 'first' }, { x: 'second' }, { x: 'third' })
|
|
106
|
-
expect(result.x).toBe('third')
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
test('undefined in later source does not override earlier defined value', () => {
|
|
110
|
-
const result = mergeProps({ x: 'keep' }, { x: undefined as string | undefined })
|
|
111
|
-
expect(result.x).toBe('keep')
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
test('getter merging: later getter overrides earlier static when defined', () => {
|
|
115
|
-
let dynamic: string | undefined = 'from-getter'
|
|
116
|
-
const getterSrc = {} as Record<string, unknown>
|
|
117
|
-
Object.defineProperty(getterSrc, 'val', {
|
|
118
|
-
get: () => dynamic,
|
|
119
|
-
enumerable: true,
|
|
120
|
-
configurable: true,
|
|
121
|
-
})
|
|
122
|
-
const result = mergeProps({ val: 'static' }, getterSrc)
|
|
123
|
-
expect(result.val).toBe('from-getter')
|
|
124
|
-
|
|
125
|
-
// When getter returns undefined, falls back to static
|
|
126
|
-
dynamic = undefined
|
|
127
|
-
expect(result.val).toBe('static')
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
test('two getters: later getter wins when defined, falls to earlier getter', () => {
|
|
131
|
-
let g1val: string | undefined = 'g1'
|
|
132
|
-
let g2val: string | undefined = 'g2'
|
|
133
|
-
|
|
134
|
-
const src1 = {} as Record<string, unknown>
|
|
135
|
-
Object.defineProperty(src1, 'x', {
|
|
136
|
-
get: () => g1val,
|
|
137
|
-
enumerable: true,
|
|
138
|
-
configurable: true,
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
const src2 = {} as Record<string, unknown>
|
|
142
|
-
Object.defineProperty(src2, 'x', {
|
|
143
|
-
get: () => g2val,
|
|
144
|
-
enumerable: true,
|
|
145
|
-
configurable: true,
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
const result = mergeProps(src1, src2)
|
|
149
|
-
expect(result.x).toBe('g2')
|
|
150
|
-
|
|
151
|
-
g2val = undefined
|
|
152
|
-
expect(result.x).toBe('g1')
|
|
153
|
-
|
|
154
|
-
g1val = undefined
|
|
155
|
-
expect(result.x).toBeUndefined()
|
|
156
|
-
})
|
|
157
|
-
})
|