wyreframe 0.1.0 → 0.1.1
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/LICENSE +692 -0
- package/README.md +65 -5
- package/package.json +8 -7
- package/src/index.ts +425 -0
- package/src/renderer/Renderer.gen.tsx +49 -0
- package/src/renderer/Renderer.mjs +41 -1
- package/src/renderer/Renderer.res +78 -0
- package/src/test/Expect.mjs +9 -0
- package/src/parser/Core/__tests__/Bounds_test.mjs +0 -326
- package/src/parser/Core/__tests__/Bounds_test.res +0 -412
- package/src/parser/Core/__tests__/Grid_test.mjs +0 -322
- package/src/parser/Core/__tests__/Grid_test.res +0 -319
- package/src/parser/Core/__tests__/Types_test.mjs +0 -614
- package/src/parser/Core/__tests__/Types_test.res +0 -650
- package/src/parser/Detector/__tests__/BoxTracer_test.mjs +0 -70
- package/src/parser/Detector/__tests__/BoxTracer_test.res +0 -92
- package/src/parser/Detector/__tests__/HierarchyBuilder_test.mjs +0 -489
- package/src/parser/Detector/__tests__/HierarchyBuilder_test.res +0 -849
- package/src/parser/Detector/__tests__/ShapeDetector_test.mjs +0 -377
- package/src/parser/Detector/__tests__/ShapeDetector_test.res +0 -563
- package/src/parser/Interactions/__tests__/InteractionMerger_test.mjs +0 -576
- package/src/parser/Interactions/__tests__/InteractionMerger_test.res +0 -646
- package/src/parser/Scanner/__tests__/Grid_manual.mjs +0 -214
- package/src/parser/Scanner/__tests__/Grid_manual.res +0 -141
- package/src/parser/Semantic/Elements/__tests__/ButtonParser_test.mjs +0 -189
- package/src/parser/Semantic/Elements/__tests__/ButtonParser_test.res +0 -257
- package/src/parser/Semantic/Elements/__tests__/CheckboxParser_test.mjs +0 -202
- package/src/parser/Semantic/Elements/__tests__/CheckboxParser_test.res +0 -250
- package/src/parser/Semantic/Elements/__tests__/CodeTextParser_manual.mjs +0 -293
- package/src/parser/Semantic/Elements/__tests__/CodeTextParser_manual.res +0 -134
- package/src/parser/Semantic/Elements/__tests__/InputParser_test.mjs +0 -253
- package/src/parser/Semantic/Elements/__tests__/InputParser_test.res +0 -304
- package/src/parser/Semantic/Elements/__tests__/LinkParser_test.mjs +0 -289
- package/src/parser/Semantic/Elements/__tests__/LinkParser_test.res +0 -402
- package/src/parser/Semantic/Elements/__tests__/TextParser_test.mjs +0 -149
- package/src/parser/Semantic/Elements/__tests__/TextParser_test.res +0 -167
- package/src/parser/Semantic/__tests__/ASTBuilder_test.mjs +0 -187
- package/src/parser/Semantic/__tests__/ASTBuilder_test.res +0 -192
- package/src/parser/Semantic/__tests__/ParserRegistry_test.mjs +0 -154
- package/src/parser/Semantic/__tests__/ParserRegistry_test.res +0 -191
- package/src/parser/Semantic/__tests__/SemanticParser_integration_test.mjs +0 -768
- package/src/parser/Semantic/__tests__/SemanticParser_integration_test.res +0 -1069
- package/src/parser/Semantic/__tests__/SemanticParser_manual.mjs +0 -1329
- package/src/parser/Semantic/__tests__/SemanticParser_manual.res +0 -544
- package/src/parser/__tests__/GridScanner_integration.test.mjs +0 -632
- package/src/parser/__tests__/GridScanner_integration.test.res +0 -816
- package/src/parser/__tests__/Performance.test.mjs +0 -244
- package/src/parser/__tests__/Performance.test.res +0 -371
- package/src/parser/__tests__/PerformanceFixtures.mjs +0 -200
- package/src/parser/__tests__/PerformanceFixtures.res +0 -284
- package/src/parser/__tests__/WyreframeParser_integration.test.mjs +0 -770
- package/src/parser/__tests__/WyreframeParser_integration.test.res +0 -1008
- package/src/parser/__tests__/fixtures/alignment-test.txt +0 -9
- package/src/parser/__tests__/fixtures/all-elements.txt +0 -16
- package/src/parser/__tests__/fixtures/login-scene.txt +0 -17
- package/src/parser/__tests__/fixtures/multi-scene.txt +0 -25
- package/src/parser/__tests__/fixtures/nested-boxes.txt +0 -15
- package/src/parser/__tests__/fixtures/simple-box.txt +0 -5
- package/src/parser/__tests__/fixtures/with-dividers.txt +0 -14
|
@@ -1,1008 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integration Tests: WyreframeParser
|
|
3
|
-
*
|
|
4
|
-
* Tests login scene, multi-scene, nested boxes, interactions, errors, warnings
|
|
5
|
-
* with realistic examples.
|
|
6
|
-
* Requirements: REQ-25
|
|
7
|
-
*
|
|
8
|
-
* This test suite validates the complete WyreframeParser pipeline
|
|
9
|
-
* (Grid Scanner → Shape Detector → Semantic Parser) with realistic wireframe examples.
|
|
10
|
-
*
|
|
11
|
-
* Test Framework: Vitest with rescript-vitest
|
|
12
|
-
* Date: 2025-12-22
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
open Vitest
|
|
16
|
-
|
|
17
|
-
// Helper for passing tests
|
|
18
|
-
let pass = ()
|
|
19
|
-
|
|
20
|
-
// =============================================================================
|
|
21
|
-
// Recursive Element Search Helpers
|
|
22
|
-
// =============================================================================
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Recursively collect all elements including those nested inside Box children.
|
|
26
|
-
* This is needed because named boxes contain their elements in Box.children,
|
|
27
|
-
* not directly in scene.elements.
|
|
28
|
-
*/
|
|
29
|
-
let rec collectAllElements = (elements: array<Types.element>): array<Types.element> => {
|
|
30
|
-
elements->Array.flatMap(el => {
|
|
31
|
-
switch el {
|
|
32
|
-
| Box({children}) => Array.concat([el], collectAllElements(children))
|
|
33
|
-
| other => [other]
|
|
34
|
-
}
|
|
35
|
-
})
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Find an element matching a predicate, searching recursively into Box children.
|
|
40
|
-
*/
|
|
41
|
-
let findElement = (elements: array<Types.element>, predicate: Types.element => bool): option<Types.element> => {
|
|
42
|
-
let allElements = collectAllElements(elements)
|
|
43
|
-
Belt.Array.getBy(allElements, predicate)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Check if any element matches a predicate, searching recursively.
|
|
48
|
-
*/
|
|
49
|
-
let hasElement = (elements: array<Types.element>, predicate: Types.element => bool): bool => {
|
|
50
|
-
let allElements = collectAllElements(elements)
|
|
51
|
-
Belt.Array.some(allElements, predicate)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Count elements matching a predicate, searching recursively.
|
|
56
|
-
*/
|
|
57
|
-
let countElements = (elements: array<Types.element>, predicate: Types.element => bool): int => {
|
|
58
|
-
let allElements = collectAllElements(elements)
|
|
59
|
-
Belt.Array.reduce(allElements, 0, (count, el) => predicate(el) ? count + 1 : count)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// =============================================================================
|
|
63
|
-
// E2E-01: Parse Simple Login Scene
|
|
64
|
-
// =============================================================================
|
|
65
|
-
|
|
66
|
-
describe("E2E-01: Parse Simple Login Scene", t => {
|
|
67
|
-
test("parses complete login scene with all element types", t => {
|
|
68
|
-
let loginWireframe = `
|
|
69
|
-
@scene: login
|
|
70
|
-
@title: Login Page
|
|
71
|
-
@transition: fade
|
|
72
|
-
|
|
73
|
-
+--Login----------------+
|
|
74
|
-
| |
|
|
75
|
-
| * Welcome Back |
|
|
76
|
-
| |
|
|
77
|
-
| Email: |
|
|
78
|
-
| #email |
|
|
79
|
-
| |
|
|
80
|
-
| Password: |
|
|
81
|
-
| #password |
|
|
82
|
-
| |
|
|
83
|
-
| [x] Remember me |
|
|
84
|
-
| |
|
|
85
|
-
| [ Login ] |
|
|
86
|
-
| |
|
|
87
|
-
| "Forgot password?" |
|
|
88
|
-
| |
|
|
89
|
-
+-----------------------+
|
|
90
|
-
`
|
|
91
|
-
|
|
92
|
-
// Parse wireframe
|
|
93
|
-
let result = Parser.parse(loginWireframe)
|
|
94
|
-
|
|
95
|
-
// Verify successful parse
|
|
96
|
-
switch result {
|
|
97
|
-
| Ok(ast) => {
|
|
98
|
-
// Verify scene properties
|
|
99
|
-
t->expect(Belt.Array.length(ast.scenes))->Expect.toBe(1)
|
|
100
|
-
|
|
101
|
-
let scene = ast.scenes->Array.getUnsafe(0)
|
|
102
|
-
t->expect(scene.id)->Expect.toBe("login")
|
|
103
|
-
t->expect(scene.title)->Expect.toBe("Login Page")
|
|
104
|
-
t->expect(scene.transition)->Expect.toBe("fade")
|
|
105
|
-
|
|
106
|
-
// Verify emphasis text element (search recursively inside boxes)
|
|
107
|
-
let emphasisFound = hasElement(scene.elements, el => {
|
|
108
|
-
switch el {
|
|
109
|
-
| Text({content, emphasis: true}) => Js.String2.includes(content, "Welcome Back")
|
|
110
|
-
| _ => false
|
|
111
|
-
}
|
|
112
|
-
})
|
|
113
|
-
t->expect(emphasisFound)->Expect.toBe(true)
|
|
114
|
-
|
|
115
|
-
// Verify input elements (search recursively inside boxes)
|
|
116
|
-
let emailInput = findElement(scene.elements, el => {
|
|
117
|
-
switch el {
|
|
118
|
-
| Input({id: "email"}) => true
|
|
119
|
-
| _ => false
|
|
120
|
-
}
|
|
121
|
-
})
|
|
122
|
-
t->expect(emailInput)->Expect.not->Expect.toBe(None)
|
|
123
|
-
|
|
124
|
-
let passwordInput = findElement(scene.elements, el => {
|
|
125
|
-
switch el {
|
|
126
|
-
| Input({id: "password"}) => true
|
|
127
|
-
| _ => false
|
|
128
|
-
}
|
|
129
|
-
})
|
|
130
|
-
t->expect(passwordInput)->Expect.not->Expect.toBe(None)
|
|
131
|
-
|
|
132
|
-
// Verify checkbox element (search recursively inside boxes)
|
|
133
|
-
let checkbox = findElement(scene.elements, el => {
|
|
134
|
-
switch el {
|
|
135
|
-
| Checkbox({checked: true, label}) => Js.String2.includes(label, "Remember me")
|
|
136
|
-
| _ => false
|
|
137
|
-
}
|
|
138
|
-
})
|
|
139
|
-
t->expect(checkbox)->Expect.not->Expect.toBe(None)
|
|
140
|
-
|
|
141
|
-
// Verify button element (search recursively inside boxes)
|
|
142
|
-
let button = findElement(scene.elements, el => {
|
|
143
|
-
switch el {
|
|
144
|
-
| Button({text: "Login", align: Center}) => true
|
|
145
|
-
| _ => false
|
|
146
|
-
}
|
|
147
|
-
})
|
|
148
|
-
t->expect(button)->Expect.not->Expect.toBe(None)
|
|
149
|
-
|
|
150
|
-
// Verify link element (search recursively inside boxes)
|
|
151
|
-
let link = findElement(scene.elements, el => {
|
|
152
|
-
switch el {
|
|
153
|
-
| Link({text}) => Js.String2.includes(text, "Forgot password")
|
|
154
|
-
| _ => false
|
|
155
|
-
}
|
|
156
|
-
})
|
|
157
|
-
t->expect(link)->Expect.not->Expect.toBe(None)
|
|
158
|
-
}
|
|
159
|
-
| Error(errors) => {
|
|
160
|
-
Console.error2("Parse errors:", errors)
|
|
161
|
-
t->expect(true)->Expect.toBe(false) // fail: Expected successful parse of login scene
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
})
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
// =============================================================================
|
|
168
|
-
// E2E-02: Parse Multi-Scene Wireframe
|
|
169
|
-
// =============================================================================
|
|
170
|
-
|
|
171
|
-
describe("E2E-02: Parse Multi-Scene Wireframe", t => {
|
|
172
|
-
test("parses multiple scenes with correct transitions", t => {
|
|
173
|
-
let multiSceneWireframe = `
|
|
174
|
-
@scene: home
|
|
175
|
-
@title: Home Screen
|
|
176
|
-
@transition: slide-right
|
|
177
|
-
|
|
178
|
-
+--Home---------------+
|
|
179
|
-
| |
|
|
180
|
-
| "Go to Settings" |
|
|
181
|
-
| |
|
|
182
|
-
+---------------------+
|
|
183
|
-
|
|
184
|
-
---
|
|
185
|
-
|
|
186
|
-
@scene: settings
|
|
187
|
-
@title: Settings
|
|
188
|
-
@transition: slide-left
|
|
189
|
-
|
|
190
|
-
+--Settings----------+
|
|
191
|
-
| |
|
|
192
|
-
| [x] Notifications |
|
|
193
|
-
| [ ] Dark Mode |
|
|
194
|
-
| |
|
|
195
|
-
| [ Save ] |
|
|
196
|
-
| |
|
|
197
|
-
+--------------------+
|
|
198
|
-
`
|
|
199
|
-
|
|
200
|
-
let result = Parser.parse(multiSceneWireframe)
|
|
201
|
-
|
|
202
|
-
switch result {
|
|
203
|
-
| Ok(ast) => {
|
|
204
|
-
// Verify scene count
|
|
205
|
-
t->expect(Belt.Array.length(ast.scenes))->Expect.toBe(2)
|
|
206
|
-
|
|
207
|
-
// Verify first scene
|
|
208
|
-
let homeScene = ast.scenes->Array.getUnsafe(0)
|
|
209
|
-
t->expect(homeScene.id)->Expect.toBe("home")
|
|
210
|
-
t->expect(homeScene.title)->Expect.toBe("Home Screen")
|
|
211
|
-
t->expect(homeScene.transition)->Expect.toBe("slide-right")
|
|
212
|
-
|
|
213
|
-
// Verify second scene
|
|
214
|
-
let settingsScene = ast.scenes->Array.getUnsafe(1)
|
|
215
|
-
t->expect(settingsScene.id)->Expect.toBe("settings")
|
|
216
|
-
t->expect(settingsScene.title)->Expect.toBe("Settings")
|
|
217
|
-
t->expect(settingsScene.transition)->Expect.toBe("slide-left")
|
|
218
|
-
|
|
219
|
-
// Verify settings scene contains checkboxes (search recursively)
|
|
220
|
-
let notificationsCheckbox = findElement(settingsScene.elements, el => {
|
|
221
|
-
switch el {
|
|
222
|
-
| Checkbox({checked: true, label}) => Js.String2.includes(label, "Notifications")
|
|
223
|
-
| _ => false
|
|
224
|
-
}
|
|
225
|
-
})
|
|
226
|
-
t->expect(notificationsCheckbox)->Expect.not->Expect.toBe(None)
|
|
227
|
-
|
|
228
|
-
let darkModeCheckbox = findElement(settingsScene.elements, el => {
|
|
229
|
-
switch el {
|
|
230
|
-
| Checkbox({checked: false, label}) => Js.String2.includes(label, "Dark Mode")
|
|
231
|
-
| _ => false
|
|
232
|
-
}
|
|
233
|
-
})
|
|
234
|
-
t->expect(darkModeCheckbox)->Expect.not->Expect.toBe(None)
|
|
235
|
-
|
|
236
|
-
// Verify button (search recursively)
|
|
237
|
-
let saveButton = findElement(settingsScene.elements, el => {
|
|
238
|
-
switch el {
|
|
239
|
-
| Button({text: "Save"}) => true
|
|
240
|
-
| _ => false
|
|
241
|
-
}
|
|
242
|
-
})
|
|
243
|
-
t->expect(saveButton)->Expect.not->Expect.toBe(None)
|
|
244
|
-
}
|
|
245
|
-
| Error(errors) => {
|
|
246
|
-
Console.error2("Parse errors:", errors)
|
|
247
|
-
t->expect(true)->Expect.toBe(false) // fail: Expected successful parse of multi-scene wireframe
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
})
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
// =============================================================================
|
|
254
|
-
// E2E-03: Parse Deeply Nested Boxes
|
|
255
|
-
// =============================================================================
|
|
256
|
-
|
|
257
|
-
describe("E2E-03: Parse Deeply Nested Boxes", t => {
|
|
258
|
-
test("parses 3-level nested boxes with correct hierarchy", t => {
|
|
259
|
-
let nestedWireframe = `
|
|
260
|
-
@scene: nested-test
|
|
261
|
-
|
|
262
|
-
+--Level1-(Outer)------------------+
|
|
263
|
-
| |
|
|
264
|
-
| +--Level2-(Middle)-----------+ |
|
|
265
|
-
| | | |
|
|
266
|
-
| | +--Level3-(Inner)------+ | |
|
|
267
|
-
| | | | | |
|
|
268
|
-
| | | [ Action Button ] | | |
|
|
269
|
-
| | | | | |
|
|
270
|
-
| | +----------------------+ | |
|
|
271
|
-
| | | |
|
|
272
|
-
| +----------------------------+ |
|
|
273
|
-
| |
|
|
274
|
-
+----------------------------------+
|
|
275
|
-
`
|
|
276
|
-
|
|
277
|
-
let result = Parser.parse(nestedWireframe)
|
|
278
|
-
|
|
279
|
-
switch result {
|
|
280
|
-
| Ok(ast) => {
|
|
281
|
-
let scene = ast.scenes->Array.getUnsafe(0)
|
|
282
|
-
|
|
283
|
-
// Find root box
|
|
284
|
-
let rootBox = Belt.Array.getBy(scene.elements, el => {
|
|
285
|
-
switch el {
|
|
286
|
-
| Box({name: Some(boxName)}) => Js.String2.includes(boxName, "Level1")
|
|
287
|
-
| _ => false
|
|
288
|
-
}
|
|
289
|
-
})
|
|
290
|
-
|
|
291
|
-
t->expect(rootBox)->Expect.not->Expect.toBe(None)
|
|
292
|
-
|
|
293
|
-
// Verify nesting hierarchy
|
|
294
|
-
switch rootBox {
|
|
295
|
-
| Some(Box({children, name: Some(boxName)})) => {
|
|
296
|
-
t->expect(Js.String2.includes(boxName, "Outer"))->Expect.toBe(true)
|
|
297
|
-
t->expect(Belt.Array.length(children))->Expect.Int.toBeGreaterThan(0)
|
|
298
|
-
|
|
299
|
-
// Check for Level 2 box
|
|
300
|
-
let level2Box = Belt.Array.getBy(children, el => {
|
|
301
|
-
switch el {
|
|
302
|
-
| Box({name: Some(name)}) => Js.String2.includes(name, "Level2")
|
|
303
|
-
| _ => false
|
|
304
|
-
}
|
|
305
|
-
})
|
|
306
|
-
t->expect(level2Box)->Expect.not->Expect.toBe(None)
|
|
307
|
-
|
|
308
|
-
// Check for Level 3 box within Level 2
|
|
309
|
-
switch level2Box {
|
|
310
|
-
| Some(Box({children: level2Children})) => {
|
|
311
|
-
let level3Box = Belt.Array.getBy(level2Children, el => {
|
|
312
|
-
switch el {
|
|
313
|
-
| Box({name: Some(name)}) => Js.String2.includes(name, "Level3")
|
|
314
|
-
| _ => false
|
|
315
|
-
}
|
|
316
|
-
})
|
|
317
|
-
t->expect(level3Box)->Expect.not->Expect.toBe(None)
|
|
318
|
-
|
|
319
|
-
// Verify button is in Level 3
|
|
320
|
-
switch level3Box {
|
|
321
|
-
| Some(Box({children: level3Children})) => {
|
|
322
|
-
let button = Belt.Array.getBy(level3Children, el => {
|
|
323
|
-
switch el {
|
|
324
|
-
| Button({text}) => Js.String2.includes(text, "Action Button")
|
|
325
|
-
| _ => false
|
|
326
|
-
}
|
|
327
|
-
})
|
|
328
|
-
t->expect(button)->Expect.not->Expect.toBe(None)
|
|
329
|
-
}
|
|
330
|
-
| _ => t->expect(true)->Expect.toBe(false) // fail: Expected Level 3 box to be a Box element
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
| _ => t->expect(true)->Expect.toBe(false) // fail: Expected Level 2 box to be a Box element
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
| _ => t->expect(true)->Expect.toBe(false) // fail: Expected root box to be a Box element
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
| Error(errors) => {
|
|
340
|
-
Console.error2("Parse errors:", errors)
|
|
341
|
-
t->expect(true)->Expect.toBe(false) // fail: Expected successful parse of nested boxes
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
})
|
|
345
|
-
})
|
|
346
|
-
|
|
347
|
-
// =============================================================================
|
|
348
|
-
// E2E-04: Parse Wireframe with Dividers
|
|
349
|
-
// =============================================================================
|
|
350
|
-
|
|
351
|
-
describe("E2E-04: Parse Wireframe with Dividers", t => {
|
|
352
|
-
test("recognizes dividers as section separators", t => {
|
|
353
|
-
let dividerWireframe = `
|
|
354
|
-
@scene: profile
|
|
355
|
-
|
|
356
|
-
+--UserProfile---------+
|
|
357
|
-
| |
|
|
358
|
-
| * John Doe |
|
|
359
|
-
| john@example.com |
|
|
360
|
-
| |
|
|
361
|
-
+======================+
|
|
362
|
-
| |
|
|
363
|
-
| Settings |
|
|
364
|
-
| [x] Email Updates |
|
|
365
|
-
| [ ] SMS Alerts |
|
|
366
|
-
| |
|
|
367
|
-
+======================+
|
|
368
|
-
| |
|
|
369
|
-
| [ Save Profile ] |
|
|
370
|
-
| |
|
|
371
|
-
+----------------------+
|
|
372
|
-
`
|
|
373
|
-
|
|
374
|
-
let result = Parser.parse(dividerWireframe)
|
|
375
|
-
|
|
376
|
-
switch result {
|
|
377
|
-
| Ok(ast) => {
|
|
378
|
-
let scene = ast.scenes->Array.getUnsafe(0)
|
|
379
|
-
|
|
380
|
-
// Verify box with dividers exists
|
|
381
|
-
let profileBox = Belt.Array.getBy(scene.elements, el => {
|
|
382
|
-
switch el {
|
|
383
|
-
| Box({name: Some(boxName)}) => Js.String2.includes(boxName, "UserProfile")
|
|
384
|
-
| _ => false
|
|
385
|
-
}
|
|
386
|
-
})
|
|
387
|
-
t->expect(profileBox)->Expect.not->Expect.toBe(None)
|
|
388
|
-
|
|
389
|
-
// Count divider elements (search recursively inside boxes)
|
|
390
|
-
let dividerCount = countElements(scene.elements, el => {
|
|
391
|
-
switch el {
|
|
392
|
-
| Divider(_) => true
|
|
393
|
-
| _ => false
|
|
394
|
-
}
|
|
395
|
-
})
|
|
396
|
-
|
|
397
|
-
// Should have detected dividers
|
|
398
|
-
t->expect(dividerCount)->Expect.Int.toBeGreaterThan(0)
|
|
399
|
-
|
|
400
|
-
// Verify all content elements are present (search recursively)
|
|
401
|
-
let emphasisFound = hasElement(scene.elements, el => {
|
|
402
|
-
switch el {
|
|
403
|
-
| Text({emphasis: true}) => true
|
|
404
|
-
| _ => false
|
|
405
|
-
}
|
|
406
|
-
})
|
|
407
|
-
t->expect(emphasisFound)->Expect.toBe(true)
|
|
408
|
-
|
|
409
|
-
let checkboxesFound = hasElement(scene.elements, el => {
|
|
410
|
-
switch el {
|
|
411
|
-
| Checkbox(_) => true
|
|
412
|
-
| _ => false
|
|
413
|
-
}
|
|
414
|
-
})
|
|
415
|
-
t->expect(checkboxesFound)->Expect.toBe(true)
|
|
416
|
-
|
|
417
|
-
let buttonFound = hasElement(scene.elements, el => {
|
|
418
|
-
switch el {
|
|
419
|
-
| Button({text}) => Js.String2.includes(text, "Save Profile")
|
|
420
|
-
| _ => false
|
|
421
|
-
}
|
|
422
|
-
})
|
|
423
|
-
t->expect(buttonFound)->Expect.toBe(true)
|
|
424
|
-
}
|
|
425
|
-
| Error(errors) => {
|
|
426
|
-
Console.error2("Parse errors:", errors)
|
|
427
|
-
t->expect(true)->Expect.toBe(false) // fail: Expected successful parse of wireframe with dividers
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
})
|
|
431
|
-
})
|
|
432
|
-
|
|
433
|
-
// =============================================================================
|
|
434
|
-
// E2E-05: Parse Wireframe with Interactions
|
|
435
|
-
// =============================================================================
|
|
436
|
-
|
|
437
|
-
describe("E2E-05: Parse Wireframe with Interactions", t => {
|
|
438
|
-
test("merges interaction DSL with wireframe AST", t => {
|
|
439
|
-
let wireframe = `
|
|
440
|
-
@scene: login
|
|
441
|
-
|
|
442
|
-
+--Login---------+
|
|
443
|
-
| |
|
|
444
|
-
| #username |
|
|
445
|
-
| #password |
|
|
446
|
-
| |
|
|
447
|
-
| [ Submit ] |
|
|
448
|
-
| |
|
|
449
|
-
+----------------+
|
|
450
|
-
`
|
|
451
|
-
|
|
452
|
-
let interactions = `
|
|
453
|
-
@scene: login
|
|
454
|
-
|
|
455
|
-
#username:
|
|
456
|
-
placeholder: "Enter username"
|
|
457
|
-
required: true
|
|
458
|
-
|
|
459
|
-
#password:
|
|
460
|
-
type: "password"
|
|
461
|
-
placeholder: "Enter password"
|
|
462
|
-
|
|
463
|
-
[ Submit ]:
|
|
464
|
-
variant: "primary"
|
|
465
|
-
@click -> validate(username, password)
|
|
466
|
-
@click -> goto(dashboard, fade)
|
|
467
|
-
`
|
|
468
|
-
|
|
469
|
-
let result = Parser.parse(wireframe ++ "\n" ++ interactions)
|
|
470
|
-
|
|
471
|
-
switch result {
|
|
472
|
-
| Ok(ast) => {
|
|
473
|
-
let scene = ast.scenes->Array.getUnsafe(0)
|
|
474
|
-
|
|
475
|
-
// Verify username input exists (search recursively)
|
|
476
|
-
let usernameInput = findElement(scene.elements, el => {
|
|
477
|
-
switch el {
|
|
478
|
-
| Input({id: "username"}) => true
|
|
479
|
-
| _ => false
|
|
480
|
-
}
|
|
481
|
-
})
|
|
482
|
-
t->expect(usernameInput)->Expect.not->Expect.toBe(None)
|
|
483
|
-
|
|
484
|
-
// Verify password input exists (search recursively)
|
|
485
|
-
let passwordInput = findElement(scene.elements, el => {
|
|
486
|
-
switch el {
|
|
487
|
-
| Input({id: "password"}) => true
|
|
488
|
-
| _ => false
|
|
489
|
-
}
|
|
490
|
-
})
|
|
491
|
-
t->expect(passwordInput)->Expect.not->Expect.toBe(None)
|
|
492
|
-
|
|
493
|
-
// Verify submit button exists with actions (search recursively)
|
|
494
|
-
let submitButton = findElement(scene.elements, el => {
|
|
495
|
-
switch el {
|
|
496
|
-
| Button({text: "Submit", actions}) => Belt.Array.length(actions) > 0
|
|
497
|
-
| _ => false
|
|
498
|
-
}
|
|
499
|
-
})
|
|
500
|
-
t->expect(submitButton)->Expect.not->Expect.toBe(None)
|
|
501
|
-
}
|
|
502
|
-
| Error(errors) => {
|
|
503
|
-
Console.error2("Parse errors:", errors)
|
|
504
|
-
t->expect(true)->Expect.toBe(false) // fail: Expected successful parse with interactions
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
})
|
|
508
|
-
})
|
|
509
|
-
|
|
510
|
-
// =============================================================================
|
|
511
|
-
// E2E-06: Detect Structural Errors (Unclosed Boxes)
|
|
512
|
-
// =============================================================================
|
|
513
|
-
|
|
514
|
-
describe("E2E-06: Detect Structural Errors", t => {
|
|
515
|
-
test("detects unclosed box errors", t => {
|
|
516
|
-
let unclosedBoxWireframe = `
|
|
517
|
-
@scene: error-test
|
|
518
|
-
|
|
519
|
-
+--Unclosed Box---+
|
|
520
|
-
| |
|
|
521
|
-
| Content here |
|
|
522
|
-
+--
|
|
523
|
-
`
|
|
524
|
-
|
|
525
|
-
let result = Parser.parse(unclosedBoxWireframe)
|
|
526
|
-
|
|
527
|
-
switch result {
|
|
528
|
-
| Ok(_) => t->expect(true)->Expect.toBe(false) // fail: Expected error for unclosed box
|
|
529
|
-
| Error(errors) => {
|
|
530
|
-
t->expect(Belt.Array.length(errors))->Expect.Int.toBeGreaterThan(0)
|
|
531
|
-
|
|
532
|
-
// Verify error is about unclosed box
|
|
533
|
-
let hasUncloseError = Belt.Array.some(errors, error => {
|
|
534
|
-
switch error.code {
|
|
535
|
-
| UncloseBox(_) => true
|
|
536
|
-
| _ => false
|
|
537
|
-
}
|
|
538
|
-
})
|
|
539
|
-
t->expect(hasUncloseError)->Expect.toBe(true)
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
})
|
|
543
|
-
})
|
|
544
|
-
|
|
545
|
-
// =============================================================================
|
|
546
|
-
// E2E-07: Detect Width Mismatch Errors
|
|
547
|
-
// =============================================================================
|
|
548
|
-
|
|
549
|
-
describe("E2E-07: Detect Width Mismatch Errors", t => {
|
|
550
|
-
test("detects mismatched top and bottom widths", t => {
|
|
551
|
-
let mismatchedWidthWireframe = `
|
|
552
|
-
@scene: error-test
|
|
553
|
-
|
|
554
|
-
+--ShortTop--+
|
|
555
|
-
| |
|
|
556
|
-
+--------------+
|
|
557
|
-
`
|
|
558
|
-
|
|
559
|
-
let result = Parser.parse(mismatchedWidthWireframe)
|
|
560
|
-
|
|
561
|
-
switch result {
|
|
562
|
-
| Ok(_) => t->expect(true)->Expect.toBe(false) // fail: Expected error for width mismatch
|
|
563
|
-
| Error(errors) => {
|
|
564
|
-
t->expect(Belt.Array.length(errors))->Expect.Int.toBeGreaterThan(0)
|
|
565
|
-
|
|
566
|
-
// Verify error is about width mismatch
|
|
567
|
-
let hasMismatchError = Belt.Array.some(errors, error => {
|
|
568
|
-
switch error.code {
|
|
569
|
-
| MismatchedWidth({topWidth, bottomWidth}) => {
|
|
570
|
-
t->expect(topWidth)->Expect.not->Expect.toBe(bottomWidth)
|
|
571
|
-
true
|
|
572
|
-
}
|
|
573
|
-
| _ => false
|
|
574
|
-
}
|
|
575
|
-
})
|
|
576
|
-
t->expect(hasMismatchError)->Expect.toBe(true)
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
})
|
|
580
|
-
})
|
|
581
|
-
|
|
582
|
-
// =============================================================================
|
|
583
|
-
// E2E-08: Detect Overlapping Boxes Error
|
|
584
|
-
// NOTE: Overlapping box detection is not yet implemented in the parser.
|
|
585
|
-
// This test is skipped until the feature is added (REQ-XX).
|
|
586
|
-
// =============================================================================
|
|
587
|
-
|
|
588
|
-
describe("E2E-08: Detect Overlapping Boxes", _t => {
|
|
589
|
-
test("detects overlapping non-nested boxes - feature not implemented", ~skip=true, t => {
|
|
590
|
-
let overlappingBoxesWireframe = `
|
|
591
|
-
@scene: error-test
|
|
592
|
-
|
|
593
|
-
+--Box1-------+
|
|
594
|
-
| |
|
|
595
|
-
| +--Box2----+---+
|
|
596
|
-
| | | |
|
|
597
|
-
+--|----------+ |
|
|
598
|
-
| |
|
|
599
|
-
+--------------+
|
|
600
|
-
`
|
|
601
|
-
|
|
602
|
-
let result = Parser.parse(overlappingBoxesWireframe)
|
|
603
|
-
|
|
604
|
-
switch result {
|
|
605
|
-
| Ok(_) => t->expect(true)->Expect.toBe(false) // fail: Expected error for overlapping boxes
|
|
606
|
-
| Error(errors) => {
|
|
607
|
-
t->expect(Belt.Array.length(errors))->Expect.Int.toBeGreaterThan(0)
|
|
608
|
-
|
|
609
|
-
// Verify error is about overlapping boxes
|
|
610
|
-
let hasOverlapError = Belt.Array.some(errors, error => {
|
|
611
|
-
switch error.code {
|
|
612
|
-
| OverlappingBoxes(_) => true
|
|
613
|
-
| _ => false
|
|
614
|
-
}
|
|
615
|
-
})
|
|
616
|
-
t->expect(hasOverlapError)->Expect.toBe(true)
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
})
|
|
620
|
-
})
|
|
621
|
-
|
|
622
|
-
// =============================================================================
|
|
623
|
-
// E2E-09: Generate Warnings for Deep Nesting
|
|
624
|
-
// =============================================================================
|
|
625
|
-
|
|
626
|
-
describe("E2E-09: Generate Warnings for Deep Nesting", t => {
|
|
627
|
-
test("generates warning for nesting depth > 4", t => {
|
|
628
|
-
let deepNestingWireframe = `
|
|
629
|
-
@scene: warning-test
|
|
630
|
-
|
|
631
|
-
+--L1------------------+
|
|
632
|
-
| +--L2-------------+ |
|
|
633
|
-
| | +--L3--------+ | |
|
|
634
|
-
| | | +--L4----+ | | |
|
|
635
|
-
| | | | +--L5-+ | | | |
|
|
636
|
-
| | | | | | | | | |
|
|
637
|
-
| | | | +-----+ | | | |
|
|
638
|
-
| | | +---------+ | | |
|
|
639
|
-
| | +-------------+ | |
|
|
640
|
-
| +-----------------+ |
|
|
641
|
-
+-----------------------+
|
|
642
|
-
`
|
|
643
|
-
|
|
644
|
-
let result = Parser.parse(deepNestingWireframe)
|
|
645
|
-
|
|
646
|
-
// Parsing should succeed even with warnings
|
|
647
|
-
switch result {
|
|
648
|
-
| Ok(ast) => {
|
|
649
|
-
// Check if warnings are included in result
|
|
650
|
-
// (Implementation may vary - warnings might be in a separate field)
|
|
651
|
-
t->expect(Belt.Array.length(ast.scenes))->Expect.toBe(1)
|
|
652
|
-
}
|
|
653
|
-
| Error(errors) => {
|
|
654
|
-
// Check if any errors are actually warnings
|
|
655
|
-
let hasDeepNestingWarning = Belt.Array.some(errors, error => {
|
|
656
|
-
switch error.code {
|
|
657
|
-
| DeepNesting({depth}) => {
|
|
658
|
-
t->expect(depth)->Expect.Int.toBeGreaterThan(4)
|
|
659
|
-
true
|
|
660
|
-
}
|
|
661
|
-
| _ => false
|
|
662
|
-
}
|
|
663
|
-
})
|
|
664
|
-
|
|
665
|
-
// If errors contain warnings, that's acceptable
|
|
666
|
-
if hasDeepNestingWarning {
|
|
667
|
-
pass
|
|
668
|
-
} else {
|
|
669
|
-
Console.error2("Parse errors:", errors)
|
|
670
|
-
t->expect(true)->Expect.toBe(false) // fail: Expected warning for deep nesting
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
})
|
|
675
|
-
})
|
|
676
|
-
|
|
677
|
-
// =============================================================================
|
|
678
|
-
// E2E-10: Parse Complete Registration Flow
|
|
679
|
-
// =============================================================================
|
|
680
|
-
|
|
681
|
-
describe("E2E-10: Parse Complete Registration Flow", t => {
|
|
682
|
-
test("parses realistic registration scene", t => {
|
|
683
|
-
let registrationWireframe = `
|
|
684
|
-
@scene: register
|
|
685
|
-
@title: Create Account
|
|
686
|
-
@transition: fade
|
|
687
|
-
|
|
688
|
-
+--CreateAccount--------------------+
|
|
689
|
-
| |
|
|
690
|
-
| * Join our community |
|
|
691
|
-
| |
|
|
692
|
-
| First Name: |
|
|
693
|
-
| #firstName |
|
|
694
|
-
| |
|
|
695
|
-
| Last Name: |
|
|
696
|
-
| #lastName |
|
|
697
|
-
| |
|
|
698
|
-
| Email Address: |
|
|
699
|
-
| #email |
|
|
700
|
-
| |
|
|
701
|
-
| Password: |
|
|
702
|
-
| #password |
|
|
703
|
-
| |
|
|
704
|
-
| Confirm Password: |
|
|
705
|
-
| #confirmPassword |
|
|
706
|
-
| |
|
|
707
|
-
| [x] I agree to Terms of Service |
|
|
708
|
-
| [x] Subscribe to newsletter |
|
|
709
|
-
| |
|
|
710
|
-
| [ Create Account ] |
|
|
711
|
-
| |
|
|
712
|
-
| "Already have an account?" |
|
|
713
|
-
| |
|
|
714
|
-
+-----------------------------------+
|
|
715
|
-
`
|
|
716
|
-
|
|
717
|
-
let result = Parser.parse(registrationWireframe)
|
|
718
|
-
|
|
719
|
-
switch result {
|
|
720
|
-
| Ok(ast) => {
|
|
721
|
-
let scene = ast.scenes->Array.getUnsafe(0)
|
|
722
|
-
|
|
723
|
-
t->expect(scene.id)->Expect.toBe("register")
|
|
724
|
-
t->expect(scene.title)->Expect.toBe("Create Account")
|
|
725
|
-
|
|
726
|
-
// Count input fields (search recursively)
|
|
727
|
-
let inputCount = countElements(scene.elements, el => {
|
|
728
|
-
switch el {
|
|
729
|
-
| Input(_) => true
|
|
730
|
-
| _ => false
|
|
731
|
-
}
|
|
732
|
-
})
|
|
733
|
-
t->expect(inputCount)->Expect.toBe(5)
|
|
734
|
-
|
|
735
|
-
// Count checkboxes (search recursively)
|
|
736
|
-
let checkboxCount = countElements(scene.elements, el => {
|
|
737
|
-
switch el {
|
|
738
|
-
| Checkbox({checked: true}) => true
|
|
739
|
-
| _ => false
|
|
740
|
-
}
|
|
741
|
-
})
|
|
742
|
-
t->expect(checkboxCount)->Expect.toBe(2)
|
|
743
|
-
|
|
744
|
-
// Verify button is center-aligned (search recursively)
|
|
745
|
-
let centerButton = findElement(scene.elements, el => {
|
|
746
|
-
switch el {
|
|
747
|
-
| Button({text, align: Center}) => Js.String2.includes(text, "Create Account")
|
|
748
|
-
| _ => false
|
|
749
|
-
}
|
|
750
|
-
})
|
|
751
|
-
t->expect(centerButton)->Expect.not->Expect.toBe(None)
|
|
752
|
-
|
|
753
|
-
// Verify link exists (search recursively)
|
|
754
|
-
let linkFound = hasElement(scene.elements, el => {
|
|
755
|
-
switch el {
|
|
756
|
-
| Link(_) => true
|
|
757
|
-
| _ => false
|
|
758
|
-
}
|
|
759
|
-
})
|
|
760
|
-
t->expect(linkFound)->Expect.toBe(true)
|
|
761
|
-
|
|
762
|
-
// Verify emphasis text (search recursively)
|
|
763
|
-
let emphasisFound = hasElement(scene.elements, el => {
|
|
764
|
-
switch el {
|
|
765
|
-
| Text({emphasis: true}) => true
|
|
766
|
-
| _ => false
|
|
767
|
-
}
|
|
768
|
-
})
|
|
769
|
-
t->expect(emphasisFound)->Expect.toBe(true)
|
|
770
|
-
}
|
|
771
|
-
| Error(errors) => {
|
|
772
|
-
Console.error2("Parse errors:", errors)
|
|
773
|
-
t->expect(true)->Expect.toBe(false) // fail: Expected successful parse of registration flow
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
})
|
|
777
|
-
})
|
|
778
|
-
|
|
779
|
-
// =============================================================================
|
|
780
|
-
// E2E-11: Parse Dashboard with Multiple Components
|
|
781
|
-
// =============================================================================
|
|
782
|
-
|
|
783
|
-
describe("E2E-11: Parse Dashboard with Multiple Components", t => {
|
|
784
|
-
test("parses complex dashboard layout", t => {
|
|
785
|
-
let dashboardWireframe = `
|
|
786
|
-
@scene: dashboard
|
|
787
|
-
@title: Dashboard
|
|
788
|
-
|
|
789
|
-
+--Dashboard------------------------+
|
|
790
|
-
| |
|
|
791
|
-
| +--Header---------------------+ |
|
|
792
|
-
| | * Dashboard | |
|
|
793
|
-
| | "Logout" | |
|
|
794
|
-
| +-----------------------------+ |
|
|
795
|
-
| |
|
|
796
|
-
| +--Stats----------------------+ |
|
|
797
|
-
| | | |
|
|
798
|
-
| | Users: 1,234 | |
|
|
799
|
-
| | Revenue: $45,678 | |
|
|
800
|
-
| | | |
|
|
801
|
-
| +-----------------------------+ |
|
|
802
|
-
| |
|
|
803
|
-
| +--Actions--------------------+ |
|
|
804
|
-
| | | |
|
|
805
|
-
| | [ Add User ] | |
|
|
806
|
-
| | [ Generate Report ] | |
|
|
807
|
-
| | | |
|
|
808
|
-
| +-----------------------------+ |
|
|
809
|
-
| |
|
|
810
|
-
+-----------------------------------+
|
|
811
|
-
`
|
|
812
|
-
|
|
813
|
-
let result = Parser.parse(dashboardWireframe)
|
|
814
|
-
|
|
815
|
-
switch result {
|
|
816
|
-
| Ok(ast) => {
|
|
817
|
-
let scene = ast.scenes->Array.getUnsafe(0)
|
|
818
|
-
|
|
819
|
-
// Find root dashboard box
|
|
820
|
-
let dashboardBox = Belt.Array.getBy(scene.elements, el => {
|
|
821
|
-
switch el {
|
|
822
|
-
| Box({name: Some(boxName)}) => Js.String2.includes(boxName, "Dashboard")
|
|
823
|
-
| _ => false
|
|
824
|
-
}
|
|
825
|
-
})
|
|
826
|
-
t->expect(dashboardBox)->Expect.not->Expect.toBe(None)
|
|
827
|
-
|
|
828
|
-
// Verify nested boxes
|
|
829
|
-
switch dashboardBox {
|
|
830
|
-
| Some(Box({children})) => {
|
|
831
|
-
// Should have 3 nested boxes: Header, Stats, Actions
|
|
832
|
-
let boxCount = Belt.Array.reduce(children, 0, (count, el) => {
|
|
833
|
-
switch el {
|
|
834
|
-
| Box(_) => count + 1
|
|
835
|
-
| _ => count
|
|
836
|
-
}
|
|
837
|
-
})
|
|
838
|
-
t->expect(boxCount)->Expect.toBe(3)
|
|
839
|
-
|
|
840
|
-
// Verify Header box has emphasis and link
|
|
841
|
-
let headerBox = Belt.Array.getBy(children, el => {
|
|
842
|
-
switch el {
|
|
843
|
-
| Box({name: Some(name)}) => Js.String2.includes(name, "Header")
|
|
844
|
-
| _ => false
|
|
845
|
-
}
|
|
846
|
-
})
|
|
847
|
-
t->expect(headerBox)->Expect.not->Expect.toBe(None)
|
|
848
|
-
|
|
849
|
-
// Verify Actions box has buttons
|
|
850
|
-
let actionsBox = Belt.Array.getBy(children, el => {
|
|
851
|
-
switch el {
|
|
852
|
-
| Box({name: Some(name), children: actionChildren}) => {
|
|
853
|
-
if Js.String2.includes(name, "Actions") {
|
|
854
|
-
let buttonCount = Belt.Array.reduce(actionChildren, 0, (count, child) => {
|
|
855
|
-
switch child {
|
|
856
|
-
| Button(_) => count + 1
|
|
857
|
-
| _ => count
|
|
858
|
-
}
|
|
859
|
-
})
|
|
860
|
-
buttonCount >= 2
|
|
861
|
-
} else {
|
|
862
|
-
false
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
| _ => false
|
|
866
|
-
}
|
|
867
|
-
})
|
|
868
|
-
t->expect(actionsBox)->Expect.not->Expect.toBe(None)
|
|
869
|
-
}
|
|
870
|
-
| _ => t->expect(true)->Expect.toBe(false) // fail: Expected dashboard box to be a Box element
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
| Error(errors) => {
|
|
874
|
-
Console.error2("Parse errors:", errors)
|
|
875
|
-
t->expect(true)->Expect.toBe(false) // fail: Expected successful parse of dashboard
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
})
|
|
879
|
-
})
|
|
880
|
-
|
|
881
|
-
// =============================================================================
|
|
882
|
-
// E2E-12: Handle Mixed Valid and Invalid Boxes
|
|
883
|
-
// =============================================================================
|
|
884
|
-
|
|
885
|
-
describe("E2E-12: Handle Mixed Valid and Invalid Boxes", t => {
|
|
886
|
-
test("continues parsing after errors and collects all issues", t => {
|
|
887
|
-
let mixedWireframe = `
|
|
888
|
-
@scene: mixed
|
|
889
|
-
|
|
890
|
-
+--Good Box 1-----+
|
|
891
|
-
| |
|
|
892
|
-
| [ Button 1 ] |
|
|
893
|
-
| |
|
|
894
|
-
+-----------------+
|
|
895
|
-
|
|
896
|
-
+--Bad Box 1------+
|
|
897
|
-
| |
|
|
898
|
-
+-------
|
|
899
|
-
|
|
900
|
-
+--Good Box 2-----+
|
|
901
|
-
| |
|
|
902
|
-
| [ Button 2 ] |
|
|
903
|
-
| |
|
|
904
|
-
+-----------------+
|
|
905
|
-
|
|
906
|
-
+--Bad Box 2+
|
|
907
|
-
| |
|
|
908
|
-
+-------------+
|
|
909
|
-
`
|
|
910
|
-
|
|
911
|
-
let result = Parser.parse(mixedWireframe)
|
|
912
|
-
|
|
913
|
-
switch result {
|
|
914
|
-
| Ok(_) => t->expect(true)->Expect.toBe(false) // fail: Expected errors for invalid boxes
|
|
915
|
-
| Error(errors) => {
|
|
916
|
-
// Should have multiple errors
|
|
917
|
-
t->expect(Belt.Array.length(errors))->Expect.Int.toBeGreaterThan(1)
|
|
918
|
-
|
|
919
|
-
// Check for different error types
|
|
920
|
-
let hasUncloseError = Belt.Array.some(errors, error => {
|
|
921
|
-
switch error.code {
|
|
922
|
-
| UncloseBox(_) => true
|
|
923
|
-
| _ => false
|
|
924
|
-
}
|
|
925
|
-
})
|
|
926
|
-
|
|
927
|
-
let hasMismatchError = Belt.Array.some(errors, error => {
|
|
928
|
-
switch error.code {
|
|
929
|
-
| MismatchedWidth(_) => true
|
|
930
|
-
| _ => false
|
|
931
|
-
}
|
|
932
|
-
})
|
|
933
|
-
|
|
934
|
-
// Should have detected both error types
|
|
935
|
-
t->expect(hasUncloseError || hasMismatchError)->Expect.toBe(true)
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
})
|
|
939
|
-
|
|
940
|
-
test("successfully parses valid boxes even when errors exist", t => {
|
|
941
|
-
let mixedWireframe = `
|
|
942
|
-
@scene: mixed
|
|
943
|
-
|
|
944
|
-
+--Good Box 1-----+
|
|
945
|
-
| |
|
|
946
|
-
| [ Button 1 ] |
|
|
947
|
-
| |
|
|
948
|
-
+-----------------+
|
|
949
|
-
|
|
950
|
-
+--Bad Box-------+
|
|
951
|
-
| |
|
|
952
|
-
+-----
|
|
953
|
-
|
|
954
|
-
+--Good Box 2-----+
|
|
955
|
-
| |
|
|
956
|
-
| [ Button 2 ] |
|
|
957
|
-
| |
|
|
958
|
-
+-----------------+
|
|
959
|
-
`
|
|
960
|
-
|
|
961
|
-
let result = Parser.parse(mixedWireframe)
|
|
962
|
-
|
|
963
|
-
// Even with errors, should attempt to parse valid boxes
|
|
964
|
-
// (Implementation may return partial AST with errors)
|
|
965
|
-
switch result {
|
|
966
|
-
| Ok(_) => {
|
|
967
|
-
// If implementation returns Ok with warnings
|
|
968
|
-
pass
|
|
969
|
-
}
|
|
970
|
-
| Error(errors) => {
|
|
971
|
-
// Verify we collected errors
|
|
972
|
-
t->expect(Belt.Array.length(errors))->Expect.Int.toBeGreaterThan(0)
|
|
973
|
-
|
|
974
|
-
// Check that errors are reported
|
|
975
|
-
let hasStructuralError = Belt.Array.some(errors, error => {
|
|
976
|
-
switch error.code {
|
|
977
|
-
| UncloseBox(_) | MismatchedWidth(_) => true
|
|
978
|
-
| _ => false
|
|
979
|
-
}
|
|
980
|
-
})
|
|
981
|
-
t->expect(hasStructuralError)->Expect.toBe(true)
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
})
|
|
985
|
-
})
|
|
986
|
-
|
|
987
|
-
// Test Suite Summary
|
|
988
|
-
//
|
|
989
|
-
// This test suite validates:
|
|
990
|
-
// 1. E2E-01: Simple login scene with all element types
|
|
991
|
-
// 2. E2E-02: Multi-scene wireframe with transitions
|
|
992
|
-
// 3. E2E-03: Deeply nested boxes (3 levels)
|
|
993
|
-
// 4. E2E-04: Wireframe with dividers
|
|
994
|
-
// 5. E2E-05: Wireframe with interactions DSL
|
|
995
|
-
// 6. E2E-06: Unclosed box errors
|
|
996
|
-
// 7. E2E-07: Width mismatch errors
|
|
997
|
-
// 8. E2E-08: Overlapping boxes error
|
|
998
|
-
// 9. E2E-09: Deep nesting warnings
|
|
999
|
-
// 10. E2E-10: Complete registration flow
|
|
1000
|
-
// 11. E2E-11: Dashboard with multiple components
|
|
1001
|
-
// 12. E2E-12: Mixed valid and invalid boxes
|
|
1002
|
-
//
|
|
1003
|
-
// Total Test Cases: 14 (12 describe blocks with multiple assertions)
|
|
1004
|
-
// Coverage: Comprehensive integration validation of all parser stages
|
|
1005
|
-
//
|
|
1006
|
-
// To run:
|
|
1007
|
-
// npm test -- WyreframeParser_integration
|
|
1008
|
-
// npm run test:coverage
|