wyreframe 0.1.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 +123 -0
- package/dist/index.d.ts +267 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +195 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
- package/src/parser/Core/Bounds.mjs +61 -0
- package/src/parser/Core/Bounds.res +65 -0
- package/src/parser/Core/Grid.mjs +268 -0
- package/src/parser/Core/Grid.res +265 -0
- package/src/parser/Core/Position.mjs +83 -0
- package/src/parser/Core/Position.res +54 -0
- package/src/parser/Core/Types.mjs +435 -0
- package/src/parser/Core/Types.res +331 -0
- package/src/parser/Core/__tests__/Bounds_test.mjs +326 -0
- package/src/parser/Core/__tests__/Bounds_test.res +412 -0
- package/src/parser/Core/__tests__/Grid_test.mjs +322 -0
- package/src/parser/Core/__tests__/Grid_test.res +319 -0
- package/src/parser/Core/__tests__/Types_test.mjs +614 -0
- package/src/parser/Core/__tests__/Types_test.res +650 -0
- package/src/parser/Detector/BoxTracer.mjs +302 -0
- package/src/parser/Detector/BoxTracer.res +374 -0
- package/src/parser/Detector/HierarchyBuilder.mjs +158 -0
- package/src/parser/Detector/HierarchyBuilder.res +315 -0
- package/src/parser/Detector/ShapeDetector.mjs +134 -0
- package/src/parser/Detector/ShapeDetector.res +236 -0
- package/src/parser/Detector/__tests__/BoxTracer_test.mjs +70 -0
- package/src/parser/Detector/__tests__/BoxTracer_test.res +92 -0
- package/src/parser/Detector/__tests__/HierarchyBuilder_test.mjs +489 -0
- package/src/parser/Detector/__tests__/HierarchyBuilder_test.res +849 -0
- package/src/parser/Detector/__tests__/ShapeDetector_test.mjs +377 -0
- package/src/parser/Detector/__tests__/ShapeDetector_test.res +563 -0
- package/src/parser/Errors/ErrorContext.mjs +106 -0
- package/src/parser/Errors/ErrorContext.res +191 -0
- package/src/parser/Errors/ErrorMessages.mjs +289 -0
- package/src/parser/Errors/ErrorMessages.res +303 -0
- package/src/parser/Errors/ErrorTypes.mjs +105 -0
- package/src/parser/Errors/ErrorTypes.res +169 -0
- package/src/parser/Interactions/InteractionMerger.mjs +266 -0
- package/src/parser/Interactions/InteractionMerger.res +450 -0
- package/src/parser/Interactions/InteractionParser.mjs +88 -0
- package/src/parser/Interactions/InteractionParser.res +127 -0
- package/src/parser/Interactions/SimpleInteractionParser.mjs +278 -0
- package/src/parser/Interactions/SimpleInteractionParser.res +262 -0
- package/src/parser/Interactions/__tests__/InteractionMerger_test.mjs +576 -0
- package/src/parser/Interactions/__tests__/InteractionMerger_test.res +646 -0
- package/src/parser/Parser.gen.tsx +96 -0
- package/src/parser/Parser.mjs +212 -0
- package/src/parser/Parser.res +481 -0
- package/src/parser/Scanner/__tests__/Grid_manual.mjs +214 -0
- package/src/parser/Scanner/__tests__/Grid_manual.res +141 -0
- package/src/parser/Semantic/ASTBuilder.mjs +197 -0
- package/src/parser/Semantic/ASTBuilder.res +288 -0
- package/src/parser/Semantic/AlignmentCalc.mjs +41 -0
- package/src/parser/Semantic/AlignmentCalc.res +104 -0
- package/src/parser/Semantic/Elements/ButtonParser.mjs +58 -0
- package/src/parser/Semantic/Elements/ButtonParser.res +131 -0
- package/src/parser/Semantic/Elements/CheckboxParser.mjs +58 -0
- package/src/parser/Semantic/Elements/CheckboxParser.res +79 -0
- package/src/parser/Semantic/Elements/CodeTextParser.mjs +50 -0
- package/src/parser/Semantic/Elements/CodeTextParser.res +111 -0
- package/src/parser/Semantic/Elements/ElementParser.mjs +15 -0
- package/src/parser/Semantic/Elements/ElementParser.res +83 -0
- package/src/parser/Semantic/Elements/EmphasisParser.mjs +46 -0
- package/src/parser/Semantic/Elements/EmphasisParser.res +67 -0
- package/src/parser/Semantic/Elements/InputParser.mjs +41 -0
- package/src/parser/Semantic/Elements/InputParser.res +97 -0
- package/src/parser/Semantic/Elements/LinkParser.mjs +60 -0
- package/src/parser/Semantic/Elements/LinkParser.res +156 -0
- package/src/parser/Semantic/Elements/TextParser.mjs +19 -0
- package/src/parser/Semantic/Elements/TextParser.res +42 -0
- package/src/parser/Semantic/Elements/__tests__/ButtonParser_test.mjs +189 -0
- package/src/parser/Semantic/Elements/__tests__/ButtonParser_test.res +257 -0
- package/src/parser/Semantic/Elements/__tests__/CheckboxParser_test.mjs +202 -0
- package/src/parser/Semantic/Elements/__tests__/CheckboxParser_test.res +250 -0
- package/src/parser/Semantic/Elements/__tests__/CodeTextParser_manual.mjs +293 -0
- package/src/parser/Semantic/Elements/__tests__/CodeTextParser_manual.res +134 -0
- package/src/parser/Semantic/Elements/__tests__/InputParser_test.mjs +253 -0
- package/src/parser/Semantic/Elements/__tests__/InputParser_test.res +304 -0
- package/src/parser/Semantic/Elements/__tests__/LinkParser_test.mjs +289 -0
- package/src/parser/Semantic/Elements/__tests__/LinkParser_test.res +402 -0
- package/src/parser/Semantic/Elements/__tests__/TextParser_test.mjs +149 -0
- package/src/parser/Semantic/Elements/__tests__/TextParser_test.res +167 -0
- package/src/parser/Semantic/ParserRegistry.mjs +82 -0
- package/src/parser/Semantic/ParserRegistry.res +145 -0
- package/src/parser/Semantic/SemanticParser.mjs +850 -0
- package/src/parser/Semantic/SemanticParser.res +1368 -0
- package/src/parser/Semantic/__tests__/ASTBuilder_test.mjs +187 -0
- package/src/parser/Semantic/__tests__/ASTBuilder_test.res +192 -0
- package/src/parser/Semantic/__tests__/ParserRegistry_test.mjs +154 -0
- package/src/parser/Semantic/__tests__/ParserRegistry_test.res +191 -0
- package/src/parser/Semantic/__tests__/SemanticParser_integration_test.mjs +768 -0
- package/src/parser/Semantic/__tests__/SemanticParser_integration_test.res +1069 -0
- package/src/parser/Semantic/__tests__/SemanticParser_manual.mjs +1329 -0
- package/src/parser/Semantic/__tests__/SemanticParser_manual.res +544 -0
- package/src/parser/TestMain.mjs +21 -0
- package/src/parser/TestMain.res +14 -0
- package/src/parser/TextExtractor.mjs +179 -0
- package/src/parser/TextExtractor.res +264 -0
- package/src/parser/__tests__/GridScanner_integration.test.mjs +632 -0
- package/src/parser/__tests__/GridScanner_integration.test.res +816 -0
- package/src/parser/__tests__/Performance.test.mjs +244 -0
- package/src/parser/__tests__/Performance.test.res +371 -0
- package/src/parser/__tests__/PerformanceFixtures.mjs +200 -0
- package/src/parser/__tests__/PerformanceFixtures.res +284 -0
- package/src/parser/__tests__/WyreframeParser_integration.test.mjs +770 -0
- package/src/parser/__tests__/WyreframeParser_integration.test.res +1008 -0
- package/src/parser/__tests__/fixtures/alignment-test.txt +9 -0
- package/src/parser/__tests__/fixtures/all-elements.txt +16 -0
- package/src/parser/__tests__/fixtures/login-scene.txt +17 -0
- package/src/parser/__tests__/fixtures/multi-scene.txt +25 -0
- package/src/parser/__tests__/fixtures/nested-boxes.txt +15 -0
- package/src/parser/__tests__/fixtures/simple-box.txt +5 -0
- package/src/parser/__tests__/fixtures/with-dividers.txt +14 -0
- package/src/renderer/Renderer.gen.tsx +32 -0
- package/src/renderer/Renderer.mjs +391 -0
- package/src/renderer/Renderer.res +558 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// ShapeDetector.res
|
|
2
|
+
// Main Shape Detector Module - Stage 2 of the parser pipeline
|
|
3
|
+
//
|
|
4
|
+
// Integrates BoxTracer, DividerDetector, and HierarchyBuilder to:
|
|
5
|
+
// 1. Find all boxes in the grid
|
|
6
|
+
// 2. Detect dividers within boxes
|
|
7
|
+
// 3. Build nesting hierarchy
|
|
8
|
+
// 4. Collect all errors without early stopping
|
|
9
|
+
//
|
|
10
|
+
// Requirements:
|
|
11
|
+
// - REQ-28: Reliability - Error Recovery (collect all errors)
|
|
12
|
+
|
|
13
|
+
open Types
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Type Definitions
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Result type for shape detection.
|
|
21
|
+
* Returns either:
|
|
22
|
+
* - Ok(boxes): Array of root-level boxes with nested hierarchy
|
|
23
|
+
* - Error(errors): Array of all errors encountered during detection
|
|
24
|
+
*/
|
|
25
|
+
type detectResult = result<array<BoxTracer.box>, array<ErrorTypes.t>>
|
|
26
|
+
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Helper Functions
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Convert HierarchyBuilder.hierarchyError to ErrorTypes.t
|
|
33
|
+
*/
|
|
34
|
+
let hierarchyErrorToParseError = (error: HierarchyBuilder.hierarchyError): ErrorTypes.t => {
|
|
35
|
+
switch error {
|
|
36
|
+
| OverlappingBoxes({box1, box2}) =>
|
|
37
|
+
ErrorTypes.make(
|
|
38
|
+
InvalidElement({
|
|
39
|
+
content: `Overlapping boxes detected at (${Int.toString(box1.bounds.top)},${Int.toString(
|
|
40
|
+
box1.bounds.left,
|
|
41
|
+
)}) and (${Int.toString(box2.bounds.top)},${Int.toString(box2.bounds.left)})`,
|
|
42
|
+
position: {row: box1.bounds.top, col: box1.bounds.left},
|
|
43
|
+
}),
|
|
44
|
+
None,
|
|
45
|
+
)
|
|
46
|
+
| CircularNesting =>
|
|
47
|
+
ErrorTypes.make(
|
|
48
|
+
InvalidElement({
|
|
49
|
+
content: "Circular nesting detected in box hierarchy",
|
|
50
|
+
position: {row: 0, col: 0},
|
|
51
|
+
}),
|
|
52
|
+
None,
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ============================================================================
|
|
58
|
+
// Helper Functions (must be defined before detect)
|
|
59
|
+
// ============================================================================
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Remove duplicate boxes that have the same bounds.
|
|
63
|
+
*
|
|
64
|
+
* This handles the case where multiple corners might trace the same box.
|
|
65
|
+
* We keep only the first occurrence of each unique box.
|
|
66
|
+
*
|
|
67
|
+
* @param boxes - Array of traced boxes
|
|
68
|
+
* @return Array of unique boxes (deduplicated by bounds)
|
|
69
|
+
*/
|
|
70
|
+
let deduplicateBoxes = (boxes: array<BoxTracer.box>): array<BoxTracer.box> => {
|
|
71
|
+
let seen = []
|
|
72
|
+
let unique = []
|
|
73
|
+
|
|
74
|
+
boxes->Array.forEach(box => {
|
|
75
|
+
// Check if we've seen a box with these bounds before
|
|
76
|
+
let isDuplicate = seen->Array.some(seenBounds => {
|
|
77
|
+
Bounds.equals(seenBounds, box.bounds)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
if !isDuplicate {
|
|
81
|
+
// New unique box
|
|
82
|
+
unique->Array.push(box)
|
|
83
|
+
seen->Array.push(box.bounds)
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
unique
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// Main Detection Function
|
|
92
|
+
// ============================================================================
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Detect all shapes (boxes, dividers) in the grid and build hierarchy.
|
|
96
|
+
*
|
|
97
|
+
* Algorithm:
|
|
98
|
+
* 1. Find all '+' corner positions using grid.cornerIndex
|
|
99
|
+
* 2. For each corner, attempt to trace a box using BoxTracer
|
|
100
|
+
* 3. Collect all successfully traced boxes
|
|
101
|
+
* 4. For each box, detect dividers within its bounds
|
|
102
|
+
* 5. Build parent-child hierarchy using HierarchyBuilder
|
|
103
|
+
* 6. Return Result with boxes or errors
|
|
104
|
+
*
|
|
105
|
+
* IMPORTANT: Not all '+' corners are top-left corners of boxes.
|
|
106
|
+
* A '+' can be a top-right, bottom-left, bottom-right corner, or
|
|
107
|
+
* part of nested box structure. traceBox failures for non-top-left
|
|
108
|
+
* corners are EXPECTED and should NOT be treated as errors.
|
|
109
|
+
*
|
|
110
|
+
* @param grid - The 2D character grid from GridScanner
|
|
111
|
+
* @return detectResult - Ok(root boxes) or Error(all errors)
|
|
112
|
+
*
|
|
113
|
+
* Requirements: REQ-28 (Error Recovery - no early stopping)
|
|
114
|
+
*/
|
|
115
|
+
let detect = (grid: Grid.t): detectResult => {
|
|
116
|
+
// Step 1: Find all '+' corner positions using pre-built index
|
|
117
|
+
let corners = grid.cornerIndex
|
|
118
|
+
|
|
119
|
+
// Step 2: Trace boxes from each corner
|
|
120
|
+
// Note: Most corners will NOT be valid top-left corners, which is expected.
|
|
121
|
+
// Only corners that are actually top-left of a box will trace successfully.
|
|
122
|
+
let boxes = []
|
|
123
|
+
let traceFailures = [] // Keep track of failures
|
|
124
|
+
|
|
125
|
+
corners->Array.forEach(corner => {
|
|
126
|
+
switch BoxTracer.traceBox(grid, corner) {
|
|
127
|
+
| Ok(box) => {
|
|
128
|
+
// Successfully traced a box - add to collection
|
|
129
|
+
boxes->Array.push(box)
|
|
130
|
+
}
|
|
131
|
+
| Error(traceError) => {
|
|
132
|
+
// This corner failed to trace as a top-left corner.
|
|
133
|
+
// Could be a non-top-left corner (expected) or a malformed box (error).
|
|
134
|
+
traceFailures->Array.push(traceError)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
// Step 3: Check if we found any boxes
|
|
140
|
+
if Array.length(boxes) === 0 {
|
|
141
|
+
// No boxes traced successfully
|
|
142
|
+
if Array.length(corners) === 0 {
|
|
143
|
+
// No corners at all - truly empty wireframe
|
|
144
|
+
Ok([])
|
|
145
|
+
} else if Array.length(traceFailures) > 0 {
|
|
146
|
+
// Had corners but all traces failed - this indicates malformed boxes
|
|
147
|
+
// Return all trace failures as errors
|
|
148
|
+
Error(traceFailures)
|
|
149
|
+
} else {
|
|
150
|
+
// No corners and no failures - empty result
|
|
151
|
+
Ok([])
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
// Step 4: Detect dividers for each successfully traced box
|
|
155
|
+
// TODO: Implement when DividerDetector module is available
|
|
156
|
+
|
|
157
|
+
// Step 5: Remove duplicate boxes (same bounds traced multiple times)
|
|
158
|
+
let uniqueBoxes = deduplicateBoxes(boxes)
|
|
159
|
+
|
|
160
|
+
// Step 6: Build hierarchy using HierarchyBuilder
|
|
161
|
+
switch HierarchyBuilder.buildHierarchy(uniqueBoxes) {
|
|
162
|
+
| Ok(rootBoxes) => {
|
|
163
|
+
// Hierarchy built successfully - return the boxes
|
|
164
|
+
Ok(rootBoxes)
|
|
165
|
+
}
|
|
166
|
+
| Error(hierarchyError) => {
|
|
167
|
+
// Hierarchy building failed (e.g., overlapping boxes)
|
|
168
|
+
// This IS a real error that should be reported
|
|
169
|
+
let parseError = hierarchyErrorToParseError(hierarchyError)
|
|
170
|
+
Error([parseError])
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ============================================================================
|
|
177
|
+
// Additional Helper Functions
|
|
178
|
+
// ============================================================================
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Count total number of boxes including nested children.
|
|
182
|
+
*
|
|
183
|
+
* @param boxes - Array of boxes (can include nested children)
|
|
184
|
+
* @return Total count of all boxes
|
|
185
|
+
*/
|
|
186
|
+
let rec countBoxes = (boxes: array<BoxTracer.box>): int => {
|
|
187
|
+
boxes->Array.reduce(0, (acc, box) => {
|
|
188
|
+
1 + acc + countBoxes(box.children)
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get all boxes as a flat array (including nested children).
|
|
194
|
+
*
|
|
195
|
+
* @param boxes - Array of root boxes
|
|
196
|
+
* @return Flat array of all boxes
|
|
197
|
+
*/
|
|
198
|
+
let rec flattenBoxes = (boxes: array<BoxTracer.box>): array<BoxTracer.box> => {
|
|
199
|
+
boxes->Array.reduce([], (acc, box) => {
|
|
200
|
+
let childBoxes = flattenBoxes(box.children)
|
|
201
|
+
Array.concat(Array.concat(acc, [box]), childBoxes)
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Get statistics about detected shapes for debugging.
|
|
207
|
+
*
|
|
208
|
+
* @param result - Detection result
|
|
209
|
+
* @return String with statistics
|
|
210
|
+
*/
|
|
211
|
+
let getStats = (result: detectResult): string => {
|
|
212
|
+
switch result {
|
|
213
|
+
| Ok(boxes) => {
|
|
214
|
+
let rootCount = Array.length(boxes)
|
|
215
|
+
let totalCount = countBoxes(boxes)
|
|
216
|
+
`Shape Detection Success:
|
|
217
|
+
Root boxes: ${Int.toString(rootCount)}
|
|
218
|
+
Total boxes (including nested): ${Int.toString(totalCount)}`
|
|
219
|
+
}
|
|
220
|
+
| Error(errors) => {
|
|
221
|
+
let errorCount = Array.length(errors)
|
|
222
|
+
let warningCount = errors->Array.reduce(0, (acc, err) => {
|
|
223
|
+
if ErrorTypes.isWarning(err) {
|
|
224
|
+
acc + 1
|
|
225
|
+
} else {
|
|
226
|
+
acc
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
let realErrorCount = errorCount - warningCount
|
|
230
|
+
|
|
231
|
+
`Shape Detection Failed:
|
|
232
|
+
Errors: ${Int.toString(realErrorCount)}
|
|
233
|
+
Warnings: ${Int.toString(warningCount)}`
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Grid from "../../Core/Grid.mjs";
|
|
4
|
+
import * as Types from "../../Core/Types.mjs";
|
|
5
|
+
import * as Vitest from "rescript-vitest/src/Vitest.mjs";
|
|
6
|
+
import * as BoxTracer from "../BoxTracer.mjs";
|
|
7
|
+
|
|
8
|
+
Vitest.describe("BoxTracer - Width Validation", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, () => {
|
|
9
|
+
Vitest.test("should detect mismatched width between top and bottom edges", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
|
|
10
|
+
let input = [
|
|
11
|
+
"+-----+",
|
|
12
|
+
"| |",
|
|
13
|
+
"+-------+"
|
|
14
|
+
];
|
|
15
|
+
let grid = Grid.fromLines(input);
|
|
16
|
+
let topLeft = Types.Position.make(0, 0);
|
|
17
|
+
let result = BoxTracer.traceBox(grid, topLeft);
|
|
18
|
+
if (result.TAG === "Ok") {
|
|
19
|
+
return t.expect(true).toBe(false);
|
|
20
|
+
}
|
|
21
|
+
let match = result._0.code;
|
|
22
|
+
if (match.TAG !== "MismatchedWidth") {
|
|
23
|
+
return t.expect(true).toBe(false);
|
|
24
|
+
}
|
|
25
|
+
let pos = match.topLeft;
|
|
26
|
+
t.expect(match.topWidth).toBe(6);
|
|
27
|
+
t.expect(match.bottomWidth).toBe(8);
|
|
28
|
+
t.expect(pos.row).toBe(0);
|
|
29
|
+
t.expect(pos.col).toBe(0);
|
|
30
|
+
});
|
|
31
|
+
Vitest.test("should successfully trace box with matching widths", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
|
|
32
|
+
let input = [
|
|
33
|
+
"+-----+",
|
|
34
|
+
"| |",
|
|
35
|
+
"+-----+"
|
|
36
|
+
];
|
|
37
|
+
let grid = Grid.fromLines(input);
|
|
38
|
+
let topLeft = Types.Position.make(0, 0);
|
|
39
|
+
let result = BoxTracer.traceBox(grid, topLeft);
|
|
40
|
+
if (result.TAG === "Ok") {
|
|
41
|
+
let box = result._0;
|
|
42
|
+
t.expect(box.bounds.top).toBe(0);
|
|
43
|
+
t.expect(box.bounds.left).toBe(0);
|
|
44
|
+
t.expect(box.bounds.bottom).toBe(2);
|
|
45
|
+
t.expect(box.bounds.right).toBe(6);
|
|
46
|
+
return t.expect(box.name).toBe(undefined);
|
|
47
|
+
}
|
|
48
|
+
console.log(result._0);
|
|
49
|
+
t.expect(true).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
Vitest.test("should extract box name and validate width", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
|
|
52
|
+
let input = [
|
|
53
|
+
"+--Login--+",
|
|
54
|
+
"| |",
|
|
55
|
+
"+--Login--+"
|
|
56
|
+
];
|
|
57
|
+
let grid = Grid.fromLines(input);
|
|
58
|
+
let topLeft = Types.Position.make(0, 0);
|
|
59
|
+
let result = BoxTracer.traceBox(grid, topLeft);
|
|
60
|
+
if (result.TAG !== "Ok") {
|
|
61
|
+
return t.expect(true).toBe(false);
|
|
62
|
+
}
|
|
63
|
+
let box = result._0;
|
|
64
|
+
t.expect(box.name).toBe("Login");
|
|
65
|
+
t.expect(Types.Bounds.width(box.bounds)).toBe(10);
|
|
66
|
+
t.expect(Types.Bounds.height(box.bounds)).toBe(2);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
/* Not a pure module */
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// BoxTracer_test.res
|
|
2
|
+
// Unit tests for BoxTracer width validation and box tracing
|
|
3
|
+
|
|
4
|
+
open Vitest
|
|
5
|
+
|
|
6
|
+
describe("BoxTracer - Width Validation", () => {
|
|
7
|
+
test("should detect mismatched width between top and bottom edges", t => {
|
|
8
|
+
// Create a malformed box with different top and bottom widths
|
|
9
|
+
let input = [
|
|
10
|
+
"+-----+", // Top: 7 chars wide (5 dashes + 2 corners)
|
|
11
|
+
"| |",
|
|
12
|
+
"+-------+", // Bottom: 9 chars wide (7 dashes + 2 corners) - MISMATCH!
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
let grid = Grid.fromLines(input)
|
|
16
|
+
let topLeft = Types.Position.make(0, 0)
|
|
17
|
+
|
|
18
|
+
let result = BoxTracer.traceBox(grid, topLeft)
|
|
19
|
+
|
|
20
|
+
switch result {
|
|
21
|
+
| Error(err) => {
|
|
22
|
+
// Verify we get a MismatchedWidth error
|
|
23
|
+
switch err.code {
|
|
24
|
+
| ErrorTypes.MismatchedWidth({topWidth, bottomWidth, topLeft: pos}) => {
|
|
25
|
+
t->expect(topWidth)->Expect.toBe(6) // 0 to 6 = 6 chars
|
|
26
|
+
t->expect(bottomWidth)->Expect.toBe(8) // 0 to 8 = 8 chars
|
|
27
|
+
t->expect(pos.row)->Expect.toBe(0)
|
|
28
|
+
t->expect(pos.col)->Expect.toBe(0)
|
|
29
|
+
}
|
|
30
|
+
| _ => t->expect(true)->Expect.toBe(false) // fail: Expected MismatchedWidth error
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
| Ok(_) => t->expect(true)->Expect.toBe(false) // fail: Expected error for mismatched width, but got Ok
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test("should successfully trace box with matching widths", t => {
|
|
38
|
+
// Create a well-formed box with matching top and bottom widths
|
|
39
|
+
let input = [
|
|
40
|
+
"+-----+",
|
|
41
|
+
"| |",
|
|
42
|
+
"+-----+", // Same width as top
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
let grid = Grid.fromLines(input)
|
|
46
|
+
let topLeft = Types.Position.make(0, 0)
|
|
47
|
+
|
|
48
|
+
let result = BoxTracer.traceBox(grid, topLeft)
|
|
49
|
+
|
|
50
|
+
switch result {
|
|
51
|
+
| Ok(box) => {
|
|
52
|
+
// Verify box bounds are correct
|
|
53
|
+
t->expect(box.bounds.top)->Expect.toBe(0)
|
|
54
|
+
t->expect(box.bounds.left)->Expect.toBe(0)
|
|
55
|
+
t->expect(box.bounds.bottom)->Expect.toBe(2)
|
|
56
|
+
t->expect(box.bounds.right)->Expect.toBe(6)
|
|
57
|
+
|
|
58
|
+
// Verify no name (unnamed box)
|
|
59
|
+
t->expect(box.name)->Expect.toBe(None)
|
|
60
|
+
}
|
|
61
|
+
| Error(err) => {
|
|
62
|
+
Console.log(err)
|
|
63
|
+
t->expect(true)->Expect.toBe(false) // fail: Expected successful box trace for matching widths
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test("should extract box name and validate width", t => {
|
|
69
|
+
let input = [
|
|
70
|
+
"+--Login--+",
|
|
71
|
+
"| |",
|
|
72
|
+
"+--Login--+",
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
let grid = Grid.fromLines(input)
|
|
76
|
+
let topLeft = Types.Position.make(0, 0)
|
|
77
|
+
|
|
78
|
+
let result = BoxTracer.traceBox(grid, topLeft)
|
|
79
|
+
|
|
80
|
+
switch result {
|
|
81
|
+
| Ok(box) => {
|
|
82
|
+
// Verify box name extraction
|
|
83
|
+
t->expect(box.name)->Expect.toBe(Some("Login"))
|
|
84
|
+
|
|
85
|
+
// Verify bounds
|
|
86
|
+
t->expect(Types.Bounds.width(box.bounds))->Expect.toBe(10)
|
|
87
|
+
t->expect(Types.Bounds.height(box.bounds))->Expect.toBe(2)
|
|
88
|
+
}
|
|
89
|
+
| Error(_) => t->expect(true)->Expect.toBe(false) // fail: Expected successful trace for named box
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
})
|