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.
Files changed (59) hide show
  1. package/LICENSE +692 -0
  2. package/README.md +65 -5
  3. package/package.json +8 -7
  4. package/src/index.ts +425 -0
  5. package/src/renderer/Renderer.gen.tsx +49 -0
  6. package/src/renderer/Renderer.mjs +41 -1
  7. package/src/renderer/Renderer.res +78 -0
  8. package/src/test/Expect.mjs +9 -0
  9. package/src/parser/Core/__tests__/Bounds_test.mjs +0 -326
  10. package/src/parser/Core/__tests__/Bounds_test.res +0 -412
  11. package/src/parser/Core/__tests__/Grid_test.mjs +0 -322
  12. package/src/parser/Core/__tests__/Grid_test.res +0 -319
  13. package/src/parser/Core/__tests__/Types_test.mjs +0 -614
  14. package/src/parser/Core/__tests__/Types_test.res +0 -650
  15. package/src/parser/Detector/__tests__/BoxTracer_test.mjs +0 -70
  16. package/src/parser/Detector/__tests__/BoxTracer_test.res +0 -92
  17. package/src/parser/Detector/__tests__/HierarchyBuilder_test.mjs +0 -489
  18. package/src/parser/Detector/__tests__/HierarchyBuilder_test.res +0 -849
  19. package/src/parser/Detector/__tests__/ShapeDetector_test.mjs +0 -377
  20. package/src/parser/Detector/__tests__/ShapeDetector_test.res +0 -563
  21. package/src/parser/Interactions/__tests__/InteractionMerger_test.mjs +0 -576
  22. package/src/parser/Interactions/__tests__/InteractionMerger_test.res +0 -646
  23. package/src/parser/Scanner/__tests__/Grid_manual.mjs +0 -214
  24. package/src/parser/Scanner/__tests__/Grid_manual.res +0 -141
  25. package/src/parser/Semantic/Elements/__tests__/ButtonParser_test.mjs +0 -189
  26. package/src/parser/Semantic/Elements/__tests__/ButtonParser_test.res +0 -257
  27. package/src/parser/Semantic/Elements/__tests__/CheckboxParser_test.mjs +0 -202
  28. package/src/parser/Semantic/Elements/__tests__/CheckboxParser_test.res +0 -250
  29. package/src/parser/Semantic/Elements/__tests__/CodeTextParser_manual.mjs +0 -293
  30. package/src/parser/Semantic/Elements/__tests__/CodeTextParser_manual.res +0 -134
  31. package/src/parser/Semantic/Elements/__tests__/InputParser_test.mjs +0 -253
  32. package/src/parser/Semantic/Elements/__tests__/InputParser_test.res +0 -304
  33. package/src/parser/Semantic/Elements/__tests__/LinkParser_test.mjs +0 -289
  34. package/src/parser/Semantic/Elements/__tests__/LinkParser_test.res +0 -402
  35. package/src/parser/Semantic/Elements/__tests__/TextParser_test.mjs +0 -149
  36. package/src/parser/Semantic/Elements/__tests__/TextParser_test.res +0 -167
  37. package/src/parser/Semantic/__tests__/ASTBuilder_test.mjs +0 -187
  38. package/src/parser/Semantic/__tests__/ASTBuilder_test.res +0 -192
  39. package/src/parser/Semantic/__tests__/ParserRegistry_test.mjs +0 -154
  40. package/src/parser/Semantic/__tests__/ParserRegistry_test.res +0 -191
  41. package/src/parser/Semantic/__tests__/SemanticParser_integration_test.mjs +0 -768
  42. package/src/parser/Semantic/__tests__/SemanticParser_integration_test.res +0 -1069
  43. package/src/parser/Semantic/__tests__/SemanticParser_manual.mjs +0 -1329
  44. package/src/parser/Semantic/__tests__/SemanticParser_manual.res +0 -544
  45. package/src/parser/__tests__/GridScanner_integration.test.mjs +0 -632
  46. package/src/parser/__tests__/GridScanner_integration.test.res +0 -816
  47. package/src/parser/__tests__/Performance.test.mjs +0 -244
  48. package/src/parser/__tests__/Performance.test.res +0 -371
  49. package/src/parser/__tests__/PerformanceFixtures.mjs +0 -200
  50. package/src/parser/__tests__/PerformanceFixtures.res +0 -284
  51. package/src/parser/__tests__/WyreframeParser_integration.test.mjs +0 -770
  52. package/src/parser/__tests__/WyreframeParser_integration.test.res +0 -1008
  53. package/src/parser/__tests__/fixtures/alignment-test.txt +0 -9
  54. package/src/parser/__tests__/fixtures/all-elements.txt +0 -16
  55. package/src/parser/__tests__/fixtures/login-scene.txt +0 -17
  56. package/src/parser/__tests__/fixtures/multi-scene.txt +0 -25
  57. package/src/parser/__tests__/fixtures/nested-boxes.txt +0 -15
  58. package/src/parser/__tests__/fixtures/simple-box.txt +0 -5
  59. package/src/parser/__tests__/fixtures/with-dividers.txt +0 -14
