@pyreon/core 0.24.4 → 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.
Files changed (56) hide show
  1. package/lib/analysis/index.js.html +1 -1
  2. package/lib/index.js +53 -31
  3. package/package.json +2 -6
  4. package/src/compat-marker.ts +0 -79
  5. package/src/compat-shared.ts +0 -80
  6. package/src/component.ts +0 -98
  7. package/src/context.ts +0 -349
  8. package/src/defer.ts +0 -279
  9. package/src/dynamic.ts +0 -32
  10. package/src/env.d.ts +0 -6
  11. package/src/error-boundary.ts +0 -90
  12. package/src/for.ts +0 -51
  13. package/src/h.ts +0 -80
  14. package/src/index.ts +0 -80
  15. package/src/jsx-dev-runtime.ts +0 -2
  16. package/src/jsx-runtime.ts +0 -747
  17. package/src/lazy.ts +0 -25
  18. package/src/lifecycle.ts +0 -152
  19. package/src/manifest.ts +0 -579
  20. package/src/map-array.ts +0 -42
  21. package/src/portal.ts +0 -39
  22. package/src/props.ts +0 -269
  23. package/src/ref.ts +0 -32
  24. package/src/show.ts +0 -121
  25. package/src/style.ts +0 -102
  26. package/src/suspense.ts +0 -52
  27. package/src/telemetry.ts +0 -120
  28. package/src/tests/compat-marker.test.ts +0 -96
  29. package/src/tests/compat-shared.test.ts +0 -99
  30. package/src/tests/component.test.ts +0 -281
  31. package/src/tests/context.test.ts +0 -629
  32. package/src/tests/core.test.ts +0 -1290
  33. package/src/tests/cx.test.ts +0 -70
  34. package/src/tests/defer.test.ts +0 -359
  35. package/src/tests/dynamic.test.ts +0 -87
  36. package/src/tests/error-boundary.test.ts +0 -181
  37. package/src/tests/extract-props-overloads.types.test.ts +0 -135
  38. package/src/tests/for.test.ts +0 -117
  39. package/src/tests/h.test.ts +0 -221
  40. package/src/tests/jsx-compat.test.tsx +0 -86
  41. package/src/tests/lazy.test.ts +0 -100
  42. package/src/tests/lifecycle.test.ts +0 -350
  43. package/src/tests/manifest-snapshot.test.ts +0 -100
  44. package/src/tests/map-array.test.ts +0 -313
  45. package/src/tests/native-marker-error-boundary.test.ts +0 -12
  46. package/src/tests/portal.test.ts +0 -48
  47. package/src/tests/props-extended.test.ts +0 -157
  48. package/src/tests/props.test.ts +0 -250
  49. package/src/tests/reactive-context.test.ts +0 -69
  50. package/src/tests/reactive-props.test.ts +0 -157
  51. package/src/tests/ref.test.ts +0 -70
  52. package/src/tests/show.test.ts +0 -314
  53. package/src/tests/style.test.ts +0 -157
  54. package/src/tests/suspense.test.ts +0 -139
  55. package/src/tests/telemetry.test.ts +0 -297
  56. package/src/types.ts +0 -116
