@pyreon/elements 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.
- package/package.json +10 -12
- package/src/Element/component.tsx +0 -315
- package/src/Element/constants.ts +0 -96
- package/src/Element/index.ts +0 -6
- package/src/Element/types.ts +0 -168
- package/src/Element/utils.ts +0 -15
- package/src/List/component.tsx +0 -105
- package/src/List/index.ts +0 -5
- package/src/Overlay/component.tsx +0 -140
- package/src/Overlay/context.tsx +0 -36
- package/src/Overlay/index.ts +0 -7
- package/src/Overlay/positioning.ts +0 -191
- package/src/Overlay/useOverlay.tsx +0 -461
- package/src/Portal/component.tsx +0 -54
- package/src/Portal/index.ts +0 -5
- package/src/Text/component.tsx +0 -67
- package/src/Text/index.ts +0 -5
- package/src/Text/styled.ts +0 -30
- package/src/Util/component.tsx +0 -43
- package/src/Util/index.ts +0 -5
- package/src/__tests__/Content.test.tsx +0 -123
- package/src/__tests__/Element-slot-reactivity.browser.test.tsx +0 -152
- package/src/__tests__/Element.test.ts +0 -819
- package/src/__tests__/Iterator.test.ts +0 -492
- package/src/__tests__/Iterator.types.test.ts +0 -237
- package/src/__tests__/List.test.ts +0 -199
- package/src/__tests__/Overlay.test.ts +0 -492
- package/src/__tests__/Portal.test.ts +0 -156
- package/src/__tests__/Text.test.ts +0 -274
- package/src/__tests__/Util.test.ts +0 -63
- package/src/__tests__/Wrapper-innerhtml.test.tsx +0 -178
- package/src/__tests__/Wrapper.test.tsx +0 -196
- package/src/__tests__/elements.browser.test.tsx +0 -132
- package/src/__tests__/equalBeforeAfter.test.ts +0 -122
- package/src/__tests__/helpers.test.ts +0 -65
- package/src/__tests__/integration.test.tsx +0 -118
- package/src/__tests__/internElementBundle.test.ts +0 -102
- package/src/__tests__/iterator-function-children.test.tsx +0 -120
- package/src/__tests__/native-markers.test.ts +0 -13
- package/src/__tests__/overlayContext.test.tsx +0 -78
- package/src/__tests__/perf-stress.browser.test.tsx +0 -119
- package/src/__tests__/positioning.test.ts +0 -90
- package/src/__tests__/responsiveProps.test.ts +0 -328
- package/src/__tests__/slot-component-reference.test.tsx +0 -157
- package/src/__tests__/useOverlay.test.ts +0 -1336
- package/src/__tests__/utils.test.ts +0 -69
- package/src/__tests__/wrapper-block-cascade.test.ts +0 -121
- package/src/constants.ts +0 -1
- package/src/env.d.ts +0 -6
- package/src/helpers/Content/component.tsx +0 -75
- package/src/helpers/Content/index.ts +0 -3
- package/src/helpers/Content/styled.ts +0 -105
- package/src/helpers/Content/types.ts +0 -49
- package/src/helpers/Iterator/component.tsx +0 -316
- package/src/helpers/Iterator/index.ts +0 -30
- package/src/helpers/Iterator/types.ts +0 -138
- package/src/helpers/Wrapper/component.tsx +0 -180
- package/src/helpers/Wrapper/constants.ts +0 -10
- package/src/helpers/Wrapper/index.ts +0 -3
- package/src/helpers/Wrapper/styled.ts +0 -64
- package/src/helpers/Wrapper/types.ts +0 -56
- package/src/helpers/Wrapper/utils.ts +0 -7
- package/src/helpers/index.ts +0 -4
- package/src/helpers/internElementBundle.ts +0 -37
- package/src/helpers/isPyreonComponent.ts +0 -46
- package/src/index.ts +0 -42
- package/src/manifest.ts +0 -190
- package/src/tests/manifest-snapshot.test.ts +0 -45
- package/src/types.ts +0 -112
- package/src/utils.ts +0 -5
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
import type { VNode } from '@pyreon/core'
|
|
2
|
-
import { h } from '@pyreon/core'
|
|
3
|
-
import { describe, expect, it } from 'vitest'
|
|
4
|
-
import { Element } from '../Element'
|
|
5
|
-
import Content from '../helpers/Content/component'
|
|
6
|
-
|
|
7
|
-
const asVNode = (v: unknown) => v as VNode
|
|
8
|
-
|
|
9
|
-
// See Element.test.ts for context — Element's simple-element fast path moves
|
|
10
|
-
// layout props from `result.props.{tag, direction, …}` to
|
|
11
|
-
// `result.props.{as, $element.direction, …}`. This helper reads from
|
|
12
|
-
// whichever shape the result is in.
|
|
13
|
-
const getLayoutProps = (result: VNode): Record<string, unknown> => {
|
|
14
|
-
const p = result.props as Record<string, unknown>
|
|
15
|
-
if (p.$element && typeof p.$element === 'object') {
|
|
16
|
-
const el = p.$element as Record<string, unknown>
|
|
17
|
-
return {
|
|
18
|
-
tag: p.as,
|
|
19
|
-
direction: el.direction,
|
|
20
|
-
alignX: el.alignX,
|
|
21
|
-
alignY: el.alignY,
|
|
22
|
-
block: el.block,
|
|
23
|
-
equalCols: el.equalCols,
|
|
24
|
-
extendCss: el.extraStyles,
|
|
25
|
-
isInline: undefined,
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return {
|
|
29
|
-
tag: p.tag,
|
|
30
|
-
direction: p.direction,
|
|
31
|
-
alignX: p.alignX,
|
|
32
|
-
alignY: p.alignY,
|
|
33
|
-
block: p.block,
|
|
34
|
-
equalCols: p.equalCols,
|
|
35
|
-
extendCss: p.extendCss,
|
|
36
|
-
isInline: p.isInline,
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const getContentSlots = (result: VNode): VNode[] => {
|
|
41
|
-
const children = result.props.children
|
|
42
|
-
if (!Array.isArray(children)) return []
|
|
43
|
-
return children.filter(
|
|
44
|
-
(c: unknown) =>
|
|
45
|
-
c != null && typeof c === 'object' && 'type' in (c as VNode) && (c as VNode).type === Content,
|
|
46
|
-
) as VNode[]
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
describe('Element responsive props', () => {
|
|
50
|
-
describe('single values', () => {
|
|
51
|
-
it('renders with alignX as string', () => {
|
|
52
|
-
const result = asVNode(Element({ alignX: 'center', children: 'content' }))
|
|
53
|
-
expect(typeof result.type).toBe("function")
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('renders with alignY as string', () => {
|
|
57
|
-
const result = asVNode(Element({ alignY: 'top', children: 'content' }))
|
|
58
|
-
expect(typeof result.type).toBe("function")
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it('renders with direction as string', () => {
|
|
62
|
-
const result = asVNode(Element({ direction: 'rows', children: 'content' }))
|
|
63
|
-
expect(typeof result.type).toBe("function")
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('renders with gap as number', () => {
|
|
67
|
-
const result = asVNode(
|
|
68
|
-
Element({
|
|
69
|
-
gap: 16,
|
|
70
|
-
beforeContent: h('span', null, 'Before'),
|
|
71
|
-
afterContent: h('span', null, 'After'),
|
|
72
|
-
children: 'content',
|
|
73
|
-
}),
|
|
74
|
-
)
|
|
75
|
-
expect(typeof result.type).toBe("function")
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
it('renders with block as boolean', () => {
|
|
79
|
-
const result = asVNode(Element({ block: true, children: 'content' }))
|
|
80
|
-
expect(typeof result.type).toBe("function")
|
|
81
|
-
expect(getLayoutProps(result).block).toBe(true)
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('renders with equalCols as boolean', () => {
|
|
85
|
-
const result = asVNode(
|
|
86
|
-
Element({
|
|
87
|
-
equalCols: true,
|
|
88
|
-
beforeContent: h('span', null, 'Before'),
|
|
89
|
-
afterContent: h('span', null, 'After'),
|
|
90
|
-
children: 'content',
|
|
91
|
-
}),
|
|
92
|
-
)
|
|
93
|
-
expect(typeof result.type).toBe("function")
|
|
94
|
-
})
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
describe('array values (positional breakpoints)', () => {
|
|
98
|
-
it('renders with alignX as array', () => {
|
|
99
|
-
const result = asVNode(
|
|
100
|
-
Element({ alignX: ['left', 'center', 'right'] as any, children: 'content' }),
|
|
101
|
-
)
|
|
102
|
-
expect(typeof result.type).toBe("function")
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
it('renders with alignY as array', () => {
|
|
106
|
-
const result = asVNode(
|
|
107
|
-
Element({ alignY: ['top', 'center', 'bottom'] as any, children: 'content' }),
|
|
108
|
-
)
|
|
109
|
-
expect(typeof result.type).toBe("function")
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
it('renders with direction as array', () => {
|
|
113
|
-
const result = asVNode(Element({ direction: ['rows', 'inline'] as any, children: 'content' }))
|
|
114
|
-
expect(typeof result.type).toBe("function")
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
it('renders with gap as array', () => {
|
|
118
|
-
const result = asVNode(
|
|
119
|
-
Element({
|
|
120
|
-
gap: [8, 16, 24] as any,
|
|
121
|
-
beforeContent: h('span', null, 'Before'),
|
|
122
|
-
children: 'content',
|
|
123
|
-
}),
|
|
124
|
-
)
|
|
125
|
-
expect(typeof result.type).toBe("function")
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
it('renders with block as array', () => {
|
|
129
|
-
const result = asVNode(Element({ block: [false, true] as any, children: 'content' }))
|
|
130
|
-
expect(typeof result.type).toBe("function")
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
it('renders with equalCols as array', () => {
|
|
134
|
-
const result = asVNode(
|
|
135
|
-
Element({
|
|
136
|
-
equalCols: [false, true] as any,
|
|
137
|
-
beforeContent: h('span', null, 'Before'),
|
|
138
|
-
afterContent: h('span', null, 'After'),
|
|
139
|
-
children: 'content',
|
|
140
|
-
}),
|
|
141
|
-
)
|
|
142
|
-
expect(typeof result.type).toBe("function")
|
|
143
|
-
})
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
describe('breakpoint object values', () => {
|
|
147
|
-
it('renders with alignX as breakpoint object', () => {
|
|
148
|
-
const result = asVNode(
|
|
149
|
-
Element({
|
|
150
|
-
alignX: { xs: 'left', md: 'center', xl: 'right' } as any,
|
|
151
|
-
children: 'content',
|
|
152
|
-
}),
|
|
153
|
-
)
|
|
154
|
-
expect(typeof result.type).toBe("function")
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
it('renders with alignY as breakpoint object', () => {
|
|
158
|
-
const result = asVNode(
|
|
159
|
-
Element({ alignY: { xs: 'top', lg: 'center' } as any, children: 'content' }),
|
|
160
|
-
)
|
|
161
|
-
expect(typeof result.type).toBe("function")
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
it('renders with direction as breakpoint object', () => {
|
|
165
|
-
const result = asVNode(
|
|
166
|
-
Element({
|
|
167
|
-
direction: { xs: 'rows', md: 'inline' } as any,
|
|
168
|
-
children: 'content',
|
|
169
|
-
}),
|
|
170
|
-
)
|
|
171
|
-
expect(typeof result.type).toBe("function")
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
it('renders with gap as breakpoint object', () => {
|
|
175
|
-
const result = asVNode(
|
|
176
|
-
Element({
|
|
177
|
-
gap: { xs: 8, md: 16, lg: 24 } as any,
|
|
178
|
-
beforeContent: h('span', null, 'Before'),
|
|
179
|
-
children: 'content',
|
|
180
|
-
}),
|
|
181
|
-
)
|
|
182
|
-
expect(typeof result.type).toBe("function")
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
it('renders with block as breakpoint object', () => {
|
|
186
|
-
const result = asVNode(
|
|
187
|
-
Element({ block: { xs: false, md: true } as any, children: 'content' }),
|
|
188
|
-
)
|
|
189
|
-
expect(typeof result.type).toBe("function")
|
|
190
|
-
})
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
describe('combined responsive props', () => {
|
|
194
|
-
it('renders with multiple responsive props simultaneously', () => {
|
|
195
|
-
const result = asVNode(
|
|
196
|
-
Element({
|
|
197
|
-
alignX: { xs: 'left', md: 'center' } as any,
|
|
198
|
-
alignY: ['top', 'center'] as any,
|
|
199
|
-
direction: { xs: 'rows', lg: 'inline' } as any,
|
|
200
|
-
block: [false, true] as any,
|
|
201
|
-
gap: 16,
|
|
202
|
-
beforeContent: h('span', { 'data-testid': 'before' }, 'Before'),
|
|
203
|
-
afterContent: h('span', { 'data-testid': 'after' }, 'After'),
|
|
204
|
-
children: h('span', { 'data-testid': 'main' }, 'Main'),
|
|
205
|
-
}),
|
|
206
|
-
)
|
|
207
|
-
expect(typeof result.type).toBe("function")
|
|
208
|
-
const slots = getContentSlots(result)
|
|
209
|
-
expect(slots).toHaveLength(3)
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
it('renders with responsive content directions', () => {
|
|
213
|
-
const result = asVNode(
|
|
214
|
-
Element({
|
|
215
|
-
contentDirection: { xs: 'rows', md: 'inline' } as any,
|
|
216
|
-
beforeContentDirection: { xs: 'inline', lg: 'rows' } as any,
|
|
217
|
-
afterContentDirection: 'inline',
|
|
218
|
-
beforeContent: h('span', null, 'Before'),
|
|
219
|
-
afterContent: h('span', null, 'After'),
|
|
220
|
-
children: h('span', null, 'Main'),
|
|
221
|
-
}),
|
|
222
|
-
)
|
|
223
|
-
expect(typeof result.type).toBe("function")
|
|
224
|
-
const slots = getContentSlots(result)
|
|
225
|
-
expect(slots).toHaveLength(3)
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
it('renders with responsive content alignment', () => {
|
|
229
|
-
const result = asVNode(
|
|
230
|
-
Element({
|
|
231
|
-
contentAlignX: { xs: 'left', md: 'center' } as any,
|
|
232
|
-
contentAlignY: ['top', 'center', 'bottom'] as any,
|
|
233
|
-
beforeContentAlignX: 'left',
|
|
234
|
-
afterContentAlignX: 'right',
|
|
235
|
-
beforeContent: h('span', null, 'Before'),
|
|
236
|
-
afterContent: h('span', null, 'After'),
|
|
237
|
-
children: 'content',
|
|
238
|
-
}),
|
|
239
|
-
)
|
|
240
|
-
expect(typeof result.type).toBe("function")
|
|
241
|
-
})
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
describe('responsive css prop', () => {
|
|
245
|
-
it('renders with css as string', () => {
|
|
246
|
-
const result = asVNode(Element({ css: 'background: red;', children: 'content' }))
|
|
247
|
-
expect(typeof result.type).toBe("function")
|
|
248
|
-
expect(getLayoutProps(result).extendCss).toBe('background: red;')
|
|
249
|
-
})
|
|
250
|
-
|
|
251
|
-
it('renders with contentCss', () => {
|
|
252
|
-
const result = asVNode(
|
|
253
|
-
Element({
|
|
254
|
-
contentCss: 'color: blue;',
|
|
255
|
-
beforeContent: h('span', null, 'Before'),
|
|
256
|
-
children: 'content',
|
|
257
|
-
}),
|
|
258
|
-
)
|
|
259
|
-
expect(typeof result.type).toBe("function")
|
|
260
|
-
const slots = getContentSlots(result)
|
|
261
|
-
const contentSlot = slots.find((v) => v.props.contentType === 'content')
|
|
262
|
-
expect(contentSlot?.props.extendCss).toBe('color: blue;')
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
it('renders with beforeContentCss and afterContentCss', () => {
|
|
266
|
-
const result = asVNode(
|
|
267
|
-
Element({
|
|
268
|
-
beforeContentCss: 'padding: 4px;',
|
|
269
|
-
afterContentCss: 'padding: 8px;',
|
|
270
|
-
beforeContent: h('span', null, 'Before'),
|
|
271
|
-
afterContent: h('span', null, 'After'),
|
|
272
|
-
children: 'content',
|
|
273
|
-
}),
|
|
274
|
-
)
|
|
275
|
-
expect(typeof result.type).toBe("function")
|
|
276
|
-
const slots = getContentSlots(result)
|
|
277
|
-
const beforeSlot = slots.find((v) => v.props.contentType === 'before')
|
|
278
|
-
const afterSlot = slots.find((v) => v.props.contentType === 'after')
|
|
279
|
-
expect(beforeSlot?.props.extendCss).toBe('padding: 4px;')
|
|
280
|
-
expect(afterSlot?.props.extendCss).toBe('padding: 8px;')
|
|
281
|
-
})
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
describe('alignment values', () => {
|
|
285
|
-
const alignXValues = [
|
|
286
|
-
'left',
|
|
287
|
-
'center',
|
|
288
|
-
'right',
|
|
289
|
-
'spaceBetween',
|
|
290
|
-
'spaceAround',
|
|
291
|
-
'block',
|
|
292
|
-
] as const
|
|
293
|
-
|
|
294
|
-
const alignYValues = [
|
|
295
|
-
'top',
|
|
296
|
-
'center',
|
|
297
|
-
'bottom',
|
|
298
|
-
'spaceBetween',
|
|
299
|
-
'spaceAround',
|
|
300
|
-
'block',
|
|
301
|
-
] as const
|
|
302
|
-
|
|
303
|
-
for (const value of alignXValues) {
|
|
304
|
-
it(`renders with alignX="${value}"`, () => {
|
|
305
|
-
const result = asVNode(Element({ alignX: value, children: 'content' }))
|
|
306
|
-
expect(typeof result.type).toBe("function")
|
|
307
|
-
})
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
for (const value of alignYValues) {
|
|
311
|
-
it(`renders with alignY="${value}"`, () => {
|
|
312
|
-
const result = asVNode(Element({ alignY: value, children: 'content' }))
|
|
313
|
-
expect(typeof result.type).toBe("function")
|
|
314
|
-
})
|
|
315
|
-
}
|
|
316
|
-
})
|
|
317
|
-
|
|
318
|
-
describe('direction values', () => {
|
|
319
|
-
const directionValues = ['inline', 'rows', 'reverseInline', 'reverseRows'] as const
|
|
320
|
-
|
|
321
|
-
for (const value of directionValues) {
|
|
322
|
-
it(`renders with direction="${value}"`, () => {
|
|
323
|
-
const result = asVNode(Element({ direction: value, children: 'content' }))
|
|
324
|
-
expect(typeof result.type).toBe("function")
|
|
325
|
-
})
|
|
326
|
-
}
|
|
327
|
-
})
|
|
328
|
-
})
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
// Regression: 0.24.3 (PR #839) added `resolveSlot` to make function-valued
|
|
2
|
-
// slot props reactive — `content={() => <X name={signal()} />}`. The
|
|
3
|
-
// implementation calls ANY function-typed slot value with no args, which
|
|
4
|
-
// crashes when the consumer passes a COMPONENT reference using the
|
|
5
|
-
// shorthand `beforeContent={Header}` (Header is `typeof === 'function'`
|
|
6
|
-
// too — `resolveSlot` calls it with no props, downstream
|
|
7
|
-
// `removeUndefinedProps(undefined)` throws
|
|
8
|
-
// `TypeError: Cannot convert undefined or null to object`).
|
|
9
|
-
//
|
|
10
|
-
// Reported by a real consumer (bokisch.com 0.24.3 → SSG build fails on
|
|
11
|
-
// every route that uses `beforeContent={Component}` shorthand —
|
|
12
|
-
// `Prerendered 0 page(s) + 404.html`).
|
|
13
|
-
//
|
|
14
|
-
// The discriminator: framework component functions carry a marker
|
|
15
|
-
// (`IS_ROCKETSTYLE` for rocketstyle wrappers, `PYREON__COMPONENT` for
|
|
16
|
-
// `@pyreon/elements` components). `resolveSlot` must mount marked
|
|
17
|
-
// components via `h(Component, null)` instead of calling bare.
|
|
18
|
-
import type { VNode, VNodeChild } from '@pyreon/core'
|
|
19
|
-
import { h } from '@pyreon/core'
|
|
20
|
-
import { describe, expect, it } from 'vitest'
|
|
21
|
-
import Element from '../Element/component'
|
|
22
|
-
|
|
23
|
-
// Match the bokisch.com bug shape: a rocketstyle-marked component used
|
|
24
|
-
// as a slot reference (NOT wrapped in `() => <Logo />`). The body
|
|
25
|
-
// requires non-undefined props — calling it with no args throws,
|
|
26
|
-
// exactly mirroring the real `removeUndefinedProps(undefined)` crash.
|
|
27
|
-
function makeRocketstyleStub(name: string, content: string) {
|
|
28
|
-
const Component: any = (props: { className?: string } | undefined) => {
|
|
29
|
-
Object.getOwnPropertyDescriptors(props as object)
|
|
30
|
-
return h('div', { 'data-component': name, class: props?.className }, content)
|
|
31
|
-
}
|
|
32
|
-
Component.IS_ROCKETSTYLE = true
|
|
33
|
-
Component.displayName = name
|
|
34
|
-
return Component
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Match the @pyreon/elements framework-component shape.
|
|
38
|
-
function makeElementStub(name: string, content: string) {
|
|
39
|
-
const Component: any = (props: { className?: string } | undefined) => {
|
|
40
|
-
Object.getOwnPropertyDescriptors(props as object)
|
|
41
|
-
return h('div', { 'data-component': name, class: props?.className }, content)
|
|
42
|
-
}
|
|
43
|
-
Component.PYREON__COMPONENT = `@pyreon/elements/${name}`
|
|
44
|
-
Component.pkgName = '@pyreon/elements'
|
|
45
|
-
return Component
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Walk Element's VNode tree to find ALL accessor-function children and
|
|
50
|
-
* invoke them. Element wraps slot rendering as `{() => resolveSlot(value)}`
|
|
51
|
-
* in the JSX child position — those closures are what mount the slot, and
|
|
52
|
-
* are what crashes in the broken state when `value` is a component
|
|
53
|
-
* reference. Calling the closures mirrors how runtime-dom and
|
|
54
|
-
* runtime-server invoke them during render.
|
|
55
|
-
*
|
|
56
|
-
* Returns the array of accessor results in tree order.
|
|
57
|
-
*/
|
|
58
|
-
function invokeAllSlotAccessors(root: VNode): unknown[] {
|
|
59
|
-
const results: unknown[] = []
|
|
60
|
-
const visit = (node: VNodeChild | unknown): void => {
|
|
61
|
-
if (typeof node === 'function') {
|
|
62
|
-
// Reactive-accessor child position
|
|
63
|
-
results.push((node as () => unknown)())
|
|
64
|
-
return
|
|
65
|
-
}
|
|
66
|
-
if (!node || typeof node !== 'object' || Array.isArray(node)) {
|
|
67
|
-
if (Array.isArray(node)) node.forEach(visit)
|
|
68
|
-
return
|
|
69
|
-
}
|
|
70
|
-
const vnode = node as VNode & { props?: { children?: unknown } }
|
|
71
|
-
// Pyreon `h()` stores JSX children in BOTH `vnode.children` (array)
|
|
72
|
-
// AND `props.children` (single value). The slot-accessor closure
|
|
73
|
-
// typically lands in `props.children` when passed explicitly.
|
|
74
|
-
if (vnode.props && 'children' in vnode.props) {
|
|
75
|
-
visit(vnode.props.children)
|
|
76
|
-
}
|
|
77
|
-
if (Array.isArray(vnode.children)) vnode.children.forEach(visit)
|
|
78
|
-
else if (vnode.children) visit(vnode.children as VNodeChild)
|
|
79
|
-
}
|
|
80
|
-
visit(root)
|
|
81
|
-
return results
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
describe('Element slot — component-reference shorthand (regression #839 follow-up)', () => {
|
|
85
|
-
it('beforeContent={RocketstyleComponent} mounts via h(Component) — does NOT crash', () => {
|
|
86
|
-
const Logo = makeRocketstyleStub('Logo', 'logo')
|
|
87
|
-
const result = Element({
|
|
88
|
-
tag: 'header',
|
|
89
|
-
beforeContent: Logo,
|
|
90
|
-
content: 'title',
|
|
91
|
-
}) as VNode
|
|
92
|
-
expect(() => invokeAllSlotAccessors(result)).not.toThrow()
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('afterContent={RocketstyleComponent} does not crash', () => {
|
|
96
|
-
const Badge = makeRocketstyleStub('Badge', 'NEW')
|
|
97
|
-
const result = Element({
|
|
98
|
-
tag: 'header',
|
|
99
|
-
content: 'title',
|
|
100
|
-
afterContent: Badge,
|
|
101
|
-
}) as VNode
|
|
102
|
-
expect(() => invokeAllSlotAccessors(result)).not.toThrow()
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
it('content={RocketstyleComponent} (simple-element fast path) yields h(Component) VNode', () => {
|
|
106
|
-
const Header = makeRocketstyleStub('Header', 'page header')
|
|
107
|
-
const result = Element({ tag: 'header', content: Header }) as VNode
|
|
108
|
-
const results = invokeAllSlotAccessors(result)
|
|
109
|
-
expect(results.length).toBeGreaterThan(0)
|
|
110
|
-
// First accessor result is the slot content. It must be the VNode
|
|
111
|
-
// `h(Header, null)` — NOT the result of calling Header bare (which
|
|
112
|
-
// would crash in the broken state, OR succeed in pre-PR-839 state
|
|
113
|
-
// by accident if Header doesn't access props).
|
|
114
|
-
const first = results[0] as VNode
|
|
115
|
-
expect(first.type).toBe(Header)
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
it('content={ElementComponent} (PYREON__COMPONENT marker) yields h(Component) VNode', () => {
|
|
119
|
-
const Inner = makeElementStub('Inner', 'inner')
|
|
120
|
-
const result = Element({ tag: 'section', content: Inner }) as VNode
|
|
121
|
-
const results = invokeAllSlotAccessors(result)
|
|
122
|
-
expect(results.length).toBeGreaterThan(0)
|
|
123
|
-
const first = results[0] as VNode
|
|
124
|
-
expect(first.type).toBe(Inner)
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
// Counter-cases — the discriminator must NOT break the reactive-accessor
|
|
128
|
-
// shape PR #839 fixed.
|
|
129
|
-
it('content={() => <X />} (plain accessor) still calls function bare — reactive intact', () => {
|
|
130
|
-
let called = 0
|
|
131
|
-
const accessor = () => {
|
|
132
|
-
called++
|
|
133
|
-
return h('div', { 'data-accessor': 'called' }, 'accessor-output')
|
|
134
|
-
}
|
|
135
|
-
const result = Element({ tag: 'div', content: accessor }) as VNode
|
|
136
|
-
const results = invokeAllSlotAccessors(result)
|
|
137
|
-
expect(called).toBeGreaterThan(0)
|
|
138
|
-
// Returns the VNode the accessor produced (NOT an `h(accessor, null)` wrap).
|
|
139
|
-
const first = results[0] as VNode
|
|
140
|
-
expect(first.type).toBe('div')
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
it('beforeContent={() => h(Component)} (accessor returning a VNode) still works', () => {
|
|
144
|
-
const Logo = makeRocketstyleStub('Logo', 'logo')
|
|
145
|
-
let called = 0
|
|
146
|
-
const result = Element({
|
|
147
|
-
tag: 'header',
|
|
148
|
-
beforeContent: () => {
|
|
149
|
-
called++
|
|
150
|
-
return h(Logo, null)
|
|
151
|
-
},
|
|
152
|
-
content: 'title',
|
|
153
|
-
}) as VNode
|
|
154
|
-
expect(() => invokeAllSlotAccessors(result)).not.toThrow()
|
|
155
|
-
expect(called).toBeGreaterThan(0)
|
|
156
|
-
})
|
|
157
|
-
})
|