@mdxui/terminal 2.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/README.md +571 -0
- package/dist/ansi-css-Sk5mWtdK.d.ts +119 -0
- package/dist/ansi-css-V6JIHGsM.d.ts +119 -0
- package/dist/ansi-css-_3eSEU9d.d.ts +119 -0
- package/dist/chunk-3EFDH7PK.js +5235 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-3X5IR6WE.js +884 -0
- package/dist/chunk-4FV5ZDCE.js +5236 -0
- package/dist/chunk-4OVMSF2J.js +243 -0
- package/dist/chunk-63FEETIS.js +4048 -0
- package/dist/chunk-B43KP7XJ.js +884 -0
- package/dist/chunk-BMTJXWUV.js +655 -0
- package/dist/chunk-C3SVH4N7.js +882 -0
- package/dist/chunk-EVWR7Y47.js +874 -0
- package/dist/chunk-F6A5VWUC.js +1285 -0
- package/dist/chunk-FD7KW7GE.js +882 -0
- package/dist/chunk-GBQ6UD6I.js +655 -0
- package/dist/chunk-GMDD3M6U.js +5227 -0
- package/dist/chunk-JBHRXOXM.js +1058 -0
- package/dist/chunk-JFOO3EYO.js +1182 -0
- package/dist/chunk-JQ5H3WXL.js +1291 -0
- package/dist/chunk-JQD5NASE.js +234 -0
- package/dist/chunk-KRHJP5R7.js +592 -0
- package/dist/chunk-KWF6WVJE.js +962 -0
- package/dist/chunk-LHYQVN3H.js +1038 -0
- package/dist/chunk-M3TLQLGC.js +1032 -0
- package/dist/chunk-MVW4Q5OP.js +240 -0
- package/dist/chunk-NXCZSWLU.js +1294 -0
- package/dist/chunk-O25TNRO6.js +607 -0
- package/dist/chunk-PNECDA2I.js +884 -0
- package/dist/chunk-QIHWRLJR.js +962 -0
- package/dist/chunk-QW5YMQ7K.js +882 -0
- package/dist/chunk-R5U7XKVJ.js +16 -0
- package/dist/chunk-RP2MVQLR.js +962 -0
- package/dist/chunk-TP6RXGXA.js +1087 -0
- package/dist/chunk-TQQSTITZ.js +655 -0
- package/dist/chunk-X24GWXQV.js +1281 -0
- package/dist/components/index.d.ts +802 -0
- package/dist/components/index.js +149 -0
- package/dist/data/index.d.ts +2554 -0
- package/dist/data/index.js +51 -0
- package/dist/forms/index.d.ts +1596 -0
- package/dist/forms/index.js +464 -0
- package/dist/index-CQRFZntR.d.ts +867 -0
- package/dist/index.d.ts +579 -0
- package/dist/index.js +786 -0
- package/dist/interactive-D0JkWosD.d.ts +217 -0
- package/dist/keyboard/index.d.ts +2 -0
- package/dist/keyboard/index.js +43 -0
- package/dist/renderers/index.d.ts +546 -0
- package/dist/renderers/index.js +2157 -0
- package/dist/storybook/index.d.ts +396 -0
- package/dist/storybook/index.js +641 -0
- package/dist/theme/index.d.ts +1339 -0
- package/dist/theme/index.js +123 -0
- package/dist/types-Bxu5PAgA.d.ts +710 -0
- package/dist/types-CIlop5Ji.d.ts +701 -0
- package/dist/types-Ca8p_p5X.d.ts +710 -0
- package/package.json +90 -0
- package/src/__tests__/components/data/card.test.ts +458 -0
- package/src/__tests__/components/data/list.test.ts +473 -0
- package/src/__tests__/components/data/metrics.test.ts +541 -0
- package/src/__tests__/components/data/table.test.ts +448 -0
- package/src/__tests__/components/input/field.test.ts +555 -0
- package/src/__tests__/components/input/form.test.ts +870 -0
- package/src/__tests__/components/input/search.test.ts +1238 -0
- package/src/__tests__/components/input/select.test.ts +658 -0
- package/src/__tests__/components/navigation/breadcrumb.test.ts +923 -0
- package/src/__tests__/components/navigation/command-palette.test.ts +1095 -0
- package/src/__tests__/components/navigation/sidebar.test.ts +1018 -0
- package/src/__tests__/components/navigation/tabs.test.ts +995 -0
- package/src/__tests__/components.test.tsx +1197 -0
- package/src/__tests__/core/compiler.test.ts +986 -0
- package/src/__tests__/core/parser.test.ts +785 -0
- package/src/__tests__/core/tier-switcher.test.ts +1103 -0
- package/src/__tests__/core/types.test.ts +1398 -0
- package/src/__tests__/data/collections.test.ts +1337 -0
- package/src/__tests__/data/db.test.ts +1265 -0
- package/src/__tests__/data/reactive.test.ts +1010 -0
- package/src/__tests__/data/sync.test.ts +1614 -0
- package/src/__tests__/errors.test.ts +660 -0
- package/src/__tests__/forms/integration.test.ts +444 -0
- package/src/__tests__/integration.test.ts +905 -0
- package/src/__tests__/keyboard.test.ts +1791 -0
- package/src/__tests__/renderer.test.ts +489 -0
- package/src/__tests__/renderers/ansi-css.test.ts +948 -0
- package/src/__tests__/renderers/ansi.test.ts +1366 -0
- package/src/__tests__/renderers/ascii.test.ts +1360 -0
- package/src/__tests__/renderers/interactive.test.ts +2353 -0
- package/src/__tests__/renderers/markdown.test.ts +1483 -0
- package/src/__tests__/renderers/text.test.ts +1369 -0
- package/src/__tests__/renderers/unicode.test.ts +1307 -0
- package/src/__tests__/theme.test.ts +639 -0
- package/src/__tests__/utils/assertions.ts +685 -0
- package/src/__tests__/utils/index.ts +115 -0
- package/src/__tests__/utils/test-renderer.ts +381 -0
- package/src/__tests__/utils/utils.test.ts +560 -0
- package/src/components/containers/card.ts +56 -0
- package/src/components/containers/dialog.ts +53 -0
- package/src/components/containers/index.ts +9 -0
- package/src/components/containers/panel.ts +59 -0
- package/src/components/feedback/badge.ts +40 -0
- package/src/components/feedback/index.ts +8 -0
- package/src/components/feedback/spinner.ts +23 -0
- package/src/components/helpers.ts +81 -0
- package/src/components/index.ts +153 -0
- package/src/components/layout/breadcrumb.ts +31 -0
- package/src/components/layout/index.ts +10 -0
- package/src/components/layout/list.ts +29 -0
- package/src/components/layout/sidebar.ts +79 -0
- package/src/components/layout/table.ts +62 -0
- package/src/components/primitives/box.ts +95 -0
- package/src/components/primitives/button.ts +54 -0
- package/src/components/primitives/index.ts +11 -0
- package/src/components/primitives/input.ts +88 -0
- package/src/components/primitives/select.ts +97 -0
- package/src/components/primitives/text.ts +60 -0
- package/src/components/render.ts +155 -0
- package/src/components/templates/app.ts +43 -0
- package/src/components/templates/index.ts +8 -0
- package/src/components/templates/site.ts +54 -0
- package/src/components/types.ts +777 -0
- package/src/core/compiler.ts +718 -0
- package/src/core/parser.ts +127 -0
- package/src/core/tier-switcher.ts +607 -0
- package/src/core/types.ts +672 -0
- package/src/data/collection.ts +316 -0
- package/src/data/collections.ts +50 -0
- package/src/data/context.tsx +174 -0
- package/src/data/db.ts +127 -0
- package/src/data/hooks.ts +532 -0
- package/src/data/index.ts +138 -0
- package/src/data/reactive.ts +1225 -0
- package/src/data/saas-collections.ts +375 -0
- package/src/data/sync.ts +1213 -0
- package/src/data/types.ts +660 -0
- package/src/forms/converters.ts +512 -0
- package/src/forms/index.ts +133 -0
- package/src/forms/schemas.ts +403 -0
- package/src/forms/types.ts +476 -0
- package/src/index.ts +542 -0
- package/src/keyboard/focus.ts +748 -0
- package/src/keyboard/index.ts +96 -0
- package/src/keyboard/integration.ts +371 -0
- package/src/keyboard/manager.ts +377 -0
- package/src/keyboard/presets.ts +90 -0
- package/src/renderers/ansi-css.ts +576 -0
- package/src/renderers/ansi.ts +802 -0
- package/src/renderers/ascii.ts +680 -0
- package/src/renderers/breadcrumb.ts +480 -0
- package/src/renderers/command-palette.ts +802 -0
- package/src/renderers/components/field.ts +210 -0
- package/src/renderers/components/form.ts +327 -0
- package/src/renderers/components/index.ts +21 -0
- package/src/renderers/components/search.ts +449 -0
- package/src/renderers/components/select.ts +222 -0
- package/src/renderers/index.ts +101 -0
- package/src/renderers/interactive/component-handlers.ts +622 -0
- package/src/renderers/interactive/cursor-manager.ts +147 -0
- package/src/renderers/interactive/focus-manager.ts +279 -0
- package/src/renderers/interactive/index.ts +661 -0
- package/src/renderers/interactive/input-handler.ts +164 -0
- package/src/renderers/interactive/keyboard-handler.ts +212 -0
- package/src/renderers/interactive/mouse-handler.ts +167 -0
- package/src/renderers/interactive/state-manager.ts +109 -0
- package/src/renderers/interactive/types.ts +338 -0
- package/src/renderers/interactive-string.ts +299 -0
- package/src/renderers/interactive.ts +59 -0
- package/src/renderers/markdown.ts +950 -0
- package/src/renderers/sidebar.ts +549 -0
- package/src/renderers/tabs.ts +682 -0
- package/src/renderers/text.ts +791 -0
- package/src/renderers/unicode.ts +917 -0
- package/src/renderers/utils.ts +942 -0
- package/src/router/adapters.ts +383 -0
- package/src/router/types.ts +140 -0
- package/src/router/utils.ts +452 -0
- package/src/schemas.ts +205 -0
- package/src/storybook/index.ts +91 -0
- package/src/storybook/interactive-decorator.tsx +659 -0
- package/src/storybook/keyboard-simulator.ts +501 -0
- package/src/theme/ansi-codes.ts +80 -0
- package/src/theme/box-drawing.ts +132 -0
- package/src/theme/color-convert.ts +254 -0
- package/src/theme/color-support.ts +321 -0
- package/src/theme/index.ts +134 -0
- package/src/theme/strip-ansi.ts +50 -0
- package/src/theme/tailwind-map.ts +469 -0
- package/src/theme/text-styles.ts +206 -0
- package/src/theme/theme-system.ts +568 -0
- package/src/types.ts +103 -0
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mdxui/terminal JSON → UINode Parser Tests (RED phase)
|
|
3
|
+
*
|
|
4
|
+
* TDD RED Phase: These tests define the contract for JSON/YAML parsing
|
|
5
|
+
* functionality that converts JSON strings and objects into UINode structures.
|
|
6
|
+
*
|
|
7
|
+
* The parser must handle:
|
|
8
|
+
* - Valid JSON object → UINode conversion
|
|
9
|
+
* - Nested structure parsing with recursive children
|
|
10
|
+
* - Array children parsing
|
|
11
|
+
* - Schema validation using UINodeSchema
|
|
12
|
+
* - Comprehensive error handling for invalid JSON
|
|
13
|
+
* - Comprehensive error handling for invalid UINode structures
|
|
14
|
+
* - Optional: YAML string parsing support
|
|
15
|
+
*
|
|
16
|
+
* NOTE: These tests are expected to FAIL until the parser implementation
|
|
17
|
+
* is complete. Run: pnpm --filter @mdxui/terminal test
|
|
18
|
+
*/
|
|
19
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
20
|
+
import { z } from 'zod'
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// These imports WILL FAIL until src/core/parser.ts is implemented
|
|
24
|
+
// ============================================================================
|
|
25
|
+
import { parseUINode, parseUINodeFromJSON, ParseError } from '../../core/parser'
|
|
26
|
+
|
|
27
|
+
import type { UINode } from '../../core/types'
|
|
28
|
+
import { UINodeSchema } from '../../core/types'
|
|
29
|
+
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// Test Utilities
|
|
32
|
+
// ============================================================================
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Helper to create a valid minimal UINode for testing
|
|
36
|
+
*/
|
|
37
|
+
function createNode(
|
|
38
|
+
type: string,
|
|
39
|
+
props: Record<string, unknown> = {},
|
|
40
|
+
children?: UINode[],
|
|
41
|
+
data?: unknown,
|
|
42
|
+
key?: string
|
|
43
|
+
): UINode {
|
|
44
|
+
return { type, props, ...(children && { children }), ...(data && { data }), ...(key && { key }) }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// parseUINode Function Tests - Valid Parsing
|
|
49
|
+
// ============================================================================
|
|
50
|
+
|
|
51
|
+
describe('parseUINode function', () => {
|
|
52
|
+
describe('valid JSON string parsing', () => {
|
|
53
|
+
it('parses minimal valid JSON string to UINode', () => {
|
|
54
|
+
const json = '{"type":"text","props":{}}'
|
|
55
|
+
const result = parseUINode(json)
|
|
56
|
+
|
|
57
|
+
expect(result).toBeDefined()
|
|
58
|
+
expect(result.type).toBe('text')
|
|
59
|
+
expect(result.props).toEqual({})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('parses JSON string with props', () => {
|
|
63
|
+
const json = '{"type":"box","props":{"padding":2,"border":"single"}}'
|
|
64
|
+
const result = parseUINode(json)
|
|
65
|
+
|
|
66
|
+
expect(result.type).toBe('box')
|
|
67
|
+
expect(result.props.padding).toBe(2)
|
|
68
|
+
expect(result.props.border).toBe('single')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('parses JSON string with empty children array', () => {
|
|
72
|
+
const json = '{"type":"box","props":{},"children":[]}'
|
|
73
|
+
const result = parseUINode(json)
|
|
74
|
+
|
|
75
|
+
expect(result.children).toEqual([])
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('parses JSON string with single child', () => {
|
|
79
|
+
const json =
|
|
80
|
+
'{"type":"box","props":{},"children":[{"type":"text","props":{"content":"Child"}}]}'
|
|
81
|
+
const result = parseUINode(json)
|
|
82
|
+
|
|
83
|
+
expect(result.children).toHaveLength(1)
|
|
84
|
+
expect(result.children![0].type).toBe('text')
|
|
85
|
+
expect(result.children![0].props.content).toBe('Child')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('parses JSON string with multiple children', () => {
|
|
89
|
+
const json =
|
|
90
|
+
'{"type":"box","props":{},"children":[{"type":"text","props":{"content":"First"}},{"type":"text","props":{"content":"Second"}}]}'
|
|
91
|
+
const result = parseUINode(json)
|
|
92
|
+
|
|
93
|
+
expect(result.children).toHaveLength(2)
|
|
94
|
+
expect(result.children![0].props.content).toBe('First')
|
|
95
|
+
expect(result.children![1].props.content).toBe('Second')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('parses JSON string with data field', () => {
|
|
99
|
+
const json = '{"type":"table","props":{},"data":{"rows":[{"id":1,"name":"Alice"}]}}'
|
|
100
|
+
const result = parseUINode(json)
|
|
101
|
+
|
|
102
|
+
expect(result.type).toBe('table')
|
|
103
|
+
expect(result.data).toBeDefined()
|
|
104
|
+
expect((result.data as any).rows).toHaveLength(1)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('parses JSON string with key field', () => {
|
|
108
|
+
const json = '{"type":"item","props":{},"key":"item-123"}'
|
|
109
|
+
const result = parseUINode(json)
|
|
110
|
+
|
|
111
|
+
expect(result.key).toBe('item-123')
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('parses JSON string with all optional fields', () => {
|
|
115
|
+
const json =
|
|
116
|
+
'{"type":"complex","props":{"flag":true},"children":[{"type":"child","props":{}}],"data":{"value":42},"key":"unique"}'
|
|
117
|
+
const result = parseUINode(json)
|
|
118
|
+
|
|
119
|
+
expect(result.type).toBe('complex')
|
|
120
|
+
expect(result.props.flag).toBe(true)
|
|
121
|
+
expect(result.children).toHaveLength(1)
|
|
122
|
+
expect(result.data).toEqual({ value: 42 })
|
|
123
|
+
expect(result.key).toBe('unique')
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('parses JSON with whitespace and newlines', () => {
|
|
127
|
+
const json = `{
|
|
128
|
+
"type": "text",
|
|
129
|
+
"props": {
|
|
130
|
+
"content": "Hello"
|
|
131
|
+
}
|
|
132
|
+
}`
|
|
133
|
+
const result = parseUINode(json)
|
|
134
|
+
|
|
135
|
+
expect(result.type).toBe('text')
|
|
136
|
+
expect(result.props.content).toBe('Hello')
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
describe('valid parsed object parsing', () => {
|
|
141
|
+
it('accepts pre-parsed JavaScript object', () => {
|
|
142
|
+
const obj: UINode = {
|
|
143
|
+
type: 'text',
|
|
144
|
+
props: { content: 'Direct object' },
|
|
145
|
+
}
|
|
146
|
+
const result = parseUINode(obj)
|
|
147
|
+
|
|
148
|
+
expect(result.type).toBe('text')
|
|
149
|
+
expect(result.props.content).toBe('Direct object')
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('accepts object with nested children', () => {
|
|
153
|
+
const obj: UINode = {
|
|
154
|
+
type: 'box',
|
|
155
|
+
props: {},
|
|
156
|
+
children: [{ type: 'text', props: { content: 'Nested' } }],
|
|
157
|
+
}
|
|
158
|
+
const result = parseUINode(obj)
|
|
159
|
+
|
|
160
|
+
expect(result.children![0].type).toBe('text')
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
describe('deeply nested structure parsing', () => {
|
|
165
|
+
it('parses 2-level nested structure', () => {
|
|
166
|
+
const json = `{
|
|
167
|
+
"type": "box",
|
|
168
|
+
"props": {},
|
|
169
|
+
"children": [
|
|
170
|
+
{
|
|
171
|
+
"type": "panel",
|
|
172
|
+
"props": { "title": "Panel" },
|
|
173
|
+
"children": [
|
|
174
|
+
{ "type": "text", "props": { "content": "Nested" } }
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
}`
|
|
179
|
+
const result = parseUINode(json)
|
|
180
|
+
|
|
181
|
+
expect(result.children![0].type).toBe('panel')
|
|
182
|
+
expect(result.children![0].children![0].type).toBe('text')
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('parses 3-level nested structure', () => {
|
|
186
|
+
const json = `{
|
|
187
|
+
"type": "container",
|
|
188
|
+
"props": {},
|
|
189
|
+
"children": [
|
|
190
|
+
{
|
|
191
|
+
"type": "row",
|
|
192
|
+
"props": {},
|
|
193
|
+
"children": [
|
|
194
|
+
{
|
|
195
|
+
"type": "column",
|
|
196
|
+
"props": {},
|
|
197
|
+
"children": [
|
|
198
|
+
{ "type": "text", "props": { "content": "Deep" } }
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
]
|
|
204
|
+
}`
|
|
205
|
+
const result = parseUINode(json)
|
|
206
|
+
|
|
207
|
+
expect(result.children![0].children![0].children![0].type).toBe('text')
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('parses complex nested tree with multiple branches', () => {
|
|
211
|
+
const json = `{
|
|
212
|
+
"type": "root",
|
|
213
|
+
"props": {},
|
|
214
|
+
"children": [
|
|
215
|
+
{
|
|
216
|
+
"type": "branch1",
|
|
217
|
+
"props": {},
|
|
218
|
+
"children": [
|
|
219
|
+
{ "type": "leaf1a", "props": {} },
|
|
220
|
+
{ "type": "leaf1b", "props": {} }
|
|
221
|
+
]
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"type": "branch2",
|
|
225
|
+
"props": {},
|
|
226
|
+
"children": [
|
|
227
|
+
{ "type": "leaf2a", "props": {} }
|
|
228
|
+
]
|
|
229
|
+
}
|
|
230
|
+
]
|
|
231
|
+
}`
|
|
232
|
+
const result = parseUINode(json)
|
|
233
|
+
|
|
234
|
+
expect(result.children).toHaveLength(2)
|
|
235
|
+
expect(result.children![0].children).toHaveLength(2)
|
|
236
|
+
expect(result.children![1].children).toHaveLength(1)
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
describe('array children parsing', () => {
|
|
241
|
+
it('handles empty children array', () => {
|
|
242
|
+
const json = '{"type":"box","props":{},"children":[]}'
|
|
243
|
+
const result = parseUINode(json)
|
|
244
|
+
|
|
245
|
+
expect(Array.isArray(result.children)).toBe(true)
|
|
246
|
+
expect(result.children).toHaveLength(0)
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
it('handles children with mixed prop types', () => {
|
|
250
|
+
const json = `{
|
|
251
|
+
"type": "container",
|
|
252
|
+
"props": {},
|
|
253
|
+
"children": [
|
|
254
|
+
{ "type": "node1", "props": { "string": "text", "number": 42, "bool": true } },
|
|
255
|
+
{ "type": "node2", "props": { "array": [1,2,3], "obj": { "nested": true } } }
|
|
256
|
+
]
|
|
257
|
+
}`
|
|
258
|
+
const result = parseUINode(json)
|
|
259
|
+
|
|
260
|
+
expect(result.children![0].props.string).toBe('text')
|
|
261
|
+
expect(result.children![0].props.number).toBe(42)
|
|
262
|
+
expect(result.children![0].props.bool).toBe(true)
|
|
263
|
+
expect(Array.isArray(result.children![1].props.array)).toBe(true)
|
|
264
|
+
expect(typeof result.children![1].props.obj).toBe('object')
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('preserves child order', () => {
|
|
268
|
+
const json = `{
|
|
269
|
+
"type": "list",
|
|
270
|
+
"props": {},
|
|
271
|
+
"children": [
|
|
272
|
+
{ "type": "item", "props": { "index": 0 } },
|
|
273
|
+
{ "type": "item", "props": { "index": 1 } },
|
|
274
|
+
{ "type": "item", "props": { "index": 2 } }
|
|
275
|
+
]
|
|
276
|
+
}`
|
|
277
|
+
const result = parseUINode(json)
|
|
278
|
+
|
|
279
|
+
expect(result.children![0].props.index).toBe(0)
|
|
280
|
+
expect(result.children![1].props.index).toBe(1)
|
|
281
|
+
expect(result.children![2].props.index).toBe(2)
|
|
282
|
+
})
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
describe('schema validation', () => {
|
|
286
|
+
it('validates parsed node against UINodeSchema', () => {
|
|
287
|
+
const json = '{"type":"text","props":{"content":"Valid"}}'
|
|
288
|
+
const result = parseUINode(json)
|
|
289
|
+
|
|
290
|
+
const validation = UINodeSchema.safeParse(result)
|
|
291
|
+
expect(validation.success).toBe(true)
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
it('validates nested children against UINodeSchema', () => {
|
|
295
|
+
const json = `{
|
|
296
|
+
"type": "box",
|
|
297
|
+
"props": {},
|
|
298
|
+
"children": [
|
|
299
|
+
{ "type": "text", "props": {} }
|
|
300
|
+
]
|
|
301
|
+
}`
|
|
302
|
+
const result = parseUINode(json)
|
|
303
|
+
|
|
304
|
+
const validation = UINodeSchema.safeParse(result)
|
|
305
|
+
expect(validation.success).toBe(true)
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
it('ensures all required fields are present after parsing', () => {
|
|
309
|
+
const json = '{"type":"component","props":{"prop1":"value"}}'
|
|
310
|
+
const result = parseUINode(json)
|
|
311
|
+
|
|
312
|
+
expect(result.type).toBeDefined()
|
|
313
|
+
expect(typeof result.type).toBe('string')
|
|
314
|
+
expect(result.props).toBeDefined()
|
|
315
|
+
expect(typeof result.props).toBe('object')
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
// ============================================================================
|
|
321
|
+
// parseUINode Function Tests - Invalid Input Handling
|
|
322
|
+
// ============================================================================
|
|
323
|
+
|
|
324
|
+
describe('parseUINode error handling', () => {
|
|
325
|
+
describe('invalid JSON strings', () => {
|
|
326
|
+
it('throws ParseError for malformed JSON', () => {
|
|
327
|
+
const invalidJson = '{type: "text"}'
|
|
328
|
+
expect(() => parseUINode(invalidJson)).toThrow(ParseError)
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
it('throws ParseError for incomplete JSON', () => {
|
|
332
|
+
const invalidJson = '{"type":"text"'
|
|
333
|
+
expect(() => parseUINode(invalidJson)).toThrow(ParseError)
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
it('throws ParseError for trailing comma in JSON', () => {
|
|
337
|
+
const invalidJson = '{"type":"text","props":{},}'
|
|
338
|
+
expect(() => parseUINode(invalidJson)).toThrow(ParseError)
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
it('throws ParseError for single quotes in JSON', () => {
|
|
342
|
+
const invalidJson = "{'type':'text'}"
|
|
343
|
+
expect(() => parseUINode(invalidJson)).toThrow(ParseError)
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
it('throws ParseError for undefined value in JSON', () => {
|
|
347
|
+
const invalidJson = '{"type":"text","props":undefined}'
|
|
348
|
+
expect(() => parseUINode(invalidJson)).toThrow(ParseError)
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
it('throws ParseError for JSON array as root', () => {
|
|
352
|
+
const invalidJson = '[{"type":"text","props":{}}]'
|
|
353
|
+
expect(() => parseUINode(invalidJson)).toThrow(ParseError)
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
it('throws ParseError for null value', () => {
|
|
357
|
+
const invalidJson = 'null'
|
|
358
|
+
expect(() => parseUINode(invalidJson)).toThrow(ParseError)
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
it('throws ParseError for JSON string', () => {
|
|
362
|
+
const invalidJson = '"just a string"'
|
|
363
|
+
expect(() => parseUINode(invalidJson)).toThrow(ParseError)
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
it('throws ParseError for JSON number', () => {
|
|
367
|
+
const invalidJson = '42'
|
|
368
|
+
expect(() => parseUINode(invalidJson)).toThrow(ParseError)
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
it('throws ParseError for empty string', () => {
|
|
372
|
+
const invalidJson = ''
|
|
373
|
+
expect(() => parseUINode(invalidJson)).toThrow(ParseError)
|
|
374
|
+
})
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
describe('invalid UINode structure', () => {
|
|
378
|
+
it('throws ParseError for missing type field', () => {
|
|
379
|
+
const json = '{"props":{}}'
|
|
380
|
+
expect(() => parseUINode(json)).toThrow(ParseError)
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
it('accepts missing props field (props is optional)', () => {
|
|
384
|
+
const json = '{"type":"text"}'
|
|
385
|
+
const result = parseUINode(json)
|
|
386
|
+
expect(result.type).toBe('text')
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
it('throws ParseError for non-string type', () => {
|
|
390
|
+
const json = '{"type":123,"props":{}}'
|
|
391
|
+
expect(() => parseUINode(json)).toThrow(ParseError)
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
it('throws ParseError for non-object props', () => {
|
|
395
|
+
const json = '{"type":"text","props":"invalid"}'
|
|
396
|
+
expect(() => parseUINode(json)).toThrow(ParseError)
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
it('throws ParseError for array props', () => {
|
|
400
|
+
const json = '{"type":"text","props":[]}'
|
|
401
|
+
expect(() => parseUINode(json)).toThrow(ParseError)
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
it('accepts string children (children can be string or array)', () => {
|
|
405
|
+
const json = '{"type":"box","props":{},"children":"string content"}'
|
|
406
|
+
const result = parseUINode(json)
|
|
407
|
+
expect(result.children).toBe('string content')
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
it('throws ParseError for invalid child in children array', () => {
|
|
411
|
+
const json = `{
|
|
412
|
+
"type": "box",
|
|
413
|
+
"props": {},
|
|
414
|
+
"children": [
|
|
415
|
+
{ "type": "valid", "props": {} },
|
|
416
|
+
"invalid child",
|
|
417
|
+
{ "props": {} }
|
|
418
|
+
]
|
|
419
|
+
}`
|
|
420
|
+
expect(() => parseUINode(json)).toThrow(ParseError)
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
it('throws ParseError for non-string key', () => {
|
|
424
|
+
const json = '{"type":"item","props":{},"key":123}'
|
|
425
|
+
expect(() => parseUINode(json)).toThrow(ParseError)
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
it('throws ParseError for deeply nested invalid child', () => {
|
|
429
|
+
const json = `{
|
|
430
|
+
"type": "root",
|
|
431
|
+
"props": {},
|
|
432
|
+
"children": [
|
|
433
|
+
{
|
|
434
|
+
"type": "parent",
|
|
435
|
+
"props": {},
|
|
436
|
+
"children": [
|
|
437
|
+
{ "invalid": true }
|
|
438
|
+
]
|
|
439
|
+
}
|
|
440
|
+
]
|
|
441
|
+
}`
|
|
442
|
+
expect(() => parseUINode(json)).toThrow(ParseError)
|
|
443
|
+
})
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
describe('error information', () => {
|
|
447
|
+
it('ParseError contains helpful message for missing type', () => {
|
|
448
|
+
const json = '{"props":{}}'
|
|
449
|
+
try {
|
|
450
|
+
parseUINode(json)
|
|
451
|
+
expect.fail('Should have thrown ParseError')
|
|
452
|
+
} catch (error) {
|
|
453
|
+
expect(error).toBeInstanceOf(ParseError)
|
|
454
|
+
expect((error as ParseError).message).toContain('type')
|
|
455
|
+
}
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
it('ParseError contains helpful message for invalid JSON', () => {
|
|
459
|
+
const json = '{invalid}'
|
|
460
|
+
try {
|
|
461
|
+
parseUINode(json)
|
|
462
|
+
expect.fail('Should have thrown ParseError')
|
|
463
|
+
} catch (error) {
|
|
464
|
+
expect(error).toBeInstanceOf(ParseError)
|
|
465
|
+
expect((error as ParseError).message).toBeDefined()
|
|
466
|
+
}
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
it('ParseError includes parsing context', () => {
|
|
470
|
+
const json = '{"type":"test","props":{"nested":{"value":incomplete}}}'
|
|
471
|
+
try {
|
|
472
|
+
parseUINode(json)
|
|
473
|
+
expect.fail('Should have thrown ParseError')
|
|
474
|
+
} catch (error) {
|
|
475
|
+
expect(error).toBeInstanceOf(ParseError)
|
|
476
|
+
}
|
|
477
|
+
})
|
|
478
|
+
})
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
// ============================================================================
|
|
482
|
+
// parseUINodeFromJSON Helper Function Tests
|
|
483
|
+
// ============================================================================
|
|
484
|
+
|
|
485
|
+
describe('parseUINodeFromJSON function', () => {
|
|
486
|
+
describe('parsing from JSON string', () => {
|
|
487
|
+
it('parses valid JSON string', () => {
|
|
488
|
+
const json = '{"type":"text","props":{}}'
|
|
489
|
+
const result = parseUINodeFromJSON(json)
|
|
490
|
+
|
|
491
|
+
expect(result).toBeDefined()
|
|
492
|
+
expect(result.type).toBe('text')
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
it('parses JSON with complex props', () => {
|
|
496
|
+
const json = `{
|
|
497
|
+
"type": "component",
|
|
498
|
+
"props": {
|
|
499
|
+
"title": "Example",
|
|
500
|
+
"count": 5,
|
|
501
|
+
"active": true,
|
|
502
|
+
"tags": ["a", "b", "c"],
|
|
503
|
+
"metadata": { "key": "value" }
|
|
504
|
+
}
|
|
505
|
+
}`
|
|
506
|
+
const result = parseUINodeFromJSON(json)
|
|
507
|
+
|
|
508
|
+
expect(result.props.title).toBe('Example')
|
|
509
|
+
expect(result.props.count).toBe(5)
|
|
510
|
+
expect(result.props.active).toBe(true)
|
|
511
|
+
expect(Array.isArray(result.props.tags)).toBe(true)
|
|
512
|
+
expect(typeof result.props.metadata).toBe('object')
|
|
513
|
+
})
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
describe('error handling for JSON string', () => {
|
|
517
|
+
it('throws ParseError for invalid JSON', () => {
|
|
518
|
+
const json = '{type: "invalid"}'
|
|
519
|
+
expect(() => parseUINodeFromJSON(json)).toThrow(ParseError)
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
it('throws ParseError for non-object JSON', () => {
|
|
523
|
+
const json = '[1, 2, 3]'
|
|
524
|
+
expect(() => parseUINodeFromJSON(json)).toThrow(ParseError)
|
|
525
|
+
})
|
|
526
|
+
})
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
// ============================================================================
|
|
530
|
+
// Edge Cases and Special Scenarios
|
|
531
|
+
// ============================================================================
|
|
532
|
+
|
|
533
|
+
describe('edge cases', () => {
|
|
534
|
+
describe('special prop values', () => {
|
|
535
|
+
it('handles null props values', () => {
|
|
536
|
+
const json = '{"type":"component","props":{"nullable":null}}'
|
|
537
|
+
const result = parseUINode(json)
|
|
538
|
+
|
|
539
|
+
expect(result.props.nullable).toBeNull()
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
it('handles boolean props', () => {
|
|
543
|
+
const json = '{"type":"component","props":{"disabled":false,"enabled":true}}'
|
|
544
|
+
const result = parseUINode(json)
|
|
545
|
+
|
|
546
|
+
expect(result.props.disabled).toBe(false)
|
|
547
|
+
expect(result.props.enabled).toBe(true)
|
|
548
|
+
})
|
|
549
|
+
|
|
550
|
+
it('handles numeric props (integers and floats)', () => {
|
|
551
|
+
const json = '{"type":"component","props":{"integer":42,"float":3.14}}'
|
|
552
|
+
const result = parseUINode(json)
|
|
553
|
+
|
|
554
|
+
expect(result.props.integer).toBe(42)
|
|
555
|
+
expect(result.props.float).toBe(3.14)
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
it('handles string props with special characters', () => {
|
|
559
|
+
const json = '{"type":"component","props":{"text":"Hello\\nWorld\\t!"}}'
|
|
560
|
+
const result = parseUINode(json)
|
|
561
|
+
|
|
562
|
+
expect(result.props.text).toContain('Hello')
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
it('handles empty string props', () => {
|
|
566
|
+
const json = '{"type":"component","props":{"empty":""}}'
|
|
567
|
+
const result = parseUINode(json)
|
|
568
|
+
|
|
569
|
+
expect(result.props.empty).toBe('')
|
|
570
|
+
})
|
|
571
|
+
|
|
572
|
+
it('handles array props', () => {
|
|
573
|
+
const json = '{"type":"component","props":{"items":[1,2,3,{"nested":true}]}}'
|
|
574
|
+
const result = parseUINode(json)
|
|
575
|
+
|
|
576
|
+
expect(Array.isArray(result.props.items)).toBe(true)
|
|
577
|
+
expect(result.props.items).toHaveLength(4)
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
it('handles object props with nested structure', () => {
|
|
581
|
+
const json =
|
|
582
|
+
'{"type":"component","props":{"config":{"level1":{"level2":{"level3":"deep"}}}}}'
|
|
583
|
+
const result = parseUINode(json)
|
|
584
|
+
|
|
585
|
+
expect((result.props.config as any).level1.level2.level3).toBe('deep')
|
|
586
|
+
})
|
|
587
|
+
})
|
|
588
|
+
|
|
589
|
+
describe('data field variations', () => {
|
|
590
|
+
it('handles data as array', () => {
|
|
591
|
+
const json = '{"type":"list","props":{},"data":[1,2,3]}'
|
|
592
|
+
const result = parseUINode(json)
|
|
593
|
+
|
|
594
|
+
expect(Array.isArray(result.data)).toBe(true)
|
|
595
|
+
})
|
|
596
|
+
|
|
597
|
+
it('handles data as object', () => {
|
|
598
|
+
const json = '{"type":"table","props":{},"data":{"rows":[],"cols":[]}}'
|
|
599
|
+
const result = parseUINode(json)
|
|
600
|
+
|
|
601
|
+
expect(typeof result.data).toBe('object')
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
it('handles data as primitive', () => {
|
|
605
|
+
const json = '{"type":"counter","props":{},"data":42}'
|
|
606
|
+
const result = parseUINode(json)
|
|
607
|
+
|
|
608
|
+
expect(result.data).toBe(42)
|
|
609
|
+
})
|
|
610
|
+
|
|
611
|
+
it('handles data as string', () => {
|
|
612
|
+
const json = '{"type":"text","props":{},"data":"bound data"}'
|
|
613
|
+
const result = parseUINode(json)
|
|
614
|
+
|
|
615
|
+
expect(result.data).toBe('bound data')
|
|
616
|
+
})
|
|
617
|
+
})
|
|
618
|
+
|
|
619
|
+
describe('Unicode and internationalization', () => {
|
|
620
|
+
it('handles Unicode characters in strings', () => {
|
|
621
|
+
const json = '{"type":"text","props":{"content":"Hello 世界 مرحبا"}}'
|
|
622
|
+
const result = parseUINode(json)
|
|
623
|
+
|
|
624
|
+
expect(result.props.content).toContain('世界')
|
|
625
|
+
})
|
|
626
|
+
|
|
627
|
+
it('handles emoji in props', () => {
|
|
628
|
+
const json = '{"type":"component","props":{"icon":"🎉"}}'
|
|
629
|
+
const result = parseUINode(json)
|
|
630
|
+
|
|
631
|
+
expect(result.props.icon).toBe('🎉')
|
|
632
|
+
})
|
|
633
|
+
})
|
|
634
|
+
|
|
635
|
+
describe('large and complex structures', () => {
|
|
636
|
+
it('handles large number of children', () => {
|
|
637
|
+
const childrenJson = Array.from({ length: 100 }, (_, i) =>
|
|
638
|
+
`{"type":"item","props":{"index":${i}}}`
|
|
639
|
+
).join(',')
|
|
640
|
+
const json = `{"type":"list","props":{},"children":[${childrenJson}]}`
|
|
641
|
+
|
|
642
|
+
const result = parseUINode(json)
|
|
643
|
+
expect(result.children).toHaveLength(100)
|
|
644
|
+
})
|
|
645
|
+
|
|
646
|
+
it('handles deeply nested structure (10+ levels)', () => {
|
|
647
|
+
let json = '{"type":"root","props":{},"children":[{'
|
|
648
|
+
for (let i = 0; i < 10; i++) {
|
|
649
|
+
json += `"type":"level${i}","props":{},"children":[{`
|
|
650
|
+
}
|
|
651
|
+
json += '"type":"leaf","props":{}'
|
|
652
|
+
for (let i = 0; i < 10; i++) {
|
|
653
|
+
json += '}]'
|
|
654
|
+
}
|
|
655
|
+
json += '}]}'
|
|
656
|
+
|
|
657
|
+
const result = parseUINode(json)
|
|
658
|
+
let current = result
|
|
659
|
+
// Traverse 11 levels: root -> level0 -> level1 -> ... -> level9 -> leaf
|
|
660
|
+
for (let i = 0; i < 11; i++) {
|
|
661
|
+
expect(current.children).toBeDefined()
|
|
662
|
+
current = current.children![0]
|
|
663
|
+
}
|
|
664
|
+
expect(current.type).toBe('leaf')
|
|
665
|
+
})
|
|
666
|
+
})
|
|
667
|
+
})
|
|
668
|
+
|
|
669
|
+
// ============================================================================
|
|
670
|
+
// Optional: YAML Parsing Support
|
|
671
|
+
// ============================================================================
|
|
672
|
+
|
|
673
|
+
describe('YAML parsing (optional)', () => {
|
|
674
|
+
describe('parseUINode with YAML input', () => {
|
|
675
|
+
it('parses YAML string to UINode', () => {
|
|
676
|
+
const yaml = `
|
|
677
|
+
type: text
|
|
678
|
+
props:
|
|
679
|
+
content: Hello
|
|
680
|
+
`
|
|
681
|
+
// This test may be skipped if YAML support is not implemented
|
|
682
|
+
try {
|
|
683
|
+
const result = parseUINode(yaml)
|
|
684
|
+
expect(result.type).toBe('text')
|
|
685
|
+
expect(result.props.content).toBe('Hello')
|
|
686
|
+
} catch (error) {
|
|
687
|
+
// YAML support is optional, so we don't fail the test suite
|
|
688
|
+
console.log('YAML support not implemented (optional)')
|
|
689
|
+
}
|
|
690
|
+
})
|
|
691
|
+
|
|
692
|
+
it('parses YAML with nested children', () => {
|
|
693
|
+
const yaml = `
|
|
694
|
+
type: box
|
|
695
|
+
props:
|
|
696
|
+
padding: 2
|
|
697
|
+
children:
|
|
698
|
+
- type: text
|
|
699
|
+
props:
|
|
700
|
+
content: Child
|
|
701
|
+
`
|
|
702
|
+
try {
|
|
703
|
+
const result = parseUINode(yaml)
|
|
704
|
+
expect(result.type).toBe('box')
|
|
705
|
+
expect(result.children![0].type).toBe('text')
|
|
706
|
+
} catch (error) {
|
|
707
|
+
console.log('YAML support not implemented (optional)')
|
|
708
|
+
}
|
|
709
|
+
})
|
|
710
|
+
|
|
711
|
+
it('handles YAML multiline strings', () => {
|
|
712
|
+
const yaml = `
|
|
713
|
+
type: text
|
|
714
|
+
props:
|
|
715
|
+
content: |
|
|
716
|
+
Line 1
|
|
717
|
+
Line 2
|
|
718
|
+
Line 3
|
|
719
|
+
`
|
|
720
|
+
try {
|
|
721
|
+
const result = parseUINode(yaml)
|
|
722
|
+
expect((result.props.content as string).includes('Line 1')).toBe(true)
|
|
723
|
+
} catch (error) {
|
|
724
|
+
console.log('YAML support not implemented (optional)')
|
|
725
|
+
}
|
|
726
|
+
})
|
|
727
|
+
})
|
|
728
|
+
|
|
729
|
+
describe('YAML error handling', () => {
|
|
730
|
+
it('throws ParseError for invalid YAML', () => {
|
|
731
|
+
const invalidYaml = `
|
|
732
|
+
type: box
|
|
733
|
+
props:
|
|
734
|
+
- invalid list syntax
|
|
735
|
+
`
|
|
736
|
+
try {
|
|
737
|
+
expect(() => parseUINode(invalidYaml)).toThrow()
|
|
738
|
+
} catch (error) {
|
|
739
|
+
console.log('YAML support not implemented (optional)')
|
|
740
|
+
}
|
|
741
|
+
})
|
|
742
|
+
})
|
|
743
|
+
})
|
|
744
|
+
|
|
745
|
+
// ============================================================================
|
|
746
|
+
// Roundtrip / Idempotency Tests
|
|
747
|
+
// ============================================================================
|
|
748
|
+
|
|
749
|
+
describe('parsing idempotency', () => {
|
|
750
|
+
it('parsing JSON-stringified UINode produces equivalent UINode', () => {
|
|
751
|
+
const original: UINode = {
|
|
752
|
+
type: 'box',
|
|
753
|
+
props: { padding: 2 },
|
|
754
|
+
children: [{ type: 'text', props: { content: 'Test' } }],
|
|
755
|
+
data: { value: 42 },
|
|
756
|
+
key: 'test-key',
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
const json = JSON.stringify(original)
|
|
760
|
+
const parsed = parseUINode(json)
|
|
761
|
+
|
|
762
|
+
expect(parsed.type).toBe(original.type)
|
|
763
|
+
expect(parsed.props).toEqual(original.props)
|
|
764
|
+
expect(parsed.children).toEqual(original.children)
|
|
765
|
+
expect(parsed.data).toEqual(original.data)
|
|
766
|
+
expect(parsed.key).toEqual(original.key)
|
|
767
|
+
})
|
|
768
|
+
|
|
769
|
+
it('validates parsed result against schema', () => {
|
|
770
|
+
const json = `{
|
|
771
|
+
"type": "component",
|
|
772
|
+
"props": { "prop1": "value" },
|
|
773
|
+
"children": [
|
|
774
|
+
{ "type": "child", "props": {} }
|
|
775
|
+
],
|
|
776
|
+
"data": { "records": [] },
|
|
777
|
+
"key": "unique-id"
|
|
778
|
+
}`
|
|
779
|
+
|
|
780
|
+
const parsed = parseUINode(json)
|
|
781
|
+
const validation = UINodeSchema.safeParse(parsed)
|
|
782
|
+
|
|
783
|
+
expect(validation.success).toBe(true)
|
|
784
|
+
})
|
|
785
|
+
})
|