@@ -1,1290 +0,0 @@
1
- import {
2
- createContext,
3
- createRef,
4
- Dynamic,
5
- defineComponent,
6
- dispatchToErrorBoundary,
7
- ErrorBoundary,
8
- For,
9
- ForSymbol,
10
- Fragment,
11
- h,
12
- lazy,
13
- Match,
14
- MatchSymbol,
15
- mapArray,
16
- onErrorCaptured,
17
- onMount,
18
- onUnmount,
19
- onUpdate,
20
- Portal,
21
- PortalSymbol,
22
- popContext,
23
- propagateError,
24
- provide,
25
- pushContext,
26
- registerErrorHandler,
27
- reportError,
28
- runWithHooks,
29
- Show,
30
- Suspense,
31
- Switch,
32
- useContext,
33
- withContext,
34
- } from '../index'
35
- import { jsxDEV } from '../jsx-dev-runtime'
36
- import { Fragment as JsxFragment, jsx, jsxs } from '../jsx-runtime'
37
- import type { ComponentFn, Props, VNode, VNodeChild } from '../types'
38
-
39
- // ─── h() ─────────────────────────────────────────────────────────────────────
40
-
41
- describe('h()', () => {
42
- test('creates a VNode with string type', () => {
43
- const node = h('div', null)
44
- expect(node.type).toBe('div')
45
- expect(node.props).toEqual({})
46
- expect(node.children).toEqual([])
47
- expect(node.key).toBeNull()
48
- })
49
-
50
- test('passes props through', () => {
51
- const node = h('div', { class: 'foo', id: 'bar' })
52
- expect(node.props).toEqual({ class: 'foo', id: 'bar' })
53
- })
54
-
55
- test('extracts key from props', () => {
56
- const node = h('li', { key: 'item-1' })
57
- expect(node.key).toBe('item-1')
58
- })
59
-
60
- test('numeric key', () => {
61
- const node = h('li', { key: 42 })
62
- expect(node.key).toBe(42)
63
- })
64
-
65
- test('null props becomes empty object', () => {
66
- const node = h('span', null)
67
- expect(node.props).toEqual({})
68
- })
69
-
70
- test('children are stored in vnode.children', () => {
71
- const node = h('div', null, 'hello', 'world')
72
- expect(node.children).toEqual(['hello', 'world'])
73
- })
74
-
75
- test('nested array children are flattened', () => {
76
- const node = h('ul', null, [h('li', null, 'a'), h('li', null, 'b')])
77
- expect(node.children).toHaveLength(2)
78
- expect((node.children[0] as VNode).type).toBe('li')
79
- expect((node.children[1] as VNode).type).toBe('li')
80
- })
81
-
82
- test('deeply nested arrays are flattened', () => {
83
- const node = h('div', null, [[['deep']]] as unknown as VNodeChild)
84
- expect(node.children).toEqual(['deep'])
85
- })
86
-
87
- test('handles boolean/null/undefined children', () => {
88
- const node = h('div', null, true, false, null, undefined, 'text')
89
- expect(node.children).toEqual([true, false, null, undefined, 'text'])
90
- })
91
-
92
- test('handles component function type', () => {
93
- const Comp = ((props: { name: string }) => h('span', null, props.name)) as ComponentFn<{
94
- name: string
95
- }>
96
- const node = h(Comp, { name: 'test' })
97
- expect(node.type as unknown).toBe(Comp)
98
- expect(node.props).toEqual({ name: 'test' })
99
- })
100
-
101
- test('handles symbol type (Fragment)', () => {
102
- const node = h(Fragment, null, 'a', 'b')
103
- expect(node.type).toBe(Fragment)
104
- expect(node.children).toEqual(['a', 'b'])
105
- })
106
-
107
- test('function children are preserved (reactive getters)', () => {
108
- const getter = () => 'dynamic'
109
- const node = h('div', null, getter)
110
- expect(node.children).toHaveLength(1)
111
- expect(typeof node.children[0]).toBe('function')
112
- expect((node.children[0] as () => string)()).toBe('dynamic')
113
- })
114
-
115
- test('VNode children are preserved', () => {
116
- const child = h('span', null, 'inner')
117
- const parent = h('div', null, child)
118
- expect(parent.children).toHaveLength(1)
119
- expect((parent.children[0] as VNode).type).toBe('span')
120
- })
121
- })
122
-
123
- // ─── Fragment ────────────────────────────────────────────────────────────────
124
-
125
- describe('Fragment', () => {
126
- test('is a symbol', () => {
127
- expect(typeof Fragment).toBe('symbol')
128
- })
129
-
130
- test('Fragment VNode wraps children without a DOM element', () => {
131
- const node = h(Fragment, null, h('span', null, 'a'), h('span', null, 'b'))
132
- expect(node.type).toBe(Fragment)
133
- expect(node.children).toHaveLength(2)
134
- })
135
- })
136
-
137
- // ─── defineComponent ────────────────────────────────────────────────────────
138
-
139
- describe('defineComponent()', () => {
140
- test('returns the same function', () => {
141
- const fn: ComponentFn = () => h('div', null)
142
- const defined = defineComponent(fn)
143
- expect(defined).toBe(fn)
144
- })
145
-
146
- test('preserves typed props', () => {
147
- const Comp = defineComponent<{ count: number }>((props) => {
148
- return h('span', null, String(props.count))
149
- })
150
- const node = Comp({ count: 5 })
151
- expect(node).not.toBeNull()
152
- expect((node as VNode).type).toBe('span')
153
- })
154
- })
155
-
156
- // ─── runWithHooks / lifecycle ────────────────────────────────────────────────
157
-
158
- describe('runWithHooks()', () => {
159
- test('captures lifecycle hooks registered during component execution', () => {
160
- const mountFn = () => undefined
161
- const unmountFn = () => {}
162
- const updateFn = () => {}
163
- const errorFn = () => true
164
-
165
- const Comp: ComponentFn = () => {
166
- onMount(mountFn)
167
- onUnmount(unmountFn)
168
- onUpdate(updateFn)
169
- onErrorCaptured(errorFn)
170
- return h('div', null)
171
- }
172
-
173
- const { vnode, hooks } = runWithHooks(Comp, {})
174
- expect(vnode).not.toBeNull()
175
- expect(hooks.mount!).toHaveLength(1)
176
- expect(hooks.mount![0]).toBe(mountFn)
177
- expect(hooks.unmount!).toHaveLength(1)
178
- expect(hooks.unmount![0]).toBe(unmountFn)
179
- expect(hooks.update!).toHaveLength(1)
180
- expect(hooks.update![0]).toBe(updateFn)
181
- expect(hooks.error!).toHaveLength(1)
182
- expect(hooks.error![0]).toBe(errorFn)
183
- })
184
-
185
- test('returns null vnode when component returns null', () => {
186
- const Comp: ComponentFn = () => null
187
- const { vnode } = runWithHooks(Comp, {})
188
- expect(vnode).toBeNull()
189
- })
190
-
191
- test('clears hooks context after execution (hooks outside component are no-ops)', () => {
192
- const Comp: ComponentFn = () => h('div', null)
193
- runWithHooks(Comp, {})
194
-
195
- // Calling lifecycle hooks outside a component should not throw
196
- onMount(() => undefined)
197
- onUnmount(() => {})
198
- onUpdate(() => {})
199
- onErrorCaptured(() => true)
200
- })
201
-
202
- test('multiple hooks of the same type are all captured', () => {
203
- const Comp: ComponentFn = () => {
204
- onMount(() => undefined)
205
- onMount(() => undefined)
206
- onMount(() => undefined)
207
- return h('div', null)
208
- }
209
-
210
- const { hooks } = runWithHooks(Comp, {})
211
- expect(hooks.mount!).toHaveLength(3)
212
- })
213
-
214
- test('passes props to component function', () => {
215
- let received: unknown = null
216
- const Comp: ComponentFn<{ msg: string }> = (props) => {
217
- received = props
218
- return null
219
- }
220
- runWithHooks(Comp, { msg: 'hello' })
221
- expect(received).toEqual({ msg: 'hello' })
222
- })
223
- })
224
-
225
- // ─── propagateError ──────────────────────────────────────────────────────────
226
-
227
- describe('propagateError()', () => {
228
- test('returns true when handler marks error as handled', () => {
229
- const hooks = {
230
- mount: [],
231
- unmount: [],
232
- update: [],
233
- error: [(_err: unknown) => true as boolean | undefined],
234
- }
235
- expect(propagateError(new Error('test'), hooks)).toBe(true)
236
- })
237
-
238
- test('returns false when no handlers', () => {
239
- const hooks = {
240
- mount: [],
241
- unmount: [],
242
- update: [],
243
- error: [] as ((err: unknown) => boolean | undefined)[],
244
- }
245
- expect(propagateError(new Error('test'), hooks)).toBe(false)
246
- })
247
-
248
- test('returns false when handler does not return true', () => {
249
- const hooks = {
250
- mount: [],
251
- unmount: [],
252
- update: [],
253
- error: [(_err: unknown) => undefined as boolean | undefined],
254
- }
255
- expect(propagateError(new Error('test'), hooks)).toBe(false)
256
- })
257
-
258
- test('stops at first handler that returns true', () => {
259
- let secondCalled = false
260
- const hooks = {
261
- mount: [],
262
- unmount: [],
263
- update: [],
264
- error: [
265
- (_err: unknown) => true as boolean | undefined,
266
- (_err: unknown) => {
267
- secondCalled = true
268
- return true as boolean | undefined
269
- },
270
- ],
271
- }
272
- propagateError(new Error('test'), hooks)
273
- expect(secondCalled).toBe(false)
274
- })
275
- })
276
-
277
- // ─── Context ─────────────────────────────────────────────────────────────────
278
-
279
- describe('createContext / useContext', () => {
280
- test('createContext returns context with default value', () => {
281
- const ctx = createContext(42)
282
- expect(ctx.defaultValue).toBe(42)
283
- expect(typeof ctx.id).toBe('symbol')
284
- })
285
-
286
- test('useContext returns default when no provider', () => {
287
- const ctx = createContext('default-value')
288
- expect(useContext(ctx)).toBe('default-value')
289
- })
290
-
291
- test('withContext provides value during callback', () => {
292
- const ctx = createContext(0)
293
- let captured = -1
294
- withContext(ctx, 99, () => {
295
- captured = useContext(ctx)
296
- })
297
- expect(captured).toBe(99)
298
- })
299
-
300
- test('withContext restores stack after callback (even on throw)', () => {
301
- const ctx = createContext('original')
302
- try {
303
- withContext(ctx, 'override', () => {
304
- throw new Error('boom')
305
- })
306
- } catch {}
307
- expect(useContext(ctx)).toBe('original')
308
- })
309
-
310
- test('nested contexts override outer', () => {
311
- const ctx = createContext(0)
312
- withContext(ctx, 1, () => {
313
- expect(useContext(ctx)).toBe(1)
314
- withContext(ctx, 2, () => {
315
- expect(useContext(ctx)).toBe(2)
316
- })
317
- expect(useContext(ctx)).toBe(1)
318
- })
319
- })
320
-
321
- test('multiple contexts are independent', () => {
322
- const ctxA = createContext('a')
323
- const ctxB = createContext('b')
324
- withContext(ctxA, 'A', () => {
325
- withContext(ctxB, 'B', () => {
326
- expect(useContext(ctxA)).toBe('A')
327
- expect(useContext(ctxB)).toBe('B')
328
- })
329
- })
330
- })
331
-
332
- test('pushContext / popContext work directly', () => {
333
- const ctx = createContext('default')
334
- const frame = new Map<symbol, unknown>([[ctx.id, 'pushed']])
335
- pushContext(frame)
336
- expect(useContext(ctx)).toBe('pushed')
337
- popContext()
338
- expect(useContext(ctx)).toBe('default')
339
- })
340
-
341
- test('provide() pushes context and registers cleanup on unmount', () => {
342
- const ctx = createContext('default')
343
- const { hooks } = runWithHooks(
344
- (() => {
345
- provide(ctx, 'provided')
346
- return null
347
- }) as ComponentFn,
348
- {} as Props,
349
- )
350
- // Context is available after provide()
351
- expect(useContext(ctx)).toBe('provided')
352
- // Running unmount hooks should pop the context
353
- for (const fn of hooks.unmount!) fn()
354
- expect(useContext(ctx)).toBe('default')
355
- })
356
- })
357
-
358
- // ─── createRef ───────────────────────────────────────────────────────────────
359
-
360
- describe('createRef()', () => {
361
- test('returns object with current = null', () => {
362
- const ref = createRef()
363
- expect(ref.current).toBeNull()
364
- })
365
-
366
- test('current is mutable', () => {
367
- const ref = createRef<number>()
368
- ref.current = 42
369
- expect(ref.current).toBe(42)
370
- })
371
-
372
- test('typed ref works', () => {
373
- const ref = createRef<string>()
374
- ref.current = 'hello'
375
- expect(ref.current).toBe('hello')
376
- })
377
- })
378
-
379
- // ─── Show ────────────────────────────────────────────────────────────────────
380
-
381
- describe('Show', () => {
382
- test('returns a reactive getter', () => {
383
- const result = Show({ when: () => true, children: 'visible' })
384
- expect(typeof result).toBe('function')
385
- })
386
-
387
- test('returns children when condition is truthy', () => {
388
- const getter = Show({ when: () => true, children: 'visible' }) as unknown as () => VNodeChild
389
- expect(getter()).toBe('visible')
390
- })
391
-
392
- test('returns null when condition is falsy and no fallback', () => {
393
- const getter = Show({ when: () => false, children: 'visible' }) as unknown as () => VNodeChild
394
- expect(getter()).toBeNull()
395
- })
396
-
397
- test('returns fallback when condition is falsy', () => {
398
- const fallbackNode = h('span', null, 'nope')
399
- const getter = Show({
400
- when: () => false,
401
- fallback: fallbackNode,
402
- children: 'visible',
403
- }) as unknown as () => VNodeChild
404
- expect(getter()).toBe(fallbackNode)
405
- })
406
-
407
- test('reacts to condition changes', () => {
408
- let flag = true
409
- const getter = Show({
410
- when: () => flag,
411
- children: 'yes',
412
- fallback: 'no',
413
- }) as unknown as () => VNodeChild
414
- expect(getter()).toBe('yes')
415
- flag = false
416
- expect(getter()).toBe('no')
417
- })
418
-
419
- test('returns null for children when children not provided and condition truthy', () => {
420
- const getter = Show({ when: () => true }) as unknown as () => VNodeChild
421
- expect(getter()).toBeNull()
422
- })
423
- })
424
-
425
- // ─── Switch / Match ──────────────────────────────────────────────────────────
426
-
427
- describe('Switch / Match', () => {
428
- test('renders first matching branch', () => {
429
- const result = Switch({
430
- children: [
431
- h(Match, { when: () => false }, 'first'),
432
- h(Match, { when: () => true }, 'second'),
433
- h(Match, { when: () => true }, 'third'),
434
- ],
435
- })
436
- const getter = result as unknown as () => VNodeChild
437
- expect(getter()).toBe('second')
438
- })
439
-
440
- test('renders fallback when no branch matches', () => {
441
- const fb = h('p', null, '404')
442
- const result = Switch({
443
- fallback: fb,
444
- children: [h(Match, { when: () => false }, 'a'), h(Match, { when: () => false }, 'b')],
445
- })
446
- const getter = result as unknown as () => VNodeChild
447
- expect(getter()).toBe(fb)
448
- })
449
-
450
- test('returns null when no match and no fallback', () => {
451
- const result = Switch({
452
- children: [h(Match, { when: () => false }, 'a')],
453
- })
454
- const getter = result as unknown as () => VNodeChild
455
- expect(getter()).toBeNull()
456
- })
457
-
458
- test('handles single child (not array)', () => {
459
- const result = Switch({
460
- children: h(Match, { when: () => true }, 'only'),
461
- })
462
- const getter = result as unknown as () => VNodeChild
463
- expect(getter()).toBe('only')
464
- })
465
-
466
- test('handles no children', () => {
467
- const result = Switch({})
468
- const getter = result as unknown as () => VNodeChild
469
- expect(getter()).toBeNull()
470
- })
471
-
472
- test('Match function returns null (marker only)', () => {
473
- const result = Match({ when: () => true, children: 'content' })
474
- expect(result).toBeNull()
475
- })
476
-
477
- test('MatchSymbol is a symbol', () => {
478
- expect(typeof MatchSymbol).toBe('symbol')
479
- })
480
-
481
- test('reacts to condition changes', () => {
482
- let a = false
483
- let b = false
484
- const result = Switch({
485
- fallback: 'none',
486
- children: [h(Match, { when: () => a }, 'A'), h(Match, { when: () => b }, 'B')],
487
- })
488
- const getter = result as unknown as () => VNodeChild
489
- expect(getter()).toBe('none')
490
- b = true
491
- expect(getter()).toBe('B')
492
- a = true
493
- expect(getter()).toBe('A') // first match wins
494
- })
495
-
496
- test('handles multiple children in a Match branch', () => {
497
- const result = Switch({
498
- children: [h(Match, { when: () => true }, 'child1', 'child2')],
499
- })
500
- const getter = result as unknown as () => VNodeChild
501
- const value = getter()
502
- expect(Array.isArray(value)).toBe(true)
503
- expect(value as unknown).toEqual(['child1', 'child2'])
504
- })
505
- })
506
-
507
- // ─── For ─────────────────────────────────────────────────────────────────────
508
-
509
- describe('For()', () => {
510
- test('returns a VNode with ForSymbol type', () => {
511
- const node = For({
512
- each: () => [1, 2, 3],
513
- by: (item) => item,
514
- children: (item) => h('li', null, String(item)),
515
- })
516
- expect(node.type).toBe(ForSymbol)
517
- expect(node.children).toEqual([])
518
- expect(node.key).toBeNull()
519
- })
520
-
521
- test('ForSymbol is a symbol', () => {
522
- expect(typeof ForSymbol).toBe('symbol')
523
- })
524
-
525
- test('props contain each, by, children functions', () => {
526
- const eachFn = () => [1, 2]
527
- const keyFn = (item: number) => item
528
- const childFn = (item: number) => h('span', null, String(item))
529
- const node = For({ each: eachFn, by: keyFn, children: childFn })
530
- const props = node.props as unknown as {
531
- each: typeof eachFn
532
- by: typeof keyFn
533
- children: typeof childFn
534
- }
535
- expect(props.each).toBe(eachFn)
536
- expect(props.by).toBe(keyFn)
537
- expect(props.children).toBe(childFn)
538
- })
539
- })
540
-
541
- // ─── Portal ──────────────────────────────────────────────────────────────────
542
-
543
- describe('Portal()', () => {
544
- test('returns a VNode with PortalSymbol type', () => {
545
- const fakeTarget = {} as Element
546
- const node = Portal({ target: fakeTarget, children: h('div', null) })
547
- expect(node.type).toBe(PortalSymbol)
548
- expect(node.key).toBeNull()
549
- expect(node.children).toEqual([])
550
- })
551
-
552
- test('PortalSymbol is a symbol', () => {
553
- expect(typeof PortalSymbol).toBe('symbol')
554
- })
555
-
556
- test('props contain target and children', () => {
557
- const fakeTarget = {} as Element
558
- const child = h('span', null, 'content')
559
- const node = Portal({ target: fakeTarget, children: child })
560
- const props = node.props as unknown as { target: Element; children: VNode }
561
- expect(props.target).toBe(fakeTarget)
562
- expect(props.children).toBe(child)
563
- })
564
- })
565
-
566
- // ─── Suspense ────────────────────────────────────────────────────────────────
567
-
568
- describe('Suspense', () => {
569
- test('returns a Fragment VNode', () => {
570
- const node = Suspense({
571
- fallback: h('div', null, 'loading...'),
572
- children: h('div', null, 'content'),
573
- })
574
- expect(node.type).toBe(Fragment)
575
- })
576
-
577
- test('renders children when not loading', () => {
578
- const child = h('div', null, 'loaded')
579
- const node = Suspense({
580
- fallback: h('span', null, 'loading'),
581
- children: child,
582
- })
583
- // The child of Fragment is a reactive getter
584
- expect(node.children).toHaveLength(1)
585
- const getter = node.children[0] as () => VNodeChild
586
- expect(typeof getter).toBe('function')
587
- // Should return the child since it's not a lazy component
588
- expect(getter()).toBe(child)
589
- })
590
-
591
- test('renders fallback when child type has __loading() returning true', () => {
592
- const fallback = h('span', null, 'loading')
593
- const lazyFn = (() => h('div', null)) as unknown as ComponentFn & { __loading: () => boolean }
594
- lazyFn.__loading = () => true
595
- const child = h(lazyFn, null)
596
-
597
- const node = Suspense({ fallback, children: child })
598
- const getter = node.children[0] as () => VNodeChild
599
- expect(getter()).toBe(fallback)
600
- })
601
-
602
- test('renders children when __loading returns false', () => {
603
- const fallback = h('span', null, 'loading')
604
- const lazyFn = (() => h('div', null)) as unknown as ComponentFn & { __loading: () => boolean }
605
- lazyFn.__loading = () => false
606
- const child = h(lazyFn, null)
607
-
608
- const node = Suspense({ fallback, children: child })
609
- const getter = node.children[0] as () => VNodeChild
610
- expect(getter()).toBe(child)
611
- })
612
-
613
- test('handles function children (reactive getter)', () => {
614
- const child = h('div', null, 'content')
615
- const node = Suspense({
616
- fallback: h('span', null, 'loading'),
617
- children: () => child,
618
- })
619
- const getter = node.children[0] as () => VNodeChild
620
- // The getter should unwrap the function child
621
- expect(getter()).toBe(child)
622
- })
623
-
624
- test('warns when fallback prop is missing', () => {
625
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
626
- Suspense({ fallback: undefined as unknown as VNodeChild, children: h('div', null) })
627
- expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('<Suspense>'))
628
- warnSpy.mockRestore()
629
- })
630
- })
631
-
632
- // ─── ErrorBoundary ───────────────────────────────────────────────────────────
633
-
634
- describe('ErrorBoundary', () => {
635
- test('is a component function', () => {
636
- expect(typeof ErrorBoundary).toBe('function')
637
- })
638
-
639
- test('warns when fallback is not a function', () => {
640
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
641
- runWithHooks(() => {
642
- ErrorBoundary({
643
- fallback: 'not a function' as unknown as (err: unknown, reset: () => void) => VNodeChild,
644
- children: 'child',
645
- })
646
- return null
647
- }, {})
648
- expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('<ErrorBoundary>'))
649
- warnSpy.mockRestore()
650
- })
651
-
652
- test('returns a reactive getter', () => {
653
- // Must run inside runWithHooks since ErrorBoundary calls onUnmount
654
- const { vnode, hooks } = runWithHooks(() => {
655
- return h(
656
- 'div',
657
- null,
658
- ErrorBoundary({
659
- fallback: (err) => `Error: ${err}`,
660
- children: 'child content',
661
- }) as VNodeChild,
662
- )
663
- }, {})
664
- expect(vnode).not.toBeNull()
665
- // Should have registered onUnmount for cleanup
666
- expect(hooks.unmount!.length).toBeGreaterThanOrEqual(1)
667
- })
668
-
669
- test('renders children when no error', () => {
670
- let result: VNodeChild = null
671
- runWithHooks(() => {
672
- result = ErrorBoundary({
673
- fallback: (err) => `Error: ${err}`,
674
- children: 'child content',
675
- })
676
- return null
677
- }, {})
678
- expect(typeof result).toBe('function')
679
- const getter = result as unknown as () => VNodeChild
680
- expect(getter()).toBe('child content')
681
- })
682
-
683
- test('renders function children by calling them', () => {
684
- let result: VNodeChild = null
685
- runWithHooks(() => {
686
- result = ErrorBoundary({
687
- fallback: (err) => `Error: ${err}`,
688
- children: () => 'dynamic child',
689
- })
690
- return null
691
- }, {})
692
- const getter = result as unknown as () => VNodeChild
693
- expect(getter()).toBe('dynamic child')
694
- })
695
- })
696
-
697
- // ─── dispatchToErrorBoundary ─────────────────────────────────────────────────
698
-
699
- describe('dispatchToErrorBoundary()', () => {
700
- test('dispatches to the most recently pushed boundary', async () => {
701
- // Previous ErrorBoundary tests may have left handlers on the stack,
702
- // so we test by pushing our own known handler.
703
- let caughtErr: unknown = null
704
- const { pushErrorBoundary: push, popErrorBoundary: pop } = await import('../component')
705
- push((err: unknown) => {
706
- caughtErr = err
707
- return true
708
- })
709
- expect(dispatchToErrorBoundary(new Error('caught'))).toBe(true)
710
- expect((caughtErr as Error).message).toBe('caught')
711
- pop()
712
- })
713
- })
714
-
715
- // ─── mapArray ────────────────────────────────────────────────────────────────
716
-
717
- describe('mapArray()', () => {
718
- test('maps items with caching', () => {
719
- let callCount = 0
720
- const items = [1, 2, 3]
721
- const mapped = mapArray(
722
- () => items,
723
- (item) => item,
724
- (item) => {
725
- callCount++
726
- return item * 10
727
- },
728
- )
729
-
730
- const result1 = mapped()
731
- expect(result1).toEqual([10, 20, 30])
732
- expect(callCount).toBe(3)
733
-
734
- // Second call should use cache
735
- const result2 = mapped()
736
- expect(result2).toEqual([10, 20, 30])
737
- expect(callCount).toBe(3) // no new calls
738
- })
739
-
740
- test('only maps new keys on update', () => {
741
- let callCount = 0
742
- let items = [1, 2, 3]
743
- const mapped = mapArray(
744
- () => items,
745
- (item) => item,
746
- (item) => {
747
- callCount++
748
- return item * 10
749
- },
750
- )
751
-
752
- mapped() // initial: 3 calls
753
- expect(callCount).toBe(3)
754
-
755
- items = [1, 2, 3, 4]
756
- mapped() // only item 4 is new
757
- expect(callCount).toBe(4)
758
- })
759
-
760
- test('evicts removed keys', () => {
761
- let items = [1, 2, 3]
762
- const mapped = mapArray(
763
- () => items,
764
- (item) => item,
765
- (item) => item * 10,
766
- )
767
-
768
- mapped()
769
- items = [1, 3] // remove key 2
770
- const result = mapped()
771
- expect(result).toEqual([10, 30])
772
-
773
- // Re-add key 2 — should re-map since it was evicted
774
- let callCount = 0
775
- items = [1, 2, 3]
776
- const mapped2 = mapArray(
777
- () => items,
778
- (item) => item,
779
- (item) => {
780
- callCount++
781
- return item * 100
782
- },
783
- )
784
- mapped2()
785
- expect(callCount).toBe(3)
786
- })
787
-
788
- test('handles empty source', () => {
789
- const mapped = mapArray(
790
- () => [],
791
- (item: number) => item,
792
- (item) => item * 10,
793
- )
794
- expect(mapped()).toEqual([])
795
- })
796
-
797
- test('handles reordering', () => {
798
- let items = [1, 2, 3]
799
- let callCount = 0
800
- const mapped = mapArray(
801
- () => items,
802
- (item) => item,
803
- (item) => {
804
- callCount++
805
- return item * 10
806
- },
807
- )
808
-
809
- mapped()
810
- expect(callCount).toBe(3)
811
-
812
- items = [3, 1, 2] // reorder
813
- const result = mapped()
814
- expect(result).toEqual([30, 10, 20])
815
- expect(callCount).toBe(3) // no new calls — all cached
816
- })
817
- })
818
-
819
- // ─── Telemetry ───────────────────────────────────────────────────────────────
820
-
821
- describe('registerErrorHandler / reportError', () => {
822
- test('registerErrorHandler registers and calls handler', () => {
823
- const errors: unknown[] = []
824
- const unregister = registerErrorHandler((ctx) => {
825
- errors.push(ctx.error)
826
- })
827
-
828
- reportError({ component: 'Test', phase: 'render', error: 'boom', timestamp: Date.now() })
829
- expect(errors).toEqual(['boom'])
830
-
831
- unregister()
832
- reportError({ component: 'Test', phase: 'render', error: 'after', timestamp: Date.now() })
833
- expect(errors).toEqual(['boom']) // not called after unregister
834
- })
835
-
836
- test('multiple handlers are all called', () => {
837
- let count = 0
838
- const unsub1 = registerErrorHandler(() => {
839
- count++
840
- })
841
- const unsub2 = registerErrorHandler(() => {
842
- count++
843
- })
844
-
845
- reportError({ component: 'X', phase: 'setup', error: 'err', timestamp: 0 })
846
- expect(count).toBe(2)
847
-
848
- unsub1()
849
- unsub2()
850
- })
851
-
852
- test('handler errors are swallowed', () => {
853
- let secondCalled = false
854
- const unsub1 = registerErrorHandler(() => {
855
- throw new Error('handler crash')
856
- })
857
- const unsub2 = registerErrorHandler(() => {
858
- secondCalled = true
859
- })
860
-
861
- // Should not throw
862
- reportError({ component: 'Y', phase: 'mount', error: 'err', timestamp: 0 })
863
- expect(secondCalled).toBe(true)
864
-
865
- unsub1()
866
- unsub2()
867
- })
868
- })
869
-
870
- // ─── JSX Runtime ─────────────────────────────────────────────────────────────
871
-
872
- describe('jsx / jsxs / jsxDEV', () => {
873
- test('jsx creates VNode for DOM element', () => {
874
- const node = jsx('div', { class: 'x' })
875
- expect(node.type).toBe('div')
876
- expect(node.props).toEqual({ class: 'x' })
877
- expect(node.children).toEqual([])
878
- })
879
-
880
- test('jsx handles children in props for DOM elements', () => {
881
- const node = jsx('div', { children: 'hello' })
882
- expect(node.children).toEqual(['hello'])
883
- // children should not be in props for DOM elements
884
- expect(node.props).toEqual({})
885
- })
886
-
887
- test('jsx handles array children for DOM elements', () => {
888
- const node = jsx('div', { children: ['a', 'b', 'c'] })
889
- expect(node.children).toEqual(['a', 'b', 'c'])
890
- })
891
-
892
- test('jsx passes children in props for component functions', () => {
893
- const Comp: ComponentFn = (props) => h('span', null, String(props.children))
894
- const node = jsx(Comp, { children: 'content' })
895
- expect(node.type).toBe(Comp)
896
- // For components, children stay in props
897
- expect(node.props.children).toBe('content')
898
- expect(node.children).toEqual([])
899
- })
900
-
901
- test('jsx handles key parameter', () => {
902
- const node = jsx('li', { id: 'x' }, 'my-key')
903
- expect(node.key).toBe('my-key')
904
- // key is added to props by jsx runtime
905
- expect(node.props).toEqual({ id: 'x', key: 'my-key' })
906
- })
907
-
908
- test('jsx handles Fragment (symbol type) with children', () => {
909
- const node = jsx(Fragment, { children: ['a', 'b'] })
910
- expect(node.type).toBe(Fragment)
911
- expect(node.children).toEqual(['a', 'b'])
912
- })
913
-
914
- test('jsxs is the same as jsx', () => {
915
- expect(jsxs).toBe(jsx)
916
- })
917
-
918
- test('jsxDEV is the same as jsx', () => {
919
- expect(jsxDEV).toBe(jsx)
920
- })
921
-
922
- test('JsxFragment is the same as Fragment', () => {
923
- expect(JsxFragment).toBe(Fragment)
924
- })
925
-
926
- test('jsx with no children in props', () => {
927
- const node = jsx('span', { id: 'test' })
928
- expect(node.children).toEqual([])
929
- expect(node.props).toEqual({ id: 'test' })
930
- })
931
-
932
- test('jsx component with no children', () => {
933
- const Comp: ComponentFn = () => null
934
- const node = jsx(Comp, { name: 'test' })
935
- expect(node.props).toEqual({ name: 'test' })
936
- // children should not be injected if not provided
937
- expect(node.props.children).toBeUndefined()
938
- })
939
- })
940
-
941
- // ─── Lifecycle hooks outside component ───────────────────────────────────────
942
-
943
- describe('lifecycle hooks', () => {
944
- test('onMount outside component warns and is a no-op', () => {
945
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
946
- expect(() => onMount(() => {})).not.toThrow()
947
- expect(warnSpy).toHaveBeenCalledWith(
948
- expect.stringContaining('onMount() called outside component setup'),
949
- )
950
- warnSpy.mockRestore()
951
- })
952
-
953
- test('onUnmount outside component warns and is a no-op', () => {
954
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
955
- expect(() => onUnmount(() => {})).not.toThrow()
956
- expect(warnSpy).toHaveBeenCalledWith(
957
- expect.stringContaining('onUnmount() called outside component setup'),
958
- )
959
- warnSpy.mockRestore()
960
- })
961
-
962
- test('onUpdate outside component warns and is a no-op', () => {
963
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
964
- expect(() => onUpdate(() => {})).not.toThrow()
965
- expect(warnSpy).toHaveBeenCalledWith(
966
- expect.stringContaining('onUpdate() called outside component setup'),
967
- )
968
- warnSpy.mockRestore()
969
- })
970
-
971
- test('onErrorCaptured outside component warns and is a no-op', () => {
972
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
973
- expect(() => onErrorCaptured(() => true)).not.toThrow()
974
- expect(warnSpy).toHaveBeenCalledWith(
975
- expect.stringContaining('onErrorCaptured() called outside component setup'),
976
- )
977
- warnSpy.mockRestore()
978
- })
979
- })
980
-
981
- // ─── Edge cases ──────────────────────────────────────────────────────────────
982
-
983
- describe('edge cases', () => {
984
- test('h() with empty children array', () => {
985
- const node = h('div', null, )
986
- expect(node.children).toEqual([])
987
- })
988
-
989
- test('h() with mixed children types', () => {
990
- const node = h('div', null, 'text', 42, h('span', null), null, () => 'reactive')
991
- expect(node.children).toHaveLength(5)
992
- expect(node.children[0]).toBe('text')
993
- expect(node.children[1]).toBe(42)
994
- expect((node.children[2] as VNode).type).toBe('span')
995
- expect(node.children[3]).toBeNull()
996
- expect(typeof node.children[4]).toBe('function')
997
- })
998
-
999
- test('nested Fragments', () => {
1000
- const node = h(Fragment, null, h(Fragment, null, 'a', 'b'), h(Fragment, null, 'c'))
1001
- expect(node.type).toBe(Fragment)
1002
- expect(node.children).toHaveLength(2)
1003
- expect((node.children[0] as VNode).type).toBe(Fragment)
1004
- expect((node.children[1] as VNode).type).toBe(Fragment)
1005
- })
1006
-
1007
- test('component that throws during setup is propagated by runWithHooks', () => {
1008
- const Comp: ComponentFn = () => {
1009
- throw new Error('setup error')
1010
- }
1011
- expect(() => runWithHooks(Comp, {})).toThrow('setup error')
1012
- })
1013
-
1014
- test('createContext with undefined default', () => {
1015
- const ctx = createContext<string | undefined>(undefined)
1016
- expect(ctx.defaultValue).toBeUndefined()
1017
- expect(useContext(ctx)).toBeUndefined()
1018
- })
1019
-
1020
- test('createContext with object default', () => {
1021
- const defaultObj = { a: 1, b: 'two' }
1022
- const ctx = createContext(defaultObj)
1023
- expect(useContext(ctx)).toBe(defaultObj)
1024
- })
1025
-
1026
- test('Show with VNode children', () => {
1027
- const child = h('div', null, 'content')
1028
- const getter = Show({ when: () => true, children: child }) as unknown as () => VNodeChild
1029
- expect(getter()).toBe(child)
1030
- })
1031
-
1032
- test('For with objects', () => {
1033
- const items = [
1034
- { id: 1, name: 'a' },
1035
- { id: 2, name: 'b' },
1036
- ]
1037
- const node = For<{ id: number; name: string }>({
1038
- each: () => items,
1039
- by: (item) => item.id,
1040
- children: (item) => h('span', null, item.name),
1041
- })
1042
- expect(node.type).toBe(ForSymbol)
1043
- const props = node.props as unknown as { each: () => typeof items }
1044
- expect(props.each()).toBe(items)
1045
- })
1046
- })
1047
-
1048
- // ─── lazy() ───────────────────────────────────────────────────────────────────
1049
-
1050
- describe('lazy()', () => {
1051
- test('returns a LazyComponent with __loading flag', () => {
1052
- const Comp = lazy<Props>(() => new Promise(() => {})) // never resolves
1053
- expect(typeof Comp).toBe('function')
1054
- expect(typeof Comp.__loading).toBe('function')
1055
- expect(Comp.__loading()).toBe(true)
1056
- })
1057
-
1058
- test('resolves to the loaded component', async () => {
1059
- const Inner: ComponentFn<{ name: string }> = (props) => h('span', null, props.name)
1060
- const Comp = lazy(() => Promise.resolve({ default: Inner }))
1061
-
1062
- // Wait for microtask to resolve
1063
- await new Promise((r) => setTimeout(r, 0))
1064
-
1065
- expect(Comp.__loading()).toBe(false)
1066
- const result = Comp({ name: 'hello' })
1067
- expect(result).not.toBeNull()
1068
- // lazy wraps via h(comp, props) so type is the component function
1069
- expect((result as VNode).type).toBe(Inner)
1070
- })
1071
-
1072
- test('throws on import error so ErrorBoundary can catch', async () => {
1073
- const Comp = lazy<Props>(() => Promise.reject(new Error('load failed')))
1074
-
1075
- await new Promise((r) => setTimeout(r, 0))
1076
-
1077
- expect(Comp.__loading()).toBe(false)
1078
- expect(() => Comp({})).toThrow('load failed')
1079
- })
1080
-
1081
- test('wraps non-Error rejection in Error', async () => {
1082
- const Comp = lazy<Props>(() => Promise.reject('string error'))
1083
-
1084
- await new Promise((r) => setTimeout(r, 0))
1085
-
1086
- expect(() => Comp({})).toThrow('string error')
1087
- })
1088
- })
1089
-
1090
- // ─── setContextStackProvider ──────────────────────────────────────────────────
1091
-
1092
- describe('setContextStackProvider', () => {
1093
- test('allows overriding the context stack provider', async () => {
1094
- const { setContextStackProvider } = await import('../context')
1095
- const customStack: Map<symbol, unknown>[] = []
1096
- const ctx = createContext('custom-default')
1097
-
1098
- // Override with custom stack
1099
- setContextStackProvider(() => customStack)
1100
-
1101
- // Push onto custom stack
1102
- customStack.push(new Map([[ctx.id, 'custom-value']]))
1103
- expect(useContext(ctx)).toBe('custom-value')
1104
- customStack.pop()
1105
- expect(useContext(ctx)).toBe('custom-default')
1106
-
1107
- // Fully restore to module-level default stack
1108
- const { setContextStackProvider: restore } = await import('../context')
1109
- const _defaultStack: Map<symbol, unknown>[] = []
1110
- restore(() => _defaultStack)
1111
- })
1112
- })
1113
-
1114
- // ─── ErrorBoundary advanced ──────────────────────────────────────────────────
1115
-
1116
- describe('ErrorBoundary — advanced', () => {
1117
- test('handler returns false when already in error state (double error)', async () => {
1118
- let result: VNodeChild = null
1119
-
1120
- runWithHooks(() => {
1121
- result = ErrorBoundary({
1122
- fallback: (err) => `Error: ${err}`,
1123
- children: 'child',
1124
- })
1125
- return null
1126
- }, {})
1127
-
1128
- const getter = result as unknown as () => VNodeChild
1129
- expect(getter()).toBe('child')
1130
-
1131
- // First error should be handled
1132
- const handled1 = dispatchToErrorBoundary(new Error('first'))
1133
- expect(handled1).toBe(true)
1134
- expect(getter()).toBe('Error: Error: first')
1135
-
1136
- // Second error while already in error state should NOT be handled
1137
- const handled2 = dispatchToErrorBoundary(new Error('second'))
1138
- expect(handled2).toBe(false)
1139
-
1140
- // Clean up the boundary
1141
- const { popErrorBoundary: pop } = await import('../component')
1142
- pop()
1143
- })
1144
-
1145
- test('reset function clears error and re-renders children', async () => {
1146
- let result: VNodeChild = null
1147
- let capturedReset: (() => void) | undefined
1148
-
1149
- runWithHooks(() => {
1150
- result = ErrorBoundary({
1151
- fallback: (err, reset) => {
1152
- capturedReset = reset
1153
- return `Error: ${err}`
1154
- },
1155
- children: 'child content',
1156
- })
1157
- return null
1158
- }, {})
1159
-
1160
- const getter = result as unknown as () => VNodeChild
1161
- expect(getter()).toBe('child content')
1162
-
1163
- // Trigger error
1164
- dispatchToErrorBoundary(new Error('test error'))
1165
- expect(getter()).toBe('Error: Error: test error')
1166
- expect(capturedReset).toBeDefined()
1167
-
1168
- // Reset
1169
- capturedReset?.()
1170
- expect(getter()).toBe('child content')
1171
-
1172
- // Clean up
1173
- const { popErrorBoundary: pop } = await import('../component')
1174
- pop()
1175
- })
1176
- })
1177
-
1178
- // ─── Suspense advanced ──────────────────────────────────────────────────────
1179
-
1180
- describe('Suspense — advanced', () => {
1181
- test('evaluates function fallback when child is loading', () => {
1182
- const fallbackVNode = h('div', null, 'fb-content')
1183
- const lazyFn = (() => h('div', null)) as unknown as ComponentFn & { __loading: () => boolean }
1184
- lazyFn.__loading = () => true
1185
- const child = h(lazyFn, null)
1186
-
1187
- const node = Suspense({ fallback: () => fallbackVNode, children: child })
1188
- const getter = node.children[0] as () => VNodeChild
1189
- expect(getter()).toBe(fallbackVNode)
1190
- })
1191
-
1192
- test('handles null children', () => {
1193
- const node = Suspense({ fallback: h('span', null, 'loading') })
1194
- const getter = node.children[0] as () => VNodeChild
1195
- expect(getter()).toBeUndefined()
1196
- })
1197
-
1198
- test('handles array children (not loading)', () => {
1199
- const children = [h('div', null, 'a'), h('div', null, 'b')]
1200
- const node = Suspense({
1201
- fallback: h('span', null, 'loading'),
1202
- children: children as unknown as VNodeChild,
1203
- })
1204
- const getter = node.children[0] as () => VNodeChild
1205
- // Array is not a VNode with a type, so isLoading check should be false
1206
- const result = getter()
1207
- expect(result).toBe(children)
1208
- })
1209
- })
1210
-
1211
- // ─── Show edge cases ────────────────────────────────────────────────────────
1212
-
1213
- describe('Show — edge cases', () => {
1214
- test('returns null when condition truthy but children is undefined', () => {
1215
- const getter = Show({ when: () => true }) as unknown as () => VNodeChild
1216
- expect(getter()).toBeNull()
1217
- })
1218
-
1219
- test('returns null when condition falsy and fallback is undefined', () => {
1220
- const getter = Show({ when: () => false }) as unknown as () => VNodeChild
1221
- expect(getter()).toBeNull()
1222
- })
1223
- })
1224
-
1225
- // ─── Switch edge cases ──────────────────────────────────────────────────────
1226
-
1227
- describe('Switch — edge cases', () => {
1228
- test('skips non-Match VNode children', () => {
1229
- const result = Switch({
1230
- fallback: 'default',
1231
- children: [h('div', null, 'not-match'), h(Match, { when: () => true }, 'match-child')],
1232
- })
1233
- const getter = result as unknown as () => VNodeChild
1234
- // Should skip the div and match the Match branch
1235
- expect(getter()).toBe('match-child')
1236
- })
1237
-
1238
- test('skips null children in branches', () => {
1239
- const result = Switch({
1240
- fallback: 'default',
1241
- children: [null as unknown as VNodeChild, h(Match, { when: () => true }, 'found')],
1242
- })
1243
- const getter = result as unknown as () => VNodeChild
1244
- expect(getter()).toBe('found')
1245
- })
1246
-
1247
- test('Match with children in props.children (not vnode.children)', () => {
1248
- // When using explicit props.children instead of h() rest args
1249
- const matchVNode = {
1250
- type: Match,
1251
- props: { when: () => true, children: 'from-props' },
1252
- children: [],
1253
- key: null,
1254
- } as unknown as VNodeChild
1255
- const result = Switch({ children: [matchVNode] })
1256
- const getter = result as unknown as () => VNodeChild
1257
- expect(getter()).toBe('from-props')
1258
- })
1259
- })
1260
-
1261
- // ─── Dynamic ──────────────────────────────────────────────────────────────────
1262
-
1263
- describe('Dynamic', () => {
1264
- test('renders the given component', () => {
1265
- const Greeting: ComponentFn = (props) => h('span', null, (props as { name: string }).name)
1266
- const result = Dynamic({ component: Greeting, name: 'world' })
1267
- expect(result).not.toBeNull()
1268
- expect((result as VNode).type).toBe(Greeting)
1269
- expect((result as VNode).props).toEqual({ name: 'world' })
1270
- })
1271
-
1272
- test('renders a string element', () => {
1273
- const result = Dynamic({ component: 'div', class: 'box' })
1274
- expect(result).not.toBeNull()
1275
- expect((result as VNode).type).toBe('div')
1276
- expect((result as VNode).props).toEqual({ class: 'box' })
1277
- })
1278
-
1279
- test('returns null when component is falsy', () => {
1280
- const result = Dynamic({ component: '' })
1281
- expect(result).toBeNull()
1282
- })
1283
-
1284
- test('warns when component prop is falsy', () => {
1285
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
1286
- Dynamic({ component: '' })
1287
- expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('<Dynamic>'))
1288
- warnSpy.mockRestore()
1289
- })
1290
- })