@easyops-cn/a2ui-react 0.0.0

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 (161) hide show
  1. package/.claude/commands/speckit.analyze.md +184 -0
  2. package/.claude/commands/speckit.checklist.md +294 -0
  3. package/.claude/commands/speckit.clarify.md +181 -0
  4. package/.claude/commands/speckit.constitution.md +82 -0
  5. package/.claude/commands/speckit.implement.md +135 -0
  6. package/.claude/commands/speckit.plan.md +89 -0
  7. package/.claude/commands/speckit.specify.md +256 -0
  8. package/.claude/commands/speckit.tasks.md +137 -0
  9. package/.claude/commands/speckit.taskstoissues.md +30 -0
  10. package/.github/workflows/deploy.yml +69 -0
  11. package/.husky/pre-commit +1 -0
  12. package/.prettierignore +4 -0
  13. package/.prettierrc +7 -0
  14. package/.specify/memory/constitution.md +73 -0
  15. package/.specify/scripts/bash/check-prerequisites.sh +166 -0
  16. package/.specify/scripts/bash/common.sh +156 -0
  17. package/.specify/scripts/bash/create-new-feature.sh +297 -0
  18. package/.specify/scripts/bash/setup-plan.sh +61 -0
  19. package/.specify/scripts/bash/update-agent-context.sh +799 -0
  20. package/.specify/templates/agent-file-template.md +28 -0
  21. package/.specify/templates/checklist-template.md +40 -0
  22. package/.specify/templates/plan-template.md +105 -0
  23. package/.specify/templates/spec-template.md +115 -0
  24. package/.specify/templates/tasks-template.md +250 -0
  25. package/CLAUDE.md +105 -0
  26. package/CONTRIBUTING.md +97 -0
  27. package/README.md +126 -0
  28. package/components.json +21 -0
  29. package/eslint.config.js +25 -0
  30. package/netlify.toml +50 -0
  31. package/package.json +94 -0
  32. package/playground/README.md +75 -0
  33. package/playground/index.html +22 -0
  34. package/playground/package.json +32 -0
  35. package/playground/public/favicon.svg +8 -0
  36. package/playground/src/App.css +256 -0
  37. package/playground/src/App.tsx +115 -0
  38. package/playground/src/assets/react.svg +1 -0
  39. package/playground/src/components/ErrorDisplay.tsx +13 -0
  40. package/playground/src/components/ExampleSelector.tsx +64 -0
  41. package/playground/src/components/Header.tsx +47 -0
  42. package/playground/src/components/JsonEditor.tsx +32 -0
  43. package/playground/src/components/Preview.tsx +78 -0
  44. package/playground/src/components/ThemeToggle.tsx +19 -0
  45. package/playground/src/data/examples.ts +1571 -0
  46. package/playground/src/hooks/useTheme.ts +55 -0
  47. package/playground/src/index.css +220 -0
  48. package/playground/src/main.tsx +10 -0
  49. package/playground/tsconfig.app.json +34 -0
  50. package/playground/tsconfig.json +13 -0
  51. package/playground/tsconfig.node.json +26 -0
  52. package/playground/vite.config.ts +31 -0
  53. package/specs/001-a2ui-renderer/checklists/requirements.md +41 -0
  54. package/specs/001-a2ui-renderer/data-model.md +140 -0
  55. package/specs/001-a2ui-renderer/plan.md +123 -0
  56. package/specs/001-a2ui-renderer/quickstart.md +141 -0
  57. package/specs/001-a2ui-renderer/research.md +140 -0
  58. package/specs/001-a2ui-renderer/spec.md +165 -0
  59. package/specs/001-a2ui-renderer/tasks.md +310 -0
  60. package/specs/002-playground/checklists/requirements.md +37 -0
  61. package/specs/002-playground/contracts/components.md +120 -0
  62. package/specs/002-playground/data-model.md +149 -0
  63. package/specs/002-playground/plan.md +73 -0
  64. package/specs/002-playground/quickstart.md +158 -0
  65. package/specs/002-playground/research.md +117 -0
  66. package/specs/002-playground/spec.md +109 -0
  67. package/specs/002-playground/tasks.md +224 -0
  68. package/src/0.8/A2UIRender.test.tsx +793 -0
  69. package/src/0.8/A2UIRender.tsx +142 -0
  70. package/src/0.8/components/ComponentRenderer.test.tsx +373 -0
  71. package/src/0.8/components/ComponentRenderer.tsx +163 -0
  72. package/src/0.8/components/UnknownComponent.tsx +49 -0
  73. package/src/0.8/components/display/AudioPlayerComponent.tsx +37 -0
  74. package/src/0.8/components/display/DividerComponent.tsx +23 -0
  75. package/src/0.8/components/display/IconComponent.tsx +137 -0
  76. package/src/0.8/components/display/ImageComponent.tsx +57 -0
  77. package/src/0.8/components/display/TextComponent.tsx +56 -0
  78. package/src/0.8/components/display/VideoComponent.tsx +31 -0
  79. package/src/0.8/components/display/display.test.tsx +660 -0
  80. package/src/0.8/components/display/index.ts +10 -0
  81. package/src/0.8/components/index.ts +14 -0
  82. package/src/0.8/components/interactive/ButtonComponent.tsx +44 -0
  83. package/src/0.8/components/interactive/CheckBoxComponent.tsx +45 -0
  84. package/src/0.8/components/interactive/DateTimeInputComponent.tsx +176 -0
  85. package/src/0.8/components/interactive/MultipleChoiceComponent.tsx +157 -0
  86. package/src/0.8/components/interactive/SliderComponent.tsx +53 -0
  87. package/src/0.8/components/interactive/TextFieldComponent.tsx +65 -0
  88. package/src/0.8/components/interactive/index.ts +10 -0
  89. package/src/0.8/components/interactive/interactive.test.tsx +618 -0
  90. package/src/0.8/components/layout/CardComponent.tsx +30 -0
  91. package/src/0.8/components/layout/ColumnComponent.tsx +93 -0
  92. package/src/0.8/components/layout/ListComponent.tsx +81 -0
  93. package/src/0.8/components/layout/ModalComponent.tsx +41 -0
  94. package/src/0.8/components/layout/RowComponent.tsx +94 -0
  95. package/src/0.8/components/layout/TabsComponent.tsx +59 -0
  96. package/src/0.8/components/layout/index.ts +10 -0
  97. package/src/0.8/components/layout/layout.test.tsx +558 -0
  98. package/src/0.8/contexts/A2UIProvider.test.tsx +226 -0
  99. package/src/0.8/contexts/A2UIProvider.tsx +54 -0
  100. package/src/0.8/contexts/ActionContext.test.tsx +242 -0
  101. package/src/0.8/contexts/ActionContext.tsx +105 -0
  102. package/src/0.8/contexts/ComponentsMapContext.tsx +125 -0
  103. package/src/0.8/contexts/DataModelContext.test.tsx +335 -0
  104. package/src/0.8/contexts/DataModelContext.tsx +184 -0
  105. package/src/0.8/contexts/SurfaceContext.test.tsx +339 -0
  106. package/src/0.8/contexts/SurfaceContext.tsx +197 -0
  107. package/src/0.8/hooks/useA2UIMessageHandler.test.tsx +399 -0
  108. package/src/0.8/hooks/useA2UIMessageHandler.ts +123 -0
  109. package/src/0.8/hooks/useComponent.test.tsx +148 -0
  110. package/src/0.8/hooks/useComponent.ts +39 -0
  111. package/src/0.8/hooks/useDataBinding.test.tsx +334 -0
  112. package/src/0.8/hooks/useDataBinding.ts +99 -0
  113. package/src/0.8/hooks/useDispatchAction.test.tsx +83 -0
  114. package/src/0.8/hooks/useDispatchAction.ts +35 -0
  115. package/src/0.8/hooks/useSurface.test.tsx +114 -0
  116. package/src/0.8/hooks/useSurface.ts +34 -0
  117. package/src/0.8/index.ts +38 -0
  118. package/src/0.8/schemas/client_to_server.json +50 -0
  119. package/src/0.8/schemas/server_to_client.json +148 -0
  120. package/src/0.8/schemas/standard_catalog_definition.json +661 -0
  121. package/src/0.8/types/index.ts +448 -0
  122. package/src/0.8/utils/dataBinding.test.ts +443 -0
  123. package/src/0.8/utils/dataBinding.ts +212 -0
  124. package/src/0.8/utils/pathUtils.test.ts +353 -0
  125. package/src/0.8/utils/pathUtils.ts +200 -0
  126. package/src/components/ui/button.tsx +62 -0
  127. package/src/components/ui/calendar.tsx +220 -0
  128. package/src/components/ui/card.tsx +92 -0
  129. package/src/components/ui/checkbox.tsx +30 -0
  130. package/src/components/ui/dialog.tsx +141 -0
  131. package/src/components/ui/input.tsx +21 -0
  132. package/src/components/ui/label.tsx +22 -0
  133. package/src/components/ui/native-select.tsx +53 -0
  134. package/src/components/ui/popover.tsx +46 -0
  135. package/src/components/ui/select.tsx +188 -0
  136. package/src/components/ui/separator.tsx +26 -0
  137. package/src/components/ui/slider.tsx +61 -0
  138. package/src/components/ui/tabs.tsx +64 -0
  139. package/src/components/ui/textarea.tsx +18 -0
  140. package/src/index.ts +1 -0
  141. package/src/lib/utils.ts +6 -0
  142. package/tsconfig.json +28 -0
  143. package/vite.config.ts +29 -0
  144. package/vitest.config.ts +22 -0
  145. package/vitest.setup.ts +8 -0
  146. package/website/README.md +4 -0
  147. package/website/assets/favicon.svg +8 -0
  148. package/website/content/.gitkeep +0 -0
  149. package/website/content/index.md +122 -0
  150. package/website/global.d.ts +9 -0
  151. package/website/package.json +17 -0
  152. package/website/plain.config.js +28 -0
  153. package/website/serve.json +6 -0
  154. package/website/src/client/color-mode-switch.css +47 -0
  155. package/website/src/client/index.js +61 -0
  156. package/website/src/client/moon.svg +1 -0
  157. package/website/src/client/sun.svg +1 -0
  158. package/website/src/components/Footer.jsx +9 -0
  159. package/website/src/components/Header.jsx +44 -0
  160. package/website/src/components/Page.jsx +28 -0
  161. package/website/src/global.css +423 -0
