wyreframe 0.1.0 → 0.1.5

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 (58) 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/parser/Core/__tests__/Bounds_test.mjs +0 -326
  9. package/src/parser/Core/__tests__/Bounds_test.res +0 -412
  10. package/src/parser/Core/__tests__/Grid_test.mjs +0 -322
  11. package/src/parser/Core/__tests__/Grid_test.res +0 -319
  12. package/src/parser/Core/__tests__/Types_test.mjs +0 -614
  13. package/src/parser/Core/__tests__/Types_test.res +0 -650
  14. package/src/parser/Detector/__tests__/BoxTracer_test.mjs +0 -70
  15. package/src/parser/Detector/__tests__/BoxTracer_test.res +0 -92
  16. package/src/parser/Detector/__tests__/HierarchyBuilder_test.mjs +0 -489
  17. package/src/parser/Detector/__tests__/HierarchyBuilder_test.res +0 -849
  18. package/src/parser/Detector/__tests__/ShapeDetector_test.mjs +0 -377
  19. package/src/parser/Detector/__tests__/ShapeDetector_test.res +0 -563
  20. package/src/parser/Interactions/__tests__/InteractionMerger_test.mjs +0 -576
  21. package/src/parser/Interactions/__tests__/InteractionMerger_test.res +0 -646
  22. package/src/parser/Scanner/__tests__/Grid_manual.mjs +0 -214
  23. package/src/parser/Scanner/__tests__/Grid_manual.res +0 -141
  24. package/src/parser/Semantic/Elements/__tests__/ButtonParser_test.mjs +0 -189
  25. package/src/parser/Semantic/Elements/__tests__/ButtonParser_test.res +0 -257
  26. package/src/parser/Semantic/Elements/__tests__/CheckboxParser_test.mjs +0 -202
  27. package/src/parser/Semantic/Elements/__tests__/CheckboxParser_test.res +0 -250
  28. package/src/parser/Semantic/Elements/__tests__/CodeTextParser_manual.mjs +0 -293
  29. package/src/parser/Semantic/Elements/__tests__/CodeTextParser_manual.res +0 -134
  30. package/src/parser/Semantic/Elements/__tests__/InputParser_test.mjs +0 -253
  31. package/src/parser/Semantic/Elements/__tests__/InputParser_test.res +0 -304
  32. package/src/parser/Semantic/Elements/__tests__/LinkParser_test.mjs +0 -289
  33. package/src/parser/Semantic/Elements/__tests__/LinkParser_test.res +0 -402
  34. package/src/parser/Semantic/Elements/__tests__/TextParser_test.mjs +0 -149
  35. package/src/parser/Semantic/Elements/__tests__/TextParser_test.res +0 -167
  36. package/src/parser/Semantic/__tests__/ASTBuilder_test.mjs +0 -187
  37. package/src/parser/Semantic/__tests__/ASTBuilder_test.res +0 -192
  38. package/src/parser/Semantic/__tests__/ParserRegistry_test.mjs +0 -154
  39. package/src/parser/Semantic/__tests__/ParserRegistry_test.res +0 -191
  40. package/src/parser/Semantic/__tests__/SemanticParser_integration_test.mjs +0 -768
  41. package/src/parser/Semantic/__tests__/SemanticParser_integration_test.res +0 -1069
  42. package/src/parser/Semantic/__tests__/SemanticParser_manual.mjs +0 -1329
  43. package/src/parser/Semantic/__tests__/SemanticParser_manual.res +0 -544
  44. package/src/parser/__tests__/GridScanner_integration.test.mjs +0 -632
  45. package/src/parser/__tests__/GridScanner_integration.test.res +0 -816
  46. package/src/parser/__tests__/Performance.test.mjs +0 -244
  47. package/src/parser/__tests__/Performance.test.res +0 -371
  48. package/src/parser/__tests__/PerformanceFixtures.mjs +0 -200
  49. package/src/parser/__tests__/PerformanceFixtures.res +0 -284
  50. package/src/parser/__tests__/WyreframeParser_integration.test.mjs +0 -770
  51. package/src/parser/__tests__/WyreframeParser_integration.test.res +0 -1008
  52. package/src/parser/__tests__/fixtures/alignment-test.txt +0 -9
  53. package/src/parser/__tests__/fixtures/all-elements.txt +0 -16
  54. package/src/parser/__tests__/fixtures/login-scene.txt +0 -17
  55. package/src/parser/__tests__/fixtures/multi-scene.txt +0 -25
  56. package/src/parser/__tests__/fixtures/nested-boxes.txt +0 -15
  57. package/src/parser/__tests__/fixtures/simple-box.txt +0 -5
  58. package/src/parser/__tests__/fixtures/with-dividers.txt +0 -14
