@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,314 +0,0 @@
1
- import { h } from '../h'
2
- import { Match, MatchSymbol, Show, Switch } from '../show'
3
- import type { VNodeChild } from '../types'
4
-
5
- describe('Show', () => {
6
- test('returns a reactive getter (function)', () => {
7
- const result = Show({ when: () => true, children: 'visible' })
8
- expect(typeof result).toBe('function')
9
- })
10
-
11
- test('getter returns children when condition is truthy', () => {
12
- const getter = Show({ when: () => true, children: 'visible' }) as unknown as () => VNodeChild
13
- expect(getter()).toBe('visible')
14
- })
15
-
16
- test('getter returns null when condition is falsy and no fallback', () => {
17
- const getter = Show({ when: () => false, children: 'hidden' }) as unknown as () => VNodeChild
18
- expect(getter()).toBeNull()
19
- })
20
-
21
- test('getter returns fallback when condition is falsy', () => {
22
- const fb = h('span', null, 'fallback')
23
- const getter = Show({
24
- when: () => false,
25
- fallback: fb,
26
- children: 'main',
27
- }) as unknown as () => VNodeChild
28
- expect(getter()).toBe(fb)
29
- })
30
-
31
- test('reacts to condition changes', () => {
32
- let flag = true
33
- const getter = Show({
34
- when: () => flag,
35
- children: 'yes',
36
- fallback: 'no',
37
- }) as unknown as () => VNodeChild
38
- expect(getter()).toBe('yes')
39
- flag = false
40
- expect(getter()).toBe('no')
41
- flag = true
42
- expect(getter()).toBe('yes')
43
- })
44
-
45
- test('returns null for undefined children when truthy', () => {
46
- const getter = Show({ when: () => true }) as unknown as () => VNodeChild
47
- expect(getter()).toBeNull()
48
- })
49
-
50
- test('returns null for undefined fallback when falsy', () => {
51
- const getter = Show({ when: () => false, children: 'x' }) as unknown as () => VNodeChild
52
- expect(getter()).toBeNull()
53
- })
54
-
55
- test('VNode children are preserved as-is', () => {
56
- const child = h('div', null, 'content')
57
- const getter = Show({ when: () => true, children: child }) as unknown as () => VNodeChild
58
- expect(getter()).toBe(child)
59
- })
60
-
61
- test('truthiness: non-empty string', () => {
62
- const getter = Show({
63
- when: () => 'truthy-string',
64
- children: 'shown',
65
- }) as unknown as () => VNodeChild
66
- expect(getter()).toBe('shown')
67
- })
68
-
69
- test('truthiness: 0 is falsy', () => {
70
- const getter = Show({
71
- when: () => 0,
72
- children: 'shown',
73
- fallback: 'hidden',
74
- }) as unknown as () => VNodeChild
75
- expect(getter()).toBe('hidden')
76
- })
77
-
78
- test('truthiness: empty string is falsy', () => {
79
- const getter = Show({
80
- when: () => '',
81
- children: 'shown',
82
- fallback: 'hidden',
83
- }) as unknown as () => VNodeChild
84
- expect(getter()).toBe('hidden')
85
- })
86
-
87
- test('truthiness: null is falsy', () => {
88
- const getter = Show({
89
- when: () => null,
90
- children: 'shown',
91
- fallback: 'hidden',
92
- }) as unknown as () => VNodeChild
93
- expect(getter()).toBe('hidden')
94
- })
95
-
96
- test('truthiness: object is truthy', () => {
97
- const getter = Show({
98
- when: () => ({ a: 1 }),
99
- children: 'shown',
100
- }) as unknown as () => VNodeChild
101
- expect(getter()).toBe('shown')
102
- })
103
-
104
- // Value-or-accessor normalization. Previously `when` was strictly an
105
- // accessor; passing a value crashed with "props.when is not a function".
106
- // The compiler's signal auto-call rewrites bare `when={mySignal}` to
107
- // `when={mySignal()}` at the prop site, producing a value — so the
108
- // framework now accepts both shapes defensively.
109
- test('accepts boolean value (true)', () => {
110
- const getter = Show({ when: true, children: 'shown' }) as unknown as () => VNodeChild
111
- expect(getter()).toBe('shown')
112
- })
113
-
114
- test('accepts boolean value (false)', () => {
115
- const getter = Show({
116
- when: false,
117
- children: 'shown',
118
- fallback: 'hidden',
119
- }) as unknown as () => VNodeChild
120
- expect(getter()).toBe('hidden')
121
- })
122
-
123
- test('accepts truthy value (string)', () => {
124
- const getter = Show({ when: 'yes', children: 'shown' }) as unknown as () => VNodeChild
125
- expect(getter()).toBe('shown')
126
- })
127
-
128
- test('accepts falsy value (0)', () => {
129
- const getter = Show({
130
- when: 0,
131
- children: 'shown',
132
- fallback: 'hidden',
133
- }) as unknown as () => VNodeChild
134
- expect(getter()).toBe('hidden')
135
- })
136
-
137
- test('accepts undefined as falsy', () => {
138
- const getter = Show({
139
- when: undefined as unknown as boolean,
140
- children: 'shown',
141
- fallback: 'hidden',
142
- }) as unknown as () => VNodeChild
143
- expect(getter()).toBe('hidden')
144
- })
145
-
146
- test('accessor and value forms produce identical behavior', () => {
147
- const valueGetter = Show({ when: true, children: 'x' }) as unknown as () => VNodeChild
148
- const accessorGetter = Show({ when: () => true, children: 'x' }) as unknown as () => VNodeChild
149
- expect(valueGetter()).toBe(accessorGetter())
150
- })
151
- })
152
-
153
- describe('Match', () => {
154
- test('returns null (marker-only component)', () => {
155
- const result = Match({ when: () => true, children: 'content' })
156
- expect(result).toBeNull()
157
- })
158
-
159
- test('MatchSymbol is a unique symbol', () => {
160
- expect(typeof MatchSymbol).toBe('symbol')
161
- expect(MatchSymbol.toString()).toContain('pyreon.Match')
162
- })
163
- })
164
-
165
- describe('Switch', () => {
166
- test('renders first truthy Match branch', () => {
167
- const result = Switch({
168
- children: [
169
- h(Match, { when: () => false }, 'first'),
170
- h(Match, { when: () => true }, 'second'),
171
- h(Match, { when: () => true }, 'third'),
172
- ],
173
- })
174
- const getter = result as unknown as () => VNodeChild
175
- expect(getter()).toBe('second')
176
- })
177
-
178
- test('renders fallback when no match', () => {
179
- const fb = h('p', null, '404')
180
- const result = Switch({
181
- fallback: fb,
182
- children: [h(Match, { when: () => false }, 'a'), h(Match, { when: () => false }, 'b')],
183
- })
184
- const getter = result as unknown as () => VNodeChild
185
- expect(getter()).toBe(fb)
186
- })
187
-
188
- test('returns null when no match and no fallback', () => {
189
- const result = Switch({
190
- children: [h(Match, { when: () => false }, 'a')],
191
- })
192
- const getter = result as unknown as () => VNodeChild
193
- expect(getter()).toBeNull()
194
- })
195
-
196
- test('handles single child (not array)', () => {
197
- const result = Switch({
198
- children: h(Match, { when: () => true }, 'only'),
199
- })
200
- const getter = result as unknown as () => VNodeChild
201
- expect(getter()).toBe('only')
202
- })
203
-
204
- test('handles no children', () => {
205
- const result = Switch({})
206
- const getter = result as unknown as () => VNodeChild
207
- expect(getter()).toBeNull()
208
- })
209
-
210
- test('handles null/undefined children', () => {
211
- const result = Switch({ children: null as unknown as VNodeChild })
212
- const getter = result as unknown as () => VNodeChild
213
- expect(getter()).toBeNull()
214
- })
215
-
216
- test('skips non-Match VNode children', () => {
217
- const result = Switch({
218
- fallback: 'default',
219
- children: [h('div', null, 'not-a-match'), h(Match, { when: () => true }, 'found')],
220
- })
221
- const getter = result as unknown as () => VNodeChild
222
- expect(getter()).toBe('found')
223
- })
224
-
225
- test('skips non-object children (strings, null)', () => {
226
- const result = Switch({
227
- fallback: 'default',
228
- children: [
229
- null as unknown as VNodeChild,
230
- 'string-child' as unknown as VNodeChild,
231
- h(Match, { when: () => true }, 'found'),
232
- ],
233
- })
234
- const getter = result as unknown as () => VNodeChild
235
- expect(getter()).toBe('found')
236
- })
237
-
238
- test('reacts to condition changes', () => {
239
- let a = false
240
- let b = false
241
- const result = Switch({
242
- fallback: 'none',
243
- children: [h(Match, { when: () => a }, 'A'), h(Match, { when: () => b }, 'B')],
244
- })
245
- const getter = result as unknown as () => VNodeChild
246
- expect(getter()).toBe('none')
247
- b = true
248
- expect(getter()).toBe('B')
249
- a = true
250
- expect(getter()).toBe('A') // first match wins
251
- b = false
252
- expect(getter()).toBe('A')
253
- a = false
254
- expect(getter()).toBe('none')
255
- })
256
-
257
- test('Match with multiple children returns array', () => {
258
- const result = Switch({
259
- children: [h(Match, { when: () => true }, 'child1', 'child2')],
260
- })
261
- const getter = result as unknown as () => VNodeChild
262
- const value = getter()
263
- expect(Array.isArray(value)).toBe(true)
264
- expect(value).toEqual(['child1', 'child2'])
265
- })
266
-
267
- test('Match with zero vnode.children falls back to props.children', () => {
268
- const matchVNode = {
269
- type: Match,
270
- props: { when: () => true, children: 'from-props' },
271
- children: [],
272
- key: null,
273
- } as unknown as VNodeChild
274
- const result = Switch({ children: [matchVNode] })
275
- const getter = result as unknown as () => VNodeChild
276
- expect(getter()).toBe('from-props')
277
- })
278
-
279
- test('Match with single vnode.children returns it directly (not array)', () => {
280
- const result = Switch({
281
- children: [h(Match, { when: () => true }, 'single')],
282
- })
283
- const getter = result as unknown as () => VNodeChild
284
- expect(getter()).toBe('single')
285
- })
286
-
287
- // Match `when` accepts value or accessor — same defensive normalization as Show.
288
- test('Match accepts boolean value', () => {
289
- const result = Switch({
290
- fallback: 'none',
291
- children: [h(Match, { when: true }, 'matched')],
292
- })
293
- const getter = result as unknown as () => VNodeChild
294
- expect(getter()).toBe('matched')
295
- })
296
-
297
- test('Match with all-false boolean values renders fallback', () => {
298
- const result = Switch({
299
- fallback: 'none',
300
- children: [h(Match, { when: false }, 'a'), h(Match, { when: false }, 'b')],
301
- })
302
- const getter = result as unknown as () => VNodeChild
303
- expect(getter()).toBe('none')
304
- })
305
-
306
- test('Match mixes value and accessor branches in same Switch', () => {
307
- const result = Switch({
308
- fallback: 'none',
309
- children: [h(Match, { when: false }, 'a'), h(Match, { when: () => true }, 'b')],
310
- })
311
- const getter = result as unknown as () => VNodeChild
312
- expect(getter()).toBe('b')
313
- })
314
- })
@@ -1,157 +0,0 @@
1
- import { CSS_UNITLESS, cx, normalizeStyleValue, toKebabCase } from '../style'
2
-
3
- // cx() is extensively tested in cx.test.ts — these tests cover toKebabCase,
4
- // normalizeStyleValue, and CSS_UNITLESS which are used by runtime-dom/runtime-server.
5
-
6
- describe('toKebabCase', () => {
7
- test('converts camelCase to kebab-case', () => {
8
- expect(toKebabCase('backgroundColor')).toBe('background-color')
9
- })
10
-
11
- test('handles single uppercase letter', () => {
12
- expect(toKebabCase('zIndex')).toBe('z-index')
13
- })
14
-
15
- test('handles multiple uppercase letters', () => {
16
- expect(toKebabCase('borderTopLeftRadius')).toBe('border-top-left-radius')
17
- })
18
-
19
- test('returns lowercase string unchanged', () => {
20
- expect(toKebabCase('color')).toBe('color')
21
- })
22
-
23
- test('handles empty string', () => {
24
- expect(toKebabCase('')).toBe('')
25
- })
26
-
27
- test('handles consecutive uppercase (treated individually)', () => {
28
- expect(toKebabCase('MSTransform')).toBe('-m-s-transform')
29
- })
30
-
31
- test('handles leading lowercase with single word', () => {
32
- expect(toKebabCase('opacity')).toBe('opacity')
33
- })
34
- })
35
-
36
- describe('normalizeStyleValue', () => {
37
- test('appends px to numbers for non-unitless properties', () => {
38
- expect(normalizeStyleValue('width', 100)).toBe('100px')
39
- expect(normalizeStyleValue('height', 50)).toBe('50px')
40
- expect(normalizeStyleValue('padding', 0)).toBe('0px')
41
- expect(normalizeStyleValue('marginTop', 20)).toBe('20px')
42
- })
43
-
44
- test('does not append px to unitless properties', () => {
45
- expect(normalizeStyleValue('opacity', 0.5)).toBe('0.5')
46
- expect(normalizeStyleValue('zIndex', 10)).toBe('10')
47
- expect(normalizeStyleValue('flexGrow', 1)).toBe('1')
48
- expect(normalizeStyleValue('fontWeight', 700)).toBe('700')
49
- expect(normalizeStyleValue('lineHeight', 1.5)).toBe('1.5')
50
- expect(normalizeStyleValue('order', 3)).toBe('3')
51
- expect(normalizeStyleValue('columns', 2)).toBe('2')
52
- expect(normalizeStyleValue('flex', 1)).toBe('1')
53
- expect(normalizeStyleValue('scale', 2)).toBe('2')
54
- expect(normalizeStyleValue('widows', 2)).toBe('2')
55
- expect(normalizeStyleValue('orphans', 3)).toBe('3')
56
- })
57
-
58
- test('passes through string values unchanged', () => {
59
- expect(normalizeStyleValue('width', '100%')).toBe('100%')
60
- expect(normalizeStyleValue('color', 'red')).toBe('red')
61
- expect(normalizeStyleValue('display', 'flex')).toBe('flex')
62
- })
63
-
64
- test('converts non-string/non-number to string', () => {
65
- expect(normalizeStyleValue('display', null)).toBe('null')
66
- expect(normalizeStyleValue('display', undefined)).toBe('undefined')
67
- expect(normalizeStyleValue('display', true)).toBe('true')
68
- })
69
-
70
- test('handles zero correctly for non-unitless props', () => {
71
- expect(normalizeStyleValue('margin', 0)).toBe('0px')
72
- })
73
-
74
- test('handles negative numbers', () => {
75
- expect(normalizeStyleValue('marginLeft', -10)).toBe('-10px')
76
- expect(normalizeStyleValue('zIndex', -1)).toBe('-1')
77
- })
78
- })
79
-
80
- describe('CSS_UNITLESS', () => {
81
- test('is a Set', () => {
82
- expect(CSS_UNITLESS).toBeInstanceOf(Set)
83
- })
84
-
85
- test('contains common unitless properties', () => {
86
- expect(CSS_UNITLESS.has('opacity')).toBe(true)
87
- expect(CSS_UNITLESS.has('zIndex')).toBe(true)
88
- expect(CSS_UNITLESS.has('fontWeight')).toBe(true)
89
- expect(CSS_UNITLESS.has('lineHeight')).toBe(true)
90
- expect(CSS_UNITLESS.has('flex')).toBe(true)
91
- expect(CSS_UNITLESS.has('flexGrow')).toBe(true)
92
- expect(CSS_UNITLESS.has('flexShrink')).toBe(true)
93
- expect(CSS_UNITLESS.has('order')).toBe(true)
94
- expect(CSS_UNITLESS.has('columnCount')).toBe(true)
95
- expect(CSS_UNITLESS.has('animationIterationCount')).toBe(true)
96
- })
97
-
98
- test('contains SVG unitless properties', () => {
99
- expect(CSS_UNITLESS.has('fillOpacity')).toBe(true)
100
- expect(CSS_UNITLESS.has('floodOpacity')).toBe(true)
101
- expect(CSS_UNITLESS.has('stopOpacity')).toBe(true)
102
- expect(CSS_UNITLESS.has('strokeOpacity')).toBe(true)
103
- expect(CSS_UNITLESS.has('strokeWidth')).toBe(true)
104
- expect(CSS_UNITLESS.has('strokeMiterlimit')).toBe(true)
105
- expect(CSS_UNITLESS.has('strokeDasharray')).toBe(true)
106
- expect(CSS_UNITLESS.has('strokeDashoffset')).toBe(true)
107
- })
108
-
109
- test('does not contain properties that need units', () => {
110
- expect(CSS_UNITLESS.has('width')).toBe(false)
111
- expect(CSS_UNITLESS.has('height')).toBe(false)
112
- expect(CSS_UNITLESS.has('margin')).toBe(false)
113
- expect(CSS_UNITLESS.has('padding')).toBe(false)
114
- expect(CSS_UNITLESS.has('fontSize')).toBe(false)
115
- expect(CSS_UNITLESS.has('borderWidth')).toBe(false)
116
- expect(CSS_UNITLESS.has('top')).toBe(false)
117
- expect(CSS_UNITLESS.has('left')).toBe(false)
118
- })
119
- })
120
-
121
- describe('cx — additional edge cases', () => {
122
- test('object with all false values', () => {
123
- expect(cx({ a: false, b: false, c: false })).toBe('')
124
- })
125
-
126
- test('object with null and undefined values', () => {
127
- expect(cx({ a: null, b: undefined, c: true })).toBe('c')
128
- })
129
-
130
- test('mixed array of numbers, strings, and objects', () => {
131
- expect(cx([1, 'two', { three: true, four: false }])).toBe('1 two three')
132
- })
133
-
134
- test('single-element array', () => {
135
- expect(cx(['only'])).toBe('only')
136
- })
137
-
138
- test('number 0 in an array', () => {
139
- expect(cx([0, 'one'])).toBe('0 one')
140
- })
141
-
142
- test('boolean true in array is filtered', () => {
143
- expect(cx([true, 'visible'])).toBe('visible')
144
- })
145
-
146
- test('nested empty arrays', () => {
147
- expect(cx([[], [[]], [[[]]]])).toBe('')
148
- })
149
-
150
- test('object with function returning false', () => {
151
- expect(cx({ hidden: () => false })).toBe('')
152
- })
153
-
154
- test('single key object', () => {
155
- expect(cx({ active: true })).toBe('active')
156
- })
157
- })
@@ -1,139 +0,0 @@
1
- import { Fragment, h } from '../h'
2
- import { Suspense } from '../suspense'
3
- import type { ComponentFn, VNodeChild } from '../types'
4
-
5
- describe('Suspense', () => {
6
- test('returns a Fragment VNode', () => {
7
- const node = Suspense({
8
- fallback: h('div', null, 'loading'),
9
- children: h('div', null, 'content'),
10
- })
11
- expect(node.type).toBe(Fragment)
12
- })
13
-
14
- test('Fragment contains a single reactive getter child', () => {
15
- const node = Suspense({
16
- fallback: h('span', null, 'loading'),
17
- children: h('div', null, 'content'),
18
- })
19
- expect(node.children).toHaveLength(1)
20
- expect(typeof node.children[0]).toBe('function')
21
- })
22
-
23
- test('renders children when not loading (plain VNode)', () => {
24
- const child = h('div', null, 'loaded')
25
- const node = Suspense({
26
- fallback: h('span', null, 'loading'),
27
- children: child,
28
- })
29
- const getter = node.children[0] as () => VNodeChild
30
- expect(getter()).toBe(child)
31
- })
32
-
33
- test('renders children when child type has no __loading', () => {
34
- const regularComp: ComponentFn = () => h('div', null)
35
- const child = h(regularComp, null)
36
- const node = Suspense({
37
- fallback: 'loading',
38
- children: child,
39
- })
40
- const getter = node.children[0] as () => VNodeChild
41
- expect(getter()).toBe(child)
42
- })
43
-
44
- test('renders fallback when child __loading() is true', () => {
45
- const fallback = h('span', null, 'loading...')
46
- const lazyFn = (() => h('div', null)) as unknown as ComponentFn & {
47
- __loading: () => boolean
48
- }
49
- lazyFn.__loading = () => true
50
- const child = h(lazyFn, null)
51
-
52
- const node = Suspense({ fallback, children: child })
53
- const getter = node.children[0] as () => VNodeChild
54
- expect(getter()).toBe(fallback)
55
- })
56
-
57
- test('renders children when __loading() is false', () => {
58
- const fallback = h('span', null, 'loading...')
59
- const lazyFn = (() => h('div', null)) as unknown as ComponentFn & {
60
- __loading: () => boolean
61
- }
62
- lazyFn.__loading = () => false
63
- const child = h(lazyFn, null)
64
-
65
- const node = Suspense({ fallback, children: child })
66
- const getter = node.children[0] as () => VNodeChild
67
- expect(getter()).toBe(child)
68
- })
69
-
70
- test('handles function children (reactive getter)', () => {
71
- const child = h('div', null, 'content')
72
- const node = Suspense({
73
- fallback: h('span', null, 'loading'),
74
- children: () => child,
75
- })
76
- const getter = node.children[0] as () => VNodeChild
77
- expect(getter()).toBe(child)
78
- })
79
-
80
- test('evaluates function fallback', () => {
81
- const fbNode = h('div', null, 'fb')
82
- const lazyFn = (() => h('div', null)) as unknown as ComponentFn & {
83
- __loading: () => boolean
84
- }
85
- lazyFn.__loading = () => true
86
- const child = h(lazyFn, null)
87
-
88
- const node = Suspense({ fallback: () => fbNode, children: child })
89
- const getter = node.children[0] as () => VNodeChild
90
- expect(getter()).toBe(fbNode)
91
- })
92
-
93
- test('handles null/undefined children', () => {
94
- const node = Suspense({ fallback: 'loading' })
95
- const getter = node.children[0] as () => VNodeChild
96
- // undefined children — not a VNode, not loading
97
- expect(getter()).toBeUndefined()
98
- })
99
-
100
- test('handles string children', () => {
101
- const node = Suspense({ fallback: 'loading', children: 'text content' })
102
- const getter = node.children[0] as () => VNodeChild
103
- expect(getter()).toBe('text content')
104
- })
105
-
106
- test('handles array children (not loading)', () => {
107
- const children = [h('a', null), h('b', null)]
108
- const node = Suspense({
109
- fallback: 'loading',
110
- children: children as unknown as VNodeChild,
111
- })
112
- const getter = node.children[0] as () => VNodeChild
113
- expect(getter()).toBe(children)
114
- })
115
-
116
- test('warns when fallback prop is missing', () => {
117
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
118
- Suspense({ fallback: undefined as unknown as VNodeChild, children: 'x' })
119
- expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('<Suspense>'))
120
- warnSpy.mockRestore()
121
- })
122
-
123
- test('transition from loading to loaded', () => {
124
- let isLoading = true
125
- const lazyFn = (() => h('div', null)) as unknown as ComponentFn & {
126
- __loading: () => boolean
127
- }
128
- lazyFn.__loading = () => isLoading
129
- const child = h(lazyFn, null)
130
- const fallback = h('span', null, 'loading')
131
-
132
- const node = Suspense({ fallback, children: child })
133
- const getter = node.children[0] as () => VNodeChild
134
-
135
- expect(getter()).toBe(fallback)
136
- isLoading = false
137
- expect(getter()).toBe(child)
138
- })
139
- })