zombie-mermaid 2.2.1 → 2.2.2

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 (251) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +7 -2
  3. package/dist/ascii.cjs +9 -9
  4. package/dist/ascii.cjs.map +1 -1
  5. package/dist/ascii.js +4069 -3969
  6. package/dist/ascii.js.map +1 -1
  7. package/dist/cli.js +7605 -7381
  8. package/dist/index.cjs +52 -52
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.js +7613 -7459
  11. package/dist/index.js.map +1 -1
  12. package/dist/mcp.cjs +14142 -13717
  13. package/dist/mcp.cjs.map +1 -1
  14. package/dist/mcp.js +14142 -13717
  15. package/dist/mcp.js.map +1 -1
  16. package/package.json +27 -5
  17. package/packages/ascii-renderer/LICENSE +22 -0
  18. package/{src/ascii → packages/ascii-renderer/src}/class-diagram.ts +277 -57
  19. package/{src/ascii → packages/ascii-renderer/src}/er-diagram.ts +2 -2
  20. package/packages/ascii-renderer/src/flowchart.ts +94 -0
  21. package/{src/ascii → packages/ascii-renderer/src}/grid.ts +23 -0
  22. package/{src/ascii → packages/ascii-renderer/src}/index.ts +13 -84
  23. package/{src/ascii → packages/ascii-renderer/src}/registry.ts +23 -12
  24. package/{src/ascii → packages/ascii-renderer/src}/sequence.ts +55 -8
  25. package/{src/ascii → packages/ascii-renderer/src}/xychart.ts +6 -3
  26. package/packages/core/LICENSE +22 -0
  27. package/packages/core/src/click-directive.ts +106 -0
  28. package/packages/core/src/color-utils.ts +205 -0
  29. package/packages/core/src/diagram-type.ts +35 -0
  30. package/packages/core/src/direction-override.ts +46 -0
  31. package/packages/core/src/direction.ts +56 -0
  32. package/packages/core/src/index.ts +34 -0
  33. package/packages/core/src/init-directive.ts +248 -0
  34. package/packages/core/src/multiline-utils.ts +275 -0
  35. package/packages/core/src/statements.ts +197 -0
  36. package/packages/core/src/style-directives.ts +214 -0
  37. package/packages/core/src/text-metrics.ts +498 -0
  38. package/packages/core/src/theme.ts +696 -0
  39. package/packages/core/src/types.ts +670 -0
  40. package/packages/mcp/LICENSE +22 -0
  41. package/{src/mcp → packages/mcp/src}/server.ts +34 -2
  42. package/{src/mcp → packages/mcp/src}/tools/check-sequence-activations.ts +8 -5
  43. package/packages/mcp/src/tools/fix-sequence-activations.ts +77 -0
  44. package/{src/mcp → packages/mcp/src}/tools/render-ascii.ts +2 -2
  45. package/{src/mcp → packages/mcp/src}/tools/render-svg.ts +1 -1
  46. package/packages/mermaid-parser/LICENSE +22 -0
  47. package/{src → packages/mermaid-parser/src}/class/parser.ts +86 -8
  48. package/{src → packages/mermaid-parser/src}/er/parser.ts +59 -26
  49. package/packages/mermaid-parser/src/index.ts +48 -0
  50. package/{src → packages/mermaid-parser/src}/sequence/activation-check.ts +4 -3
  51. package/packages/mermaid-parser/src/sequence/activation-fix.ts +112 -0
  52. package/{src → packages/mermaid-parser/src}/sequence/parser.ts +74 -16
  53. package/{src → packages/mermaid-parser/src}/xychart/parser.ts +84 -6
  54. package/packages/svg-renderer/LICENSE +22 -0
  55. package/{src → packages/svg-renderer/src}/class/layout.ts +8 -4
  56. package/{src → packages/svg-renderer/src}/class/renderer.ts +7 -6
  57. package/packages/svg-renderer/src/edge-curves.ts +204 -0
  58. package/packages/svg-renderer/src/elk-instance.ts +292 -0
  59. package/{src → packages/svg-renderer/src}/er/layout.ts +6 -4
  60. package/{src → packages/svg-renderer/src}/er/renderer.ts +4 -4
  61. package/packages/svg-renderer/src/index.ts +58 -0
  62. package/packages/svg-renderer/src/layout-engine/constants.ts +19 -0
  63. package/packages/svg-renderer/src/layout-engine/edge-bundling.ts +379 -0
  64. package/packages/svg-renderer/src/layout-engine/elk-adapter-utils.ts +81 -0
  65. package/packages/svg-renderer/src/layout-engine/elk-graph-builder.ts +240 -0
  66. package/packages/svg-renderer/src/layout-engine/from-elk.ts +685 -0
  67. package/packages/svg-renderer/src/layout-engine/layer-alignment.ts +174 -0
  68. package/packages/svg-renderer/src/layout-engine/to-elk.ts +695 -0
  69. package/packages/svg-renderer/src/layout-engine.ts +74 -0
  70. package/packages/svg-renderer/src/layout.ts +8 -0
  71. package/packages/svg-renderer/src/renderer.ts +1485 -0
  72. package/packages/svg-renderer/src/resolve-colors.ts +339 -0
  73. package/{src → packages/svg-renderer/src}/sequence/layout.ts +2 -6
  74. package/{src → packages/svg-renderer/src}/sequence/renderer.ts +4 -4
  75. package/packages/svg-renderer/src/shape-clipping.ts +197 -0
  76. package/packages/svg-renderer/src/styles.ts +118 -0
  77. package/{src → packages/svg-renderer/src}/xychart/layout.ts +2 -2
  78. package/{src → packages/svg-renderer/src}/xychart/renderer.ts +6 -6
  79. package/src/__tests__/ascii-ansi.test.ts +6 -2
  80. package/src/__tests__/ascii-canvas-write.test.ts +2 -2
  81. package/src/__tests__/ascii-charset-border-junctions.test.ts +1 -1
  82. package/src/__tests__/ascii-cjk-width.test.ts +1 -1
  83. package/src/__tests__/ascii-class-box-occupancy.test.ts +147 -45
  84. package/src/__tests__/ascii-class-column-width-488-489.test.ts +1 -1
  85. package/src/__tests__/ascii-class-cross-level-jog-corruption.test.ts +187 -0
  86. package/src/__tests__/ascii-class-detour-label-routing-487.test.ts +1 -1
  87. package/src/__tests__/ascii-class-diagram-compartments.test.ts +1 -1
  88. package/src/__tests__/ascii-class-label-box-collision.test.ts +1 -1
  89. package/src/__tests__/ascii-class-label-row-collision-531.test.ts +1 -1
  90. package/src/__tests__/ascii-class-label-territory-row-awareness.test.ts +1 -1
  91. package/src/__tests__/ascii-class-padding.test.ts +1 -1
  92. package/src/__tests__/ascii-class-reciprocal-relationships-448.test.ts +86 -1
  93. package/src/__tests__/ascii-combining-mark-width.test.ts +2 -2
  94. package/src/__tests__/ascii-coords-overlay.test.ts +2 -2
  95. package/src/__tests__/ascii-decision-lr-box-start.test.ts +2 -2
  96. package/src/__tests__/ascii-display-width-unit.test.ts +1 -1
  97. package/src/__tests__/ascii-draw-arrows-coverage.test.ts +1 -1
  98. package/src/__tests__/ascii-draw-arrows-single-point-path.test.ts +12 -5
  99. package/src/__tests__/ascii-draw-bundles.test.ts +5 -5
  100. package/src/__tests__/ascii-edge-bundling-rank-violation-454.test.ts +1 -1
  101. package/src/__tests__/ascii-edge-bundling-routing.test.ts +9 -5
  102. package/src/__tests__/ascii-edge-cross-style-overlap.test.ts +10 -6
  103. package/src/__tests__/ascii-edge-ending-glyphs.test.ts +1 -1
  104. package/src/__tests__/ascii-edge-label-diagonal-fallback-418.test.ts +2 -2
  105. package/src/__tests__/ascii-edge-routing-fixes.test.ts +1 -1
  106. package/src/__tests__/ascii-edge-routing-single-point-path.test.ts +8 -4
  107. package/src/__tests__/ascii-edge-styles.test.ts +1 -1
  108. package/src/__tests__/ascii-emoji-cluster-width.test.ts +2 -2
  109. package/src/__tests__/ascii-er-box-occupancy.test.ts +1 -1
  110. package/src/__tests__/ascii-er-cardinality.test.ts +1 -1
  111. package/src/__tests__/ascii-er-corner-glyphs.test.ts +1 -1
  112. package/src/__tests__/ascii-er-jog-stray-line.test.ts +1 -1
  113. package/src/__tests__/ascii-er-label-padding.test.ts +1 -1
  114. package/src/__tests__/ascii-er-padding.test.ts +1 -1
  115. package/src/__tests__/ascii-er-relationship-label-corruption-350.test.ts +1 -1
  116. package/src/__tests__/ascii-er-relationship-overwrite.test.ts +1 -1
  117. package/src/__tests__/ascii-er-stray-connectors.test.ts +7 -4
  118. package/src/__tests__/ascii-er-unrelated-stem-separation-411.test.ts +1 -1
  119. package/src/__tests__/ascii-er-vertical-one-marker.test.ts +1 -1
  120. package/src/__tests__/ascii-grid-deep-chain.test.ts +4 -4
  121. package/src/__tests__/ascii-hyperlinks.test.ts +9 -6
  122. package/src/__tests__/ascii-label-line-terminal-fallback.test.ts +8 -4
  123. package/src/__tests__/ascii-lane-search.test.ts +1 -1
  124. package/src/__tests__/ascii-multibox-cjk-width.test.ts +1 -1
  125. package/src/__tests__/ascii-multiline.test.ts +6 -3
  126. package/src/__tests__/ascii-nested-subgraph-sibling-order-444.test.ts +8 -5
  127. package/src/__tests__/ascii-package-boundary.test.ts +47 -37
  128. package/src/__tests__/ascii-padding-edge-cases.test.ts +1 -1
  129. package/src/__tests__/ascii-parallel-edges-329.test.ts +8 -5
  130. package/src/__tests__/ascii-pathfinder-route-edge.test.ts +14 -4
  131. package/src/__tests__/ascii-reverse-direction-edges-629.test.ts +9 -5
  132. package/src/__tests__/ascii-sequence-alt-else-label.test.ts +1 -1
  133. package/src/__tests__/ascii-sequence-block-wall-clearance.test.ts +1 -1
  134. package/src/__tests__/ascii-sequence-box-group.test.ts +1 -1
  135. package/src/__tests__/ascii-sequence-cjk-width.test.ts +1 -1
  136. package/src/__tests__/ascii-sequence-create-destroy.test.ts +1 -1
  137. package/src/__tests__/ascii-sequence-form-invariants.test.ts +48 -50
  138. package/src/__tests__/ascii-sequence-mermaid-parity.test.ts +1 -1
  139. package/src/__tests__/ascii-sequence-notes.test.ts +1 -1
  140. package/src/__tests__/ascii-sequence-padding.test.ts +1 -1
  141. package/src/__tests__/ascii-sequence-self-arrow.test.ts +1 -1
  142. package/src/__tests__/ascii-shape-diamond.test.ts +3 -3
  143. package/src/__tests__/ascii-shape-rectangle.test.ts +5 -5
  144. package/src/__tests__/ascii-shape-rounded.test.ts +3 -3
  145. package/src/__tests__/ascii-shapes-circle.test.ts +3 -3
  146. package/src/__tests__/ascii-shapes-hexagon.test.ts +3 -3
  147. package/src/__tests__/ascii-shapes-special.test.ts +10 -5
  148. package/src/__tests__/ascii-shapes-stadium.test.ts +3 -3
  149. package/src/__tests__/ascii-shapes-state.test.ts +15 -5
  150. package/src/__tests__/ascii-state-bidirectional-label-swap-530.test.ts +1 -1
  151. package/src/__tests__/ascii-subgraph-bbox.test.ts +8 -4
  152. package/src/__tests__/ascii-subgraph-direction-honored-445.test.ts +1 -1
  153. package/src/__tests__/ascii-subgraph-label-border-clip.test.ts +88 -25
  154. package/src/__tests__/ascii-subgraph-later-edgeless-301.test.ts +8 -4
  155. package/src/__tests__/ascii-subgraph-title-padding.test.ts +1 -1
  156. package/src/__tests__/ascii-territory-unit.test.ts +2 -2
  157. package/src/__tests__/ascii-validate.test.ts +1 -1
  158. package/src/__tests__/ascii.test.ts +5 -2
  159. package/src/__tests__/box-color.test.ts +1 -1
  160. package/src/__tests__/class-arrow-directions.test.ts +1 -1
  161. package/src/__tests__/class-click-interactions.test.ts +3 -6
  162. package/src/__tests__/class-integration.test.ts +1 -1
  163. package/src/__tests__/class-notes.test.ts +3 -3
  164. package/src/__tests__/class-parser-error-messages.test.ts +162 -0
  165. package/src/__tests__/class-parser.test.ts +6 -6
  166. package/src/__tests__/class-styling.test.ts +2 -2
  167. package/src/__tests__/cli-render.test.ts +16 -1
  168. package/src/__tests__/direction-override.test.ts +1 -1
  169. package/src/__tests__/draw-lines.test.ts +2 -2
  170. package/src/__tests__/edge-cell-styles.test.ts +5 -2
  171. package/src/__tests__/elk-layout-cache.test.ts +6 -4
  172. package/src/__tests__/er-click-unsupported.test.ts +5 -8
  173. package/src/__tests__/er-parser-error-messages.test.ts +82 -0
  174. package/src/__tests__/er-parser.test.ts +3 -6
  175. package/src/__tests__/expanded-shapes.test.ts +1 -1
  176. package/src/__tests__/grid-occupancy.test.ts +1 -1
  177. package/src/__tests__/mcp-check-sequence-activations-tool.test.ts +1 -1
  178. package/src/__tests__/mcp-fix-sequence-activations-tool.test.ts +134 -0
  179. package/src/__tests__/mcp-render-ascii-tool.test.ts +5 -4
  180. package/src/__tests__/mcp-render-svg-theme-guard.test.ts +3 -3
  181. package/src/__tests__/mcp-render-svg-tool.test.ts +1 -1
  182. package/src/__tests__/mcp-server.test.ts +30 -1
  183. package/src/__tests__/multiline-labels.test.ts +29 -18
  184. package/src/__tests__/parser.test.ts +117 -5
  185. package/src/__tests__/pathfinder.test.ts +11 -4
  186. package/src/__tests__/render-options-overrides.test.ts +28 -21
  187. package/src/__tests__/render-options-per-type.test.ts +13 -9
  188. package/src/__tests__/resolve-colors.test.ts +1 -1
  189. package/src/__tests__/self-loop-edges.test.ts +112 -0
  190. package/src/__tests__/sequence-activation-check.test.ts +7 -8
  191. package/src/__tests__/sequence-activation-fix.test.ts +91 -0
  192. package/src/__tests__/sequence-layout.test.ts +4 -7
  193. package/src/__tests__/sequence-parser-syntax-errors.test.ts +179 -0
  194. package/src/__tests__/sequence-parser.test.ts +3 -6
  195. package/src/__tests__/statements.test.ts +53 -35
  196. package/src/__tests__/testdata/ascii/subgraph_direction_override.txt +21 -21
  197. package/src/__tests__/theme-palette-contrast.test.ts +84 -0
  198. package/src/__tests__/workspace-package-boundaries.test.ts +199 -25
  199. package/src/__tests__/xychart-ascii.test.ts +1 -1
  200. package/src/__tests__/xychart-colors.test.ts +3 -2
  201. package/src/__tests__/xychart-layout.test.ts +1 -1
  202. package/src/__tests__/xychart-parser-errors.test.ts +95 -0
  203. package/src/browser.ts +8 -2
  204. package/src/cli/mcp.ts +1 -1
  205. package/src/cli/render.ts +59 -25
  206. package/src/cli/web.ts +1 -1
  207. package/src/diagram-registry.ts +190 -25
  208. package/src/index.ts +34 -131
  209. package/src/parser.ts +102 -40
  210. /package/{src/ascii → packages/ascii-renderer/src}/ansi.ts +0 -0
  211. /package/{src/ascii → packages/ascii-renderer/src}/canvas.ts +0 -0
  212. /package/{src/ascii → packages/ascii-renderer/src}/converter.ts +0 -0
  213. /package/{src/ascii → packages/ascii-renderer/src}/coords.ts +0 -0
  214. /package/{src/ascii → packages/ascii-renderer/src}/display-width.ts +0 -0
  215. /package/{src/ascii → packages/ascii-renderer/src}/draw-arrows.ts +0 -0
  216. /package/{src/ascii → packages/ascii-renderer/src}/draw-boxes.ts +0 -0
  217. /package/{src/ascii → packages/ascii-renderer/src}/draw-bundles.ts +0 -0
  218. /package/{src/ascii → packages/ascii-renderer/src}/draw-lines.ts +0 -0
  219. /package/{src/ascii → packages/ascii-renderer/src}/draw-subgraphs.ts +0 -0
  220. /package/{src/ascii → packages/ascii-renderer/src}/draw.ts +0 -0
  221. /package/{src/ascii → packages/ascii-renderer/src}/edge-bundling.ts +0 -0
  222. /package/{src/ascii → packages/ascii-renderer/src}/edge-cell-styles.ts +0 -0
  223. /package/{src/ascii → packages/ascii-renderer/src}/edge-routing.ts +0 -0
  224. /package/{src/ascii → packages/ascii-renderer/src}/grid-occupancy.ts +0 -0
  225. /package/{src/ascii → packages/ascii-renderer/src}/hyperlinks.ts +0 -0
  226. /package/{src/ascii → packages/ascii-renderer/src}/lane-search.ts +0 -0
  227. /package/{src/ascii → packages/ascii-renderer/src}/multiline-utils.ts +0 -0
  228. /package/{src/ascii → packages/ascii-renderer/src}/pathfinder.ts +0 -0
  229. /package/{src/ascii → packages/ascii-renderer/src}/shapes/circle.ts +0 -0
  230. /package/{src/ascii → packages/ascii-renderer/src}/shapes/corners.ts +0 -0
  231. /package/{src/ascii → packages/ascii-renderer/src}/shapes/diamond.ts +0 -0
  232. /package/{src/ascii → packages/ascii-renderer/src}/shapes/hexagon.ts +0 -0
  233. /package/{src/ascii → packages/ascii-renderer/src}/shapes/index.ts +0 -0
  234. /package/{src/ascii → packages/ascii-renderer/src}/shapes/rectangle.ts +0 -0
  235. /package/{src/ascii → packages/ascii-renderer/src}/shapes/rounded.ts +0 -0
  236. /package/{src/ascii → packages/ascii-renderer/src}/shapes/special.ts +0 -0
  237. /package/{src/ascii → packages/ascii-renderer/src}/shapes/stadium.ts +0 -0
  238. /package/{src/ascii → packages/ascii-renderer/src}/shapes/state.ts +0 -0
  239. /package/{src/ascii → packages/ascii-renderer/src}/shapes/types.ts +0 -0
  240. /package/{src/ascii → packages/ascii-renderer/src}/territory.ts +0 -0
  241. /package/{src/ascii → packages/ascii-renderer/src}/types.ts +0 -0
  242. /package/{src/ascii → packages/ascii-renderer/src}/validate.ts +0 -0
  243. /package/{src/mcp → packages/mcp/src}/index.ts +0 -0
  244. /package/{src → packages/mermaid-parser/src}/class/format.ts +0 -0
  245. /package/{src → packages/mermaid-parser/src}/class/types.ts +0 -0
  246. /package/{src → packages/mermaid-parser/src}/er/types.ts +0 -0
  247. /package/{src → packages/mermaid-parser/src}/expanded-shapes.ts +0 -0
  248. /package/{src → packages/mermaid-parser/src}/sequence/box-color.ts +0 -0
  249. /package/{src → packages/mermaid-parser/src}/sequence/types.ts +0 -0
  250. /package/{src → packages/mermaid-parser/src}/xychart/colors.ts +0 -0
  251. /package/{src → packages/mermaid-parser/src}/xychart/types.ts +0 -0
