@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,492 +0,0 @@
|
|
|
1
|
-
import type { ComponentFn, VNode, VNodeChild } from '@pyreon/core'
|
|
2
|
-
import { Fragment, h } from '@pyreon/core'
|
|
3
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
4
|
-
import Iterator from '../helpers/Iterator/component'
|
|
5
|
-
import type { LooseProps as IteratorLooseProps } from '../helpers/Iterator/types'
|
|
6
|
-
|
|
7
|
-
// The strict overloads on Iterator's public surface reject edge-case shapes
|
|
8
|
-
// like `{}` (no data, no children) or `{ children, data }` (conflicting
|
|
9
|
-
// modes). The runtime tolerates them deliberately — we test those tolerated
|
|
10
|
-
// edge cases here, so we cast to the loose internal prop type the
|
|
11
|
-
// implementation accepts. End users hit the strict overloads; these tests
|
|
12
|
-
// exercise the runtime fallbacks the overloads structurally forbid.
|
|
13
|
-
const Loose = Iterator as unknown as (props: IteratorLooseProps) => VNodeChild
|
|
14
|
-
|
|
15
|
-
const asVNode = (v: unknown) => v as VNode
|
|
16
|
-
|
|
17
|
-
const TextItem: ComponentFn = (props: any) =>
|
|
18
|
-
h('span', { 'data-testid': 'item', ...props }, props.children)
|
|
19
|
-
|
|
20
|
-
describe('Iterator', () => {
|
|
21
|
-
describe('static properties', () => {
|
|
22
|
-
it('has isIterator flag', () => {
|
|
23
|
-
expect(Iterator.isIterator).toBe(true)
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('has RESERVED_PROPS', () => {
|
|
27
|
-
expect(Iterator.RESERVED_PROPS).toContain('children')
|
|
28
|
-
expect(Iterator.RESERVED_PROPS).toContain('component')
|
|
29
|
-
expect(Iterator.RESERVED_PROPS).toContain('data')
|
|
30
|
-
expect(Iterator.RESERVED_PROPS).toContain('itemKey')
|
|
31
|
-
expect(Iterator.RESERVED_PROPS).toContain('valueName')
|
|
32
|
-
expect(Iterator.RESERVED_PROPS).toContain('itemProps')
|
|
33
|
-
expect(Iterator.RESERVED_PROPS).toContain('wrapComponent')
|
|
34
|
-
expect(Iterator.RESERVED_PROPS).toContain('wrapProps')
|
|
35
|
-
})
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
describe('children mode', () => {
|
|
39
|
-
it('renders children directly', () => {
|
|
40
|
-
const children = [
|
|
41
|
-
h('span', { 'data-testid': 'child-1' }, 'A'),
|
|
42
|
-
h('span', { 'data-testid': 'child-2' }, 'B'),
|
|
43
|
-
]
|
|
44
|
-
const result = Iterator({ children })
|
|
45
|
-
expect(result).toEqual(children)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
it('renders single child', () => {
|
|
49
|
-
const child = h('span', { 'data-testid': 'only' }, 'Only')
|
|
50
|
-
const result = Iterator({ children: child })
|
|
51
|
-
expect(result).toBe(child)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it('returns null when children is null/undefined', () => {
|
|
55
|
-
const result = Loose({})
|
|
56
|
-
expect(result).toBeNull()
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it('renders fragment children', () => {
|
|
60
|
-
const children = [
|
|
61
|
-
h('span', { 'data-testid': 'frag-1' }, 'A'),
|
|
62
|
-
h('span', { 'data-testid': 'frag-2' }, 'B'),
|
|
63
|
-
]
|
|
64
|
-
const result = Iterator({ children })
|
|
65
|
-
expect(result).toEqual(children)
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it('renders Fragment children with itemProps', () => {
|
|
69
|
-
const itemPropsFn = vi.fn((_item: unknown, extended: any) => ({
|
|
70
|
-
'data-pos': String(extended.position),
|
|
71
|
-
}))
|
|
72
|
-
const fragChildren = [
|
|
73
|
-
h('span', { 'data-testid': 'frag-a' }, 'A'),
|
|
74
|
-
h('span', { 'data-testid': 'frag-b' }, 'B'),
|
|
75
|
-
]
|
|
76
|
-
const fragment = h(Fragment, null, ...fragChildren)
|
|
77
|
-
const result = Iterator({ children: fragment, itemProps: itemPropsFn })
|
|
78
|
-
expect(itemPropsFn).toHaveBeenCalled()
|
|
79
|
-
expect(Array.isArray(result)).toBe(true)
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
it('renders Fragment children with wrapComponent', () => {
|
|
83
|
-
const Wrap: ComponentFn = (props: any) => h('div', { 'data-testid': 'wrap' }, props.children)
|
|
84
|
-
const fragChildren = [
|
|
85
|
-
h('span', { 'data-testid': 'frag-a' }, 'A'),
|
|
86
|
-
h('span', { 'data-testid': 'frag-b' }, 'B'),
|
|
87
|
-
]
|
|
88
|
-
const fragment = h(Fragment, null, ...fragChildren)
|
|
89
|
-
const result = Iterator({ children: fragment, wrapComponent: Wrap }) as VNodeChild[]
|
|
90
|
-
expect(Array.isArray(result)).toBe(true)
|
|
91
|
-
expect((result as any[]).length).toBe(2)
|
|
92
|
-
// Each item should be wrapped
|
|
93
|
-
const first = asVNode(result[0])
|
|
94
|
-
expect(first.type).toBe(Wrap)
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
it('children take priority over data', () => {
|
|
98
|
-
const child = h('span', { 'data-testid': 'child' }, 'Child wins')
|
|
99
|
-
const result = Loose({
|
|
100
|
-
children: child,
|
|
101
|
-
component: TextItem,
|
|
102
|
-
data: ['x', 'y'],
|
|
103
|
-
})
|
|
104
|
-
expect(result).toBe(child)
|
|
105
|
-
})
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
describe('simple array mode', () => {
|
|
109
|
-
it('renders string array with component', () => {
|
|
110
|
-
const result = Iterator({
|
|
111
|
-
component: TextItem,
|
|
112
|
-
data: ['hello', 'world'],
|
|
113
|
-
}) as VNodeChild[]
|
|
114
|
-
expect(Array.isArray(result)).toBe(true)
|
|
115
|
-
expect(result).toHaveLength(2)
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
it('renders number array with component', () => {
|
|
119
|
-
const result = Iterator({
|
|
120
|
-
component: TextItem,
|
|
121
|
-
data: [1, 2, 3],
|
|
122
|
-
}) as VNodeChild[]
|
|
123
|
-
expect(Array.isArray(result)).toBe(true)
|
|
124
|
-
expect(result).toHaveLength(3)
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
it('filters null/undefined from data', () => {
|
|
128
|
-
const result = Iterator({
|
|
129
|
-
component: TextItem,
|
|
130
|
-
data: ['a', null, 'b', undefined],
|
|
131
|
-
}) as VNodeChild[]
|
|
132
|
-
expect(Array.isArray(result)).toBe(true)
|
|
133
|
-
expect(result).toHaveLength(2)
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
it('returns null for empty array', () => {
|
|
137
|
-
const result = Iterator({ component: TextItem, data: [] })
|
|
138
|
-
expect(result).toBeNull()
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
it('returns null for all-null array', () => {
|
|
142
|
-
const result = Iterator({ component: TextItem, data: [null, null] })
|
|
143
|
-
expect(result).toBeNull()
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
it('uses valueName to set prop name', () => {
|
|
147
|
-
const Item: ComponentFn = (props: any) => h('span', { 'data-testid': 'item' }, props.title)
|
|
148
|
-
const result = Iterator({
|
|
149
|
-
component: Item,
|
|
150
|
-
data: ['hello'],
|
|
151
|
-
valueName: 'title',
|
|
152
|
-
}) as VNodeChild[]
|
|
153
|
-
expect(Array.isArray(result)).toBe(true)
|
|
154
|
-
expect(result).toHaveLength(1)
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
it('defaults valueName to children', () => {
|
|
158
|
-
const result = Iterator({
|
|
159
|
-
component: TextItem,
|
|
160
|
-
data: ['test'],
|
|
161
|
-
}) as VNodeChild[]
|
|
162
|
-
expect(Array.isArray(result)).toBe(true)
|
|
163
|
-
expect(result).toHaveLength(1)
|
|
164
|
-
})
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
describe('object array mode', () => {
|
|
168
|
-
it('renders object array with component', () => {
|
|
169
|
-
const Item: ComponentFn = (props: any) => h('span', { 'data-testid': 'item' }, props.name)
|
|
170
|
-
const result = Iterator({
|
|
171
|
-
component: Item,
|
|
172
|
-
data: [
|
|
173
|
-
{ id: 1, name: 'Alice' },
|
|
174
|
-
{ id: 2, name: 'Bob' },
|
|
175
|
-
],
|
|
176
|
-
}) as VNodeChild[]
|
|
177
|
-
expect(Array.isArray(result)).toBe(true)
|
|
178
|
-
expect(result).toHaveLength(2)
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
it('filters empty objects from data', () => {
|
|
182
|
-
const Item: ComponentFn = (props: any) => h('span', { 'data-testid': 'item' }, props.name)
|
|
183
|
-
const result = Iterator({
|
|
184
|
-
component: Item,
|
|
185
|
-
data: [{ name: 'Alice' }, {}, { name: 'Bob' }],
|
|
186
|
-
}) as VNodeChild[]
|
|
187
|
-
expect(Array.isArray(result)).toBe(true)
|
|
188
|
-
expect(result).toHaveLength(2)
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
it('supports per-item component override', () => {
|
|
192
|
-
const Default: ComponentFn = (props: any) =>
|
|
193
|
-
h('span', { 'data-testid': 'default' }, props.label)
|
|
194
|
-
const Custom: ComponentFn = (props: any) => h('em', { 'data-testid': 'custom' }, props.label)
|
|
195
|
-
const result = Iterator({
|
|
196
|
-
component: Default,
|
|
197
|
-
data: [{ label: 'one' }, { label: 'two', component: Custom }],
|
|
198
|
-
}) as VNodeChild[]
|
|
199
|
-
expect(Array.isArray(result)).toBe(true)
|
|
200
|
-
expect(result).toHaveLength(2)
|
|
201
|
-
// Second item should use Custom component
|
|
202
|
-
const second = asVNode(result[1])
|
|
203
|
-
expect(second.type).toBe(Custom)
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
it('uses itemKey string to pick key from item', () => {
|
|
207
|
-
const Item: ComponentFn = (props: any) => h('span', { 'data-testid': 'item' }, props.slug)
|
|
208
|
-
const result = Iterator({
|
|
209
|
-
component: Item,
|
|
210
|
-
data: [{ slug: 'a' }, { slug: 'b' }],
|
|
211
|
-
itemKey: 'slug',
|
|
212
|
-
}) as VNodeChild[]
|
|
213
|
-
expect(Array.isArray(result)).toBe(true)
|
|
214
|
-
expect(result).toHaveLength(2)
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
it('uses itemKey function for custom keys', () => {
|
|
218
|
-
const keyFn = vi.fn((_item: unknown, index: number) => `custom-${index}`)
|
|
219
|
-
const Item: ComponentFn = (props: any) => h('span', { 'data-testid': 'item' }, props.name)
|
|
220
|
-
Iterator({
|
|
221
|
-
component: Item,
|
|
222
|
-
data: [{ name: 'a' }, { name: 'b' }],
|
|
223
|
-
itemKey: keyFn,
|
|
224
|
-
})
|
|
225
|
-
expect(keyFn).toHaveBeenCalledTimes(2)
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
it('falls back to id/key/itemId for keys', () => {
|
|
229
|
-
const Item: ComponentFn = (props: any) => h('span', { 'data-testid': 'item' }, props.name)
|
|
230
|
-
const result = Iterator({
|
|
231
|
-
component: Item,
|
|
232
|
-
data: [
|
|
233
|
-
{ id: 'x', name: 'Alice' },
|
|
234
|
-
{ key: 'y', name: 'Bob' },
|
|
235
|
-
{ itemId: 'z', name: 'Charlie' },
|
|
236
|
-
],
|
|
237
|
-
}) as VNodeChild[]
|
|
238
|
-
expect(result).toHaveLength(3)
|
|
239
|
-
})
|
|
240
|
-
})
|
|
241
|
-
|
|
242
|
-
describe('itemProps', () => {
|
|
243
|
-
it('passes static itemProps to items', () => {
|
|
244
|
-
const result = Iterator({
|
|
245
|
-
component: TextItem,
|
|
246
|
-
data: ['hello'],
|
|
247
|
-
itemProps: { extra: 'yes' },
|
|
248
|
-
}) as VNodeChild[]
|
|
249
|
-
expect(result).toHaveLength(1)
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
it('passes itemProps callback with extended props', () => {
|
|
253
|
-
const itemPropsFn = vi.fn((_item: unknown, extended: any) => ({
|
|
254
|
-
pos: extended.position,
|
|
255
|
-
isFirst: extended.first,
|
|
256
|
-
isLast: extended.last,
|
|
257
|
-
}))
|
|
258
|
-
Iterator({
|
|
259
|
-
component: TextItem,
|
|
260
|
-
data: ['a', 'b', 'c'],
|
|
261
|
-
itemProps: itemPropsFn,
|
|
262
|
-
})
|
|
263
|
-
expect(itemPropsFn).toHaveBeenCalledTimes(3)
|
|
264
|
-
// First call: position 1, first=true, last=false
|
|
265
|
-
const calls = itemPropsFn.mock.calls as unknown[][]
|
|
266
|
-
expect((calls[0] as unknown[])[1]).toMatchObject({
|
|
267
|
-
position: 1,
|
|
268
|
-
first: true,
|
|
269
|
-
last: false,
|
|
270
|
-
})
|
|
271
|
-
// Last call: position 3, first=false, last=true
|
|
272
|
-
expect((calls[2] as unknown[])[1]).toMatchObject({
|
|
273
|
-
position: 3,
|
|
274
|
-
first: false,
|
|
275
|
-
last: true,
|
|
276
|
-
})
|
|
277
|
-
})
|
|
278
|
-
})
|
|
279
|
-
|
|
280
|
-
describe('wrapComponent', () => {
|
|
281
|
-
it('wraps each item with wrapComponent', () => {
|
|
282
|
-
const Wrap: ComponentFn = (props: any) => h('div', { 'data-testid': 'wrap' }, props.children)
|
|
283
|
-
const result = Iterator({
|
|
284
|
-
component: TextItem,
|
|
285
|
-
data: ['a', 'b'],
|
|
286
|
-
wrapComponent: Wrap,
|
|
287
|
-
}) as VNodeChild[]
|
|
288
|
-
expect(result).toHaveLength(2)
|
|
289
|
-
const first = asVNode(result[0])
|
|
290
|
-
expect(first.type).toBe(Wrap)
|
|
291
|
-
})
|
|
292
|
-
|
|
293
|
-
it('wraps children with wrapComponent', () => {
|
|
294
|
-
const Wrap: ComponentFn = (props: any) => h('div', { 'data-testid': 'wrap' }, props.children)
|
|
295
|
-
const result = Iterator({
|
|
296
|
-
wrapComponent: Wrap,
|
|
297
|
-
children: [h('span', null, 'A'), h('span', null, 'B')],
|
|
298
|
-
}) as VNodeChild[]
|
|
299
|
-
expect(result).toHaveLength(2)
|
|
300
|
-
const first = asVNode(result[0])
|
|
301
|
-
expect(first.type).toBe(Wrap)
|
|
302
|
-
})
|
|
303
|
-
|
|
304
|
-
it('passes wrapProps to wrapComponent', () => {
|
|
305
|
-
const Wrap: ComponentFn = (props: any) =>
|
|
306
|
-
h('div', { 'data-testid': 'wrap', 'data-extra': props.extra }, props.children)
|
|
307
|
-
const result = Iterator({
|
|
308
|
-
component: TextItem,
|
|
309
|
-
data: ['a'],
|
|
310
|
-
wrapComponent: Wrap,
|
|
311
|
-
wrapProps: { extra: 'val' },
|
|
312
|
-
}) as VNodeChild[]
|
|
313
|
-
expect(result).toHaveLength(1)
|
|
314
|
-
const first = asVNode(result[0])
|
|
315
|
-
expect(first.type).toBe(Wrap)
|
|
316
|
-
expect(first.props.extra).toBe('val')
|
|
317
|
-
})
|
|
318
|
-
|
|
319
|
-
it('passes wrapProps callback with extended props', () => {
|
|
320
|
-
const wrapPropsFn = vi.fn((_item: unknown, extended: any) => ({
|
|
321
|
-
'data-pos': extended.position,
|
|
322
|
-
}))
|
|
323
|
-
const Wrap: ComponentFn = (props: any) =>
|
|
324
|
-
h('div', { 'data-testid': 'wrap', ...props }, props.children)
|
|
325
|
-
Iterator({
|
|
326
|
-
component: TextItem,
|
|
327
|
-
data: ['a', 'b'],
|
|
328
|
-
wrapComponent: Wrap,
|
|
329
|
-
wrapProps: wrapPropsFn,
|
|
330
|
-
})
|
|
331
|
-
expect(wrapPropsFn).toHaveBeenCalledTimes(2)
|
|
332
|
-
const wCalls = wrapPropsFn.mock.calls as unknown[][]
|
|
333
|
-
expect((wCalls[0] as unknown[])[1]).toMatchObject({ position: 1 })
|
|
334
|
-
expect((wCalls[1] as unknown[])[1]).toMatchObject({ position: 2 })
|
|
335
|
-
})
|
|
336
|
-
|
|
337
|
-
it('wraps object array items with wrapComponent and wrapProps callback', () => {
|
|
338
|
-
const wrapPropsFn = vi.fn((_item: unknown, extended: any) => ({
|
|
339
|
-
'data-pos': String(extended.position),
|
|
340
|
-
}))
|
|
341
|
-
const Item: ComponentFn = (props: any) => h('span', { 'data-testid': 'item' }, props.name)
|
|
342
|
-
const Wrap: ComponentFn = (props: any) =>
|
|
343
|
-
h('div', { 'data-testid': 'wrap', ...props }, props.children)
|
|
344
|
-
const result = Iterator({
|
|
345
|
-
component: Item,
|
|
346
|
-
data: [{ name: 'Alice' }, { name: 'Bob' }],
|
|
347
|
-
wrapComponent: Wrap,
|
|
348
|
-
wrapProps: wrapPropsFn,
|
|
349
|
-
}) as VNodeChild[]
|
|
350
|
-
expect(result).toHaveLength(2)
|
|
351
|
-
expect(wrapPropsFn).toHaveBeenCalledTimes(2)
|
|
352
|
-
})
|
|
353
|
-
|
|
354
|
-
it('passes itemProps callback to object array items', () => {
|
|
355
|
-
const itemPropsFn = vi.fn((_item: unknown, extended: any) => ({
|
|
356
|
-
'data-first': String(extended.first),
|
|
357
|
-
}))
|
|
358
|
-
const Item: ComponentFn = (props: any) =>
|
|
359
|
-
h('span', { 'data-testid': 'item', ...props }, props.name)
|
|
360
|
-
Iterator({
|
|
361
|
-
component: Item,
|
|
362
|
-
data: [{ name: 'Alice' }, { name: 'Bob' }],
|
|
363
|
-
itemProps: itemPropsFn,
|
|
364
|
-
})
|
|
365
|
-
expect(itemPropsFn).toHaveBeenCalledTimes(2)
|
|
366
|
-
const ipCalls = itemPropsFn.mock.calls as unknown[][]
|
|
367
|
-
expect((ipCalls[0] as unknown[])[1]).toMatchObject({ first: true })
|
|
368
|
-
expect((ipCalls[1] as unknown[])[1]).toMatchObject({ first: false })
|
|
369
|
-
})
|
|
370
|
-
|
|
371
|
-
it('skips wrapComponent for items with custom component in object array', () => {
|
|
372
|
-
const Default: ComponentFn = (props: any) =>
|
|
373
|
-
h('span', { 'data-testid': 'default' }, props.label)
|
|
374
|
-
const Custom: ComponentFn = (props: any) => h('em', { 'data-testid': 'custom' }, props.label)
|
|
375
|
-
const Wrap: ComponentFn = (props: any) => h('div', { 'data-testid': 'wrap' }, props.children)
|
|
376
|
-
const result = Iterator({
|
|
377
|
-
component: Default,
|
|
378
|
-
data: [{ label: 'one' }, { label: 'two', component: Custom }],
|
|
379
|
-
wrapComponent: Wrap,
|
|
380
|
-
}) as VNodeChild[]
|
|
381
|
-
expect(result).toHaveLength(2)
|
|
382
|
-
// First item (default) should be wrapped
|
|
383
|
-
const first = asVNode(result[0])
|
|
384
|
-
expect(first.type).toBe(Wrap)
|
|
385
|
-
// Second item (custom component) should NOT be wrapped
|
|
386
|
-
const second = asVNode(result[1])
|
|
387
|
-
expect(second.type).toBe(Custom)
|
|
388
|
-
})
|
|
389
|
-
})
|
|
390
|
-
|
|
391
|
-
describe('children with itemProps (no wrapComponent)', () => {
|
|
392
|
-
it('injects itemProps into children without wrapping', () => {
|
|
393
|
-
const itemPropsFn = vi.fn(() => ({ 'data-injected': 'yes' }))
|
|
394
|
-
Iterator({
|
|
395
|
-
itemProps: itemPropsFn,
|
|
396
|
-
children: [
|
|
397
|
-
h('span', { 'data-testid': 'child-a' }, 'A'),
|
|
398
|
-
h('span', { 'data-testid': 'child-b' }, 'B'),
|
|
399
|
-
],
|
|
400
|
-
})
|
|
401
|
-
expect(itemPropsFn).toHaveBeenCalled()
|
|
402
|
-
})
|
|
403
|
-
|
|
404
|
-
it('injects itemProps into single child', () => {
|
|
405
|
-
const itemPropsFn = vi.fn(() => ({}))
|
|
406
|
-
Iterator({
|
|
407
|
-
itemProps: itemPropsFn,
|
|
408
|
-
children: h('span', { 'data-testid': 'only' }, 'Only'),
|
|
409
|
-
})
|
|
410
|
-
expect(itemPropsFn).toHaveBeenCalled()
|
|
411
|
-
})
|
|
412
|
-
})
|
|
413
|
-
|
|
414
|
-
describe('children rendering paths', () => {
|
|
415
|
-
it('renders single child without itemProps or wrapComponent (direct passthrough)', () => {
|
|
416
|
-
const child = h('span', { 'data-testid': 'single' }, 'Single')
|
|
417
|
-
const result = Iterator({ children: child })
|
|
418
|
-
expect(result).toBe(child)
|
|
419
|
-
})
|
|
420
|
-
|
|
421
|
-
it('renders array children without itemProps or wrapComponent', () => {
|
|
422
|
-
const children = [
|
|
423
|
-
h('span', { 'data-testid': 'a' }, 'A'),
|
|
424
|
-
h('span', { 'data-testid': 'b' }, 'B'),
|
|
425
|
-
]
|
|
426
|
-
const result = Iterator({ children })
|
|
427
|
-
expect(result).toEqual(children)
|
|
428
|
-
})
|
|
429
|
-
|
|
430
|
-
it('renders single child with wrapComponent', () => {
|
|
431
|
-
const Wrap: ComponentFn = (props: any) => h('div', { 'data-testid': 'wrap' }, props.children)
|
|
432
|
-
const result = Iterator({
|
|
433
|
-
wrapComponent: Wrap,
|
|
434
|
-
children: h('span', { 'data-testid': 'only' }, 'Only'),
|
|
435
|
-
})
|
|
436
|
-
const vnode = asVNode(result)
|
|
437
|
-
expect(vnode.type).toBe(Wrap)
|
|
438
|
-
})
|
|
439
|
-
|
|
440
|
-
it('renders single child with itemProps function', () => {
|
|
441
|
-
const itemPropsFn = vi.fn((_item: unknown, extended: any) => ({
|
|
442
|
-
'data-pos': String(extended.position),
|
|
443
|
-
}))
|
|
444
|
-
Iterator({
|
|
445
|
-
itemProps: itemPropsFn,
|
|
446
|
-
children: h('span', { 'data-testid': 'only' }, 'Only'),
|
|
447
|
-
})
|
|
448
|
-
expect(itemPropsFn).toHaveBeenCalledTimes(1)
|
|
449
|
-
})
|
|
450
|
-
})
|
|
451
|
-
|
|
452
|
-
describe('edge cases', () => {
|
|
453
|
-
it('returns null when component is missing but data exists', () => {
|
|
454
|
-
const result = Loose({ data: ['a', 'b'] })
|
|
455
|
-
expect(result).toBeNull()
|
|
456
|
-
})
|
|
457
|
-
|
|
458
|
-
it('returns null when data is not an array', () => {
|
|
459
|
-
const result = Iterator({
|
|
460
|
-
component: TextItem,
|
|
461
|
-
data: 'not-array' as any,
|
|
462
|
-
})
|
|
463
|
-
expect(result).toBeNull()
|
|
464
|
-
})
|
|
465
|
-
|
|
466
|
-
it('returns null for mixed simple and object array', () => {
|
|
467
|
-
const result = Iterator({
|
|
468
|
-
component: TextItem,
|
|
469
|
-
data: ['hello', { name: 'world' }] as any,
|
|
470
|
-
})
|
|
471
|
-
expect(result).toBeNull()
|
|
472
|
-
})
|
|
473
|
-
|
|
474
|
-
it('returns null for unsupported data types in array', () => {
|
|
475
|
-
const result = Iterator({
|
|
476
|
-
component: TextItem,
|
|
477
|
-
data: [true, false] as any,
|
|
478
|
-
})
|
|
479
|
-
expect(result).toBeNull()
|
|
480
|
-
})
|
|
481
|
-
|
|
482
|
-
it('handles itemKey as number (fallback to index)', () => {
|
|
483
|
-
const Item: ComponentFn = (props: any) => h('span', { 'data-testid': 'item' }, props.name)
|
|
484
|
-
const result = Iterator({
|
|
485
|
-
component: Item,
|
|
486
|
-
data: [{ name: 'Alice' }, { name: 'Bob' }],
|
|
487
|
-
itemKey: 42 as any,
|
|
488
|
-
}) as VNodeChild[]
|
|
489
|
-
expect(result).toHaveLength(2)
|
|
490
|
-
})
|
|
491
|
-
})
|
|
492
|
-
})
|