@zombie-mermaid/ascii-renderer 2.2.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 (232) hide show
  1. package/LICENSE +22 -0
  2. package/dist/index.cjs +8 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +146 -0
  5. package/dist/index.d.ts +146 -0
  6. package/dist/index.js +5392 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +36 -0
  9. package/src/__tests__/ascii-arrowhead-direction-1083.test.ts +51 -0
  10. package/src/__tests__/ascii-canvas-first-claim-wins-1093.test.ts +89 -0
  11. package/src/__tests__/ascii-canvas-size-offset-1093.test.ts +95 -0
  12. package/src/__tests__/ascii-canvas-write.test.ts +132 -0
  13. package/src/__tests__/ascii-chain-edge-overlap-1067.test.ts +100 -0
  14. package/src/__tests__/ascii-charset-border-junctions.test.ts +63 -0
  15. package/src/__tests__/ascii-cjk-width.test.ts +150 -0
  16. package/src/__tests__/ascii-class-box-occupancy.test.ts +462 -0
  17. package/src/__tests__/ascii-class-column-width-488-489.test.ts +639 -0
  18. package/src/__tests__/ascii-class-cross-level-jog-corruption.test.ts +187 -0
  19. package/src/__tests__/ascii-class-detour-label-routing-487.test.ts +114 -0
  20. package/src/__tests__/ascii-class-diagram-compartments.test.ts +70 -0
  21. package/src/__tests__/ascii-class-label-box-collision.test.ts +60 -0
  22. package/src/__tests__/ascii-class-label-row-collision-531.test.ts +135 -0
  23. package/src/__tests__/ascii-class-label-territory-row-awareness.test.ts +57 -0
  24. package/src/__tests__/ascii-class-padding.test.ts +104 -0
  25. package/src/__tests__/ascii-class-parent-alignment-971.test.ts +139 -0
  26. package/src/__tests__/ascii-class-parent-alignment-972.test.ts +208 -0
  27. package/src/__tests__/ascii-class-reciprocal-relationships-448.test.ts +169 -0
  28. package/src/__tests__/ascii-combining-mark-width.test.ts +70 -0
  29. package/src/__tests__/ascii-coords-overlay.test.ts +64 -0
  30. package/src/__tests__/ascii-decision-lr-box-start.test.ts +106 -0
  31. package/src/__tests__/ascii-display-width-unit.test.ts +154 -0
  32. package/src/__tests__/ascii-draw-arrows-coverage.test.ts +224 -0
  33. package/src/__tests__/ascii-draw-arrows-single-point-path.test.ts +105 -0
  34. package/src/__tests__/ascii-edge-bundling-rank-violation-454.test.ts +72 -0
  35. package/src/__tests__/ascii-edge-ending-glyphs.test.ts +206 -0
  36. package/src/__tests__/ascii-edge-label-diagonal-fallback-418.test.ts +95 -0
  37. package/src/__tests__/ascii-edge-routing-fixes.test.ts +178 -0
  38. package/src/__tests__/ascii-edge-routing-single-point-path.test.ts +103 -0
  39. package/src/__tests__/ascii-edge-style-consistency-1067.test.ts +89 -0
  40. package/src/__tests__/ascii-edge-styles.test.ts +149 -0
  41. package/src/__tests__/ascii-emoji-cluster-width.test.ts +101 -0
  42. package/src/__tests__/ascii-er-box-occupancy.test.ts +251 -0
  43. package/src/__tests__/ascii-er-cardinality.test.ts +40 -0
  44. package/src/__tests__/ascii-er-corner-glyphs.test.ts +249 -0
  45. package/src/__tests__/ascii-er-jog-stray-line.test.ts +111 -0
  46. package/src/__tests__/ascii-er-label-padding.test.ts +118 -0
  47. package/src/__tests__/ascii-er-padding.test.ts +114 -0
  48. package/src/__tests__/ascii-er-relationship-label-corruption-350.test.ts +458 -0
  49. package/src/__tests__/ascii-er-relationship-overwrite.test.ts +325 -0
  50. package/src/__tests__/ascii-er-stray-connectors.test.ts +344 -0
  51. package/src/__tests__/ascii-er-unrelated-stem-separation-411.test.ts +98 -0
  52. package/src/__tests__/ascii-er-vertical-one-marker.test.ts +110 -0
  53. package/src/__tests__/ascii-label-line-terminal-fallback.test.ts +168 -0
  54. package/src/__tests__/ascii-lane-search.test.ts +207 -0
  55. package/src/__tests__/ascii-multibox-cjk-width.test.ts +184 -0
  56. package/src/__tests__/ascii-multiline.test.ts +288 -0
  57. package/src/__tests__/ascii-padding-edge-cases.test.ts +154 -0
  58. package/src/__tests__/ascii-pathfinder-route-edge.test.ts +184 -0
  59. package/src/__tests__/ascii-sequence-alt-else-label.test.ts +236 -0
  60. package/src/__tests__/ascii-sequence-block-wall-clearance.test.ts +217 -0
  61. package/src/__tests__/ascii-sequence-box-group.test.ts +159 -0
  62. package/src/__tests__/ascii-sequence-cjk-width.test.ts +235 -0
  63. package/src/__tests__/ascii-sequence-create-destroy.test.ts +114 -0
  64. package/src/__tests__/ascii-sequence-form-invariants.test.ts +432 -0
  65. package/src/__tests__/ascii-sequence-mermaid-parity.test.ts +219 -0
  66. package/src/__tests__/ascii-sequence-notes.test.ts +61 -0
  67. package/src/__tests__/ascii-sequence-padding.test.ts +119 -0
  68. package/src/__tests__/ascii-sequence-self-arrow.test.ts +206 -0
  69. package/src/__tests__/ascii-shape-diamond.test.ts +38 -0
  70. package/src/__tests__/ascii-shape-rectangle.test.ts +258 -0
  71. package/src/__tests__/ascii-shape-rounded.test.ts +36 -0
  72. package/src/__tests__/ascii-shapes-circle.test.ts +41 -0
  73. package/src/__tests__/ascii-shapes-hexagon.test.ts +42 -0
  74. package/src/__tests__/ascii-shapes-special.test.ts +344 -0
  75. package/src/__tests__/ascii-shapes-stadium.test.ts +217 -0
  76. package/src/__tests__/ascii-shapes-state.test.ts +224 -0
  77. package/src/__tests__/ascii-state-bidirectional-label-swap-530.test.ts +130 -0
  78. package/src/__tests__/ascii-subgraph-direction-honored-445.test.ts +90 -0
  79. package/src/__tests__/ascii-subgraph-label-border-clip.test.ts +152 -0
  80. package/src/__tests__/ascii-subgraph-title-padding.test.ts +77 -0
  81. package/src/__tests__/ascii-territory-unit.test.ts +219 -0
  82. package/src/__tests__/ascii-validate.test.ts +220 -0
  83. package/src/__tests__/ascii.test.ts +325 -0
  84. package/src/__tests__/class-arrow-directions.test.ts +505 -0
  85. package/src/__tests__/draw-lines.test.ts +93 -0
  86. package/src/__tests__/edge-cell-styles.test.ts +278 -0
  87. package/src/__tests__/grid-occupancy.test.ts +240 -0
  88. package/src/__tests__/helpers/ascii-form.ts +142 -0
  89. package/src/__tests__/helpers/terminal-display-width.ts +74 -0
  90. package/src/__tests__/pathfinder.test.ts +239 -0
  91. package/src/__tests__/testdata/ascii/ampersand_lhs.txt +18 -0
  92. package/src/__tests__/testdata/ascii/ampersand_lhs_and_rhs.txt +18 -0
  93. package/src/__tests__/testdata/ascii/ampersand_rhs.txt +18 -0
  94. package/src/__tests__/testdata/ascii/ampersand_td_fanin.txt +18 -0
  95. package/src/__tests__/testdata/ascii/ampersand_td_fanout.txt +18 -0
  96. package/src/__tests__/testdata/ascii/ampersand_without_edge.txt +18 -0
  97. package/src/__tests__/testdata/ascii/back_reference_from_child.txt +10 -0
  98. package/src/__tests__/testdata/ascii/backlink_from_bottom.txt +22 -0
  99. package/src/__tests__/testdata/ascii/backlink_from_top.txt +22 -0
  100. package/src/__tests__/testdata/ascii/backlink_with_short_y_padding.txt +20 -0
  101. package/src/__tests__/testdata/ascii/cls_all_relationships.txt +19 -0
  102. package/src/__tests__/testdata/ascii/cls_annotation.txt +29 -0
  103. package/src/__tests__/testdata/ascii/cls_association.txt +14 -0
  104. package/src/__tests__/testdata/ascii/cls_basic.txt +15 -0
  105. package/src/__tests__/testdata/ascii/cls_dependency.txt +14 -0
  106. package/src/__tests__/testdata/ascii/cls_inheritance.txt +20 -0
  107. package/src/__tests__/testdata/ascii/cls_methods.txt +21 -0
  108. package/src/__tests__/testdata/ascii/comments.txt +23 -0
  109. package/src/__tests__/testdata/ascii/custom_padding.txt +10 -0
  110. package/src/__tests__/testdata/ascii/duplicate_labels.txt +19 -0
  111. package/src/__tests__/testdata/ascii/er_attributes.txt +21 -0
  112. package/src/__tests__/testdata/ascii/er_basic.txt +8 -0
  113. package/src/__tests__/testdata/ascii/er_identifying.txt +18 -0
  114. package/src/__tests__/testdata/ascii/flowchart_tb_simple.txt +29 -0
  115. package/src/__tests__/testdata/ascii/graph_bt_direction.txt +28 -0
  116. package/src/__tests__/testdata/ascii/graph_tb_direction.txt +26 -0
  117. package/src/__tests__/testdata/ascii/nested_subgraphs_with_labels.txt +36 -0
  118. package/src/__tests__/testdata/ascii/preserve_order_of_definition.txt +23 -0
  119. package/src/__tests__/testdata/ascii/self_reference.txt +10 -0
  120. package/src/__tests__/testdata/ascii/self_reference_with_edge.txt +10 -0
  121. package/src/__tests__/testdata/ascii/seq_basic.txt +17 -0
  122. package/src/__tests__/testdata/ascii/seq_multiple_messages.txt +25 -0
  123. package/src/__tests__/testdata/ascii/seq_self_message.txt +18 -0
  124. package/src/__tests__/testdata/ascii/single_node.txt +8 -0
  125. package/src/__tests__/testdata/ascii/single_node_longer_name.txt +8 -0
  126. package/src/__tests__/testdata/ascii/subgraph_complex_mixed.txt +38 -0
  127. package/src/__tests__/testdata/ascii/subgraph_complex_nested.txt +49 -0
  128. package/src/__tests__/testdata/ascii/subgraph_direction_override.txt +47 -0
  129. package/src/__tests__/testdata/ascii/subgraph_empty.txt +10 -0
  130. package/src/__tests__/testdata/ascii/subgraph_mixed_nodes.txt +20 -0
  131. package/src/__tests__/testdata/ascii/subgraph_mixed_nodes_td.txt +48 -0
  132. package/src/__tests__/testdata/ascii/subgraph_multiple_edges.txt +32 -0
  133. package/src/__tests__/testdata/ascii/subgraph_multiple_nodes.txt +16 -0
  134. package/src/__tests__/testdata/ascii/subgraph_nested.txt +24 -0
  135. package/src/__tests__/testdata/ascii/subgraph_nested_with_external.txt +30 -0
  136. package/src/__tests__/testdata/ascii/subgraph_node_outside_lr.txt +17 -0
  137. package/src/__tests__/testdata/ascii/subgraph_single_node.txt +16 -0
  138. package/src/__tests__/testdata/ascii/subgraph_td_direction.txt +26 -0
  139. package/src/__tests__/testdata/ascii/subgraph_td_multiple.txt +44 -0
  140. package/src/__tests__/testdata/ascii/subgraph_td_multiple_paddingy.txt +42 -0
  141. package/src/__tests__/testdata/ascii/subgraph_three_levels_nested.txt +32 -0
  142. package/src/__tests__/testdata/ascii/subgraph_three_separate.txt +24 -0
  143. package/src/__tests__/testdata/ascii/subgraph_two_separate.txt +20 -0
  144. package/src/__tests__/testdata/ascii/subgraph_with_labels.txt +20 -0
  145. package/src/__tests__/testdata/ascii/three_nodes.txt +9 -0
  146. package/src/__tests__/testdata/ascii/three_nodes_single_line.txt +8 -0
  147. package/src/__tests__/testdata/ascii/two_layer_single_graph.txt +19 -0
  148. package/src/__tests__/testdata/ascii/two_layer_single_graph_longer_names.txt +19 -0
  149. package/src/__tests__/testdata/ascii/two_nodes_linked.txt +8 -0
  150. package/src/__tests__/testdata/ascii/two_nodes_longer_names.txt +8 -0
  151. package/src/__tests__/testdata/ascii/two_root_nodes.txt +19 -0
  152. package/src/__tests__/testdata/ascii/two_root_nodes_longer_names.txt +19 -0
  153. package/src/__tests__/testdata/ascii/two_single_root_nodes.txt +19 -0
  154. package/src/__tests__/testdata/unicode/ampersand_lhs.txt +18 -0
  155. package/src/__tests__/testdata/unicode/ampersand_lhs_and_rhs.txt +18 -0
  156. package/src/__tests__/testdata/unicode/ampersand_rhs.txt +18 -0
  157. package/src/__tests__/testdata/unicode/ampersand_without_edge.txt +18 -0
  158. package/src/__tests__/testdata/unicode/back_reference_from_child.txt +10 -0
  159. package/src/__tests__/testdata/unicode/backlink_from_bottom.txt +22 -0
  160. package/src/__tests__/testdata/unicode/backlink_from_top.txt +22 -0
  161. package/src/__tests__/testdata/unicode/cls_all_relationships.txt +19 -0
  162. package/src/__tests__/testdata/unicode/cls_annotation.txt +29 -0
  163. package/src/__tests__/testdata/unicode/cls_association.txt +14 -0
  164. package/src/__tests__/testdata/unicode/cls_basic.txt +15 -0
  165. package/src/__tests__/testdata/unicode/cls_dependency.txt +14 -0
  166. package/src/__tests__/testdata/unicode/cls_inheritance.txt +20 -0
  167. package/src/__tests__/testdata/unicode/cls_methods.txt +21 -0
  168. package/src/__tests__/testdata/unicode/comments.txt +23 -0
  169. package/src/__tests__/testdata/unicode/duplicate_labels.txt +19 -0
  170. package/src/__tests__/testdata/unicode/er_attributes.txt +21 -0
  171. package/src/__tests__/testdata/unicode/er_basic.txt +8 -0
  172. package/src/__tests__/testdata/unicode/er_identifying.txt +18 -0
  173. package/src/__tests__/testdata/unicode/graph_bt_direction.txt +28 -0
  174. package/src/__tests__/testdata/unicode/preserve_order_of_definition.txt +23 -0
  175. package/src/__tests__/testdata/unicode/self_reference.txt +10 -0
  176. package/src/__tests__/testdata/unicode/self_reference_with_edge.txt +10 -0
  177. package/src/__tests__/testdata/unicode/seq_basic.txt +17 -0
  178. package/src/__tests__/testdata/unicode/seq_multiple_messages.txt +25 -0
  179. package/src/__tests__/testdata/unicode/seq_self_message.txt +18 -0
  180. package/src/__tests__/testdata/unicode/single_node.txt +8 -0
  181. package/src/__tests__/testdata/unicode/single_node_longer_name.txt +8 -0
  182. package/src/__tests__/testdata/unicode/three_nodes.txt +9 -0
  183. package/src/__tests__/testdata/unicode/three_nodes_single_line.txt +8 -0
  184. package/src/__tests__/testdata/unicode/two_layer_single_graph.txt +19 -0
  185. package/src/__tests__/testdata/unicode/two_layer_single_graph_longer_names.txt +19 -0
  186. package/src/__tests__/testdata/unicode/two_nodes_linked.txt +8 -0
  187. package/src/__tests__/testdata/unicode/two_nodes_longer_names.txt +8 -0
  188. package/src/__tests__/testdata/unicode/two_root_nodes.txt +19 -0
  189. package/src/__tests__/testdata/unicode/two_root_nodes_longer_names.txt +19 -0
  190. package/src/__tests__/testdata/unicode/two_single_root_nodes.txt +19 -0
  191. package/src/__tests__/xychart-ascii.test.ts +376 -0
  192. package/src/ansi.ts +490 -0
  193. package/src/canvas.ts +757 -0
  194. package/src/class-diagram.ts +2001 -0
  195. package/src/converter.ts +446 -0
  196. package/src/coords.ts +58 -0
  197. package/src/display-width.ts +151 -0
  198. package/src/draw-arrows.ts +593 -0
  199. package/src/draw-boxes.ts +267 -0
  200. package/src/draw-bundles.ts +611 -0
  201. package/src/draw-lines.ts +174 -0
  202. package/src/draw-subgraphs.ts +108 -0
  203. package/src/draw.ts +350 -0
  204. package/src/edge-bundling.ts +435 -0
  205. package/src/edge-cell-styles.ts +209 -0
  206. package/src/edge-routing.ts +1070 -0
  207. package/src/er-diagram.ts +1488 -0
  208. package/src/flowchart.ts +94 -0
  209. package/src/grid-occupancy.ts +234 -0
  210. package/src/grid.ts +1309 -0
  211. package/src/hyperlinks.ts +248 -0
  212. package/src/index.ts +163 -0
  213. package/src/lane-search.ts +68 -0
  214. package/src/multiline-utils.ts +82 -0
  215. package/src/pathfinder.ts +448 -0
  216. package/src/registry.ts +88 -0
  217. package/src/sequence.ts +1318 -0
  218. package/src/shapes/circle.ts +31 -0
  219. package/src/shapes/corners.ts +273 -0
  220. package/src/shapes/diamond.ts +31 -0
  221. package/src/shapes/hexagon.ts +35 -0
  222. package/src/shapes/index.ts +123 -0
  223. package/src/shapes/rectangle.ts +199 -0
  224. package/src/shapes/rounded.ts +31 -0
  225. package/src/shapes/special.ts +360 -0
  226. package/src/shapes/stadium.ts +122 -0
  227. package/src/shapes/state.ts +204 -0
  228. package/src/shapes/types.ts +78 -0
  229. package/src/territory.ts +136 -0
  230. package/src/types.ts +454 -0
  231. package/src/validate.ts +189 -0
  232. package/src/xychart.ts +1085 -0