@@ -0,0 +1,558 @@
1
+ /**
2
+ * Layout Components Tests
3
+ *
4
+ * Tests for RowComponent, ColumnComponent, ListComponent, CardComponent,
5
+ * TabsComponent, and ModalComponent.
6
+ */
7
+
8
+ import { describe, it, expect, vi } from 'vitest'
9
+ import { render, screen, waitFor } from '@testing-library/react'
10
+ import userEvent from '@testing-library/user-event'
11
+ import { RowComponent } from './RowComponent'
12
+ import { ColumnComponent } from './ColumnComponent'
13
+ import { ListComponent } from './ListComponent'
14
+ import { CardComponent } from './CardComponent'
15
+ import { TabsComponent } from './TabsComponent'
16
+ import { ModalComponent } from './ModalComponent'
17
+ import {
18
+ DataModelProvider,
19
+ useDataModelContext,
20
+ } from '../../contexts/DataModelContext'
21
+ import { SurfaceProvider } from '../../contexts/SurfaceContext'
22
+ import type { ReactNode } from 'react'
23
+ import React from 'react'
24
+
25
+ // Mock ComponentRenderer
26
+ vi.mock('../ComponentRenderer', () => ({
27
+ ComponentRenderer: vi.fn(({ surfaceId, componentId }) => (
28
+ <div data-testid={`component-${componentId}`}>
29
+ Component: {componentId} (Surface: {surfaceId})
30
+ </div>
31
+ )),
32
+ }))
33
+
34
+ // Wrapper with providers
35
+ const wrapper = ({ children }: { children: ReactNode }) => (
36
+ <SurfaceProvider>
37
+ <DataModelProvider>{children}</DataModelProvider>
38
+ </SurfaceProvider>
39
+ )
40
+
41
+ describe('RowComponent', () => {
42
+ it('should render empty div when no children', () => {
43
+ const { container } = render(
44
+ <RowComponent surfaceId="surface-1" componentId="row-1" />,
45
+ { wrapper }
46
+ )
47
+ const div = container.firstChild as HTMLElement
48
+ expect(div).toBeInTheDocument()
49
+ expect(div.tagName).toBe('DIV')
50
+ })
51
+
52
+ it('should render with default distribution and alignment', () => {
53
+ const { container } = render(
54
+ <RowComponent surfaceId="surface-1" componentId="row-1" />,
55
+ { wrapper }
56
+ )
57
+ const div = container.firstChild as HTMLElement
58
+ expect(div).toHaveClass(
59
+ 'flex',
60
+ 'flex-row',
61
+ 'justify-start',
62
+ 'items-stretch'
63
+ )
64
+ })
65
+
66
+ it('should apply center distribution', () => {
67
+ const { container } = render(
68
+ <RowComponent
69
+ surfaceId="surface-1"
70
+ componentId="row-1"
71
+ distribution="center"
72
+ />,
73
+ { wrapper }
74
+ )
75
+ const div = container.firstChild as HTMLElement
76
+ expect(div).toHaveClass('justify-center')
77
+ })
78
+
79
+ it('should apply end distribution', () => {
80
+ const { container } = render(
81
+ <RowComponent
82
+ surfaceId="surface-1"
83
+ componentId="row-1"
84
+ distribution="end"
85
+ />,
86
+ { wrapper }
87
+ )
88
+ const div = container.firstChild as HTMLElement
89
+ expect(div).toHaveClass('justify-end')
90
+ })
91
+
92
+ it('should apply spaceBetween distribution', () => {
93
+ const { container } = render(
94
+ <RowComponent
95
+ surfaceId="surface-1"
96
+ componentId="row-1"
97
+ distribution="spaceBetween"
98
+ />,
99
+ { wrapper }
100
+ )
101
+ const div = container.firstChild as HTMLElement
102
+ expect(div).toHaveClass('justify-between')
103
+ })
104
+
105
+ it('should apply spaceAround distribution', () => {
106
+ const { container } = render(
107
+ <RowComponent
108
+ surfaceId="surface-1"
109
+ componentId="row-1"
110
+ distribution="spaceAround"
111
+ />,
112
+ { wrapper }
113
+ )
114
+ const div = container.firstChild as HTMLElement
115
+ expect(div).toHaveClass('justify-around')
116
+ })
117
+
118
+ it('should apply spaceEvenly distribution', () => {
119
+ const { container } = render(
120
+ <RowComponent
121
+ surfaceId="surface-1"
122
+ componentId="row-1"
123
+ distribution="spaceEvenly"
124
+ />,
125
+ { wrapper }
126
+ )
127
+ const div = container.firstChild as HTMLElement
128
+ expect(div).toHaveClass('justify-evenly')
129
+ })
130
+
131
+ it('should apply center alignment', () => {
132
+ const { container } = render(
133
+ <RowComponent
134
+ surfaceId="surface-1"
135
+ componentId="row-1"
136
+ alignment="center"
137
+ />,
138
+ { wrapper }
139
+ )
140
+ const div = container.firstChild as HTMLElement
141
+ expect(div).toHaveClass('items-center')
142
+ })
143
+
144
+ it('should apply start alignment', () => {
145
+ const { container } = render(
146
+ <RowComponent
147
+ surfaceId="surface-1"
148
+ componentId="row-1"
149
+ alignment="start"
150
+ />,
151
+ { wrapper }
152
+ )
153
+ const div = container.firstChild as HTMLElement
154
+ expect(div).toHaveClass('items-start')
155
+ })
156
+
157
+ it('should apply end alignment', () => {
158
+ const { container } = render(
159
+ <RowComponent
160
+ surfaceId="surface-1"
161
+ componentId="row-1"
162
+ alignment="end"
163
+ />,
164
+ { wrapper }
165
+ )
166
+ const div = container.firstChild as HTMLElement
167
+ expect(div).toHaveClass('items-end')
168
+ })
169
+
170
+ it('should render explicit list children', async () => {
171
+ render(
172
+ <RowComponent
173
+ surfaceId="surface-1"
174
+ componentId="row-1"
175
+ children={{ explicitList: ['child-1', 'child-2', 'child-3'] }}
176
+ />,
177
+ { wrapper }
178
+ )
179
+
180
+ await waitFor(() => {
181
+ expect(screen.getByTestId('component-child-1')).toBeInTheDocument()
182
+ expect(screen.getByTestId('component-child-2')).toBeInTheDocument()
183
+ expect(screen.getByTestId('component-child-3')).toBeInTheDocument()
184
+ })
185
+ })
186
+
187
+ it('should render template children from data model', async () => {
188
+ // Custom wrapper that sets up data model
189
+ const TestWrapper = ({ children }: { children: ReactNode }) => (
190
+ <SurfaceProvider>
191
+ <DataModelProvider>
192
+ <DataModelSetup>{children}</DataModelSetup>
193
+ </DataModelProvider>
194
+ </SurfaceProvider>
195
+ )
196
+
197
+ function DataModelSetup({ children }: { children: ReactNode }) {
198
+ const { setDataValue } = useDataModelContext()
199
+ React.useEffect(() => {
200
+ setDataValue('surface-1', '/items', { item1: {}, item2: {} })
201
+ }, [setDataValue])
202
+ return <>{children}</>
203
+ }
204
+
205
+ render(
206
+ <RowComponent
207
+ surfaceId="surface-1"
208
+ componentId="row-1"
209
+ children={{
210
+ template: { componentId: 'item-template', dataBinding: '/items' },
211
+ }}
212
+ />,
213
+ { wrapper: TestWrapper }
214
+ )
215
+
216
+ await waitFor(() => {
217
+ // Should render template component for each item
218
+ const components = screen.getAllByTestId('component-item-template')
219
+ expect(components.length).toBe(2)
220
+ })
221
+ })
222
+
223
+ it('should render empty div when template data is not found', () => {
224
+ const { container } = render(
225
+ <RowComponent
226
+ surfaceId="surface-1"
227
+ componentId="row-1"
228
+ children={{
229
+ template: {
230
+ componentId: 'item-template',
231
+ dataBinding: '/nonexistent',
232
+ },
233
+ }}
234
+ />,
235
+ { wrapper }
236
+ )
237
+ const div = container.firstChild as HTMLElement
238
+ expect(div.children.length).toBe(0)
239
+ })
240
+
241
+ it('should have correct displayName', () => {
242
+ expect(RowComponent.displayName).toBe('A2UI.Row')
243
+ })
244
+ })
245
+
246
+ describe('ColumnComponent', () => {
247
+ it('should render empty div when no children', () => {
248
+ const { container } = render(
249
+ <ColumnComponent surfaceId="surface-1" componentId="column-1" />,
250
+ { wrapper }
251
+ )
252
+ const div = container.firstChild as HTMLElement
253
+ expect(div).toBeInTheDocument()
254
+ expect(div.tagName).toBe('DIV')
255
+ })
256
+
257
+ it('should render with flex-col direction', () => {
258
+ const { container } = render(
259
+ <ColumnComponent surfaceId="surface-1" componentId="column-1" />,
260
+ { wrapper }
261
+ )
262
+ const div = container.firstChild as HTMLElement
263
+ expect(div).toHaveClass('flex', 'flex-col')
264
+ })
265
+
266
+ it('should apply distribution styles', () => {
267
+ const { container } = render(
268
+ <ColumnComponent
269
+ surfaceId="surface-1"
270
+ componentId="column-1"
271
+ distribution="center"
272
+ />,
273
+ { wrapper }
274
+ )
275
+ const div = container.firstChild as HTMLElement
276
+ expect(div).toHaveClass('justify-center')
277
+ })
278
+
279
+ it('should apply alignment styles', () => {
280
+ const { container } = render(
281
+ <ColumnComponent
282
+ surfaceId="surface-1"
283
+ componentId="column-1"
284
+ alignment="center"
285
+ />,
286
+ { wrapper }
287
+ )
288
+ const div = container.firstChild as HTMLElement
289
+ expect(div).toHaveClass('items-center')
290
+ })
291
+
292
+ it('should render explicit list children', async () => {
293
+ render(
294
+ <ColumnComponent
295
+ surfaceId="surface-1"
296
+ componentId="column-1"
297
+ children={{ explicitList: ['child-1', 'child-2'] }}
298
+ />,
299
+ { wrapper }
300
+ )
301
+
302
+ await waitFor(() => {
303
+ expect(screen.getByTestId('component-child-1')).toBeInTheDocument()
304
+ expect(screen.getByTestId('component-child-2')).toBeInTheDocument()
305
+ })
306
+ })
307
+
308
+ it('should have correct displayName', () => {
309
+ expect(ColumnComponent.displayName).toBe('A2UI.Column')
310
+ })
311
+ })
312
+
313
+ describe('ListComponent', () => {
314
+ it('should render empty div when no children', () => {
315
+ const { container } = render(
316
+ <ListComponent surfaceId="surface-1" componentId="list-1" />,
317
+ { wrapper }
318
+ )
319
+ const div = container.firstChild as HTMLElement
320
+ expect(div).toBeInTheDocument()
321
+ })
322
+
323
+ it('should render with vertical direction by default', () => {
324
+ const { container } = render(
325
+ <ListComponent surfaceId="surface-1" componentId="list-1" />,
326
+ { wrapper }
327
+ )
328
+ const div = container.firstChild as HTMLElement
329
+ expect(div).toHaveClass('flex', 'flex-col')
330
+ })
331
+
332
+ it('should render with horizontal direction', () => {
333
+ const { container } = render(
334
+ <ListComponent
335
+ surfaceId="surface-1"
336
+ componentId="list-1"
337
+ direction="horizontal"
338
+ />,
339
+ { wrapper }
340
+ )
341
+ const div = container.firstChild as HTMLElement
342
+ expect(div).toHaveClass('flex', 'flex-row')
343
+ })
344
+
345
+ it('should apply alignment styles', () => {
346
+ const { container } = render(
347
+ <ListComponent
348
+ surfaceId="surface-1"
349
+ componentId="list-1"
350
+ alignment="center"
351
+ />,
352
+ { wrapper }
353
+ )
354
+ const div = container.firstChild as HTMLElement
355
+ expect(div).toHaveClass('items-center')
356
+ })
357
+
358
+ it('should render explicit list children', async () => {
359
+ render(
360
+ <ListComponent
361
+ surfaceId="surface-1"
362
+ componentId="list-1"
363
+ children={{ explicitList: ['item-1', 'item-2'] }}
364
+ />,
365
+ { wrapper }
366
+ )
367
+
368
+ await waitFor(() => {
369
+ expect(screen.getByTestId('component-item-1')).toBeInTheDocument()
370
+ expect(screen.getByTestId('component-item-2')).toBeInTheDocument()
371
+ })
372
+ })
373
+
374
+ it('should have correct displayName', () => {
375
+ expect(ListComponent.displayName).toBe('A2UI.List')
376
+ })
377
+ })
378
+
379
+ describe('CardComponent', () => {
380
+ it('should render empty card when no child', () => {
381
+ const { container } = render(
382
+ <CardComponent surfaceId="surface-1" componentId="card-1" />,
383
+ { wrapper }
384
+ )
385
+ // Card component should be rendered
386
+ expect(container.firstChild).toBeInTheDocument()
387
+ })
388
+
389
+ it('should render child component', async () => {
390
+ render(
391
+ <CardComponent
392
+ surfaceId="surface-1"
393
+ componentId="card-1"
394
+ child="card-content"
395
+ />,
396
+ { wrapper }
397
+ )
398
+
399
+ await waitFor(() => {
400
+ expect(screen.getByTestId('component-card-content')).toBeInTheDocument()
401
+ })
402
+ })
403
+
404
+ it('should have correct displayName', () => {
405
+ expect(CardComponent.displayName).toBe('A2UI.Card')
406
+ })
407
+ })
408
+
409
+ describe('TabsComponent', () => {
410
+ it('should return null when no tabItems', () => {
411
+ const { container } = render(
412
+ <TabsComponent surfaceId="surface-1" componentId="tabs-1" />,
413
+ { wrapper }
414
+ )
415
+ expect(container.firstChild).toBeNull()
416
+ })
417
+
418
+ it('should return null when tabItems is empty', () => {
419
+ const { container } = render(
420
+ <TabsComponent
421
+ surfaceId="surface-1"
422
+ componentId="tabs-1"
423
+ tabItems={[]}
424
+ />,
425
+ { wrapper }
426
+ )
427
+ expect(container.firstChild).toBeNull()
428
+ })
429
+
430
+ it('should render tabs with items', async () => {
431
+ render(
432
+ <TabsComponent
433
+ surfaceId="surface-1"
434
+ componentId="tabs-1"
435
+ tabItems={[
436
+ { title: { literalString: 'Tab 1' }, child: 'content-1' },
437
+ { title: { literalString: 'Tab 2' }, child: 'content-2' },
438
+ ]}
439
+ />,
440
+ { wrapper }
441
+ )
442
+
443
+ await waitFor(() => {
444
+ expect(screen.getByText('Tab 1')).toBeInTheDocument()
445
+ expect(screen.getByText('Tab 2')).toBeInTheDocument()
446
+ })
447
+ })
448
+
449
+ it('should render default tab title when not provided', async () => {
450
+ render(
451
+ <TabsComponent
452
+ surfaceId="surface-1"
453
+ componentId="tabs-1"
454
+ tabItems={[{ child: 'content-1' }, { child: 'content-2' }]}
455
+ />,
456
+ { wrapper }
457
+ )
458
+
459
+ await waitFor(() => {
460
+ expect(screen.getByText('Tab 1')).toBeInTheDocument()
461
+ expect(screen.getByText('Tab 2')).toBeInTheDocument()
462
+ })
463
+ })
464
+
465
+ it('should render first tab content by default', async () => {
466
+ render(
467
+ <TabsComponent
468
+ surfaceId="surface-1"
469
+ componentId="tabs-1"
470
+ tabItems={[
471
+ { title: { literalString: 'Tab 1' }, child: 'content-1' },
472
+ { title: { literalString: 'Tab 2' }, child: 'content-2' },
473
+ ]}
474
+ />,
475
+ { wrapper }
476
+ )
477
+
478
+ await waitFor(() => {
479
+ expect(screen.getByTestId('component-content-1')).toBeInTheDocument()
480
+ })
481
+ })
482
+
483
+ it('should have correct displayName', () => {
484
+ expect(TabsComponent.displayName).toBe('A2UI.Tabs')
485
+ })
486
+ })
487
+
488
+ describe('ModalComponent', () => {
489
+ it('should return null when entryPointChild is missing', () => {
490
+ const { container } = render(
491
+ <ModalComponent
492
+ surfaceId="surface-1"
493
+ componentId="modal-1"
494
+ contentChild="content"
495
+ />,
496
+ { wrapper }
497
+ )
498
+ expect(container.firstChild).toBeNull()
499
+ })
500
+
501
+ it('should return null when contentChild is missing', () => {
502
+ const { container } = render(
503
+ <ModalComponent
504
+ surfaceId="surface-1"
505
+ componentId="modal-1"
506
+ entryPointChild="trigger"
507
+ />,
508
+ { wrapper }
509
+ )
510
+ expect(container.firstChild).toBeNull()
511
+ })
512
+
513
+ it('should render trigger component', async () => {
514
+ render(
515
+ <ModalComponent
516
+ surfaceId="surface-1"
517
+ componentId="modal-1"
518
+ entryPointChild="trigger"
519
+ contentChild="content"
520
+ />,
521
+ { wrapper }
522
+ )
523
+
524
+ await waitFor(() => {
525
+ expect(screen.getByTestId('component-trigger')).toBeInTheDocument()
526
+ })
527
+ })
528
+
529
+ it('should open modal when trigger is clicked', async () => {
530
+ const user = userEvent.setup()
531
+
532
+ render(
533
+ <ModalComponent
534
+ surfaceId="surface-1"
535
+ componentId="modal-1"
536
+ entryPointChild="trigger"
537
+ contentChild="modal-content"
538
+ />,
539
+ { wrapper }
540
+ )
541
+
542
+ await waitFor(() => {
543
+ expect(screen.getByTestId('component-trigger')).toBeInTheDocument()
544
+ })
545
+
546
+ // Click the trigger
547
+ await user.click(screen.getByTestId('component-trigger'))
548
+
549
+ // Modal content should be visible
550
+ await waitFor(() => {
551
+ expect(screen.getByTestId('component-modal-content')).toBeInTheDocument()
552
+ })
553
+ })
554
+
555
+ it('should have correct displayName', () => {
556
+ expect(ModalComponent.displayName).toBe('A2UI.Modal')
557
+ })
558
+ })