@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.
- package/.claude/commands/speckit.analyze.md +184 -0
- package/.claude/commands/speckit.checklist.md +294 -0
- package/.claude/commands/speckit.clarify.md +181 -0
- package/.claude/commands/speckit.constitution.md +82 -0
- package/.claude/commands/speckit.implement.md +135 -0
- package/.claude/commands/speckit.plan.md +89 -0
- package/.claude/commands/speckit.specify.md +256 -0
- package/.claude/commands/speckit.tasks.md +137 -0
- package/.claude/commands/speckit.taskstoissues.md +30 -0
- package/.github/workflows/deploy.yml +69 -0
- package/.husky/pre-commit +1 -0
- package/.prettierignore +4 -0
- package/.prettierrc +7 -0
- package/.specify/memory/constitution.md +73 -0
- package/.specify/scripts/bash/check-prerequisites.sh +166 -0
- package/.specify/scripts/bash/common.sh +156 -0
- package/.specify/scripts/bash/create-new-feature.sh +297 -0
- package/.specify/scripts/bash/setup-plan.sh +61 -0
- package/.specify/scripts/bash/update-agent-context.sh +799 -0
- package/.specify/templates/agent-file-template.md +28 -0
- package/.specify/templates/checklist-template.md +40 -0
- package/.specify/templates/plan-template.md +105 -0
- package/.specify/templates/spec-template.md +115 -0
- package/.specify/templates/tasks-template.md +250 -0
- package/CLAUDE.md +105 -0
- package/CONTRIBUTING.md +97 -0
- package/README.md +126 -0
- package/components.json +21 -0
- package/eslint.config.js +25 -0
- package/netlify.toml +50 -0
- package/package.json +94 -0
- package/playground/README.md +75 -0
- package/playground/index.html +22 -0
- package/playground/package.json +32 -0
- package/playground/public/favicon.svg +8 -0
- package/playground/src/App.css +256 -0
- package/playground/src/App.tsx +115 -0
- package/playground/src/assets/react.svg +1 -0
- package/playground/src/components/ErrorDisplay.tsx +13 -0
- package/playground/src/components/ExampleSelector.tsx +64 -0
- package/playground/src/components/Header.tsx +47 -0
- package/playground/src/components/JsonEditor.tsx +32 -0
- package/playground/src/components/Preview.tsx +78 -0
- package/playground/src/components/ThemeToggle.tsx +19 -0
- package/playground/src/data/examples.ts +1571 -0
- package/playground/src/hooks/useTheme.ts +55 -0
- package/playground/src/index.css +220 -0
- package/playground/src/main.tsx +10 -0
- package/playground/tsconfig.app.json +34 -0
- package/playground/tsconfig.json +13 -0
- package/playground/tsconfig.node.json +26 -0
- package/playground/vite.config.ts +31 -0
- package/specs/001-a2ui-renderer/checklists/requirements.md +41 -0
- package/specs/001-a2ui-renderer/data-model.md +140 -0
- package/specs/001-a2ui-renderer/plan.md +123 -0
- package/specs/001-a2ui-renderer/quickstart.md +141 -0
- package/specs/001-a2ui-renderer/research.md +140 -0
- package/specs/001-a2ui-renderer/spec.md +165 -0
- package/specs/001-a2ui-renderer/tasks.md +310 -0
- package/specs/002-playground/checklists/requirements.md +37 -0
- package/specs/002-playground/contracts/components.md +120 -0
- package/specs/002-playground/data-model.md +149 -0
- package/specs/002-playground/plan.md +73 -0
- package/specs/002-playground/quickstart.md +158 -0
- package/specs/002-playground/research.md +117 -0
- package/specs/002-playground/spec.md +109 -0
- package/specs/002-playground/tasks.md +224 -0
- package/src/0.8/A2UIRender.test.tsx +793 -0
- package/src/0.8/A2UIRender.tsx +142 -0
- package/src/0.8/components/ComponentRenderer.test.tsx +373 -0
- package/src/0.8/components/ComponentRenderer.tsx +163 -0
- package/src/0.8/components/UnknownComponent.tsx +49 -0
- package/src/0.8/components/display/AudioPlayerComponent.tsx +37 -0
- package/src/0.8/components/display/DividerComponent.tsx +23 -0
- package/src/0.8/components/display/IconComponent.tsx +137 -0
- package/src/0.8/components/display/ImageComponent.tsx +57 -0
- package/src/0.8/components/display/TextComponent.tsx +56 -0
- package/src/0.8/components/display/VideoComponent.tsx +31 -0
- package/src/0.8/components/display/display.test.tsx +660 -0
- package/src/0.8/components/display/index.ts +10 -0
- package/src/0.8/components/index.ts +14 -0
- package/src/0.8/components/interactive/ButtonComponent.tsx +44 -0
- package/src/0.8/components/interactive/CheckBoxComponent.tsx +45 -0
- package/src/0.8/components/interactive/DateTimeInputComponent.tsx +176 -0
- package/src/0.8/components/interactive/MultipleChoiceComponent.tsx +157 -0
- package/src/0.8/components/interactive/SliderComponent.tsx +53 -0
- package/src/0.8/components/interactive/TextFieldComponent.tsx +65 -0
- package/src/0.8/components/interactive/index.ts +10 -0
- package/src/0.8/components/interactive/interactive.test.tsx +618 -0
- package/src/0.8/components/layout/CardComponent.tsx +30 -0
- package/src/0.8/components/layout/ColumnComponent.tsx +93 -0
- package/src/0.8/components/layout/ListComponent.tsx +81 -0
- package/src/0.8/components/layout/ModalComponent.tsx +41 -0
- package/src/0.8/components/layout/RowComponent.tsx +94 -0
- package/src/0.8/components/layout/TabsComponent.tsx +59 -0
- package/src/0.8/components/layout/index.ts +10 -0
- package/src/0.8/components/layout/layout.test.tsx +558 -0
- package/src/0.8/contexts/A2UIProvider.test.tsx +226 -0
- package/src/0.8/contexts/A2UIProvider.tsx +54 -0
- package/src/0.8/contexts/ActionContext.test.tsx +242 -0
- package/src/0.8/contexts/ActionContext.tsx +105 -0
- package/src/0.8/contexts/ComponentsMapContext.tsx +125 -0
- package/src/0.8/contexts/DataModelContext.test.tsx +335 -0
- package/src/0.8/contexts/DataModelContext.tsx +184 -0
- package/src/0.8/contexts/SurfaceContext.test.tsx +339 -0
- package/src/0.8/contexts/SurfaceContext.tsx +197 -0
- package/src/0.8/hooks/useA2UIMessageHandler.test.tsx +399 -0
- package/src/0.8/hooks/useA2UIMessageHandler.ts +123 -0
- package/src/0.8/hooks/useComponent.test.tsx +148 -0
- package/src/0.8/hooks/useComponent.ts +39 -0
- package/src/0.8/hooks/useDataBinding.test.tsx +334 -0
- package/src/0.8/hooks/useDataBinding.ts +99 -0
- package/src/0.8/hooks/useDispatchAction.test.tsx +83 -0
- package/src/0.8/hooks/useDispatchAction.ts +35 -0
- package/src/0.8/hooks/useSurface.test.tsx +114 -0
- package/src/0.8/hooks/useSurface.ts +34 -0
- package/src/0.8/index.ts +38 -0
- package/src/0.8/schemas/client_to_server.json +50 -0
- package/src/0.8/schemas/server_to_client.json +148 -0
- package/src/0.8/schemas/standard_catalog_definition.json +661 -0
- package/src/0.8/types/index.ts +448 -0
- package/src/0.8/utils/dataBinding.test.ts +443 -0
- package/src/0.8/utils/dataBinding.ts +212 -0
- package/src/0.8/utils/pathUtils.test.ts +353 -0
- package/src/0.8/utils/pathUtils.ts +200 -0
- package/src/components/ui/button.tsx +62 -0
- package/src/components/ui/calendar.tsx +220 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/dialog.tsx +141 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +22 -0
- package/src/components/ui/native-select.tsx +53 -0
- package/src/components/ui/popover.tsx +46 -0
- package/src/components/ui/select.tsx +188 -0
- package/src/components/ui/separator.tsx +26 -0
- package/src/components/ui/slider.tsx +61 -0
- package/src/components/ui/tabs.tsx +64 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/index.ts +1 -0
- package/src/lib/utils.ts +6 -0
- package/tsconfig.json +28 -0
- package/vite.config.ts +29 -0
- package/vitest.config.ts +22 -0
- package/vitest.setup.ts +8 -0
- package/website/README.md +4 -0
- package/website/assets/favicon.svg +8 -0
- package/website/content/.gitkeep +0 -0
- package/website/content/index.md +122 -0
- package/website/global.d.ts +9 -0
- package/website/package.json +17 -0
- package/website/plain.config.js +28 -0
- package/website/serve.json +6 -0
- package/website/src/client/color-mode-switch.css +47 -0
- package/website/src/client/index.js +61 -0
- package/website/src/client/moon.svg +1 -0
- package/website/src/client/sun.svg +1 -0
- package/website/src/components/Footer.jsx +9 -0
- package/website/src/components/Header.jsx +44 -0
- package/website/src/components/Page.jsx +28 -0
- 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
|
+
})
|