@@ -18,7 +18,7 @@
18
18
  * migrated call site relies on `write()`'s inclusive bounds).
19
19
  */
20
20
  import { describe, it, expect } from 'vitest'
21
- import { renderMermaidASCII } from '../ascii/index.ts'
21
+ import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
22
22
 
23
23
  describe('ASCII subgraph label does not overwrite its own border', () => {
24
24
  it('leaves the right border intact when the title is wider than the interior', () => {
@@ -30,9 +30,12 @@ describe('ASCII subgraph label does not overwrite its own border', () => {
30
30
  const titleLine = output.split('\n').find((l) => l.includes('AAAAAAA'))
31
31
  expect(titleLine).toBeDefined()
32
32
 
33
- // The title is truncated by the box's fixed width, but the row must
34
- // still open and close with the border — never spill onto/through it.
35
- expect(titleLine).toBe('│AAAAAAA│')
33
+ // The box is now widened to fit the title in full (issue #632 — a
34
+ // subgraph label used to be truncated to whatever width the child
35
+ // nodes happened to need, dropping trailing characters), so the title
36
+ // is no longer clipped at all — but the row must still open and close
37
+ // with the border — never spill onto/through it.
38
+ expect(titleLine).toBe('│AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA│')
36
39
  expect(titleLine?.startsWith('│')).toBe(true)
37
40
  expect(titleLine?.endsWith('│')).toBe(true)
38
41
  })
@@ -62,28 +65,88 @@ describe('ASCII subgraph label does not overwrite its own border', () => {
62
65
  const output = renderMermaidASCII(mermaid)
63
66
  expect(output).toBe(
64
67
  [
65
- '┌───────┐',
66
- '│AAAAAAA│',
67
- '│ │',
68
- '│ │',
69
- '│ ┌───┐ │',
70
- '│ │ │ │',
71
- '│ │ a │ │',
72
- '│ │ │ │',
73
- '│ └─┬─┘ │',
74
- '│ │ │',
75
- '│ │ │',
76
- '│ │ │',
77
- '│ │ │',
78
- '│ ▼ │',
79
- '│ ┌───┐ │',
80
- '│ │ │ │',
81
- '│ │ b │ │',
82
- '│ │ │ │',
83
- '│ └───┘ │',
84
- '│ │',
85
- '└───────┘',
68
+ '┌──────────────────────────────────┐',
69
+ '│AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA│',
70
+ '│ │',
71
+ '│ │',
72
+ '│ ┌───┐ │',
73
+ '│ │ │ │',
74
+ '│ │ a │ │',
75
+ '│ │ │ │',
76
+ '│ └─┬─┘ │',
77
+ '│ │ │',
78
+ '│ │ │',
79
+ '│ │ │',
80
+ '│ │ │',
81
+ '│ ▼ │',
82
+ '│ ┌───┐ │',
83
+ '│ │ │ │',
84
+ '│ │ b │ │',
85
+ '│ │ │ │',
86
+ '│ └───┘ │',
87
+ '│ │',
88
+ '└──────────────────────────────────┘',
86
89
  ].join('\n'),
87
90
  )
88
91
  })
89
92
  })
93
+
94
+ /**
95
+ * Regression test for issue #632 — a weekly form-judge audit comparing this
96
+ * repo's ASCII output against real mermaid.js SVG flagged the "Subgraph
97
+ * Direction Override" sample (samples-data.ts): the cluster label
98
+ * "Processing Pipeline" was truncated mid-word to "Processing Pipe" even
99
+ * though the source SVG's cluster rect is wide enough to fit the full label
100
+ * untruncated.
101
+ *
102
+ * Root cause: `calculateSubgraphBoundingBox` (src/ascii/grid.ts) sized a
103
+ * subgraph's box purely from its child nodes' positions — it never
104
+ * considered the width of the subgraph's own label — so a label longer
105
+ * than the widest child node's contribution to the box got silently
106
+ * clipped by `drawSubgraphLabel`'s own border-safety guard (the fix
107
+ * verified by the tests above, which correctly stops a label from spilling
108
+ * onto the border, but does nothing to stop it needing to spill in the
109
+ * first place). The fix widens the box, symmetrically, whenever the
110
+ * label's own display width would otherwise exceed the interior the child
111
+ * nodes alone would have produced.
112
+ */
113
+ describe('ASCII subgraph label does not truncate a label wider than its child nodes (issue #632)', () => {
114
+ it('renders "Processing Pipeline" in full instead of truncating it to "Processing Pipe"', () => {
115
+ const mermaid = `graph TD
116
+ subgraph pipeline [Processing Pipeline]
117
+ direction LR
118
+ A[Input] --> B[Parse] --> C[Transform] --> D[Output]
119
+ end
120
+ E[Source] --> A
121
+ D --> F[Sink]`
122
+ const output = renderMermaidASCII(mermaid)
123
+
124
+ expect(output).toContain('Processing Pipeline')
125
+ expect(output).not.toContain('Processing Pipe│')
126
+ expect(output).not.toContain('Processing Pipe\n')
127
+
128
+ const titleLine = output
129
+ .split('\n')
130
+ .find((l) => l.includes('Processing Pipeline'))
131
+ expect(titleLine).toBeDefined()
132
+ // The label sits fully inside the border on both sides, not clipped
133
+ // against (or spilling onto) either one.
134
+ expect(titleLine?.startsWith('│')).toBe(true)
135
+ expect(titleLine?.endsWith('│')).toBe(true)
136
+ })
137
+
138
+ it('still truncates a title far wider than any reasonable box only via the border-safety clip, never mid-word from box sizing alone', () => {
139
+ // A label wider than the box has room for, on a subgraph with only a
140
+ // single tiny child node — the box widens to fit the label (this
141
+ // issue's fix), so nothing is truncated by width sizing; the only
142
+ // remaining clip source is `drawSubgraphLabel`'s own border guard
143
+ // (covered by the tests above), which only fires for pathological
144
+ // cases (e.g. a taller-than-tall label) that this fix does not touch.
145
+ const mermaid = `flowchart TD
146
+ subgraph ONE["A Rather Long Subgraph Title Here"]
147
+ A1["a"]
148
+ end`
149
+ const output = renderMermaidASCII(mermaid)
150
+ expect(output).toContain('A Rather Long Subgraph Title Here')
151
+ })
152
+ })
@@ -29,10 +29,14 @@
29
29
  */
30
30
  import { describe, it, expect } from 'vitest'
31
31
  import { parseMermaid } from '../parser.ts'
32
- import { convertToAsciiGraph } from '../ascii/converter.ts'
33
- import { createMapping } from '../ascii/grid.ts'
34
- import { renderMermaidASCII } from '../ascii/index.ts'
35
- import type { AsciiConfig, AsciiGraph, AsciiSubgraph } from '../ascii/types.ts'
32
+ import { convertToAsciiGraph } from '../../packages/ascii-renderer/src/converter.ts'
33
+ import { createMapping } from '../../packages/ascii-renderer/src/grid.ts'
34
+ import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
35
+ import type {
36
+ AsciiConfig,
37
+ AsciiGraph,
38
+ AsciiSubgraph,
39
+ } from '../../packages/ascii-renderer/src/types.ts'
36
40
 
37
41
  function layoutFlowchart(
38
42
  source: string,
@@ -15,7 +15,7 @@
15
15
  * space of left padding whenever the box has any slack at all.
16
16
  */
17
17
  import { describe, it, expect } from 'vitest'
18
- import { renderMermaidASCII } from '../ascii/index.ts'
18
+ import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
19
19
 
20
20
  /** Find the line containing a subgraph's rendered title text. */
21
21
  function findTitleLine(output: string, title: string): string {
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from 'vitest'
2
- import { allocateTerritory } from '../ascii/territory.ts'
3
- import type { TerritoryGeometry } from '../ascii/territory.ts'
2
+ import { allocateTerritory } from '../../packages/ascii-renderer/src/territory.ts'
3
+ import type { TerritoryGeometry } from '../../packages/ascii-renderer/src/territory.ts'
4
4
 
5
5
  /**
6
6
  * Test items carry their own geometry so the accessor stays trivial and the
@@ -11,7 +11,7 @@ import {
11
11
  assertNoDiagonals,
12
12
  findOrphanedJunctions,
13
13
  hasOrphanedJunctions,
14
- } from '../ascii/validate.ts'
14
+ } from '../../packages/ascii-renderer/src/validate.ts'
15
15
 
16
16
  describe('DIAGONAL_CHARS', () => {
17
17
  it('exposes ascii, unicode, and combined character sets', () => {
@@ -8,8 +8,11 @@
8
8
  * Test data: 44 ASCII files + 22 Unicode files = 66 total.
9
9
  */
10
10
  import { describe, it, expect } from 'vitest'
11
- import { renderMermaidASCII } from '../ascii/index.ts'
12
- import { hasDiagonalLines, DIAGONAL_CHARS } from '../ascii/validate.ts'
11
+ import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
12
+ import {
13
+ hasDiagonalLines,
14
+ DIAGONAL_CHARS,
15
+ } from '../../packages/ascii-renderer/src/validate.ts'
13
16
  import { readdirSync, readFileSync } from 'node:fs'
14
17
  import { join, dirname } from 'node:path'
15
18
  import { fileURLToPath } from 'node:url'
@@ -6,7 +6,7 @@
6
6
  * whole header is the label. `transparent` is treated as "no colour".
7
7
  */
8
8
  import { describe, it, expect } from 'vitest'
9
- import { isCssColor, parseBoxHeader } from '../sequence/box-color.ts'
9
+ import { isCssColor, parseBoxHeader } from '@zombie-mermaid/mermaid-parser'
10
10
 
11
11
  describe('isCssColor', () => {
12
12
  it('recognizes CSS named colours, case-insensitively', () => {
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { describe, test, expect } from 'vitest'
11
- import { renderMermaidASCII } from '../ascii/index.ts'
11
+ import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
12
12
 
13
13
  describe('Class Diagram Arrow Directions', () => {
14
14
  // ============================================================================
@@ -10,16 +10,13 @@
10
10
  * src/__tests__/er-click-unsupported.test.ts for that side of issue #292.
11
11
  */
12
12
  import { describe, it, expect } from 'vitest'
13
- import { parseClassDiagram } from '../class/parser.ts'
13
+ import { parseClassDiagram } from '@zombie-mermaid/mermaid-parser'
14
+ import { splitStatements } from '@zombie-mermaid/core'
14
15
  import { renderMermaidSVG } from '../index.ts'
15
16
 
16
17
  /** Helper to parse — preprocesses text the same way index.ts does */
17
18
  function parse(text: string) {
18
- const lines = text
19
- .split('\n')
20
- .map((l) => l.trim())
21
- .filter((l) => l.length > 0 && !l.startsWith('%%'))
22
- return parseClassDiagram(lines)
19
+ return parseClassDiagram(splitStatements(text))
23
20
  }
24
21
 
25
22
  describe('parseClassDiagram – click interactions', () => {
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { describe, it, expect } from 'vitest'
5
5
  import { renderMermaidSVG } from '../index.ts'
6
- import { renderMermaidASCII } from '../ascii/index.ts'
6
+ import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
7
7
 
8
8
  describe('renderMermaidSVG – class diagrams', () => {
9
9
  it('renders a basic class diagram to valid SVG', () => {
@@ -8,10 +8,10 @@
8
8
  * directly beside its class with a dashed connector.
9
9
  */
10
10
  import { describe, it, expect } from 'vitest'
11
- import { parseClassDiagram } from '../class/parser.ts'
12
- import { layoutClassDiagramSync } from '../class/layout.ts'
11
+ import { parseClassDiagram } from '@zombie-mermaid/mermaid-parser'
12
+ import { layoutClassDiagramSync } from '@zombie-mermaid/svg-renderer'
13
13
  import { renderMermaidSVG } from '../index.ts'
14
- import { renderMermaidASCII } from '../ascii/index.ts'
14
+ import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
15
15
  import { splitStatements } from '@zombie-mermaid/core'
16
16
 
17
17
  /** Parse through the same statement splitter index.ts uses. */
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Error-message quality tests for the class diagram parser (issue #761).
3
+ *
4
+ * Before this pass, `parseClassDiagram` had zero `throw`/`Error` statements
5
+ * in the entire file — the only one of the five diagram parsers still in
6
+ * that state after #722 added targeted throws to ER and xychart-beta. A
7
+ * dangling relationship arrow with no target, an unclosed `class X { ... }`
8
+ * body, an unclosed method-signature paren, or a malformed `<<...>>`
9
+ * annotation were all silently dropped or silently mis-parsed, with no
10
+ * indication anything was wrong. These tests assert the parser now throws
11
+ * an actionable error instead, while confirming valid syntax and genuinely
12
+ * unrelated lines are unaffected — mirroring #722's "attempt, then
13
+ * validate" scoping so a line with no relationship-arrow token at all still
14
+ * falls through silently.
15
+ */
16
+ import { describe, it, expect } from 'vitest'
17
+ import { parseClassDiagram } from '@zombie-mermaid/mermaid-parser'
18
+ import { splitStatements } from '@zombie-mermaid/core'
19
+
20
+ function parse(text: string) {
21
+ return parseClassDiagram(splitStatements(text))
22
+ }
23
+
24
+ describe('parseClassDiagram – malformed relationships', () => {
25
+ it('throws on a dangling relationship arrow with no target', () => {
26
+ expect(() =>
27
+ parse(`classDiagram
28
+ Animal <|--`),
29
+ ).toThrow(/Malformed class-diagram relationship "Animal <\|--"/)
30
+ })
31
+
32
+ it('throws on an unsupported/garbage arrow shape', () => {
33
+ expect(() =>
34
+ parse(`classDiagram
35
+ Animal <>-- Dog`),
36
+ ).toThrow(/Malformed class-diagram relationship/)
37
+ })
38
+
39
+ it('mentions the valid arrow tokens in the message', () => {
40
+ expect(() =>
41
+ parse(`classDiagram
42
+ Animal <|--`),
43
+ ).toThrow(/<\|--, <\|\.\., \*--, o--, -->/)
44
+ })
45
+
46
+ it('reports the 1-based line number', () => {
47
+ expect(() =>
48
+ parse(`classDiagram
49
+ class Animal
50
+ Animal <|--`),
51
+ ).toThrow(/^Line 3:/)
52
+ })
53
+ })
54
+
55
+ describe('parseClassDiagram – unclosed class body', () => {
56
+ it('throws when a class block is never closed before the diagram ends', () => {
57
+ expect(() =>
58
+ parse(`classDiagram
59
+ class Animal {
60
+ +String name`),
61
+ ).toThrow(/Unclosed class body for "Animal"/)
62
+ })
63
+
64
+ it('reports the line the class body opened on, not where the diagram ends', () => {
65
+ expect(() =>
66
+ parse(`classDiagram
67
+ class Animal {
68
+ +String name
69
+ +eat() void`),
70
+ ).toThrow(/^Line 2:/)
71
+ })
72
+ })
73
+
74
+ describe('parseClassDiagram – malformed method signatures', () => {
75
+ it('throws on an unclosed "(" in a method signature', () => {
76
+ expect(() =>
77
+ parse(`classDiagram
78
+ class Animal {
79
+ +eat(void
80
+ }`),
81
+ ).toThrow(/unclosed "\(" in a method signature/)
82
+ })
83
+
84
+ it('throws for the same case on a single-line inline body', () => {
85
+ expect(() => parse('classDiagram\nclass Animal { +eat(void }')).toThrow(
86
+ /unclosed "\(" in a method signature/,
87
+ )
88
+ })
89
+ })
90
+
91
+ describe('parseClassDiagram – malformed annotations', () => {
92
+ it('throws on a multi-line-body annotation missing its closing ">>"', () => {
93
+ expect(() =>
94
+ parse(`classDiagram
95
+ class Shape {
96
+ <<abstract
97
+ }`),
98
+ ).toThrow(/Malformed class annotation "<<abstract"/)
99
+ })
100
+
101
+ it('throws on an inline-body annotation missing its closing ">>"', () => {
102
+ expect(() => parse('classDiagram\nclass Shape { <<abstract }')).toThrow(
103
+ /Malformed class annotation/,
104
+ )
105
+ })
106
+ })
107
+
108
+ describe('parseClassDiagram – valid syntax is unaffected', () => {
109
+ it('still parses a fully valid relationship without throwing', () => {
110
+ const d = parse(`classDiagram
111
+ Animal <|-- Dog`)
112
+ expect(d.relationships).toHaveLength(1)
113
+ expect(d.classes.map((c) => c.id).sort()).toEqual(['Animal', 'Dog'])
114
+ })
115
+
116
+ it('still parses a closed class body with a valid method and annotation', () => {
117
+ const d = parse(`classDiagram
118
+ class Animal {
119
+ <<abstract>>
120
+ +String name
121
+ +eat() void
122
+ }`)
123
+ const animal = d.classes.find((c) => c.id === 'Animal')!
124
+ expect(animal.annotation).toBe('abstract')
125
+ expect(animal.attributes).toHaveLength(1)
126
+ expect(animal.methods).toHaveLength(1)
127
+ })
128
+
129
+ it('still parses a valid inline single-line body', () => {
130
+ const d = parse('classDiagram\nclass Shape { <<interface>> }')
131
+ expect(d.classes[0]!.annotation).toBe('interface')
132
+ })
133
+
134
+ it('still parses a valid cardinality + label relationship', () => {
135
+ const d = parse('classDiagram\nA "1" --> "*" B : uses')
136
+ expect(d.relationships[0]!.fromCardinality).toBe('1')
137
+ expect(d.relationships[0]!.toCardinality).toBe('*')
138
+ expect(d.relationships[0]!.label).toBe('uses')
139
+ })
140
+
141
+ it('does not throw for a standalone class declaration with no relationship or body', () => {
142
+ expect(() =>
143
+ parse(`classDiagram
144
+ class Animal`),
145
+ ).not.toThrow()
146
+ })
147
+
148
+ it('does not throw for a click line (no arrow token present)', () => {
149
+ expect(() =>
150
+ parse(`classDiagram
151
+ class Animal
152
+ click Animal "https://example.com" "Tooltip"`),
153
+ ).not.toThrow()
154
+ })
155
+
156
+ it('does not throw for a valid inline attribute with a method-shaped default value', () => {
157
+ expect(() =>
158
+ parse(`classDiagram
159
+ Animal : +String name`),
160
+ ).not.toThrow()
161
+ })
162
+ })
@@ -5,15 +5,15 @@
5
5
  * relationships (all 6 types), cardinality, labels, inline attributes.
6
6
  */
7
7
  import { describe, it, expect } from 'vitest'
8
- import { parseClassDiagram, parseGenericTypes } from '../class/parser.ts'
8
+ import {
9
+ parseClassDiagram,
10
+ parseGenericTypes,
11
+ } from '@zombie-mermaid/mermaid-parser'
12
+ import { splitStatements } from '@zombie-mermaid/core'
9
13
 
10
14
  /** Helper to parse — preprocesses text the same way index.ts does */
11
15
  function parse(text: string) {
12
- const lines = text
13
- .split('\n')
14
- .map((l) => l.trim())
15
- .filter((l) => l.length > 0 && !l.startsWith('%%'))
16
- return parseClassDiagram(lines)
16
+ return parseClassDiagram(splitStatements(text))
17
17
  }
18
18
 
19
19
  // ============================================================================
@@ -9,10 +9,10 @@
9
9
  * flowchart ASCII renderer does.
10
10
  */
11
11
  import { describe, it, expect } from 'vitest'
12
- import { parseClassDiagram } from '../class/parser.ts'
12
+ import { parseClassDiagram } from '@zombie-mermaid/mermaid-parser'
13
13
  import { parseMermaid } from '../parser.ts'
14
14
  import { renderMermaidSVG } from '../index.ts'
15
- import { renderMermaidASCII } from '../ascii/index.ts'
15
+ import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
16
16
 
17
17
  /**
18
18
  * Escape regex metacharacters so a value can be interpolated into a
@@ -6,7 +6,7 @@ import { runRender } from '../cli/render.ts'
6
6
  import { renderPng } from '../cli/png.ts'
7
7
  import { renderMermaidSVG } from '../index.ts'
8
8
  import { THEMES } from '@zombie-mermaid/core'
9
- import { displayWidth } from '../ascii/display-width.ts'
9
+ import { displayWidth } from '../../packages/ascii-renderer/src/display-width.ts'
10
10
  import { createMockStdout, renderArgs } from './cli-test-helpers.ts'
11
11
 
12
12
  // ============================================================================
@@ -312,6 +312,21 @@ describe('runRender – output ergonomics', () => {
312
312
  expect(mockStdout.output()).not.toContain('<svg')
313
313
  expect(await readFile(outputPath, 'utf-8')).toContain('<svg')
314
314
  })
315
+
316
+ it('writes nothing when a format resolves to neither stdout nor a file', async () => {
317
+ // Not reachable via the real CLI — parse-args always derives a file path
318
+ // or the stdout marker for --svg/--html/--png (see RenderArgs.output's
319
+ // doc comment) — but runRender doesn't itself enforce that invariant, so
320
+ // construct the args directly to exercise resolveTarget/emit's `skip`
321
+ // branch rather than leaving it untested dead code.
322
+ const inputPath = join(tmpDir, 'diagram.mmd')
323
+ await writeFile(inputPath, SIMPLE_FLOWCHART)
324
+
325
+ const mockStdout = createMockStdout()
326
+ await runRender(renderArgs({ input: inputPath, svg: true }), mockStdout)
327
+
328
+ expect(mockStdout.output()).toBe('')
329
+ })
315
330
  })
316
331
 
317
332
  // ============================================================================
@@ -21,7 +21,7 @@ import { describe, it, expect } from 'vitest'
21
21
  import { renderMermaidSVG, renderMermaidASCII, parseMermaid } from '../index.ts'
22
22
  import { withDirectionOverride } from '@zombie-mermaid/core'
23
23
  import type { Direction, RenderOptions } from '@zombie-mermaid/core'
24
- import type { AsciiRenderOptions } from '../ascii/index.ts'
24
+ import type { AsciiRenderOptions } from '@zombie-mermaid/ascii-renderer'
25
25
 
26
26
  /**
27
27
  * Top-left corner of the first `<rect>` inside the `<g class="{cls}"
@@ -13,8 +13,8 @@
13
13
  * directly with hand-built inputs rather than through a full render.
14
14
  */
15
15
  import { describe, it, expect } from 'vitest'
16
- import { drawLine } from '../ascii/draw-lines.ts'
17
- import { mkCanvas } from '../ascii/canvas.ts'
16
+ import { drawLine } from '../../packages/ascii-renderer/src/draw-lines.ts'
17
+ import { mkCanvas } from '../../packages/ascii-renderer/src/canvas.ts'
18
18
 
19
19
  describe('drawLine diagonal routing', () => {
20
20
  it('routes UpperLeft as a horizontal-then-vertical bend', () => {
@@ -3,8 +3,11 @@ import {
3
3
  createEdgeCellStyles,
4
4
  findStyleConflict,
5
5
  claimPathCells,
6
- } from '../ascii/edge-cell-styles.ts'
7
- import { createGrid, placeBlock } from '../ascii/grid-occupancy.ts'
6
+ } from '../../packages/ascii-renderer/src/edge-cell-styles.ts'
7
+ import {
8
+ createGrid,
9
+ placeBlock,
10
+ } from '../../packages/ascii-renderer/src/grid-occupancy.ts'
8
11
 
9
12
  describe('edge-cell-styles', () => {
10
13
  it('reports no conflict against an empty map', () => {
@@ -13,17 +13,19 @@
13
13
  */
14
14
  import { describe, it, expect } from 'vitest'
15
15
  import { parseMermaid } from '../parser.ts'
16
- import { parseClassDiagram } from '../class/parser.ts'
17
- import { parseErDiagram } from '../er/parser.ts'
16
+ import {
17
+ parseClassDiagram,
18
+ parseErDiagram,
19
+ } from '@zombie-mermaid/mermaid-parser'
18
20
  import { splitStatements } from '@zombie-mermaid/core'
19
21
  import {
20
22
  convertToElkFormat,
21
23
  layoutGraphSync,
22
24
  elkLayoutSync,
23
25
  createLayoutCache,
26
+ layoutClassDiagramSync,
27
+ layoutErDiagramSync,
24
28
  } from '@zombie-mermaid/svg-renderer'
25
- import { layoutClassDiagramSync } from '../class/layout.ts'
26
- import { layoutErDiagramSync } from '../er/layout.ts'
27
29
  import { renderMermaidSVG } from '../index.ts'
28
30
 
29
31
  describe('elkLayoutSync layout cache', () => {
@@ -3,8 +3,8 @@
3
3
  * unimplemented (issue #292).
4
4
  *
5
5
  * Unlike flowcharts/state diagrams (src/parser.ts) and class diagrams
6
- * (src/class/parser.ts, added alongside this file — see
7
- * class-click-interactions.test.ts), Mermaid's *own* erDiagram grammar has
6
+ * (packages/mermaid-parser/src/class/parser.ts, added alongside this file —
7
+ * see class-click-interactions.test.ts), Mermaid's *own* erDiagram grammar has
8
8
  * no `click` directive to parse: as of this writing, upstream support is an
9
9
  * open, unmerged feature request (mermaid-js/mermaid#2880, PR #6985), not a
10
10
  * shipped part of the language. Guessing at a syntax Mermaid itself hasn't
@@ -18,16 +18,13 @@
18
18
  * neither renders a link/tooltip nor corrupts the rest of the diagram.
19
19
  */
20
20
  import { describe, it, expect } from 'vitest'
21
- import { parseErDiagram } from '../er/parser.ts'
21
+ import { parseErDiagram } from '@zombie-mermaid/mermaid-parser'
22
+ import { splitStatements } from '@zombie-mermaid/core'
22
23
  import { renderMermaidSVG } from '../index.ts'
23
24
 
24
25
  /** Helper to parse — preprocesses text the same way index.ts does */
25
26
  function parse(text: string) {
26
- const lines = text
27
- .split('\n')
28
- .map((l) => l.trim())
29
- .filter((l) => l.length > 0 && !l.startsWith('%%'))
30
- return parseErDiagram(lines)
27
+ return parseErDiagram(splitStatements(text))
31
28
  }
32
29
 
33
30
  describe('parseErDiagram – click statements are a no-op', () => {