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
@@ -13,8 +13,9 @@
13
13
  */
14
14
  import { describe, it, expect } from 'vitest'
15
15
  import { renderMermaidSVG } from '../index.ts'
16
- import { layoutSequenceDiagram } from '../sequence/layout.ts'
17
- import { parseSequenceDiagram } from '../sequence/parser.ts'
16
+ import { layoutSequenceDiagram } from '@zombie-mermaid/svg-renderer'
17
+ import { parseSequenceDiagram } from '@zombie-mermaid/mermaid-parser'
18
+ import { splitStatements } from '@zombie-mermaid/core'
18
19
 
19
20
  describe('RenderOptions.fontSizes – flowchart', () => {
20
21
  it('overriding nodeLabel changes the emitted node font-size', () => {
@@ -87,7 +88,9 @@ describe('RenderOptions.fontSizes – sequence diagram', () => {
87
88
 
88
89
  describe('RenderOptions.sequence – layout overrides', () => {
89
90
  it('overriding actorHeight changes actor box height and overall diagram height', () => {
90
- const lines = ['sequenceDiagram', 'Alice->>Bob: Hi'].map((l) => l.trim())
91
+ const lines = splitStatements(
92
+ ['sequenceDiagram', 'Alice->>Bob: Hi'].join('\n'),
93
+ )
91
94
  const diagram = parseSequenceDiagram(lines)
92
95
 
93
96
  const withDefault = layoutSequenceDiagram(diagram)
@@ -115,11 +118,9 @@ describe('RenderOptions.sequence – layout overrides', () => {
115
118
  })
116
119
 
117
120
  it('overriding messageRowHeight increases vertical spacing between messages', () => {
118
- const lines = [
119
- 'sequenceDiagram',
120
- 'Alice->>Bob: One',
121
- 'Bob->>Alice: Two',
122
- ].map((l) => l.trim())
121
+ const lines = splitStatements(
122
+ ['sequenceDiagram', 'Alice->>Bob: One', 'Bob->>Alice: Two'].join('\n'),
123
+ )
123
124
  const diagram = parseSequenceDiagram(lines)
124
125
 
125
126
  const withDefault = layoutSequenceDiagram(diagram)
@@ -135,7 +136,9 @@ describe('RenderOptions.sequence – layout overrides', () => {
135
136
  })
136
137
 
137
138
  it('overriding headerGap increases the gap between actors and the first message', () => {
138
- const lines = ['sequenceDiagram', 'Alice->>Bob: Hi'].map((l) => l.trim())
139
+ const lines = splitStatements(
140
+ ['sequenceDiagram', 'Alice->>Bob: Hi'].join('\n'),
141
+ )
139
142
  const diagram = parseSequenceDiagram(lines)
140
143
 
141
144
  const withDefault = layoutSequenceDiagram(diagram)
@@ -149,12 +152,14 @@ describe('RenderOptions.sequence – layout overrides', () => {
149
152
  })
150
153
 
151
154
  it('overriding noteStackGap changes spacing between consecutively stacked notes', () => {
152
- const lines = [
153
- 'sequenceDiagram',
154
- 'Alice->>Bob: Hi',
155
- 'Note right of Bob: First note',
156
- 'Note right of Bob: Second note',
157
- ].map((l) => l.trim())
155
+ const lines = splitStatements(
156
+ [
157
+ 'sequenceDiagram',
158
+ 'Alice->>Bob: Hi',
159
+ 'Note right of Bob: First note',
160
+ 'Note right of Bob: Second note',
161
+ ].join('\n'),
162
+ )
158
163
  const diagram = parseSequenceDiagram(lines)
159
164
 
160
165
  const withDefault = layoutSequenceDiagram(diagram)
@@ -172,11 +177,11 @@ describe('RenderOptions.sequence – layout overrides', () => {
172
177
  })
173
178
 
174
179
  it('overriding noteOffsetAfterMessage changes the gap between a message and the note after it', () => {
175
- const lines = [
176
- 'sequenceDiagram',
177
- 'Alice->>Bob: Hi',
178
- 'Note right of Bob: A note',
179
- ].map((l) => l.trim())
180
+ const lines = splitStatements(
181
+ ['sequenceDiagram', 'Alice->>Bob: Hi', 'Note right of Bob: A note'].join(
182
+ '\n',
183
+ ),
184
+ )
180
185
  const diagram = parseSequenceDiagram(lines)
181
186
 
182
187
  const withDefault = layoutSequenceDiagram(diagram)
@@ -192,7 +197,9 @@ describe('RenderOptions.sequence – layout overrides', () => {
192
197
  })
193
198
 
194
199
  it('partial sequence overrides only change the specified field', () => {
195
- const lines = ['sequenceDiagram', 'Alice->>Bob: Hi'].map((l) => l.trim())
200
+ const lines = splitStatements(
201
+ ['sequenceDiagram', 'Alice->>Bob: Hi'].join('\n'),
202
+ )
196
203
  const diagram = parseSequenceDiagram(lines)
197
204
 
198
205
  const withDefault = layoutSequenceDiagram(diagram)
@@ -25,16 +25,20 @@
25
25
  */
26
26
  import { describe, it, expect } from 'vitest'
27
27
  import { renderMermaidSVG } from '../index.ts'
28
- import { layoutGraphSync } from '@zombie-mermaid/svg-renderer'
29
- import { layoutSequenceDiagram } from '../sequence/layout.ts'
30
- import { layoutClassDiagramSync } from '../class/layout.ts'
31
- import { layoutErDiagramSync } from '../er/layout.ts'
32
- import { layoutXYChart } from '../xychart/layout.ts'
28
+ import {
29
+ layoutGraphSync,
30
+ layoutSequenceDiagram,
31
+ layoutClassDiagramSync,
32
+ layoutErDiagramSync,
33
+ layoutXYChart,
34
+ } from '@zombie-mermaid/svg-renderer'
33
35
  import { parseMermaid } from '../parser.ts'
34
- import { parseSequenceDiagram } from '../sequence/parser.ts'
35
- import { parseClassDiagram } from '../class/parser.ts'
36
- import { parseErDiagram } from '../er/parser.ts'
37
- import { parseXYChart } from '../xychart/parser.ts'
36
+ import {
37
+ parseSequenceDiagram,
38
+ parseClassDiagram,
39
+ parseErDiagram,
40
+ parseXYChart,
41
+ } from '@zombie-mermaid/mermaid-parser'
38
42
  import { splitStatements } from '@zombie-mermaid/core'
39
43
  import type {
40
44
  FlowchartRenderOptions,
@@ -5,7 +5,7 @@ import {
5
5
  evaluateCssColorValue,
6
6
  resolveCssColors,
7
7
  } from '@zombie-mermaid/svg-renderer'
8
- import { diagramColorsToAsciiTheme } from '../ascii/ansi.ts'
8
+ import { diagramColorsToAsciiTheme } from '../../packages/ascii-renderer/src/ansi.ts'
9
9
 
10
10
  /**
11
11
  * Escape regex metacharacters so a value can be interpolated into a
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Self-loop edge rendering (issue #537 follow-up).
3
+ *
4
+ * packages/svg-renderer/src/layout-engine/ (ELK-based flowchart/state
5
+ * layout) previously had no self-loop-specific routing at all: ELK has no
6
+ * native concept of a self-loop, so a `B --> B` edge came back as a
7
+ * degenerate 4-point right-angle polyline sitting on top of the node
8
+ * instead of a loop beside it — see docs/research/537-self-loop-regression-check.md
9
+ * for the before/after comparison against current upstream Mermaid.
10
+ *
11
+ * These tests assert the synthesized replacement: a rounded loop bulging
12
+ * out from the node's right side, discretized into enough points to read
13
+ * as smooth, with the label offset clear of the node.
14
+ */
15
+ import { describe, it, expect } from 'vitest'
16
+ import { parseMermaid } from '../parser.ts'
17
+ import { layoutGraphSync } from '@zombie-mermaid/svg-renderer'
18
+
19
+ interface Point {
20
+ x: number
21
+ y: number
22
+ }
23
+
24
+ /** True when the segment p1->p2 runs purely horizontal or vertical. */
25
+ function isAxisAligned(p1: Point, p2: Point, tolerance = 0.5): boolean {
26
+ return Math.abs(p2.x - p1.x) < tolerance || Math.abs(p2.y - p1.y) < tolerance
27
+ }
28
+
29
+ describe('self-loop edge routing (flowchart)', () => {
30
+ it('routes a self-loop as a rounded loop beside the node, not a 4-point right-angle bracket', () => {
31
+ const graph = parseMermaid(`flowchart LR
32
+ A --->|foo| B
33
+ B --->|bar| B`)
34
+
35
+ const positioned = layoutGraphSync(graph)
36
+ const nodeB = positioned.nodes.find((n) => n.id === 'B')
37
+ const selfLoop = positioned.edges.find(
38
+ (e) => e.source === 'B' && e.target === 'B',
39
+ )
40
+
41
+ expect(nodeB).toBeDefined()
42
+ expect(selfLoop).toBeDefined()
43
+ const points = selfLoop!.points
44
+
45
+ // The old ELK-routed bracket was exactly 4 points. A real loop needs
46
+ // many more to read as rounded.
47
+ expect(points.length).toBeGreaterThan(4)
48
+
49
+ // The bracket was pure right angles throughout. A rounded loop must
50
+ // have a majority of genuinely diagonal segments.
51
+ let diagonalSegments = 0
52
+ for (let i = 1; i < points.length; i++) {
53
+ if (!isAxisAligned(points[i - 1]!, points[i]!)) diagonalSegments++
54
+ }
55
+ expect(diagonalSegments).toBeGreaterThan(points.length / 2)
56
+
57
+ // The loop bulges out to the side of the node — every point sits at or
58
+ // beyond the node's right edge — rather than sitting on top of it (the
59
+ // old bracket's points landed inside/just above the node's bounding box).
60
+ for (const p of points) {
61
+ expect(p.x).toBeGreaterThanOrEqual(nodeB!.x + nodeB!.width - 0.5)
62
+ }
63
+
64
+ // Both ends of the loop attach to the node's right edge.
65
+ expect(points[0]!.x).toBeCloseTo(nodeB!.x + nodeB!.width, 0)
66
+ expect(points[points.length - 1]!.x).toBeCloseTo(nodeB!.x + nodeB!.width, 0)
67
+
68
+ // The label sits clear of the node, out past the loop's apex — not on
69
+ // top of the node the way the old bracket's cramped shape forced it to.
70
+ expect(selfLoop!.labelPosition).toBeDefined()
71
+ expect(selfLoop!.labelPosition!.x).toBeGreaterThan(nodeB!.x + nodeB!.width)
72
+ })
73
+
74
+ it('stacks multiple self-loops on the same node instead of overlapping', () => {
75
+ const graph = parseMermaid(`flowchart LR
76
+ B -->|one| B
77
+ B -->|two| B`)
78
+
79
+ const positioned = layoutGraphSync(graph)
80
+ const selfLoops = positioned.edges.filter(
81
+ (e) => e.source === 'B' && e.target === 'B',
82
+ )
83
+
84
+ expect(selfLoops).toHaveLength(2)
85
+ const [first, second] = selfLoops as [
86
+ (typeof selfLoops)[0],
87
+ (typeof selfLoops)[0],
88
+ ]
89
+
90
+ // The second loop reaches further right than the first, so they nest
91
+ // rather than draw identically on top of each other.
92
+ const apexX = (points: Point[]) => Math.max(...points.map((p) => p.x))
93
+ expect(apexX(second.points)).toBeGreaterThan(apexX(first.points))
94
+ })
95
+ })
96
+
97
+ describe('self-loop edge routing (state diagram)', () => {
98
+ it('routes a state-diagram self-transition as a rounded loop', () => {
99
+ const graph = parseMermaid(`stateDiagram-v2
100
+ [*] --> Idle
101
+ Idle --> Idle: retry`)
102
+
103
+ const positioned = layoutGraphSync(graph)
104
+ const selfLoop = positioned.edges.find(
105
+ (e) => e.source === 'Idle' && e.target === 'Idle',
106
+ )
107
+
108
+ expect(selfLoop).toBeDefined()
109
+ expect(selfLoop!.points.length).toBeGreaterThan(4)
110
+ expect(selfLoop!.label).toBe('retry')
111
+ })
112
+ })
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Tests for the sequence diagram activation/deactivation balance check
3
- * (src/sequence/activation-check.ts).
3
+ * (packages/mermaid-parser/src/sequence/activation-check.ts).
4
4
  *
5
5
  * Covers: balanced diagrams (both the standalone activate/deactivate form
6
6
  * and the +/- arrow shorthand, including nesting), a dangling activation
@@ -8,16 +8,15 @@
8
8
  * that mix both issue types or both syntactic forms together.
9
9
  */
10
10
  import { describe, it, expect } from 'vitest'
11
- import { parseSequenceDiagram } from '../sequence/parser.ts'
12
- import { checkActivationBalance } from '../sequence/activation-check.ts'
11
+ import {
12
+ parseSequenceDiagram,
13
+ checkActivationBalance,
14
+ } from '@zombie-mermaid/mermaid-parser'
15
+ import { splitStatements } from '@zombie-mermaid/core'
13
16
 
14
17
  /** Helper to parse — preprocesses text the same way index.ts does. */
15
18
  function parse(text: string) {
16
- const lines = text
17
- .split('\n')
18
- .map((l) => l.trim())
19
- .filter((l) => l.length > 0 && !l.startsWith('%%'))
20
- return parseSequenceDiagram(lines)
19
+ return parseSequenceDiagram(splitStatements(text))
21
20
  }
22
21
 
23
22
  describe('checkActivationBalance — balanced diagrams', () => {
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Tests for the sequence diagram activation/deactivation auto-fix
3
+ * (packages/mermaid-parser/src/sequence/activation-fix.ts). Operates on raw
4
+ * Mermaid source text (unlike checkActivationBalance, which takes an
5
+ * already-parsed diagram), since the fix needs to append to the original
6
+ * source rather than just report against the parsed model.
7
+ *
8
+ * Covers: an already-balanced diagram (no-op), a single dangling
9
+ * activation (auto-fixed), multiple dangling activations across actors, an
10
+ * unmatched deactivation (deliberately NOT auto-fixed — see module header),
11
+ * and a mix of both issue types together.
12
+ */
13
+ import { describe, it, expect } from 'vitest'
14
+ import { fixActivationBalance } from '@zombie-mermaid/mermaid-parser'
15
+
16
+ describe('fixActivationBalance', () => {
17
+ it('returns the diagram unchanged when already balanced', () => {
18
+ const source = 'sequenceDiagram\n A->>+B: hello\n B-->>-A: world'
19
+ const result = fixActivationBalance(source)
20
+ expect(result.ok).toBe(true)
21
+ expect(result.fixedDiagram).toBe(source)
22
+ expect(result.fixesApplied).toEqual([])
23
+ expect(result.remainingIssues).toEqual([])
24
+ })
25
+
26
+ it('appends a deactivate statement for a single dangling activation', () => {
27
+ const source = 'sequenceDiagram\n activate A\n A->>B: hello'
28
+ const result = fixActivationBalance(source)
29
+ expect(result.ok).toBe(true)
30
+ expect(result.originalDiagram).toBe(source)
31
+ expect(result.fixedDiagram).toBe(`${source}\n deactivate A`)
32
+ expect(result.fixesApplied).toEqual([
33
+ expect.stringContaining('Appended "deactivate A"'),
34
+ ])
35
+ expect(result.remainingIssues).toEqual([])
36
+ })
37
+
38
+ it('makes the fixed diagram re-check as balanced', () => {
39
+ const source = 'sequenceDiagram\n activate A\n A->>B: hello'
40
+ const { fixedDiagram } = fixActivationBalance(source)
41
+ const reChecked = fixActivationBalance(fixedDiagram)
42
+ expect(reChecked.ok).toBe(true)
43
+ expect(reChecked.fixesApplied).toEqual([])
44
+ })
45
+
46
+ it('closes multiple dangling activations for different actors', () => {
47
+ const source = 'sequenceDiagram\n activate A\n activate B\n A->>B: hello'
48
+ const result = fixActivationBalance(source)
49
+ expect(result.ok).toBe(true)
50
+ expect(result.fixesApplied).toHaveLength(2)
51
+ expect(result.fixedDiagram).toContain(' deactivate A')
52
+ expect(result.fixedDiagram).toContain(' deactivate B')
53
+ })
54
+
55
+ it('does not fix an unmatched deactivation and reports it back', () => {
56
+ const source = 'sequenceDiagram\n A->>B: hello\n deactivate A'
57
+ const result = fixActivationBalance(source)
58
+ expect(result.ok).toBe(false)
59
+ expect(result.fixedDiagram).toBe(source)
60
+ expect(result.fixesApplied).toEqual([])
61
+ expect(result.remainingIssues).toEqual([
62
+ expect.objectContaining({
63
+ code: 'UNMATCHED_DEACTIVATION',
64
+ actorId: 'A',
65
+ }),
66
+ ])
67
+ })
68
+
69
+ it('fixes the dangling half of a mixed-issue diagram, leaving the unmatched half reported', () => {
70
+ const source =
71
+ 'sequenceDiagram\n activate A\n A->>B: hello\n deactivate B'
72
+ const result = fixActivationBalance(source)
73
+ expect(result.ok).toBe(false)
74
+ expect(result.fixesApplied).toEqual([
75
+ expect.stringContaining('Appended "deactivate A"'),
76
+ ])
77
+ expect(result.fixedDiagram).toContain(' deactivate A')
78
+ expect(result.remainingIssues).toEqual([
79
+ expect.objectContaining({
80
+ code: 'UNMATCHED_DEACTIVATION',
81
+ actorId: 'B',
82
+ }),
83
+ ])
84
+ })
85
+
86
+ it('throws a clear error for a non-sequence diagram', () => {
87
+ expect(() => fixActivationBalance('graph LR\n A --> B')).toThrow(
88
+ /only supports sequence diagrams/,
89
+ )
90
+ })
91
+ })
@@ -6,16 +6,13 @@
6
6
  * to inspect Y coordinates, rather than checking SVG output.
7
7
  */
8
8
  import { describe, it, expect } from 'vitest'
9
- import { parseSequenceDiagram } from '../sequence/parser.ts'
10
- import { layoutSequenceDiagram } from '../sequence/layout.ts'
9
+ import { parseSequenceDiagram } from '@zombie-mermaid/mermaid-parser'
10
+ import { layoutSequenceDiagram } from '@zombie-mermaid/svg-renderer'
11
+ import { splitStatements } from '@zombie-mermaid/core'
11
12
 
12
13
  /** Helper: parse and layout a sequence diagram from source lines */
13
14
  function layout(source: string) {
14
- const lines = source
15
- .split('\n')
16
- .map((l) => l.trim())
17
- .filter((l) => l.length > 0 && !l.startsWith('%%'))
18
- return layoutSequenceDiagram(parseSequenceDiagram(lines))
15
+ return layoutSequenceDiagram(parseSequenceDiagram(splitStatements(source)))
19
16
  }
20
17
 
21
18
  describe('sequence layout – block spacing', () => {
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Syntactic error-detection tests for the sequence diagram parser
3
+ * (issue #762).
4
+ *
5
+ * The 8 pre-existing `throw` sites in `parseSequenceDiagram` all guard
6
+ * *semantic* rules mirrored from mermaid.js (duplicate actor ids, box
7
+ * nesting, create/destroy message mismatch — see
8
+ * sequence-parser.test.ts/sequence-activation-check.test.ts for those). No
9
+ * *syntactic* error was ever raised: a malformed arrow like `->>>` silently
10
+ * absorbed the extra character into the actor name instead of being
11
+ * rejected (verified: `Alice->>>Bob: Hello` created an actor literally
12
+ * named `">Bob"`), an unmatched `end` was silently ignored, and an
13
+ * `alt`/`loop`/`box` left open at EOF was silently accepted as if closed.
14
+ * These tests assert the parser now throws an actionable error instead,
15
+ * while confirming valid syntax — including short arrows, activation
16
+ * shorthand, and actor names that legitimately start with "x" — is
17
+ * unaffected.
18
+ */
19
+ import { describe, it, expect } from 'vitest'
20
+ import { parseSequenceDiagram } from '@zombie-mermaid/mermaid-parser'
21
+ import { splitStatements } from '@zombie-mermaid/core'
22
+
23
+ function parse(text: string) {
24
+ return parseSequenceDiagram(splitStatements(text))
25
+ }
26
+
27
+ describe('parseSequenceDiagram – malformed arrows', () => {
28
+ it('throws on an extra ">" past the longest known long-arrow token', () => {
29
+ // The issue's own verified repro: silently created an actor named
30
+ // `">Bob"` before this pass.
31
+ expect(() =>
32
+ parse(`sequenceDiagram
33
+ Alice->>>Bob: Hello`),
34
+ ).toThrow(/Malformed sequence-diagram arrow.*"->>>"/)
35
+ })
36
+
37
+ it('throws on an extra ")" past a short open-arrow token', () => {
38
+ expect(() =>
39
+ parse(`sequenceDiagram
40
+ Alice-))Bob: Hello`),
41
+ ).toThrow(/Malformed sequence-diagram arrow.*"-\)\)"/)
42
+ })
43
+
44
+ it('throws on an extra ")" past a short dashed-open-arrow token', () => {
45
+ expect(() =>
46
+ parse(`sequenceDiagram
47
+ Alice--))Bob: Hello`),
48
+ ).toThrow(/Malformed sequence-diagram arrow/)
49
+ })
50
+
51
+ it('mentions the known-good arrow set in the message', () => {
52
+ expect(() =>
53
+ parse(`sequenceDiagram
54
+ Alice->>>Bob: Hello`),
55
+ ).toThrow(/->, -->, ->>, -->>, -x, --x, -\), --\)/)
56
+ })
57
+
58
+ it('reports the 1-based line number', () => {
59
+ expect(() =>
60
+ parse(`sequenceDiagram
61
+ participant Alice
62
+ Alice->>>Bob: Hello`),
63
+ ).toThrow(/^Line 3:/)
64
+ })
65
+ })
66
+
67
+ describe('parseSequenceDiagram – unmatched "end"', () => {
68
+ it('throws on an "end" with no open block or box', () => {
69
+ expect(() =>
70
+ parse(`sequenceDiagram
71
+ Alice->>Bob: Hi
72
+ end`),
73
+ ).toThrow(/"end" does not match any open block/)
74
+ })
75
+ })
76
+
77
+ describe('parseSequenceDiagram – unclosed blocks/boxes at EOF', () => {
78
+ it('throws when a loop is never closed', () => {
79
+ expect(() =>
80
+ parse(`sequenceDiagram
81
+ loop Every 5s
82
+ Alice->>Bob: Hi`),
83
+ ).toThrow(/unclosed "loop" block/)
84
+ })
85
+
86
+ it('throws when an alt is never closed', () => {
87
+ expect(() =>
88
+ parse(`sequenceDiagram
89
+ alt Happy path
90
+ Alice->>Bob: Hi`),
91
+ ).toThrow(/unclosed "alt" block/)
92
+ })
93
+
94
+ it('throws when a box is never closed', () => {
95
+ expect(() =>
96
+ parse(`sequenceDiagram
97
+ box Group1
98
+ participant Alice
99
+ Alice->>Alice: Hi`),
100
+ ).toThrow(/unclosed "box Group1"/)
101
+ })
102
+
103
+ it('reports the line the block opened on', () => {
104
+ expect(() =>
105
+ parse(`sequenceDiagram
106
+ Alice->>Bob: Hi
107
+ loop Every 5s
108
+ Bob->>Alice: Pong`),
109
+ ).toThrow(/^Line 3:/)
110
+ })
111
+ })
112
+
113
+ describe('parseSequenceDiagram – valid syntax is unaffected', () => {
114
+ it('still parses correct long arrows without throwing', () => {
115
+ expect(() =>
116
+ parse(`sequenceDiagram
117
+ Alice->>Bob: Hi
118
+ Bob-->>Alice: Hi back`),
119
+ ).not.toThrow()
120
+ })
121
+
122
+ it('still parses activation shorthand (+/-) without throwing', () => {
123
+ const d = parse(`sequenceDiagram
124
+ Alice->>+Bob: Hi
125
+ Bob-->>-Alice: Hi back`)
126
+ expect(d.messages[0]!.activate).toBe(true)
127
+ expect(d.messages[1]!.deactivate).toBe(true)
128
+ })
129
+
130
+ it('still parses short open/cross arrows without throwing', () => {
131
+ const d = parse(`sequenceDiagram
132
+ Alice-)Bob: fire and forget
133
+ Bob--xAlice: lost message`)
134
+ expect(d.messages).toHaveLength(2)
135
+ })
136
+
137
+ it('still parses bidirectional arrows without throwing', () => {
138
+ expect(() =>
139
+ parse(`sequenceDiagram
140
+ Alice<<->>Bob: sync
141
+ Alice<<-->>Bob: async sync`),
142
+ ).not.toThrow()
143
+ })
144
+
145
+ it('does not throw for an actor name that legitimately starts with "x" after a -x arrow', () => {
146
+ const d = parse(`sequenceDiagram
147
+ Alice-xXavier: lost`)
148
+ expect(d.actors.map((a) => a.id)).toContain('Xavier')
149
+ })
150
+
151
+ it('still parses a properly closed loop/alt/box without throwing', () => {
152
+ expect(() =>
153
+ parse(`sequenceDiagram
154
+ box Group1
155
+ participant Alice
156
+ participant Bob
157
+ end
158
+ loop Every 5s
159
+ alt Happy path
160
+ Alice->>Bob: Hi
161
+ else Sad path
162
+ Alice-xBob: nope
163
+ end
164
+ end`),
165
+ ).not.toThrow()
166
+ })
167
+
168
+ it('still closes an "end" that matches a block nested inside a box', () => {
169
+ const d = parse(`sequenceDiagram
170
+ box Group1
171
+ participant Alice
172
+ loop Every 5s
173
+ Alice->>Alice: ping
174
+ end
175
+ end`)
176
+ expect(d.blocks).toHaveLength(1)
177
+ expect(d.boxes).toHaveLength(1)
178
+ })
179
+ })
@@ -9,15 +9,12 @@ import {
9
9
  parseSequenceDiagram,
10
10
  isBlockType,
11
11
  toBlockType,
12
- } from '../sequence/parser.ts'
12
+ } from '@zombie-mermaid/mermaid-parser'
13
+ import { splitStatements } from '@zombie-mermaid/core'
13
14
 
14
15
  /** Helper to parse — preprocesses text the same way index.ts does */
15
16
  function parse(text: string) {
16
- const lines = text
17
- .split('\n')
18
- .map((l) => l.trim())
19
- .filter((l) => l.length > 0 && !l.startsWith('%%'))
20
- return parseSequenceDiagram(lines)
17
+ return parseSequenceDiagram(splitStatements(text))
21
18
  }
22
19
 
23
20
  // ============================================================================