@@ -0,0 +1,36 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { roundedRenderer } from '../shapes/rounded.ts'
3
+ import { canvasToString } from '../canvas.ts'
4
+ import type { ShapeRenderOptions } from '../shapes/types.ts'
5
+
6
+ describe('roundedRenderer.render', () => {
7
+ it('draws Unicode rounded corners around a single-line label', () => {
8
+ const options: ShapeRenderOptions = { useAscii: false, padding: 1 }
9
+ const dimensions = roundedRenderer.getDimensions('Start', options)
10
+ const canvas = roundedRenderer.render('Start', dimensions, options)
11
+ const text = canvasToString(canvas)
12
+
13
+ const lines = text.split('\n')
14
+ expect(lines[0]!.startsWith('╭')).toBe(true)
15
+ expect(lines[0]!.endsWith('╮')).toBe(true)
16
+ expect(lines[lines.length - 1]!.startsWith('╰')).toBe(true)
17
+ expect(lines[lines.length - 1]!.endsWith('╯')).toBe(true)
18
+ expect(text).toContain('Start')
19
+ })
20
+
21
+ it('draws ASCII rounded corners around a multi-line label', () => {
22
+ const options: ShapeRenderOptions = { useAscii: true, padding: 2 }
23
+ const label = 'Hello\nWorld'
24
+ const dimensions = roundedRenderer.getDimensions(label, options)
25
+ const canvas = roundedRenderer.render(label, dimensions, options)
26
+ const text = canvasToString(canvas)
27
+
28
+ const lines = text.split('\n')
29
+ expect(lines[0]!.startsWith('.')).toBe(true)
30
+ expect(lines[0]!.endsWith('.')).toBe(true)
31
+ expect(lines[lines.length - 1]!.startsWith("'")).toBe(true)
32
+ expect(lines[lines.length - 1]!.endsWith("'")).toBe(true)
33
+ expect(text).toContain('Hello')
34
+ expect(text).toContain('World')
35
+ })
36
+ })
@@ -0,0 +1,41 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { circleRenderer } from '../shapes/circle.ts'
3
+ import { canvasToString } from '../canvas.ts'
4
+ import type { ShapeRenderOptions } from '../shapes/types.ts'
5
+
6
+ function render(label: string, options: ShapeRenderOptions): string {
7
+ const dimensions = circleRenderer.getDimensions(label, options)
8
+ const canvas = circleRenderer.render(label, dimensions, options)
9
+ return canvasToString(canvas)
10
+ }
11
+
12
+ describe('circleRenderer', () => {
13
+ it('renders unicode circle corner markers around a short label', () => {
14
+ const output = render('Hi', { useAscii: false, padding: 1 })
15
+
16
+ expect(output).toBe(
17
+ ['◯────◯', '│ │', '│ Hi │', '│ │', '◯────◯'].join('\n'),
18
+ )
19
+ })
20
+
21
+ it('renders ascii circle corner markers around a short label', () => {
22
+ const output = render('Hi', { useAscii: true, padding: 1 })
23
+
24
+ expect(output).toBe(
25
+ ['o----o', '| |', '| Hi |', '| |', 'o----o'].join('\n'),
26
+ )
27
+ })
28
+
29
+ it('grows to fit a longer label and keeps it centered', () => {
30
+ const output = render('Longer Label', { useAscii: false, padding: 2 })
31
+ const lines = output.split('\n')
32
+
33
+ expect(lines[0]).toBe('◯' + '─'.repeat(16) + '◯')
34
+ expect(lines[lines.length - 1]).toBe('◯' + '─'.repeat(16) + '◯')
35
+ expect(lines.some((l) => l.includes('Longer Label'))).toBe(true)
36
+ for (const line of lines.slice(1, -1)) {
37
+ expect(line.startsWith('│')).toBe(true)
38
+ expect(line.endsWith('│')).toBe(true)
39
+ }
40
+ })
41
+ })
@@ -0,0 +1,42 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { hexagonRenderer } from '../shapes/hexagon.ts'
3
+ import { canvasToString } from '../canvas.ts'
4
+ import type { ShapeRenderOptions } from '../shapes/types.ts'
5
+
6
+ describe('hexagonRenderer.render', () => {
7
+ it('draws Unicode hexagon corner markers around a single-line label', () => {
8
+ const options: ShapeRenderOptions = { useAscii: false, padding: 1 }
9
+ const dimensions = hexagonRenderer.getDimensions('Step', options)
10
+
11
+ const canvas = hexagonRenderer.render('Step', dimensions, options)
12
+ const text = canvasToString(canvas)
13
+ const lines = text.split('\n')
14
+
15
+ expect(lines[0]).toMatch(/^⌜─+⌝$/)
16
+ expect(lines[lines.length - 1]).toMatch(/^⌞─+⌟$/)
17
+ expect(text).toContain('Step')
18
+ for (const line of lines.slice(1, -1)) {
19
+ expect(line.startsWith('│')).toBe(true)
20
+ expect(line.endsWith('│')).toBe(true)
21
+ }
22
+ })
23
+
24
+ it('draws ASCII hexagon corner markers around a multi-line label', () => {
25
+ const options: ShapeRenderOptions = { useAscii: true, padding: 1 }
26
+ const label = 'Long\nLabel'
27
+ const dimensions = hexagonRenderer.getDimensions(label, options)
28
+
29
+ const canvas = hexagonRenderer.render(label, dimensions, options)
30
+ const text = canvasToString(canvas)
31
+ const lines = text.split('\n')
32
+
33
+ expect(lines[0]).toMatch(/^\*-+\*$/)
34
+ expect(lines[lines.length - 1]).toMatch(/^\*-+\*$/)
35
+ expect(text).toContain('Long')
36
+ expect(text).toContain('Label')
37
+ for (const line of lines.slice(1, -1)) {
38
+ expect(line.startsWith('|')).toBe(true)
39
+ expect(line.endsWith('|')).toBe(true)
40
+ }
41
+ })
42
+ })
@@ -0,0 +1,344 @@
1
+ /**
2
+ * Coverage for src/ascii/shapes/special.ts — the "special" flowchart node
3
+ * shapes: subroutine `[[text]]`, doublecircle `(((text)))`, cylinder
4
+ * `[(text)]`, asymmetric `>text]`, trapezoid `[/text\]`, and trapezoid-alt
5
+ * `[\text/]`. None of these shapes were exercised anywhere before this file.
6
+ */
7
+ import { describe, it, expect } from 'vitest'
8
+ import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
9
+ import { canvasToString } from '../canvas.ts'
10
+ import { Up, Down, Left, Right } from '../types.ts'
11
+ import {
12
+ subroutineRenderer,
13
+ doublecircleRenderer,
14
+ cylinderRenderer,
15
+ asymmetricRenderer,
16
+ trapezoidRenderer,
17
+ parallelogramRenderer,
18
+ parallelogramAltRenderer,
19
+ trapezoidAltRenderer,
20
+ } from '../shapes/special.ts'
21
+ import type { ShapeRenderOptions } from '../shapes/types.ts'
22
+
23
+ const unicodeOpts: ShapeRenderOptions = { useAscii: false, padding: 1 }
24
+ const asciiOpts: ShapeRenderOptions = { useAscii: true, padding: 1 }
25
+
26
+ describe('subroutine shape [[text]]', () => {
27
+ it('renders with its distinctive corner markers in a flowchart', () => {
28
+ const src = `flowchart TD
29
+ A[[Subroutine]]`
30
+ const out = renderMermaidASCII(src, { useAscii: false })
31
+ expect(out).toContain('Subroutine')
32
+ expect(out).toContain('╟')
33
+ expect(out).toContain('╢')
34
+ })
35
+
36
+ it('renders the ASCII fallback in a flowchart', () => {
37
+ const src = `flowchart TD
38
+ A[[Task]]`
39
+ const out = renderMermaidASCII(src, { useAscii: true })
40
+ expect(out).toContain('Task')
41
+ })
42
+
43
+ it('computes dimensions with double borders on each side', () => {
44
+ const dims = subroutineRenderer.getDimensions('Hi', unicodeOpts)
45
+ expect(dims.gridColumns).toEqual([2, 4, 2])
46
+ expect(dims.gridRows).toEqual([1, 3, 1])
47
+ expect(dims.width).toBe(8)
48
+ expect(dims.height).toBe(5)
49
+ })
50
+
51
+ it('draws double vertical bars on each side and centers the label', () => {
52
+ const dims = subroutineRenderer.getDimensions('Hi', unicodeOpts)
53
+ const canvas = subroutineRenderer.render('Hi', dims, unicodeOpts)
54
+ const str = canvasToString(canvas)
55
+ const lines = str.split('\n')
56
+ expect(lines[0]).toBe('┌┬────┬┐')
57
+ expect(lines[lines.length - 1]).toBe('└┴────┴┘')
58
+ expect(lines.some((l) => l.includes('Hi'))).toBe(true)
59
+ for (const line of lines.slice(1, -1)) {
60
+ expect(line.slice(0, 2)).toBe('││')
61
+ expect(line.slice(-2)).toBe('││')
62
+ }
63
+ })
64
+
65
+ it('draws ASCII "+" borders when useAscii is set', () => {
66
+ const dims = subroutineRenderer.getDimensions('Hi', asciiOpts)
67
+ const canvas = subroutineRenderer.render('Hi', dims, asciiOpts)
68
+ const str = canvasToString(canvas)
69
+ const lines = str.split('\n')
70
+ expect(lines[0]).toBe('++----++')
71
+ expect(lines[lines.length - 1]).toBe('++----++')
72
+ })
73
+
74
+ it('stacks multiline labels centered vertically', () => {
75
+ const dims = subroutineRenderer.getDimensions('one\ntwo', unicodeOpts)
76
+ const canvas = subroutineRenderer.render('one\ntwo', dims, unicodeOpts)
77
+ const str = canvasToString(canvas)
78
+ expect(str).toContain('one')
79
+ expect(str).toContain('two')
80
+ })
81
+
82
+ it('clips label cells that fall outside the pre-computed box bounds', () => {
83
+ const dims = subroutineRenderer.getDimensions('Hi', unicodeOpts)
84
+ const canvas = subroutineRenderer.render(
85
+ 'Much Too Wide For This Box',
86
+ dims,
87
+ unicodeOpts,
88
+ )
89
+ expect(() => canvasToString(canvas)).not.toThrow()
90
+ })
91
+
92
+ it('reuses the shared box attachment point logic', () => {
93
+ const dims = subroutineRenderer.getDimensions('Hi', unicodeOpts)
94
+ const base = { x: 0, y: 0 }
95
+ expect(subroutineRenderer.getAttachmentPoint(Down, dims, base)).toEqual({
96
+ x: Math.floor(dims.width / 2),
97
+ y: dims.height - 1,
98
+ })
99
+ expect(subroutineRenderer.getAttachmentPoint(Up, dims, base)).toEqual({
100
+ x: Math.floor(dims.width / 2),
101
+ y: 0,
102
+ })
103
+ })
104
+ })
105
+
106
+ describe('doublecircle shape (((text)))', () => {
107
+ it('renders concentric-circle corner markers in a flowchart', () => {
108
+ const src = `flowchart TD
109
+ A(((Final)))`
110
+ const out = renderMermaidASCII(src, { useAscii: false })
111
+ expect(out).toContain('Final')
112
+ expect(out).toContain('◎')
113
+ })
114
+
115
+ it('renders the ASCII fallback marker', () => {
116
+ const src = `flowchart TD
117
+ A(((Done)))`
118
+ const out = renderMermaidASCII(src, { useAscii: true })
119
+ expect(out).toContain('Done')
120
+ expect(out).toContain('@')
121
+ })
122
+
123
+ it('delegates to the shared box renderer with doublecircle corners', () => {
124
+ const dims = doublecircleRenderer.getDimensions('X', unicodeOpts)
125
+ const canvas = doublecircleRenderer.render('X', dims, unicodeOpts)
126
+ const str = canvasToString(canvas)
127
+ const lines = str.split('\n')
128
+ expect(lines[0]!.startsWith('◎')).toBe(true)
129
+ expect(lines[0]!.endsWith('◎')).toBe(true)
130
+ expect(lines[lines.length - 1]!.startsWith('◎')).toBe(true)
131
+ })
132
+ })
133
+
134
+ describe('cylinder shape [(text)]', () => {
135
+ it('renders a database cylinder in a flowchart', () => {
136
+ const src = `flowchart TD
137
+ A[(Database)]`
138
+ const out = renderMermaidASCII(src, { useAscii: false })
139
+ expect(out).toContain('Database')
140
+ expect(out).toMatch(/╭─+╮/)
141
+ expect(out).toMatch(/╰─+╯/)
142
+ })
143
+
144
+ it('renders ASCII-charset ellipse fallback characters', () => {
145
+ const src = `flowchart TD
146
+ A[(Store)]`
147
+ const out = renderMermaidASCII(src, { useAscii: true })
148
+ expect(out).toContain('Store')
149
+ expect(out).toMatch(/\.-+\./)
150
+ expect(out).toMatch(/'-+'/)
151
+ })
152
+
153
+ it('computes extra height for the curved top/bottom rows', () => {
154
+ const dims = cylinderRenderer.getDimensions('DB', unicodeOpts)
155
+ expect(dims.gridRows[0]).toBe(2)
156
+ expect(dims.gridRows[2]).toBe(2)
157
+ expect(dims.labelArea.y).toBe(3)
158
+ })
159
+
160
+ it('draws the double-row ellipse caps and centers the label', () => {
161
+ const dims = cylinderRenderer.getDimensions('DB', unicodeOpts)
162
+ const canvas = cylinderRenderer.render('DB', dims, unicodeOpts)
163
+ const str = canvasToString(canvas)
164
+ const lines = str.split('\n')
165
+ expect(lines[0]!.startsWith('╭')).toBe(true)
166
+ expect(lines[0]!.endsWith('╮')).toBe(true)
167
+ expect(lines[1]).toMatch(/^│─+│$/)
168
+ expect(lines[lines.length - 2]).toMatch(/^│─+│$/)
169
+ expect(lines[lines.length - 1]!.startsWith('╰')).toBe(true)
170
+ expect(lines[lines.length - 1]!.endsWith('╯')).toBe(true)
171
+ expect(str).toContain('DB')
172
+ })
173
+
174
+ it('draws ASCII fallback caps', () => {
175
+ const dims = cylinderRenderer.getDimensions('DB', asciiOpts)
176
+ const canvas = cylinderRenderer.render('DB', dims, asciiOpts)
177
+ const str = canvasToString(canvas)
178
+ const lines = str.split('\n')
179
+ expect(lines[0]!.startsWith('.')).toBe(true)
180
+ expect(lines[0]!.endsWith('.')).toBe(true)
181
+ expect(lines[lines.length - 1]!.startsWith("'")).toBe(true)
182
+ expect(lines[lines.length - 1]!.endsWith("'")).toBe(true)
183
+ })
184
+
185
+ it('stacks multiline labels inside the cylinder body', () => {
186
+ const dims = cylinderRenderer.getDimensions('one\ntwo', unicodeOpts)
187
+ const canvas = cylinderRenderer.render('one\ntwo', dims, unicodeOpts)
188
+ const str = canvasToString(canvas)
189
+ expect(str).toContain('one')
190
+ expect(str).toContain('two')
191
+ })
192
+
193
+ it('clips label cells that fall outside the pre-computed box bounds', () => {
194
+ const dims = cylinderRenderer.getDimensions('DB', unicodeOpts)
195
+ const canvas = cylinderRenderer.render(
196
+ 'Much Too Wide For This Cylinder',
197
+ dims,
198
+ unicodeOpts,
199
+ )
200
+ expect(() => canvasToString(canvas)).not.toThrow()
201
+ })
202
+
203
+ it('reuses the shared box attachment point logic', () => {
204
+ const dims = cylinderRenderer.getDimensions('DB', unicodeOpts)
205
+ const base = { x: 2, y: 3 }
206
+ const left = cylinderRenderer.getAttachmentPoint(Left, dims, base)
207
+ const right = cylinderRenderer.getAttachmentPoint(Right, dims, base)
208
+ expect(left.x).toBe(base.x)
209
+ expect(right.x).toBe(base.x + dims.width - 1)
210
+ })
211
+ })
212
+
213
+ describe('asymmetric (flag) shape >text]', () => {
214
+ it('renders arrow markers on the left corners in a flowchart', () => {
215
+ const src = `flowchart TD
216
+ A>Flag]`
217
+ const out = renderMermaidASCII(src, { useAscii: false })
218
+ expect(out).toContain('Flag')
219
+ expect(out).toContain('▷')
220
+ })
221
+
222
+ it('renders the ASCII fallback marker', () => {
223
+ const src = `flowchart TD
224
+ A>Note]`
225
+ const out = renderMermaidASCII(src, { useAscii: true })
226
+ expect(out).toContain('Note')
227
+ expect(out).toContain('>')
228
+ })
229
+
230
+ it('delegates to the shared box renderer with asymmetric corners', () => {
231
+ const dims = asymmetricRenderer.getDimensions('X', unicodeOpts)
232
+ const canvas = asymmetricRenderer.render('X', dims, unicodeOpts)
233
+ const str = canvasToString(canvas)
234
+ const lines = str.split('\n')
235
+ expect(lines[0]!.startsWith('▷')).toBe(true)
236
+ expect(lines[0]!.endsWith('┐')).toBe(true)
237
+ expect(lines[lines.length - 1]!.startsWith('▷')).toBe(true)
238
+ expect(lines[lines.length - 1]!.endsWith('┘')).toBe(true)
239
+ })
240
+ })
241
+
242
+ describe('trapezoid shape [/text\\]', () => {
243
+ it('renders slope markers on the top corners in a flowchart', () => {
244
+ const src = `flowchart TD
245
+ A[/Wide Bottom\\]`
246
+ const out = renderMermaidASCII(src, { useAscii: false })
247
+ expect(out).toContain('Wide Bottom')
248
+ })
249
+
250
+ it('delegates to the shared box renderer with trapezoid corners', () => {
251
+ const dims = trapezoidRenderer.getDimensions('X', unicodeOpts)
252
+ const canvas = trapezoidRenderer.render('X', dims, unicodeOpts)
253
+ const str = canvasToString(canvas)
254
+ const lines = str.split('\n')
255
+ expect(lines[0]!.startsWith('/')).toBe(true)
256
+ expect(lines[0]!.endsWith('\\')).toBe(true)
257
+ expect(lines[lines.length - 1]!.startsWith('└')).toBe(true)
258
+ expect(lines[lines.length - 1]!.endsWith('┘')).toBe(true)
259
+ })
260
+ })
261
+
262
+ describe('trapezoid-alt shape [\\text/]', () => {
263
+ it('renders slope markers on the bottom corners in a flowchart', () => {
264
+ const src = `flowchart TD
265
+ A[\\Wide Top/]`
266
+ const out = renderMermaidASCII(src, { useAscii: false })
267
+ expect(out).toContain('Wide Top')
268
+ })
269
+
270
+ it('delegates to the shared box renderer with trapezoid-alt corners', () => {
271
+ const dims = trapezoidAltRenderer.getDimensions('X', unicodeOpts)
272
+ const canvas = trapezoidAltRenderer.render('X', dims, unicodeOpts)
273
+ const str = canvasToString(canvas)
274
+ const lines = str.split('\n')
275
+ expect(lines[0]!.startsWith('┌')).toBe(true)
276
+ expect(lines[0]!.endsWith('┐')).toBe(true)
277
+ expect(lines[lines.length - 1]!.startsWith('\\')).toBe(true)
278
+ expect(lines[lines.length - 1]!.endsWith('/')).toBe(true)
279
+ })
280
+ })
281
+
282
+ describe('special shapes connected in a diagram', () => {
283
+ it('renders edges between multiple special shapes without diagonal artifacts', () => {
284
+ const src = `flowchart TD
285
+ A[[Sub]] --> B[(DB)]
286
+ B --> C(((Done)))
287
+ C --> D>Flag]
288
+ D --> E[/Trap\\]
289
+ E --> F[\\TrapAlt/]`
290
+ const out = renderMermaidASCII(src, { useAscii: false })
291
+ expect(out).toContain('Sub')
292
+ expect(out).toContain('DB')
293
+ expect(out).toContain('Done')
294
+ expect(out).toContain('Flag')
295
+ expect(out).toContain('Trap')
296
+ expect(out).toContain('TrapAlt')
297
+ })
298
+ })
299
+
300
+ describe('parallelogram shapes [/text/] and [\\text\\]', () => {
301
+ it('renders a lean-right parallelogram in a flowchart', () => {
302
+ const out = renderMermaidASCII('flowchart TD\n A[/Lean Right/]', {
303
+ useAscii: false,
304
+ })
305
+ expect(out).toContain('Lean Right')
306
+ })
307
+
308
+ it('renders a lean-left parallelogram in a flowchart', () => {
309
+ const out = renderMermaidASCII('flowchart TD\n A[\\Lean Left\\]', {
310
+ useAscii: false,
311
+ })
312
+ expect(out).toContain('Lean Left')
313
+ })
314
+
315
+ it('delegates to the shared box renderer with parallelogram corners', () => {
316
+ const dims = parallelogramRenderer.getDimensions('X', unicodeOpts)
317
+ const canvas = parallelogramRenderer.render('X', dims, unicodeOpts)
318
+ const lines = canvasToString(canvas).split('\n')
319
+ // Both sides slope the same way — that is what separates a parallelogram
320
+ // from a trapezoid, whose corners oppose.
321
+ expect(lines[0]!.startsWith('/')).toBe(true)
322
+ expect(lines[0]!.endsWith('/')).toBe(true)
323
+ expect(lines[lines.length - 1]!.startsWith('/')).toBe(true)
324
+ expect(lines[lines.length - 1]!.endsWith('/')).toBe(true)
325
+ })
326
+
327
+ it('delegates to the shared box renderer with parallelogram-alt corners', () => {
328
+ const dims = parallelogramAltRenderer.getDimensions('X', unicodeOpts)
329
+ const canvas = parallelogramAltRenderer.render('X', dims, unicodeOpts)
330
+ const lines = canvasToString(canvas).split('\n')
331
+ expect(lines[0]!.startsWith('\\')).toBe(true)
332
+ expect(lines[0]!.endsWith('\\')).toBe(true)
333
+ expect(lines[lines.length - 1]!.startsWith('\\')).toBe(true)
334
+ expect(lines[lines.length - 1]!.endsWith('\\')).toBe(true)
335
+ })
336
+
337
+ it('reports the same dimensions as the other box-based shapes', () => {
338
+ // Parallelograms reuse getBoxDimensions, so their sizing must match
339
+ // trapezoid's for the same label — a divergence would misalign the grid.
340
+ expect(parallelogramRenderer.getDimensions('Label', unicodeOpts)).toEqual(
341
+ trapezoidRenderer.getDimensions('Label', unicodeOpts),
342
+ )
343
+ })
344
+ })
@@ -0,0 +1,217 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
3
+ import { stadiumRenderer } from '../shapes/stadium.ts'
4
+ import type { Canvas } from '../types.ts'
5
+
6
+ function canvasToLines(canvas: Canvas): string[] {
7
+ const height = canvas[0]?.length ?? 0
8
+ const lines: string[] = []
9
+ for (let y = 0; y < height; y++) {
10
+ let row = ''
11
+ for (let x = 0; x < canvas.length; x++) row += canvas[x]![y]
12
+ lines.push(row)
13
+ }
14
+ return lines
15
+ }
16
+
17
+ describe('stadium shape (flowchart integration)', () => {
18
+ it('renders a short single-line stadium label in unicode mode', () => {
19
+ const ascii = renderMermaidASCII('graph TD\n A([Start])', {
20
+ useAscii: false,
21
+ })
22
+ expect(ascii).toContain('Start')
23
+ expect(ascii).toContain('(')
24
+ expect(ascii).toContain(')')
25
+ })
26
+
27
+ it('renders a short single-line stadium label in ascii mode', () => {
28
+ const ascii = renderMermaidASCII('graph TD\n A([Start])', {
29
+ useAscii: true,
30
+ })
31
+ expect(ascii).toContain('Start')
32
+ expect(ascii).toContain('(')
33
+ expect(ascii).toContain(')')
34
+ expect(ascii).toContain('-')
35
+ })
36
+
37
+ it('sizes the stadium wider for a longer label', () => {
38
+ const shortAscii = renderMermaidASCII('graph TD\n A([Hi])', {
39
+ useAscii: false,
40
+ })
41
+ const longAscii = renderMermaidASCII(
42
+ 'graph TD\n A([This is a fairly long stadium label])',
43
+ { useAscii: false },
44
+ )
45
+ const shortWidth = Math.max(...shortAscii.split('\n').map((l) => l.length))
46
+ const longWidth = Math.max(...longAscii.split('\n').map((l) => l.length))
47
+ expect(longAscii).toContain('This is a fairly long stadium label')
48
+ expect(longWidth).toBeGreaterThan(shortWidth)
49
+ })
50
+
51
+ it('renders a two-line stadium label with lines stacked in order', () => {
52
+ const ascii = renderMermaidASCII('graph TD\n A([Line1<br>Line2])', {
53
+ useAscii: false,
54
+ })
55
+ expect(ascii).toContain('Line1')
56
+ expect(ascii).toContain('Line2')
57
+ const lines = ascii.split('\n')
58
+ const line1Row = lines.findIndex((l) => l.includes('Line1'))
59
+ const line2Row = lines.findIndex((l) => l.includes('Line2'))
60
+ expect(line2Row).toBeGreaterThan(line1Row)
61
+ })
62
+
63
+ it('renders a three-line stadium label in ascii mode', () => {
64
+ const ascii = renderMermaidASCII('graph TD\n A([One<br>Two<br>Three])', {
65
+ useAscii: true,
66
+ })
67
+ expect(ascii).toContain('One')
68
+ expect(ascii).toContain('Two')
69
+ expect(ascii).toContain('Three')
70
+ })
71
+
72
+ it('connects an edge between two stadium nodes', () => {
73
+ const ascii = renderMermaidASCII('graph TD\n A([Begin]) --> B([End])', {
74
+ useAscii: false,
75
+ })
76
+ expect(ascii).toContain('Begin')
77
+ expect(ascii).toContain('End')
78
+ })
79
+ })
80
+
81
+ describe('stadiumRenderer.getDimensions', () => {
82
+ it('sizes a single-line label with the given padding', () => {
83
+ const dims = stadiumRenderer.getDimensions('Start', {
84
+ useAscii: false,
85
+ padding: 1,
86
+ })
87
+ expect(dims).toEqual({
88
+ width: 11,
89
+ height: 5,
90
+ labelArea: { x: 3, y: 2, width: 5, height: 1 },
91
+ gridColumns: [2, 7, 2],
92
+ gridRows: [1, 3, 1],
93
+ })
94
+ })
95
+
96
+ it('collapses to the minimum 3-row height with zero padding', () => {
97
+ const dims = stadiumRenderer.getDimensions('Start', {
98
+ useAscii: false,
99
+ padding: 0,
100
+ })
101
+ expect(dims.height).toBe(3)
102
+ expect(dims.width).toBe(9)
103
+ })
104
+
105
+ it('grows height for multi-line labels', () => {
106
+ const dims = stadiumRenderer.getDimensions('Line1\nLine2', {
107
+ useAscii: false,
108
+ padding: 1,
109
+ })
110
+ expect(dims).toEqual({
111
+ width: 11,
112
+ height: 6,
113
+ labelArea: { x: 3, y: 2, width: 5, height: 2 },
114
+ gridColumns: [2, 7, 2],
115
+ gridRows: [1, 4, 1],
116
+ })
117
+ })
118
+
119
+ it('sizes width to the widest of several lines', () => {
120
+ const dims = stadiumRenderer.getDimensions('Short\nA much longer line', {
121
+ useAscii: false,
122
+ padding: 1,
123
+ })
124
+ expect(dims.labelArea.width).toBe('A much longer line'.length)
125
+ })
126
+ })
127
+
128
+ describe('stadiumRenderer.render', () => {
129
+ it('renders a single-row pill when height collapses to 3', () => {
130
+ const dims = stadiumRenderer.getDimensions('Start', {
131
+ useAscii: false,
132
+ padding: 0,
133
+ })
134
+ const canvas = stadiumRenderer.render('Start', dims, {
135
+ useAscii: false,
136
+ padding: 0,
137
+ })
138
+ expect(canvasToLines(canvas)).toEqual([
139
+ ' ',
140
+ '( Start )',
141
+ ' ',
142
+ ])
143
+ })
144
+
145
+ it('renders rounded corners for multi-row unicode mode', () => {
146
+ const dims = stadiumRenderer.getDimensions('Line1\nLine2', {
147
+ useAscii: false,
148
+ padding: 1,
149
+ })
150
+ const canvas = stadiumRenderer.render('Line1\nLine2', dims, {
151
+ useAscii: false,
152
+ padding: 1,
153
+ })
154
+ expect(canvasToLines(canvas)).toEqual([
155
+ '╭─────────╮',
156
+ '│ │',
157
+ '│ │',
158
+ '│ Line1 │',
159
+ '│ Line2 │',
160
+ '╰─────────╯',
161
+ ])
162
+ })
163
+
164
+ it('renders parentheses on all sides for multi-row ascii mode', () => {
165
+ const dims = stadiumRenderer.getDimensions('Line1\nLine2', {
166
+ useAscii: true,
167
+ padding: 1,
168
+ })
169
+ const canvas = stadiumRenderer.render('Line1\nLine2', dims, {
170
+ useAscii: true,
171
+ padding: 1,
172
+ })
173
+ expect(canvasToLines(canvas)).toEqual([
174
+ '(---------)',
175
+ '( )',
176
+ '( )',
177
+ '( Line1 )',
178
+ '( Line2 )',
179
+ '(---------)',
180
+ ])
181
+ })
182
+
183
+ it('centers an odd number of label lines around the vertical middle', () => {
184
+ const dims = stadiumRenderer.getDimensions('One\nTwo\nThree', {
185
+ useAscii: false,
186
+ padding: 1,
187
+ })
188
+ const canvas = stadiumRenderer.render('One\nTwo\nThree', dims, {
189
+ useAscii: false,
190
+ padding: 1,
191
+ })
192
+ expect(canvasToLines(canvas)).toEqual([
193
+ '╭─────────╮',
194
+ '│ │',
195
+ '│ One │',
196
+ '│ Two │',
197
+ '│ Three │',
198
+ '│ │',
199
+ '╰─────────╯',
200
+ ])
201
+ })
202
+
203
+ it('clips label characters that fall outside the canvas bounds', () => {
204
+ const dims = {
205
+ width: 5,
206
+ height: 3,
207
+ labelArea: { x: 1, y: 1, width: 3, height: 1 },
208
+ gridColumns: [1, 3, 1] as [number, number, number],
209
+ gridRows: [1, 1, 1] as [number, number, number],
210
+ }
211
+ const canvas = stadiumRenderer.render('VeryLongLabelText', dims, {
212
+ useAscii: false,
213
+ padding: 0,
214
+ })
215
+ expect(canvasToLines(canvas)).toEqual([' ', '(gLa)', ' '])
216
+ })
217
+ })