@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,376 @@
1
+ /**
2
+ * Tests for xychart-beta ASCII rendering.
3
+ *
4
+ * Tests bar charts, line charts, mixed charts, horizontal orientation,
5
+ * multi-series support, staircase line routing, and edge cases.
6
+ */
7
+ import { describe, it, expect } from 'vitest'
8
+ import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
9
+
10
+ // ============================================================================
11
+ // Helper — render with no colors for easy string matching
12
+ // ============================================================================
13
+
14
+ function render(text: string, useAscii = false): string {
15
+ return renderMermaidASCII(text, { colorMode: 'none', useAscii })
16
+ }
17
+
18
+ // ============================================================================
19
+ // Bar charts
20
+ // ============================================================================
21
+
22
+ describe('xychart ASCII – bar charts', () => {
23
+ it('renders a basic bar chart with block characters', () => {
24
+ const result = render(`xychart-beta
25
+ x-axis [A, B, C]
26
+ bar [10, 20, 30]`)
27
+ expect(result).toContain('█')
28
+ expect(result).toContain('A')
29
+ expect(result).toContain('B')
30
+ expect(result).toContain('C')
31
+ })
32
+
33
+ it('renders bars with # in ASCII mode', () => {
34
+ const result = render(
35
+ `xychart-beta
36
+ x-axis [A, B, C]
37
+ bar [10, 20, 30]`,
38
+ true,
39
+ )
40
+ expect(result).toContain('#')
41
+ expect(result).not.toContain('█')
42
+ })
43
+
44
+ it('renders a bar chart with title', () => {
45
+ const result = render(`xychart-beta
46
+ title "Sales Report"
47
+ x-axis [Q1, Q2, Q3, Q4]
48
+ bar [100, 200, 150, 250]`)
49
+ expect(result).toContain('Sales Report')
50
+ expect(result).toContain('Q1')
51
+ expect(result).toContain('Q4')
52
+ })
53
+
54
+ it('renders y-axis tick labels', () => {
55
+ const result = render(`xychart-beta
56
+ x-axis [A, B]
57
+ y-axis 0 --> 100
58
+ bar [25, 75]`)
59
+ // Should have at least some tick values visible
60
+ expect(result).toContain('┤')
61
+ expect(result).toContain('┼')
62
+ })
63
+
64
+ it('renders multi-series bars', () => {
65
+ const result = render(`xychart-beta
66
+ x-axis [A, B, C]
67
+ bar [10, 20, 30]
68
+ bar [15, 25, 35]`)
69
+ // Should contain legend for multi-series
70
+ expect(result).toContain('Bar 1')
71
+ expect(result).toContain('Bar 2')
72
+ })
73
+
74
+ it('overlays (not groups side-by-side) multiple bar series in the same category', () => {
75
+ // Regression guard for issue #452: real mermaid.js draws every bar
76
+ // series in a category at the identical x-position/width (an
77
+ // overlaid/occluding layout, with a later series painted on top of an
78
+ // earlier one) rather than splitting them into adjacent grouped-bar
79
+ // columns. Each category's bar characters should therefore form a
80
+ // single contiguous run per row, never two runs separated by a gap.
81
+ const result = render(`xychart-beta
82
+ x-axis [A]
83
+ bar [10]
84
+ bar [20]`)
85
+ const lines = result
86
+ .split('\n')
87
+ // Exclude the legend row ("█ Bar 1 █ Bar 2") — its two swatches are
88
+ // expected to be separate runs; only plot rows matter here.
89
+ .filter((line) => !/[A-Za-z]/.test(line))
90
+ expect(lines.length).toBeGreaterThan(0)
91
+ for (const line of lines) {
92
+ const runs = line.match(/█+/g) ?? []
93
+ expect(runs.length).toBeLessThanOrEqual(1)
94
+ }
95
+ })
96
+ })
97
+
98
+ // ============================================================================
99
+ // Line charts
100
+ // ============================================================================
101
+
102
+ describe('xychart ASCII – line charts', () => {
103
+ it('renders a line chart with staircase routing', () => {
104
+ const result = render(`xychart-beta
105
+ x-axis [A, B, C, D]
106
+ line [10, 30, 20, 40]`)
107
+ // Should use box-drawing corners for staircase
108
+ expect(result).toContain('╭')
109
+ expect(result).toContain('─')
110
+ })
111
+
112
+ it('does NOT use dot markers on line charts', () => {
113
+ const result = render(`xychart-beta
114
+ x-axis [A, B, C, D]
115
+ line [10, 30, 20, 40]`)
116
+ expect(result).not.toContain('●')
117
+ expect(result).not.toContain('*')
118
+ })
119
+
120
+ it('uses vertical segments for row transitions', () => {
121
+ const result = render(`xychart-beta
122
+ x-axis [A, B, C]
123
+ line [10, 50, 20]`)
124
+ // Vertical segments connect rows
125
+ expect(result).toContain('│')
126
+ })
127
+
128
+ it('renders ascending line with ╯ and ╭ corners', () => {
129
+ const result = render(`xychart-beta
130
+ x-axis [A, B]
131
+ line [10, 50]`)
132
+ // Ascending: ╯ at bottom (left+up), ╭ at top (bottom+right)
133
+ expect(result).toContain('╯')
134
+ expect(result).toContain('╭')
135
+ })
136
+
137
+ it('renders descending line with ╮ and ╰ corners', () => {
138
+ const result = render(`xychart-beta
139
+ x-axis [A, B]
140
+ line [50, 10]`)
141
+ // Descending: ╮ at top (left+down), ╰ at bottom (top+right)
142
+ expect(result).toContain('╮')
143
+ expect(result).toContain('╰')
144
+ })
145
+
146
+ it('renders flat line with only ─', () => {
147
+ const result = render(`xychart-beta
148
+ x-axis [A, B, C]
149
+ line [50, 50, 50]`)
150
+ expect(result).toContain('─')
151
+ // No corners needed for flat lines
152
+ expect(result).not.toContain('╭')
153
+ expect(result).not.toContain('╮')
154
+ })
155
+
156
+ it('uses + for corners in ASCII mode', () => {
157
+ const result = render(
158
+ `xychart-beta
159
+ x-axis [A, B]
160
+ line [10, 50]`,
161
+ true,
162
+ )
163
+ expect(result).toContain('+')
164
+ expect(result).not.toContain('╭')
165
+ expect(result).not.toContain('╰')
166
+ })
167
+ })
168
+
169
+ // ============================================================================
170
+ // Mixed bar + line
171
+ // ============================================================================
172
+
173
+ describe('xychart ASCII – mixed charts', () => {
174
+ it('renders bars and lines together', () => {
175
+ const result = render(`xychart-beta
176
+ x-axis [A, B, C, D]
177
+ bar [20, 40, 30, 50]
178
+ line [25, 35, 40, 45]`)
179
+ // Should have both bar blocks and line corners
180
+ expect(result).toContain('█')
181
+ expect(result).toContain('─')
182
+ // Legend for multi-series
183
+ expect(result).toContain('Bar 1')
184
+ expect(result).toContain('Line 1')
185
+ })
186
+ })
187
+
188
+ // ============================================================================
189
+ // Horizontal orientation
190
+ // ============================================================================
191
+
192
+ describe('xychart ASCII – horizontal', () => {
193
+ it('renders horizontal bar chart', () => {
194
+ const result = render(`xychart-beta horizontal
195
+ x-axis [Python, JavaScript, Go]
196
+ bar [30, 25, 12]`)
197
+ // Category labels on left side
198
+ expect(result).toContain('Python')
199
+ expect(result).toContain('JavaScript')
200
+ expect(result).toContain('Go')
201
+ expect(result).toContain('█')
202
+ })
203
+
204
+ it('renders horizontal line chart with staircase', () => {
205
+ const result = render(`xychart-beta horizontal
206
+ x-axis [A, B, C]
207
+ line [10, 30, 20]`)
208
+ // Should have horizontal staircase routing
209
+ expect(result).toContain('─')
210
+ expect(result).toContain('│')
211
+ })
212
+
213
+ it('renders a y-axis title in horizontal orientation', () => {
214
+ const result = render(`xychart-beta horizontal
215
+ x-axis [A, B, C]
216
+ y-axis "Score" 0 --> 100
217
+ bar [10, 30, 20]`)
218
+ expect(result).toContain('Score')
219
+ })
220
+ })
221
+
222
+ // ============================================================================
223
+ // Titles and axis labels
224
+ // ============================================================================
225
+
226
+ describe('xychart ASCII – titles and axes', () => {
227
+ it('renders chart title centered', () => {
228
+ const result = render(`xychart-beta
229
+ title "My Chart"
230
+ x-axis [A, B]
231
+ bar [10, 20]`)
232
+ const titleLine = result.split('\n').find((l) => l.includes('My Chart'))
233
+ expect(titleLine).toBeDefined()
234
+ })
235
+
236
+ it('renders x-axis title', () => {
237
+ const result = render(`xychart-beta
238
+ x-axis "Category" [A, B, C]
239
+ bar [10, 20, 30]`)
240
+ expect(result).toContain('Category')
241
+ })
242
+
243
+ it('renders y-axis with explicit range', () => {
244
+ const result = render(`xychart-beta
245
+ x-axis [A, B]
246
+ y-axis "Score" 0 --> 100
247
+ bar [25, 75]`)
248
+ // Y-axis ticks should appear
249
+ expect(result).toContain('0')
250
+ expect(result).toContain('100')
251
+ })
252
+
253
+ it('renders a y-axis title vertically (one character per row) in vertical orientation', () => {
254
+ const result = render(`xychart-beta
255
+ x-axis [A, B]
256
+ y-axis "Score" 0 --> 100
257
+ bar [25, 75]`)
258
+ // The title should be spelled out top-to-bottom in the leftmost column,
259
+ // mirroring how mermaid.js rotates it 90deg along the left edge — not
260
+ // dropped entirely (issue #451).
261
+ const lines = result.split('\n')
262
+ const leftColumn = lines.map((l) => l[0] ?? ' ').join('')
263
+ expect(leftColumn).toContain('Score')
264
+ })
265
+
266
+ it('does not reserve a y-axis title column when no y-axis title is set', () => {
267
+ const result = render(`xychart-beta
268
+ x-axis [A, B]
269
+ y-axis 0 --> 100
270
+ bar [25, 75]`)
271
+ const lines = result.split('\n')
272
+ // Without a title, the leftmost column should be blank/whitespace on
273
+ // every line (no stray characters from title-column reservation).
274
+ for (const line of lines) {
275
+ expect(line[0] === undefined || line[0] === ' ').toBe(true)
276
+ }
277
+ })
278
+
279
+ it('renders without title when not specified', () => {
280
+ const result = render(`xychart-beta
281
+ x-axis [A, B]
282
+ bar [10, 20]`)
283
+ // First non-empty line should be chart content, not a title
284
+ const lines = result.split('\n').filter((l) => l.trim().length > 0)
285
+ expect(lines.length).toBeGreaterThan(0)
286
+ })
287
+ })
288
+
289
+ // ============================================================================
290
+ // Edge cases
291
+ // ============================================================================
292
+
293
+ describe('xychart ASCII – edge cases', () => {
294
+ it('handles single data point', () => {
295
+ const result = render(`xychart-beta
296
+ x-axis [Only]
297
+ bar [42]`)
298
+ expect(result).toContain('Only')
299
+ expect(result).toContain('█')
300
+ })
301
+
302
+ it('handles two data points', () => {
303
+ const result = render(`xychart-beta
304
+ x-axis [A, B]
305
+ bar [10, 20]`)
306
+ expect(result).toContain('A')
307
+ expect(result).toContain('B')
308
+ })
309
+
310
+ it('handles all zeros', () => {
311
+ const result = render(`xychart-beta
312
+ x-axis [A, B, C]
313
+ bar [0, 0, 0]`)
314
+ expect(result).toContain('A')
315
+ // Should still render axes
316
+ expect(result).toContain('┼')
317
+ })
318
+
319
+ it('handles large values', () => {
320
+ const result = render(`xychart-beta
321
+ x-axis [A, B]
322
+ bar [1000000, 2000000]`)
323
+ expect(result).toContain('A')
324
+ expect(result).toContain('B')
325
+ })
326
+
327
+ it('renders line chart with single data point', () => {
328
+ const result = render(`xychart-beta
329
+ x-axis [A]
330
+ line [50]`)
331
+ expect(result).toContain('A')
332
+ expect(result).toContain('─')
333
+ })
334
+ })
335
+
336
+ // ============================================================================
337
+ // Axis structure
338
+ // ============================================================================
339
+
340
+ describe('xychart ASCII – axis structure', () => {
341
+ it('has y-axis ticks (┤) at value positions', () => {
342
+ const result = render(`xychart-beta
343
+ x-axis [A, B, C]
344
+ y-axis 0 --> 100
345
+ bar [25, 50, 75]`)
346
+ expect(result).toContain('┤')
347
+ })
348
+
349
+ it('has x-axis ticks (┬) at category positions', () => {
350
+ const result = render(`xychart-beta
351
+ x-axis [A, B, C]
352
+ bar [25, 50, 75]`)
353
+ expect(result).toContain('┬')
354
+ })
355
+
356
+ it('has origin marker (┼)', () => {
357
+ const result = render(`xychart-beta
358
+ x-axis [A, B]
359
+ bar [10, 20]`)
360
+ expect(result).toContain('┼')
361
+ })
362
+
363
+ it('uses + for axis characters in ASCII mode', () => {
364
+ const result = render(
365
+ `xychart-beta
366
+ x-axis [A, B, C]
367
+ y-axis 0 --> 100
368
+ bar [25, 50, 75]`,
369
+ true,
370
+ )
371
+ expect(result).toContain('+')
372
+ expect(result).not.toContain('┤')
373
+ expect(result).not.toContain('┬')
374
+ expect(result).not.toContain('┼')
375
+ })
376
+ })