@@ -1,849 +0,0 @@
1
- // HierarchyBuilder_test.res
2
- // Tests for hierarchy building functionality
3
-
4
- open Vitest
5
- open Types
6
-
7
- describe("HierarchyBuilder - Containment Detection", () => {
8
- test("contains returns true when outer completely contains inner", t => {
9
- let outer = Bounds.make(~top=0, ~left=0, ~bottom=10, ~right=10)
10
- let inner = Bounds.make(~top=2, ~left=2, ~bottom=8, ~right=8)
11
- t->expect(HierarchyBuilder.contains(outer, inner))->Expect.toBe(true)
12
- })
13
-
14
- test("contains returns false when boxes are equal", t => {
15
- let bounds = Bounds.make(~top=0, ~left=0, ~bottom=10, ~right=10)
16
- t->expect(HierarchyBuilder.contains(bounds, bounds))->Expect.toBe(false)
17
- })
18
-
19
- test("contains returns false when boxes are disjoint", t => {
20
- let box1 = Bounds.make(~top=0, ~left=0, ~bottom=5, ~right=5)
21
- let box2 = Bounds.make(~top=10, ~left=10, ~bottom=15, ~right=15)
22
- t->expect(HierarchyBuilder.contains(box1, box2))->Expect.toBe(false)
23
- })
24
-
25
- test("contains returns false when boxes partially overlap", t => {
26
- let box1 = Bounds.make(~top=0, ~left=0, ~bottom=10, ~right=10)
27
- let box2 = Bounds.make(~top=5, ~left=5, ~bottom=15, ~right=15)
28
- t->expect(HierarchyBuilder.contains(box1, box2))->Expect.toBe(false)
29
- })
30
-
31
- test("contains returns false when inner touches outer's edge", t => {
32
- let outer = Bounds.make(~top=0, ~left=0, ~bottom=10, ~right=10)
33
- let inner = Bounds.make(~top=0, ~left=0, ~bottom=5, ~right=5)
34
- // Inner touches outer's top-left edge, so not strict containment
35
- t->expect(HierarchyBuilder.contains(outer, inner))->Expect.toBe(false)
36
- })
37
- })
38
-
39
- describe("HierarchyBuilder - findParent", () => {
40
- test("findParent returns None for root box (no container)", t => {
41
- let box = HierarchyBuilder.makeBox(
42
- ~name="Root",
43
- Bounds.make(~top=0, ~left=0, ~bottom=10, ~right=10),
44
- )
45
-
46
- let candidates = [box]
47
-
48
- t->expect(HierarchyBuilder.findParent(box, candidates))->Expect.toEqual(None)
49
- })
50
-
51
- test("findParent returns immediate parent (smallest containing box)", t => {
52
- let grandparent = HierarchyBuilder.makeBox(
53
- ~name="Grandparent",
54
- Bounds.make(~top=0, ~left=0, ~bottom=20, ~right=20),
55
- )
56
-
57
- let parent = HierarchyBuilder.makeBox(
58
- ~name="Parent",
59
- Bounds.make(~top=2, ~left=2, ~bottom=18, ~right=18),
60
- )
61
-
62
- let child = HierarchyBuilder.makeBox(
63
- ~name="Child",
64
- Bounds.make(~top=4, ~left=4, ~bottom=16, ~right=16),
65
- )
66
-
67
- let candidates = [grandparent, parent, child]
68
-
69
- // Child's immediate parent should be parent, not grandparent
70
- t->expect(HierarchyBuilder.findParent(child, candidates))->Expect.toEqual(Some(parent))
71
- })
72
-
73
- test("findParent ignores boxes that don't contain the target", t => {
74
- let box1 = HierarchyBuilder.makeBox(
75
- Bounds.make(~top=0, ~left=0, ~bottom=5, ~right=5),
76
- )
77
-
78
- let box2 = HierarchyBuilder.makeBox(
79
- Bounds.make(~top=10, ~left=10, ~bottom=15, ~right=15),
80
- )
81
-
82
- let candidates = [box1, box2]
83
-
84
- t->expect(HierarchyBuilder.findParent(box2, candidates))->Expect.toEqual(None)
85
- })
86
- })
87
-
88
- describe("HierarchyBuilder - buildHierarchy - 2-level nesting", () => {
89
- test("builds hierarchy with one root and one child", t => {
90
- let parent = HierarchyBuilder.makeBox(
91
- ~name="Parent",
92
- Bounds.make(~top=0, ~left=0, ~bottom=10, ~right=10),
93
- )
94
-
95
- let child = HierarchyBuilder.makeBox(
96
- ~name="Child",
97
- Bounds.make(~top=2, ~left=2, ~bottom=8, ~right=8),
98
- )
99
-
100
- let boxes = [parent, child]
101
- let result = HierarchyBuilder.buildHierarchy(boxes)
102
-
103
- switch result {
104
- | Ok(roots) => {
105
- // Should return only the root (parent)
106
- t->expect(Array.length(roots))->Expect.toBe(1)
107
-
108
- switch roots[0] {
109
- | Some(root) => {
110
- t->expect(root.name)->Expect.toEqual(Some("Parent"))
111
- // Parent should have 1 child
112
- t->expect(Array.length(root.children))->Expect.toBe(1)
113
-
114
- switch root.children[0] {
115
- | Some(childBox) => t->expect(childBox.name)->Expect.toEqual(Some("Child"))
116
- | None => t->expect(true)->Expect.toBe(false) // fail: Expected child to exist
117
- }
118
- }
119
- | None => t->expect(true)->Expect.toBe(false) // fail: Expected root to exist
120
- }
121
- }
122
- | Error(_) => t->expect(true)->Expect.toBe(false) // fail: Expected Ok result
123
- }
124
- })
125
-
126
- test("builds hierarchy with one root and multiple children", t => {
127
- let parent = HierarchyBuilder.makeBox(
128
- ~name="Parent",
129
- Bounds.make(~top=0, ~left=0, ~bottom=20, ~right=20),
130
- )
131
-
132
- let child1 = HierarchyBuilder.makeBox(
133
- ~name="Child1",
134
- Bounds.make(~top=2, ~left=2, ~bottom=8, ~right=8),
135
- )
136
-
137
- let child2 = HierarchyBuilder.makeBox(
138
- ~name="Child2",
139
- Bounds.make(~top=12, ~left=12, ~bottom=18, ~right=18),
140
- )
141
-
142
- let boxes = [parent, child1, child2]
143
- let result = HierarchyBuilder.buildHierarchy(boxes)
144
-
145
- switch result {
146
- | Ok(roots) => {
147
- t->expect(Array.length(roots))->Expect.toBe(1)
148
-
149
- switch roots[0] {
150
- | Some(root) => {
151
- t->expect(root.name)->Expect.toEqual(Some("Parent"))
152
- // Parent should have 2 children
153
- t->expect(Array.length(root.children))->Expect.toBe(2)
154
- }
155
- | None => t->expect(true)->Expect.toBe(false) // fail: Expected root to exist
156
- }
157
- }
158
- | Error(_) => t->expect(true)->Expect.toBe(false) // fail: Expected Ok result
159
- }
160
- })
161
- })
162
-
163
- describe("HierarchyBuilder - buildHierarchy - 3-level nesting", () => {
164
- test("builds hierarchy with 3 levels: root -> child -> grandchild", t => {
165
- let root = HierarchyBuilder.makeBox(
166
- ~name="Root",
167
- Bounds.make(~top=0, ~left=0, ~bottom=30, ~right=30),
168
- )
169
-
170
- let child = HierarchyBuilder.makeBox(
171
- ~name="Child",
172
- Bounds.make(~top=5, ~left=5, ~bottom=25, ~right=25),
173
- )
174
-
175
- let grandchild = HierarchyBuilder.makeBox(
176
- ~name="Grandchild",
177
- Bounds.make(~top=10, ~left=10, ~bottom=20, ~right=20),
178
- )
179
-
180
- let boxes = [root, child, grandchild]
181
- let result = HierarchyBuilder.buildHierarchy(boxes)
182
-
183
- switch result {
184
- | Ok(roots) => {
185
- // Should return only the root
186
- t->expect(Array.length(roots))->Expect.toBe(1)
187
-
188
- switch roots[0] {
189
- | Some(rootBox) => {
190
- t->expect(rootBox.name)->Expect.toEqual(Some("Root"))
191
- // Root should have 1 child
192
- t->expect(Array.length(rootBox.children))->Expect.toBe(1)
193
-
194
- switch rootBox.children[0] {
195
- | Some(childBox) => {
196
- t->expect(childBox.name)->Expect.toEqual(Some("Child"))
197
- // Child should have 1 grandchild
198
- t->expect(Array.length(childBox.children))->Expect.toBe(1)
199
-
200
- switch childBox.children[0] {
201
- | Some(grandchildBox) => t->expect(grandchildBox.name)->Expect.toEqual(Some("Grandchild"))
202
- | None => t->expect(true)->Expect.toBe(false) // fail: Expected grandchild to exist
203
- }
204
- }
205
- | None => t->expect(true)->Expect.toBe(false) // fail: Expected child to exist
206
- }
207
- }
208
- | None => t->expect(true)->Expect.toBe(false) // fail: Expected root to exist
209
- }
210
- }
211
- | Error(_) => t->expect(true)->Expect.toBe(false) // fail: Expected Ok result
212
- }
213
- })
214
-
215
- test("getDepth correctly calculates depth for 3-level hierarchy", t => {
216
- let root = HierarchyBuilder.makeBox(
217
- Bounds.make(~top=0, ~left=0, ~bottom=30, ~right=30),
218
- )
219
-
220
- let child = HierarchyBuilder.makeBox(
221
- Bounds.make(~top=5, ~left=5, ~bottom=25, ~right=25),
222
- )
223
-
224
- let grandchild = HierarchyBuilder.makeBox(
225
- Bounds.make(~top=10, ~left=10, ~bottom=20, ~right=20),
226
- )
227
-
228
- let allBoxes = [root, child, grandchild]
229
-
230
- t->expect(HierarchyBuilder.getDepth(root, allBoxes))->Expect.toBe(0)
231
- t->expect(HierarchyBuilder.getDepth(child, allBoxes))->Expect.toBe(1)
232
- t->expect(HierarchyBuilder.getDepth(grandchild, allBoxes))->Expect.toBe(2)
233
- })
234
- })
235
-
236
- describe("HierarchyBuilder - buildHierarchy - 4-level nesting", () => {
237
- test("builds hierarchy with 4 levels: root -> child -> grandchild -> great-grandchild", t => {
238
- let root = HierarchyBuilder.makeBox(
239
- ~name="Root",
240
- Bounds.make(~top=0, ~left=0, ~bottom=40, ~right=40),
241
- )
242
-
243
- let child = HierarchyBuilder.makeBox(
244
- ~name="Child",
245
- Bounds.make(~top=5, ~left=5, ~bottom=35, ~right=35),
246
- )
247
-
248
- let grandchild = HierarchyBuilder.makeBox(
249
- ~name="Grandchild",
250
- Bounds.make(~top=10, ~left=10, ~bottom=30, ~right=30),
251
- )
252
-
253
- let greatGrandchild = HierarchyBuilder.makeBox(
254
- ~name="GreatGrandchild",
255
- Bounds.make(~top=15, ~left=15, ~bottom=25, ~right=25),
256
- )
257
-
258
- let boxes = [root, child, grandchild, greatGrandchild]
259
- let result = HierarchyBuilder.buildHierarchy(boxes)
260
-
261
- switch result {
262
- | Ok(roots) => {
263
- // Should return only the root
264
- t->expect(Array.length(roots))->Expect.toBe(1)
265
-
266
- switch roots[0] {
267
- | Some(rootBox) => {
268
- t->expect(rootBox.name)->Expect.toEqual(Some("Root"))
269
- t->expect(Array.length(rootBox.children))->Expect.toBe(1)
270
-
271
- switch rootBox.children[0] {
272
- | Some(childBox) => {
273
- t->expect(childBox.name)->Expect.toEqual(Some("Child"))
274
- t->expect(Array.length(childBox.children))->Expect.toBe(1)
275
-
276
- switch childBox.children[0] {
277
- | Some(grandchildBox) => {
278
- t->expect(grandchildBox.name)->Expect.toEqual(Some("Grandchild"))
279
- t->expect(Array.length(grandchildBox.children))->Expect.toBe(1)
280
-
281
- switch grandchildBox.children[0] {
282
- | Some(greatGrandchildBox) =>
283
- t->expect(greatGrandchildBox.name)->Expect.toEqual(Some("GreatGrandchild"))
284
- | None => t->expect(true)->Expect.toBe(false) // fail: Expected great-grandchild to exist
285
- }
286
- }
287
- | None => t->expect(true)->Expect.toBe(false) // fail: Expected grandchild to exist
288
- }
289
- }
290
- | None => t->expect(true)->Expect.toBe(false) // fail: Expected child to exist
291
- }
292
- }
293
- | None => t->expect(true)->Expect.toBe(false) // fail: Expected root to exist
294
- }
295
- }
296
- | Error(_) => t->expect(true)->Expect.toBe(false) // fail: Expected Ok result
297
- }
298
- })
299
-
300
- test("getDepth correctly calculates depth for 4-level hierarchy", t => {
301
- let root = HierarchyBuilder.makeBox(
302
- Bounds.make(~top=0, ~left=0, ~bottom=40, ~right=40),
303
- )
304
-
305
- let child = HierarchyBuilder.makeBox(
306
- Bounds.make(~top=5, ~left=5, ~bottom=35, ~right=35),
307
- )
308
-
309
- let grandchild = HierarchyBuilder.makeBox(
310
- Bounds.make(~top=10, ~left=10, ~bottom=30, ~right=30),
311
- )
312
-
313
- let greatGrandchild = HierarchyBuilder.makeBox(
314
- Bounds.make(~top=15, ~left=15, ~bottom=25, ~right=25),
315
- )
316
-
317
- let allBoxes = [root, child, grandchild, greatGrandchild]
318
-
319
- t->expect(HierarchyBuilder.getDepth(root, allBoxes))->Expect.toBe(0)
320
- t->expect(HierarchyBuilder.getDepth(child, allBoxes))->Expect.toBe(1)
321
- t->expect(HierarchyBuilder.getDepth(grandchild, allBoxes))->Expect.toBe(2)
322
- t->expect(HierarchyBuilder.getDepth(greatGrandchild, allBoxes))->Expect.toBe(3)
323
- })
324
- })
325
-
326
- describe("HierarchyBuilder - buildHierarchy - Multiple roots", () => {
327
- test("handles multiple root boxes with children", t => {
328
- let root1 = HierarchyBuilder.makeBox(
329
- ~name="Root1",
330
- Bounds.make(~top=0, ~left=0, ~bottom=10, ~right=10),
331
- )
332
-
333
- let root1Child = HierarchyBuilder.makeBox(
334
- ~name="Root1Child",
335
- Bounds.make(~top=2, ~left=2, ~bottom=8, ~right=8),
336
- )
337
-
338
- let root2 = HierarchyBuilder.makeBox(
339
- ~name="Root2",
340
- Bounds.make(~top=20, ~left=20, ~bottom=30, ~right=30),
341
- )
342
-
343
- let root2Child = HierarchyBuilder.makeBox(
344
- ~name="Root2Child",
345
- Bounds.make(~top=22, ~left=22, ~bottom=28, ~right=28),
346
- )
347
-
348
- let boxes = [root1, root1Child, root2, root2Child]
349
- let result = HierarchyBuilder.buildHierarchy(boxes)
350
-
351
- switch result {
352
- | Ok(roots) => {
353
- // Should return 2 roots
354
- t->expect(Array.length(roots))->Expect.toBe(2)
355
-
356
- // Each root should have 1 child
357
- roots->Array.forEach(root => {
358
- t->expect(Array.length(root.children))->Expect.toBe(1)
359
- })
360
- }
361
- | Error(_) => t->expect(true)->Expect.toBe(false) // fail: Expected Ok result
362
- }
363
- })
364
- })
365
-
366
- describe("HierarchyBuilder - buildHierarchy - Error cases", () => {
367
- test("detects overlapping boxes (invalid partial overlap)", t => {
368
- let box1 = HierarchyBuilder.makeBox(
369
- ~name="Box1",
370
- Bounds.make(~top=0, ~left=0, ~bottom=10, ~right=10),
371
- )
372
-
373
- let box2 = HierarchyBuilder.makeBox(
374
- ~name="Box2",
375
- // Overlaps with box1 but doesn't contain or is contained by it
376
- Bounds.make(~top=5, ~left=5, ~bottom=15, ~right=15),
377
- )
378
-
379
- let boxes = [box1, box2]
380
- let result = HierarchyBuilder.buildHierarchy(boxes)
381
-
382
- switch result {
383
- | Ok(_) => t->expect(true)->Expect.toBe(false) // fail: Expected Error for overlapping boxes
384
- | Error(HierarchyBuilder.OverlappingBoxes({box1: b1, box2: b2})) => {
385
- // Verify the error contains the overlapping boxes
386
- t->expect(b1 === box1 || b1 === box2)->Expect.toBe(true)
387
- t->expect(b2 === box1 || b2 === box2)->Expect.toBe(true)
388
- t->expect(b1 !== b2)->Expect.toBe(true)
389
- }
390
- | Error(_) => t->expect(true)->Expect.toBe(false) // fail: Expected OverlappingBoxes error
391
- }
392
- })
393
-
394
- test("allows disjoint boxes (no overlap)", t => {
395
- let box1 = HierarchyBuilder.makeBox(
396
- Bounds.make(~top=0, ~left=0, ~bottom=5, ~right=5),
397
- )
398
-
399
- let box2 = HierarchyBuilder.makeBox(
400
- Bounds.make(~top=10, ~left=10, ~bottom=15, ~right=15),
401
- )
402
-
403
- let boxes = [box1, box2]
404
- let result = HierarchyBuilder.buildHierarchy(boxes)
405
-
406
- switch result {
407
- | Ok(roots) => {
408
- // Should return 2 roots (both boxes are disjoint)
409
- t->expect(Array.length(roots))->Expect.toBe(2)
410
- }
411
- | Error(_) => t->expect(true)->Expect.toBe(false) // fail: Expected Ok result for disjoint boxes
412
- }
413
- })
414
-
415
- test("allows nested boxes (one contains the other)", t => {
416
- let outer = HierarchyBuilder.makeBox(
417
- Bounds.make(~top=0, ~left=0, ~bottom=10, ~right=10),
418
- )
419
-
420
- let inner = HierarchyBuilder.makeBox(
421
- Bounds.make(~top=2, ~left=2, ~bottom=8, ~right=8),
422
- )
423
-
424
- let boxes = [outer, inner]
425
- let result = HierarchyBuilder.buildHierarchy(boxes)
426
-
427
- switch result {
428
- | Ok(roots) => {
429
- // Should return 1 root with 1 child
430
- t->expect(Array.length(roots))->Expect.toBe(1)
431
-
432
- switch roots[0] {
433
- | Some(root) => t->expect(Array.length(root.children))->Expect.toBe(1)
434
- | None => t->expect(true)->Expect.toBe(false) // fail: Expected root to exist
435
- }
436
- }
437
- | Error(_) => t->expect(true)->Expect.toBe(false) // fail: Expected Ok result for nested boxes
438
- }
439
- })
440
- })
441
-
442
- // ============================================================================
443
- // Deep Nesting Warning Detection Tests
444
- // ============================================================================
445
-
446
- describe("HierarchyBuilder - Deep Nesting Detection", () => {
447
- describe("collectDeepNestingWarnings", () => {
448
- test("should not warn for boxes at or below threshold (depth 0-4)", t => {
449
- // Create a simple root box (depth 0)
450
- let root = HierarchyBuilder.makeBox(
451
- ~name="Root",
452
- Bounds.make(~top=0, ~left=0, ~bottom=10, ~right=10),
453
- )
454
-
455
- // Should not generate warnings at depth 0-4
456
- let warnings0 = HierarchyBuilder.collectDeepNestingWarnings(root, 0, ~threshold=4)
457
- t->expect(Array.length(warnings0))->Expect.toBe(0)
458
-
459
- let warnings4 = HierarchyBuilder.collectDeepNestingWarnings(root, 4, ~threshold=4)
460
- t->expect(Array.length(warnings4))->Expect.toBe(0)
461
- })
462
-
463
- test("should warn when depth exceeds threshold of 4", t => {
464
- // Create a deeply nested box at depth 5
465
- let deepBox = HierarchyBuilder.makeBox(
466
- ~name="DeepBox",
467
- Bounds.make(~top=5, ~left=5, ~bottom=8, ~right=8),
468
- )
469
-
470
- // Should generate warning at depth 5 (threshold is 4)
471
- let warnings = HierarchyBuilder.collectDeepNestingWarnings(deepBox, 5, ~threshold=4)
472
-
473
- t->expect(Array.length(warnings))->Expect.toBe(1)
474
-
475
- // Verify warning details
476
- let warning = Array.getUnsafe(warnings, 0)
477
- t->expect(warning.severity)->Expect.toEqual(ErrorTypes.Warning)
478
-
479
- switch warning.code {
480
- | DeepNesting({depth, position}) => {
481
- t->expect(depth)->Expect.toBe(5)
482
- t->expect(position.row)->Expect.toBe(5)
483
- t->expect(position.col)->Expect.toBe(5)
484
- }
485
- | _ => t->expect(true)->Expect.toBe(false) // fail: Expected DeepNesting warning
486
- }
487
- })
488
-
489
- test("should collect warnings from all children recursively", t => {
490
- // Create parent box (depth 4)
491
- let parent = HierarchyBuilder.makeBox(
492
- ~name="Parent",
493
- Bounds.make(~top=0, ~left=0, ~bottom=20, ~right=20),
494
- )
495
-
496
- // Create two children (depth 5 - both should warn)
497
- let child1 = HierarchyBuilder.makeBox(
498
- ~name="Child1",
499
- Bounds.make(~top=2, ~left=2, ~bottom=8, ~right=8),
500
- )
501
- let child2 = HierarchyBuilder.makeBox(
502
- ~name="Child2",
503
- Bounds.make(~top=12, ~left=12, ~bottom=18, ~right=18),
504
- )
505
-
506
- // Add children to parent
507
- parent.children->Array.push(child1)->ignore
508
- parent.children->Array.push(child2)->ignore
509
-
510
- // Collect warnings starting at depth 4 (so children will be at depth 5)
511
- let warnings = HierarchyBuilder.collectDeepNestingWarnings(parent, 4, ~threshold=4)
512
-
513
- // Should have 2 warnings (one for each child)
514
- t->expect(Array.length(warnings))->Expect.toBe(2)
515
- })
516
-
517
- test("should handle custom threshold", t => {
518
- let box = HierarchyBuilder.makeBox(
519
- ~name="Box",
520
- Bounds.make(~top=0, ~left=0, ~bottom=5, ~right=5),
521
- )
522
-
523
- // With threshold 2, depth 3 should warn
524
- let warnings = HierarchyBuilder.collectDeepNestingWarnings(box, 3, ~threshold=2)
525
- t->expect(Array.length(warnings))->Expect.toBe(1)
526
-
527
- // With threshold 5, depth 3 should not warn
528
- let warnings2 = HierarchyBuilder.collectDeepNestingWarnings(box, 3, ~threshold=5)
529
- t->expect(Array.length(warnings2))->Expect.toBe(0)
530
- })
531
- })
532
-
533
- describe("detectDeepNesting", () => {
534
- test("should detect deep nesting in complete hierarchy", t => {
535
- // Create a 5-level deep hierarchy
536
- let root = HierarchyBuilder.makeBox(
537
- ~name="Root",
538
- Bounds.make(~top=0, ~left=0, ~bottom=50, ~right=50),
539
- )
540
- let level1 = HierarchyBuilder.makeBox(
541
- ~name="Level1",
542
- Bounds.make(~top=2, ~left=2, ~bottom=48, ~right=48),
543
- )
544
- let level2 = HierarchyBuilder.makeBox(
545
- ~name="Level2",
546
- Bounds.make(~top=4, ~left=4, ~bottom=46, ~right=46),
547
- )
548
- let level3 = HierarchyBuilder.makeBox(
549
- ~name="Level3",
550
- Bounds.make(~top=6, ~left=6, ~bottom=44, ~right=44),
551
- )
552
- let level4 = HierarchyBuilder.makeBox(
553
- ~name="Level4",
554
- Bounds.make(~top=8, ~left=8, ~bottom=42, ~right=42),
555
- )
556
- let level5 = HierarchyBuilder.makeBox(
557
- ~name="Level5",
558
- Bounds.make(~top=10, ~left=10, ~bottom=40, ~right=40),
559
- )
560
-
561
- // Build hierarchy
562
- root.children->Array.push(level1)->ignore
563
- level1.children->Array.push(level2)->ignore
564
- level2.children->Array.push(level3)->ignore
565
- level3.children->Array.push(level4)->ignore
566
- level4.children->Array.push(level5)->ignore
567
-
568
- // Detect warnings
569
- let warnings = HierarchyBuilder.detectDeepNesting([root], ~threshold=4)
570
-
571
- // Should warn for level5 (depth 5)
572
- t->expect(Array.length(warnings))->Expect.toBe(1)
573
-
574
- switch warnings[0] {
575
- | Some(warning) =>
576
- switch warning.code {
577
- | DeepNesting({depth, position}) => {
578
- t->expect(depth)->Expect.toBe(5)
579
- t->expect(position.row)->Expect.toBe(10)
580
- t->expect(position.col)->Expect.toBe(10)
581
- }
582
- | _ => t->expect(true)->Expect.toBe(false) // fail: Expected DeepNesting warning
583
- }
584
- | None => t->expect(true)->Expect.toBe(false) // fail: Expected warning to exist
585
- }
586
- })
587
-
588
- test("should detect multiple deep boxes in different branches", t => {
589
- // Create root with two branches
590
- let root = HierarchyBuilder.makeBox(
591
- ~name="Root",
592
- Bounds.make(~top=0, ~left=0, ~bottom=100, ~right=100),
593
- )
594
-
595
- // Branch 1: 5 levels deep
596
- let b1_l1 = HierarchyBuilder.makeBox(
597
- Bounds.make(~top=2, ~left=2, ~bottom=48, ~right=48),
598
- )
599
- let b1_l2 = HierarchyBuilder.makeBox(
600
- Bounds.make(~top=4, ~left=4, ~bottom=46, ~right=46),
601
- )
602
- let b1_l3 = HierarchyBuilder.makeBox(
603
- Bounds.make(~top=6, ~left=6, ~bottom=44, ~right=44),
604
- )
605
- let b1_l4 = HierarchyBuilder.makeBox(
606
- Bounds.make(~top=8, ~left=8, ~bottom=42, ~right=42),
607
- )
608
- let b1_l5 = HierarchyBuilder.makeBox(
609
- Bounds.make(~top=10, ~left=10, ~bottom=40, ~right=40),
610
- )
611
-
612
- // Branch 2: 6 levels deep
613
- let b2_l1 = HierarchyBuilder.makeBox(
614
- Bounds.make(~top=52, ~left=52, ~bottom=98, ~right=98),
615
- )
616
- let b2_l2 = HierarchyBuilder.makeBox(
617
- Bounds.make(~top=54, ~left=54, ~bottom=96, ~right=96),
618
- )
619
- let b2_l3 = HierarchyBuilder.makeBox(
620
- Bounds.make(~top=56, ~left=56, ~bottom=94, ~right=94),
621
- )
622
- let b2_l4 = HierarchyBuilder.makeBox(
623
- Bounds.make(~top=58, ~left=58, ~bottom=92, ~right=92),
624
- )
625
- let b2_l5 = HierarchyBuilder.makeBox(
626
- Bounds.make(~top=60, ~left=60, ~bottom=90, ~right=90),
627
- )
628
- let b2_l6 = HierarchyBuilder.makeBox(
629
- Bounds.make(~top=62, ~left=62, ~bottom=88, ~right=88),
630
- )
631
-
632
- // Build hierarchies
633
- root.children->Array.push(b1_l1)->ignore
634
- root.children->Array.push(b2_l1)->ignore
635
-
636
- b1_l1.children->Array.push(b1_l2)->ignore
637
- b1_l2.children->Array.push(b1_l3)->ignore
638
- b1_l3.children->Array.push(b1_l4)->ignore
639
- b1_l4.children->Array.push(b1_l5)->ignore
640
-
641
- b2_l1.children->Array.push(b2_l2)->ignore
642
- b2_l2.children->Array.push(b2_l3)->ignore
643
- b2_l3.children->Array.push(b2_l4)->ignore
644
- b2_l4.children->Array.push(b2_l5)->ignore
645
- b2_l5.children->Array.push(b2_l6)->ignore
646
-
647
- // Detect warnings
648
- let warnings = HierarchyBuilder.detectDeepNesting([root], ~threshold=4)
649
-
650
- // Should warn for b1_l5 (depth 5), b2_l5 (depth 5), and b2_l6 (depth 6)
651
- t->expect(Array.length(warnings))->Expect.toBe(3)
652
- })
653
-
654
- test("should handle multiple root boxes", t => {
655
- // Create two separate root hierarchies
656
- let root1 = HierarchyBuilder.makeBox(
657
- ~name="Root1",
658
- Bounds.make(~top=0, ~left=0, ~bottom=30, ~right=30),
659
- )
660
- let root1_child = HierarchyBuilder.makeBox(
661
- Bounds.make(~top=2, ~left=2, ~bottom=28, ~right=28),
662
- )
663
-
664
- let root2 = HierarchyBuilder.makeBox(
665
- ~name="Root2",
666
- Bounds.make(~top=40, ~left=40, ~bottom=90, ~right=90),
667
- )
668
- let root2_l1 = HierarchyBuilder.makeBox(
669
- Bounds.make(~top=42, ~left=42, ~bottom=88, ~right=88),
670
- )
671
- let root2_l2 = HierarchyBuilder.makeBox(
672
- Bounds.make(~top=44, ~left=44, ~bottom=86, ~right=86),
673
- )
674
- let root2_l3 = HierarchyBuilder.makeBox(
675
- Bounds.make(~top=46, ~left=46, ~bottom=84, ~right=84),
676
- )
677
- let root2_l4 = HierarchyBuilder.makeBox(
678
- Bounds.make(~top=48, ~left=48, ~bottom=82, ~right=82),
679
- )
680
- let root2_l5 = HierarchyBuilder.makeBox(
681
- Bounds.make(~top=50, ~left=50, ~bottom=80, ~right=80),
682
- )
683
-
684
- // Build hierarchies
685
- root1.children->Array.push(root1_child)->ignore
686
-
687
- root2.children->Array.push(root2_l1)->ignore
688
- root2_l1.children->Array.push(root2_l2)->ignore
689
- root2_l2.children->Array.push(root2_l3)->ignore
690
- root2_l3.children->Array.push(root2_l4)->ignore
691
- root2_l4.children->Array.push(root2_l5)->ignore
692
-
693
- // Detect warnings
694
- let warnings = HierarchyBuilder.detectDeepNesting([root1, root2], ~threshold=4)
695
-
696
- // Should warn only for root2_l5 (depth 5)
697
- t->expect(Array.length(warnings))->Expect.toBe(1)
698
- })
699
-
700
- test("should return empty array when no deep nesting", t => {
701
- // Create shallow hierarchy (max depth 2)
702
- let root = HierarchyBuilder.makeBox(
703
- Bounds.make(~top=0, ~left=0, ~bottom=30, ~right=30),
704
- )
705
- let child1 = HierarchyBuilder.makeBox(
706
- Bounds.make(~top=2, ~left=2, ~bottom=14, ~right=14),
707
- )
708
- let child2 = HierarchyBuilder.makeBox(
709
- Bounds.make(~top=16, ~left=16, ~bottom=28, ~right=28),
710
- )
711
-
712
- root.children->Array.push(child1)->ignore
713
- root.children->Array.push(child2)->ignore
714
-
715
- // Detect warnings
716
- let warnings = HierarchyBuilder.detectDeepNesting([root], ~threshold=4)
717
-
718
- t->expect(Array.length(warnings))->Expect.toBe(0)
719
- })
720
- })
721
-
722
- describe("getMaxDepth", () => {
723
- test("should return 0 for empty array", t => {
724
- let maxDepth = HierarchyBuilder.getMaxDepth([])
725
- t->expect(maxDepth)->Expect.toBe(0)
726
- })
727
-
728
- test("should return 0 for single root box with no children", t => {
729
- let root = HierarchyBuilder.makeBox(
730
- Bounds.make(~top=0, ~left=0, ~bottom=10, ~right=10),
731
- )
732
- let maxDepth = HierarchyBuilder.getMaxDepth([root])
733
- t->expect(maxDepth)->Expect.toBe(0)
734
- })
735
-
736
- test("should calculate max depth for single branch", t => {
737
- // Create 3-level hierarchy
738
- let root = HierarchyBuilder.makeBox(
739
- Bounds.make(~top=0, ~left=0, ~bottom=30, ~right=30),
740
- )
741
- let level1 = HierarchyBuilder.makeBox(
742
- Bounds.make(~top=2, ~left=2, ~bottom=28, ~right=28),
743
- )
744
- let level2 = HierarchyBuilder.makeBox(
745
- Bounds.make(~top=4, ~left=4, ~bottom=26, ~right=26),
746
- )
747
-
748
- root.children->Array.push(level1)->ignore
749
- level1.children->Array.push(level2)->ignore
750
-
751
- let maxDepth = HierarchyBuilder.getMaxDepth([root])
752
- t->expect(maxDepth)->Expect.toBe(2)
753
- })
754
-
755
- test("should find max depth across multiple branches", t => {
756
- let root = HierarchyBuilder.makeBox(
757
- Bounds.make(~top=0, ~left=0, ~bottom=100, ~right=100),
758
- )
759
-
760
- // Branch 1: depth 2
761
- let b1_l1 = HierarchyBuilder.makeBox(
762
- Bounds.make(~top=2, ~left=2, ~bottom=20, ~right=20),
763
- )
764
- let b1_l2 = HierarchyBuilder.makeBox(
765
- Bounds.make(~top=4, ~left=4, ~bottom=18, ~right=18),
766
- )
767
-
768
- // Branch 2: depth 4 (this is the max)
769
- let b2_l1 = HierarchyBuilder.makeBox(
770
- Bounds.make(~top=30, ~left=30, ~bottom=98, ~right=98),
771
- )
772
- let b2_l2 = HierarchyBuilder.makeBox(
773
- Bounds.make(~top=32, ~left=32, ~bottom=96, ~right=96),
774
- )
775
- let b2_l3 = HierarchyBuilder.makeBox(
776
- Bounds.make(~top=34, ~left=34, ~bottom=94, ~right=94),
777
- )
778
- let b2_l4 = HierarchyBuilder.makeBox(
779
- Bounds.make(~top=36, ~left=36, ~bottom=92, ~right=92),
780
- )
781
-
782
- // Build hierarchies
783
- root.children->Array.push(b1_l1)->ignore
784
- root.children->Array.push(b2_l1)->ignore
785
-
786
- b1_l1.children->Array.push(b1_l2)->ignore
787
-
788
- b2_l1.children->Array.push(b2_l2)->ignore
789
- b2_l2.children->Array.push(b2_l3)->ignore
790
- b2_l3.children->Array.push(b2_l4)->ignore
791
-
792
- let maxDepth = HierarchyBuilder.getMaxDepth([root])
793
- t->expect(maxDepth)->Expect.toBe(4)
794
- })
795
- })
796
-
797
- describe("Integration with buildHierarchy", () => {
798
- test("should detect deep nesting after building hierarchy from flat array", t => {
799
- // Create flat array of boxes representing 5-level nesting
800
- let boxes = [
801
- HierarchyBuilder.makeBox(
802
- ~name="Root",
803
- Bounds.make(~top=0, ~left=0, ~bottom=50, ~right=50),
804
- ),
805
- HierarchyBuilder.makeBox(
806
- ~name="L1",
807
- Bounds.make(~top=2, ~left=2, ~bottom=48, ~right=48),
808
- ),
809
- HierarchyBuilder.makeBox(
810
- ~name="L2",
811
- Bounds.make(~top=4, ~left=4, ~bottom=46, ~right=46),
812
- ),
813
- HierarchyBuilder.makeBox(
814
- ~name="L3",
815
- Bounds.make(~top=6, ~left=6, ~bottom=44, ~right=44),
816
- ),
817
- HierarchyBuilder.makeBox(
818
- ~name="L4",
819
- Bounds.make(~top=8, ~left=8, ~bottom=42, ~right=42),
820
- ),
821
- HierarchyBuilder.makeBox(
822
- ~name="L5",
823
- Bounds.make(~top=10, ~left=10, ~bottom=40, ~right=40),
824
- ),
825
- ]
826
-
827
- // Build hierarchy
828
- let result = HierarchyBuilder.buildHierarchy(boxes)
829
-
830
- switch result {
831
- | Ok(roots) => {
832
- // Should have 1 root
833
- t->expect(Array.length(roots))->Expect.toBe(1)
834
-
835
- // Detect deep nesting
836
- let warnings = HierarchyBuilder.detectDeepNesting(roots, ~threshold=4)
837
-
838
- // Should warn for L5 (depth 5)
839
- t->expect(Array.length(warnings))->Expect.toBe(1)
840
-
841
- // Verify max depth
842
- let maxDepth = HierarchyBuilder.getMaxDepth(roots)
843
- t->expect(maxDepth)->Expect.toBe(5)
844
- }
845
- | Error(_) => t->expect(true)->Expect.toBe(false) // fail: buildHierarchy should succeed
846
- }
847
- })
848
- })
849
- })