@@ -1,563 +0,0 @@
1
- // ShapeDetector_test.res
2
- // Integration tests for ShapeDetector module
3
- //
4
- // Tests shape detection including:
5
- // - Single boxes
6
- // - Nested boxes (2-3 levels)
7
- // - Sibling boxes
8
- // - Dividers
9
- // - Box names
10
- // - Malformed boxes (error cases)
11
- //
12
- // Requirements: REQ-25 (Testability - Unit Test Coverage ≥90%)
13
-
14
- open Vitest
15
- open Types
16
-
17
- // ============================================================================
18
- // Test Helpers
19
- // ============================================================================
20
-
21
- /**
22
- * Create a grid from a multi-line string wireframe.
23
- * Handles newline splitting and normalization.
24
- */
25
- let makeGrid = (wireframe: string): Grid.t => {
26
- wireframe
27
- ->String.trim
28
- ->String.split("\n")
29
- ->Grid.fromLines
30
- }
31
-
32
- /**
33
- * Helper to verify result is Ok and contains expected number of boxes.
34
- */
35
- let expectOkWithBoxCount = (
36
- t: Vitest_Types.testCtx,
37
- result: ShapeDetector.detectResult,
38
- expectedCount: int
39
- ): array<BoxTracer.box> => {
40
- switch result {
41
- | Ok(boxes) => {
42
- t->expect(Array.length(boxes))->Expect.toBe(expectedCount)
43
- boxes
44
- }
45
- | Error(errors) => {
46
- Console.error("Expected Ok but got Error:")
47
- errors->Array.forEach(err => {
48
- Console.error(ErrorTypes.getCodeName(err.code))
49
- })
50
- t->expect(true)->Expect.toBe(false) // fail: Expected Ok with boxes, got Error
51
- []
52
- }
53
- }
54
- }
55
-
56
- /**
57
- * Helper to verify result is Error and contains expected number of errors.
58
- */
59
- let expectErrorWithCount = (
60
- t: Vitest_Types.testCtx,
61
- result: ShapeDetector.detectResult,
62
- minErrorCount: int
63
- ): array<ErrorTypes.t> => {
64
- switch result {
65
- | Error(errors) => {
66
- t->expect(Array.length(errors))->Expect.Int.toBeGreaterThanOrEqual(minErrorCount)
67
- errors
68
- }
69
- | Ok(boxes) => {
70
- t->expect(true)->Expect.toBe(false) // fail: Expected Error but got Ok
71
- []
72
- }
73
- }
74
- }
75
-
76
- // ============================================================================
77
- // SD-01: Single Box Detection
78
- // ============================================================================
79
-
80
- describe("ShapeDetector - Single Box", () => {
81
- test("SD-01: detects a simple box", t => {
82
- // Create a simple box wireframe
83
- let wireframe = `
84
- +------+
85
- | |
86
- +------+
87
- `
88
- let grid = makeGrid(wireframe)
89
- let result = ShapeDetector.detect(grid)
90
-
91
- let boxes = expectOkWithBoxCount(t, result, 1)
92
- let box = boxes[0]->Option.getExn
93
-
94
- // Verify no children and no name
95
- t->expect(Array.length(box.children))->Expect.toBe(0)
96
- t->expect(box.name)->Expect.toBe(None)
97
- })
98
-
99
- test("SD-01b: handles boxes with different dimensions", t => {
100
- let wireframe = `
101
- +----------+
102
- | |
103
- | |
104
- +----------+
105
- `
106
- let grid = makeGrid(wireframe)
107
- let result = ShapeDetector.detect(grid)
108
-
109
- let _ = expectOkWithBoxCount(t, result, 1)
110
- })
111
- })
112
-
113
- // ============================================================================
114
- // SD-02-03: Nested Boxes
115
- // ============================================================================
116
-
117
- describe("ShapeDetector - Nested Boxes", () => {
118
- test("SD-02: detects 2-level nested boxes", t => {
119
- let wireframe = `
120
- +----------+
121
- | +----+ |
122
- | | | |
123
- | +----+ |
124
- +----------+
125
- `
126
- let grid = makeGrid(wireframe)
127
- let result = ShapeDetector.detect(grid)
128
-
129
- let boxes = expectOkWithBoxCount(t, result, 1)
130
- let outer = boxes[0]->Option.getExn
131
-
132
- // Outer should have 1 child
133
- t->expect(Array.length(outer.children))->Expect.toBe(1)
134
-
135
- let inner = outer.children[0]->Option.getExn
136
-
137
- // Verify containment
138
- t->expect(Bounds.contains(outer.bounds, inner.bounds))->Expect.toBe(true)
139
- })
140
-
141
- test("SD-03: detects 3-level nested boxes", t => {
142
- let wireframe = `
143
- +--------------+
144
- | +----------+ |
145
- | | +------+ | |
146
- | | | | | |
147
- | | +------+ | |
148
- | +----------+ |
149
- +--------------+
150
- `
151
- let grid = makeGrid(wireframe)
152
- let result = ShapeDetector.detect(grid)
153
-
154
- let boxes = expectOkWithBoxCount(t, result, 1)
155
- let outer = boxes[0]->Option.getExn
156
-
157
- t->expect(Array.length(outer.children))->Expect.toBe(1)
158
- let middle = outer.children[0]->Option.getExn
159
-
160
- t->expect(Array.length(middle.children))->Expect.toBe(1)
161
- let inner = middle.children[0]->Option.getExn
162
-
163
- t->expect(Array.length(inner.children))->Expect.toBe(0)
164
-
165
- // Verify total count
166
- let total = ShapeDetector.countBoxes(boxes)
167
- t->expect(total)->Expect.toBe(3)
168
- })
169
- })
170
-
171
- // ============================================================================
172
- // SD-04: Sibling Boxes
173
- // ============================================================================
174
-
175
- describe("ShapeDetector - Sibling Boxes", () => {
176
- test("SD-04: detects sibling boxes at same level", t => {
177
- let wireframe = `
178
- +-----+ +-----+
179
- | | | |
180
- +-----+ +-----+
181
- `
182
- let grid = makeGrid(wireframe)
183
- let result = ShapeDetector.detect(grid)
184
-
185
- let boxes = expectOkWithBoxCount(t, result, 2)
186
-
187
- let box1 = boxes[0]->Option.getExn
188
- let box2 = boxes[1]->Option.getExn
189
-
190
- // Neither contains the other
191
- t->expect(Bounds.contains(box1.bounds, box2.bounds))->Expect.toBe(false)
192
- t->expect(Bounds.contains(box2.bounds, box1.bounds))->Expect.toBe(false)
193
-
194
- // No overlap
195
- t->expect(Bounds.overlaps(box1.bounds, box2.bounds))->Expect.toBe(false)
196
-
197
- // Both have no children
198
- t->expect(Array.length(box1.children))->Expect.toBe(0)
199
- t->expect(Array.length(box2.children))->Expect.toBe(0)
200
- })
201
- })
202
-
203
- // ============================================================================
204
- // SD-05-06: Dividers
205
- // ============================================================================
206
-
207
- describe("ShapeDetector - Dividers", () => {
208
- test("SD-05: handles box with single divider", t => {
209
- let wireframe = `
210
- +-----+
211
- | |
212
- +=====+
213
- | |
214
- +-----+
215
- `
216
- let grid = makeGrid(wireframe)
217
- let result = ShapeDetector.detect(grid)
218
-
219
- let _ = expectOkWithBoxCount(t, result, 1)
220
- })
221
-
222
- test("SD-06: handles box with multiple dividers", t => {
223
- let wireframe = `
224
- +-----+
225
- | |
226
- +=====+
227
- | |
228
- +=====+
229
- | |
230
- +-----+
231
- `
232
- let grid = makeGrid(wireframe)
233
- let result = ShapeDetector.detect(grid)
234
-
235
- let _ = expectOkWithBoxCount(t, result, 1)
236
- })
237
- })
238
-
239
- // ============================================================================
240
- // SD-07-08: Box Names
241
- // ============================================================================
242
-
243
- describe("ShapeDetector - Box Names", () => {
244
- test("SD-07: extracts box name from top border", t => {
245
- let wireframe = `
246
- +--Login--+
247
- | |
248
- +---------+
249
- `
250
- let grid = makeGrid(wireframe)
251
- let result = ShapeDetector.detect(grid)
252
-
253
- let boxes = expectOkWithBoxCount(t, result, 1)
254
- let box = boxes[0]->Option.getExn
255
-
256
- t->expect(box.name)->Expect.toEqual(Some("Login"))
257
- })
258
-
259
- test("SD-08: handles multiple named boxes", t => {
260
- let wireframe = `
261
- +--Header--+
262
- | |
263
- +----------+
264
-
265
- +--Content-+
266
- | |
267
- +----------+
268
- `
269
- let grid = makeGrid(wireframe)
270
- let result = ShapeDetector.detect(grid)
271
-
272
- let boxes = expectOkWithBoxCount(t, result, 2)
273
-
274
- // Find boxes by name
275
- let hasHeader = boxes->Array.some(box => {
276
- switch box.name {
277
- | Some("Header") => true
278
- | _ => false
279
- }
280
- })
281
-
282
- let hasContent = boxes->Array.some(box => {
283
- switch box.name {
284
- | Some("Content") => true
285
- | _ => false
286
- }
287
- })
288
-
289
- t->expect(hasHeader)->Expect.toBe(true)
290
- t->expect(hasContent)->Expect.toBe(true)
291
- })
292
- })
293
-
294
- // ============================================================================
295
- // SD-09-13: Error Cases
296
- // ============================================================================
297
-
298
- describe("ShapeDetector - Error Cases", () => {
299
- test("SD-09: detects unclosed box - missing top corner", t => {
300
- let wireframe = `
301
- +-----
302
- | |
303
- +-----+
304
- `
305
- let grid = makeGrid(wireframe)
306
- let result = ShapeDetector.detect(grid)
307
-
308
- let errors = expectErrorWithCount(t, result, 1)
309
-
310
- let hasUncloseError = errors->Array.some(err => {
311
- switch err.code {
312
- | UncloseBox({direction: "top"}) => true
313
- | _ => false
314
- }
315
- })
316
-
317
- t->expect(hasUncloseError)->Expect.toBe(true)
318
- })
319
-
320
- test("SD-10: detects unclosed box - missing bottom corner", t => {
321
- let wireframe = `
322
- +-----+
323
- | |
324
- +-----
325
- `
326
- let grid = makeGrid(wireframe)
327
- let result = ShapeDetector.detect(grid)
328
-
329
- let errors = expectErrorWithCount(t, result, 1)
330
-
331
- let hasUncloseError = errors->Array.some(err => {
332
- switch err.code {
333
- | UncloseBox({direction: "bottom"}) => true
334
- | _ => false
335
- }
336
- })
337
-
338
- t->expect(hasUncloseError)->Expect.toBe(true)
339
- })
340
-
341
- test("SD-11: detects mismatched width", t => {
342
- let wireframe = `
343
- +-----+
344
- | |
345
- +-------+
346
- `
347
- let grid = makeGrid(wireframe)
348
- let result = ShapeDetector.detect(grid)
349
-
350
- let errors = expectErrorWithCount(t, result, 1)
351
-
352
- let hasMismatchError = errors->Array.some(err => {
353
- switch err.code {
354
- | MismatchedWidth(_) => true
355
- | _ => false
356
- }
357
- })
358
-
359
- t->expect(hasMismatchError)->Expect.toBe(true)
360
- })
361
-
362
- test("SD-12: detects unclosed box - misaligned left edge", t => {
363
- // This wireframe has a space at row 2 col 0 where a pipe should be
364
- // This causes an unclosed box error on the left edge
365
- let wireframe = `
366
- +-----+
367
- | |
368
- | |
369
- +-----+
370
- `
371
- let grid = makeGrid(wireframe)
372
- let result = ShapeDetector.detect(grid)
373
-
374
- let errors = expectErrorWithCount(t, result, 1)
375
-
376
- let hasUncloseError = errors->Array.some(err => {
377
- switch err.code {
378
- | UncloseBox({direction: "left"}) => true
379
- | _ => false
380
- }
381
- })
382
-
383
- t->expect(hasUncloseError)->Expect.toBe(true)
384
- })
385
- })
386
-
387
- // ============================================================================
388
- // SD-14: Edge Cases
389
- // ============================================================================
390
-
391
- describe("ShapeDetector - Edge Cases", () => {
392
- test("SD-14: handles empty grid with no boxes", t => {
393
- let wireframe = `
394
- abc
395
- def
396
- ghi
397
- `
398
- let grid = makeGrid(wireframe)
399
- let result = ShapeDetector.detect(grid)
400
-
401
- // Should return Ok with empty array
402
- let _ = expectOkWithBoxCount(t, result, 0)
403
- })
404
- })
405
-
406
- // ============================================================================
407
- // SD-15: Complex Integration Test
408
- // ============================================================================
409
-
410
- describe("ShapeDetector - Complex Integration", () => {
411
- test("SD-15: handles nested structure with sibling inner boxes", t => {
412
- // Simplified test: outer box with two sibling inner boxes
413
- let wireframe = `
414
- +----------+
415
- | +--+ +--+|
416
- | | | | ||
417
- | +--+ +--+|
418
- +----------+
419
- `
420
- let grid = makeGrid(wireframe)
421
- let result = ShapeDetector.detect(grid)
422
-
423
- let boxes = expectOkWithBoxCount(t, result, 1)
424
- let outer = boxes[0]->Option.getExn
425
-
426
- // Outer box should have 2 sibling children
427
- t->expect(Array.length(outer.children))->Expect.toBe(2)
428
-
429
- // Verify total count: outer + 2 inner = 3
430
- let total = ShapeDetector.countBoxes(boxes)
431
- t->expect(total)->Expect.toBe(3)
432
- })
433
- })
434
-
435
- // ============================================================================
436
- // SD-16-17: Deduplication and Error Recovery
437
- // ============================================================================
438
-
439
- describe("ShapeDetector - Deduplication & Error Recovery", () => {
440
- test("SD-16: deduplicates boxes traced from multiple corners", t => {
441
- let wireframe = `
442
- +-----+
443
- | |
444
- +-----+
445
- `
446
- let grid = makeGrid(wireframe)
447
-
448
- // Verify 4 corners exist
449
- t->expect(Array.length(grid.cornerIndex))->Expect.toBe(4)
450
-
451
- let result = ShapeDetector.detect(grid)
452
-
453
- // Only 1 unique box
454
- let _ = expectOkWithBoxCount(t, result, 1)
455
- })
456
-
457
- test("SD-17: returns valid boxes when some traces succeed (REQ-28)", t => {
458
- // This wireframe has one valid box and two malformed ones
459
- // Current implementation returns Ok with valid boxes when some succeed
460
- let wireframe = `
461
- +---+ +-----
462
- | | | |
463
- +---+ +-----+
464
-
465
- +-----+
466
- | |
467
- +-------+
468
- `
469
- let grid = makeGrid(wireframe)
470
- let result = ShapeDetector.detect(grid)
471
-
472
- // Should have at least 1 valid box (the left one: +---+)
473
- let boxes = expectOkWithBoxCount(t, result, 1)
474
- t->expect(Array.length(boxes))->Expect.Int.toBeGreaterThanOrEqual(1)
475
- })
476
- })
477
-
478
- // ============================================================================
479
- // SD-18: Helper Functions
480
- // ============================================================================
481
-
482
- describe("ShapeDetector - Helper Functions", () => {
483
- test("SD-18a: countBoxes counts all boxes including nested", t => {
484
- let wireframe = `
485
- +----------+
486
- | +------+ |
487
- | | +--+ | |
488
- | | | | | |
489
- | | +--+ | |
490
- | +------+ |
491
- +----------+
492
- `
493
- let grid = makeGrid(wireframe)
494
- let result = ShapeDetector.detect(grid)
495
-
496
- let boxes = expectOkWithBoxCount(t, result, 1)
497
- let total = ShapeDetector.countBoxes(boxes)
498
-
499
- t->expect(total)->Expect.toBe(3)
500
- })
501
-
502
- test("SD-18b: flattenBoxes returns all boxes in flat array", t => {
503
- let wireframe = `
504
- +----------+
505
- | +------+ |
506
- | | +--+ | |
507
- | | | | | |
508
- | | +--+ | |
509
- | +------+ |
510
- +----------+
511
- `
512
- let grid = makeGrid(wireframe)
513
- let result = ShapeDetector.detect(grid)
514
-
515
- let boxes = expectOkWithBoxCount(t, result, 1)
516
- let flat = ShapeDetector.flattenBoxes(boxes)
517
-
518
- t->expect(Array.length(flat))->Expect.toBe(3)
519
- })
520
-
521
- test("SD-18c: getStats returns formatted statistics for Ok result", t => {
522
- let wireframe = `
523
- +-----+
524
- | |
525
- +-----+
526
- `
527
- let grid = makeGrid(wireframe)
528
- let result = ShapeDetector.detect(grid)
529
-
530
- let stats = ShapeDetector.getStats(result)
531
-
532
- t->expect(stats->String.includes("Success"))->Expect.toBe(true)
533
- t->expect(stats->String.includes("Root boxes: 1"))->Expect.toBe(true)
534
- })
535
-
536
- test("SD-18d: getStats returns formatted statistics for Error result", t => {
537
- let wireframe = `
538
- +-----
539
- | |
540
- +-----+
541
- `
542
- let grid = makeGrid(wireframe)
543
- let result = ShapeDetector.detect(grid)
544
-
545
- let stats = ShapeDetector.getStats(result)
546
-
547
- t->expect(stats->String.includes("Failed"))->Expect.toBe(true)
548
- })
549
-
550
- test("SD-18e: countBoxes handles empty array", t => {
551
- let emptyBoxes: array<BoxTracer.box> = []
552
- let count = ShapeDetector.countBoxes(emptyBoxes)
553
-
554
- t->expect(count)->Expect.toBe(0)
555
- })
556
-
557
- test("SD-18f: flattenBoxes handles empty array", t => {
558
- let emptyBoxes: array<BoxTracer.box> = []
559
- let flat = ShapeDetector.flattenBoxes(emptyBoxes)
560
-
561
- t->expect(Array.length(flat))->Expect.toBe(0)
562
- })
563
- })