@zombie-mermaid/svg-renderer 4.2.2 → 4.2.3

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 (33) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.js +13 -5
  4. package/dist/index.js.map +1 -1
  5. package/package.json +4 -3
  6. package/src/index.ts +34 -1
  7. package/src/__tests__/c4-label-clearance-1290.test.ts +0 -112
  8. package/src/__tests__/c4-line-ends-1208.test.ts +0 -52
  9. package/src/__tests__/c4-mermaid-reference.test.ts +0 -124
  10. package/src/__tests__/c4-svg.test.ts +0 -337
  11. package/src/__tests__/c4-text-width-1210.test.ts +0 -43
  12. package/src/__tests__/class-component-order-1249.test.ts +0 -40
  13. package/src/__tests__/class-diamond-markers-1232.test.ts +0 -59
  14. package/src/__tests__/class-member-right-padding-1238.test.ts +0 -43
  15. package/src/__tests__/class-namespace-1196.test.ts +0 -165
  16. package/src/__tests__/class-namespace-sparse-elk-1196.test.ts +0 -96
  17. package/src/__tests__/class-visibility-markers-1237.test.ts +0 -27
  18. package/src/__tests__/edge-jog-straightening-1388.test.ts +0 -57
  19. package/src/__tests__/elk-adapter-utils.test.ts +0 -166
  20. package/src/__tests__/er-attribute-right-padding-1262.test.ts +0 -47
  21. package/src/__tests__/er-cardinality-markers-1233.test.ts +0 -35
  22. package/src/__tests__/er-default-direction-1250.test.ts +0 -69
  23. package/src/__tests__/er-edge-label-contrast-1244.test.ts +0 -58
  24. package/src/__tests__/fixtures/c4-mermaid-reference.json +0 -626
  25. package/src/__tests__/flowchart-parallel-edge-lanes-1308.test.ts +0 -168
  26. package/src/__tests__/pie-layout.test.ts +0 -215
  27. package/src/__tests__/pie-svg.test.ts +0 -194
  28. package/src/__tests__/render-mermaid-svg.test.ts +0 -64
  29. package/src/__tests__/sequence-label-clearance-1242.test.ts +0 -162
  30. package/src/__tests__/sequence-nested-activation-1241.test.ts +0 -37
  31. package/src/__tests__/svg-edge-to-own-cluster-1310.test.ts +0 -32
  32. package/src/__tests__/xychart-smooth-curve.test.ts +0 -42
  33. package/src/__tests__/xychart-x-label-overlap-1243.test.ts +0 -62
@@ -1,162 +0,0 @@
1
- /**
2
- * #1242: lifelines were drawn straight through message labels and block
3
- * titles, and a stick-figure actor's label sat on the first message arrow.
4
- * The visual suite's tolerance can't see either, so assert the geometry.
5
- */
6
- import { describe, it, expect } from 'vitest'
7
- import { parseSequenceDiagram } from '@zombie-mermaid/mermaid-parser'
8
- import { renderMermaidSVG } from '../../../../src/index.ts'
9
- import { layoutSequenceDiagram } from '../sequence/layout.ts'
10
-
11
- function lifelineSegments(
12
- svg: string,
13
- actor: string,
14
- ): Array<{ x: number; y1: number; y2: number }> {
15
- const out: Array<{ x: number; y1: number; y2: number }> = []
16
- const re =
17
- /<line class="lifeline" data-actor="([^"]*)" x1="([\d.]+)" y1="([\d.]+)" x2="[\d.]+" y2="([\d.]+)"/g
18
- for (const m of svg.matchAll(re)) {
19
- if (m[1] !== actor) continue
20
- out.push({ x: Number(m[2]), y1: Number(m[3]), y2: Number(m[4]) })
21
- }
22
- return out
23
- }
24
-
25
- /** Centre y of the `<text>` whose content is `label`. */
26
- function textY(svg: string, label: string): number {
27
- for (const m of svg.matchAll(
28
- /<text [^>]*y="([\d.]+)"[^>]*>([^<]*)<\/text>/g,
29
- )) {
30
- if (m[2] === label) return Number(m[1])
31
- }
32
- return NaN
33
- }
34
-
35
- /** True when no lifeline segment of `actor` covers height `y`. */
36
- function lifelineClearAt(svg: string, actor: string, y: number): boolean {
37
- return lifelineSegments(svg, actor).every((s) => y < s.y1 || y > s.y2)
38
- }
39
-
40
- describe('lifelines stay out of message labels (#1242)', () => {
41
- const svg = renderMermaidSVG(`sequenceDiagram
42
- participant A
43
- participant B
44
- participant C
45
- A->>B: first
46
- A->>C: label crossing the middle lifeline
47
- A->>B: next`)
48
-
49
- it('cuts the crossed lifeline around the label instead of running through it', () => {
50
- const labelY = textY(svg, 'label crossing the middle lifeline')
51
- expect(lifelineSegments(svg, 'B').length).toBeGreaterThan(1)
52
- expect(lifelineClearAt(svg, 'B', labelY)).toBe(true)
53
- })
54
-
55
- it('leaves a lifeline whole where no label crosses it', () => {
56
- expect(lifelineSegments(svg, 'A')).toHaveLength(1)
57
- })
58
-
59
- it('cuts lifelines around a block title tab', () => {
60
- const blockSvg = renderMermaidSVG(`sequenceDiagram
61
- participant A
62
- participant B
63
- A->>B: hi
64
- alt Multiple panels
65
- B->>A: yes
66
- end`)
67
- // The tab sits at the block's top-left; the lifeline under it is cut.
68
- const rect = blockSvg.match(
69
- /<g class="block"[^>]*>\s*<rect x="([\d.]+)" y="([\d.]+)"/,
70
- )
71
- const blockY = Number(rect?.[2])
72
- expect(lifelineSegments(blockSvg, 'A').length).toBeGreaterThan(1)
73
- expect(lifelineClearAt(blockSvg, 'A', blockY + 9)).toBe(true)
74
- })
75
-
76
- it('cuts a lifeline beside a self-message label', () => {
77
- const selfSvg = renderMermaidSVG(`sequenceDiagram
78
- participant A
79
- participant B
80
- A->>A: a long self message label crossing B
81
- A->>B: after`)
82
- const y = textY(selfSvg, 'a long self message label crossing B')
83
- expect(lifelineSegments(selfSvg, 'B').length).toBeGreaterThan(1)
84
- expect(lifelineClearAt(selfSvg, 'B', y)).toBe(true)
85
- })
86
-
87
- it('cuts lifelines around a bare divider label', () => {
88
- const divSvg = renderMermaidSVG(`sequenceDiagram
89
- participant A
90
- participant B
91
- alt first
92
- A->>B: x
93
- else other case
94
- B->>A: y
95
- end`)
96
- const y = textY(divSvg, '[other case]')
97
- expect(lifelineSegments(divSvg, 'A').length).toBeGreaterThan(1)
98
- expect(lifelineClearAt(divSvg, 'A', y)).toBe(true)
99
- })
100
-
101
- it('ignores unlabeled blocks and dividers', () => {
102
- const bareSvg = renderMermaidSVG(`sequenceDiagram
103
- participant A
104
- participant B
105
- loop
106
- B->>A: ping
107
- else
108
- A->>B: pong
109
- end`)
110
- expect(lifelineSegments(bareSvg, 'A').length).toBeGreaterThan(0)
111
- expect(lifelineSegments(bareSvg, 'B').length).toBeGreaterThan(0)
112
- })
113
-
114
- it('never drops a lifeline entirely, even under a label taller than it', () => {
115
- const lines = Array.from({ length: 8 }, (_, i) => `l${i}`).join('<br/>')
116
- const tall = renderMermaidSVG(`sequenceDiagram
117
- participant A
118
- participant B
119
- participant C
120
- A->>C: ${lines}`)
121
- // The label spans B's whole lifeline; B keeps one uncut line.
122
- expect(lifelineSegments(tall, 'B')).toHaveLength(1)
123
- })
124
- })
125
-
126
- describe('stick-figure actor label clears the first message (#1242)', () => {
127
- const src = `sequenceDiagram
128
- actor U as User
129
- participant S as System
130
- U->>S: Click button`
131
- const svg = renderMermaidSVG(src)
132
- const layout = layoutSequenceDiagram(
133
- parseSequenceDiagram(
134
- src
135
- .split('\n')
136
- .slice(1)
137
- .map((text, line) => ({ text: text.trim(), line })),
138
- ),
139
- )
140
-
141
- it('starts the actor lifeline below its label', () => {
142
- const [seg] = lifelineSegments(svg, 'U')
143
- expect(seg!.y1).toBeGreaterThan(textY(svg, 'User') + 6)
144
- })
145
-
146
- it('places the first message arrow clear of the label', () => {
147
- const first = layout.messages[0]!
148
- // label is 13px-ish text centred at textY; arrow must be well below it
149
- expect(first.y).toBeGreaterThan(textY(svg, 'User') + 16)
150
- })
151
-
152
- it('keeps the original compact header when no stick figure is used', () => {
153
- const plain = layoutSequenceDiagram(
154
- parseSequenceDiagram([
155
- { text: 'participant A', line: 0 },
156
- { text: 'participant B', line: 1 },
157
- { text: 'A->>B: hi', line: 2 },
158
- ]),
159
- )
160
- expect(plain.messages[0]!.y - (plain.actors[0]!.y + 40)).toBe(20)
161
- })
162
- })
@@ -1,37 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { parseSequenceDiagram } from '@zombie-mermaid/mermaid-parser'
3
- import { renderMermaidSVG } from '../../../../src/index.ts'
4
- import { layoutSequenceDiagram } from '../sequence/layout.ts'
5
-
6
- const SRC = [
7
- 'participant C as Client',
8
- 'participant S as Server',
9
- 'C->>+S: Request',
10
- 'S->>+S: Process',
11
- 'S->>-S: Done',
12
- 'S-->>-C: Response',
13
- ]
14
-
15
- describe('nested self-message activation (#1241)', () => {
16
- const layout = layoutSequenceDiagram(
17
- parseSequenceDiagram(SRC.map((text, line) => ({ text, line }))),
18
- )
19
- const server = layout.activations.filter((a) => a.actorId === 'S')
20
-
21
- it('yields two activations on the actor with depths 0 and 1', () => {
22
- expect(server.map((a) => a.depth)).toEqual([0, 1])
23
- })
24
-
25
- it('offsets the nested bar right by half a bar, inside the parent span', () => {
26
- const outer = server.find((a) => a.depth === 0)!
27
- const inner = server.find((a) => a.depth === 1)!
28
- expect(inner.x - outer.x).toBe(outer.width / 2)
29
- expect(inner.topY).toBeGreaterThan(outer.topY)
30
- expect(inner.bottomY).toBeLessThan(outer.bottomY)
31
- })
32
-
33
- it('draws both bars, outer first so the nested one is painted on top', () => {
34
- const svg = renderMermaidSVG(`sequenceDiagram\n ${SRC.join('\n ')}`)
35
- expect(svg.match(/class="activation"/g)).toHaveLength(2)
36
- })
37
- })
@@ -1,32 +0,0 @@
1
- /**
2
- * #1310 — real mermaid.js (11.17.2) draws no line for an edge between a node
3
- * and a subgraph that contains it (the edge path is zero-length). The SVG
4
- * renderer used to draw degenerate stubs for it; it now omits the edge, so the
5
- * output equals the same diagram without that edge.
6
- */
7
- import { describe, it, expect } from 'vitest'
8
- import { renderMermaidSVG } from '../../../../src/index.ts'
9
-
10
- const base = 'flowchart TD; subgraph Sub; B; end; A --> B'
11
-
12
- describe('SVG: an edge between a node and its own subgraph (#1310)', () => {
13
- it.each([
14
- ['member to subgraph', `${base}; B --> Sub`],
15
- ['subgraph to member', `${base}; Sub --> B`],
16
- [
17
- 'nested containment',
18
- 'flowchart TD; subgraph Outer; subgraph Inner; B; end; end; A --> B; B --> Outer; Inner --> B; Outer --> Inner',
19
- ],
20
- ])('renders %s identically to the diagram without the edge', (_, src) => {
21
- const without = src.includes('Outer')
22
- ? 'flowchart TD; subgraph Outer; subgraph Inner; B; end; end; A --> B'
23
- : base
24
- expect(renderMermaidSVG(src)).toBe(renderMermaidSVG(without))
25
- })
26
-
27
- it('still draws an edge from an outside node to the subgraph', () => {
28
- expect(renderMermaidSVG(`${base}; X --> Sub`)).not.toBe(
29
- renderMermaidSVG(base),
30
- )
31
- })
32
- })
@@ -1,42 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { smoothCurvePath } from '../xychart/renderer.ts'
3
-
4
- /** Every y coordinate in the path (Bezier control points bound the curve). */
5
- function pathYs(d: string): number[] {
6
- return [...d.matchAll(/(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)/g)].map((m) =>
7
- Number(m[2]),
8
- )
9
- }
10
-
11
- function expectWithinRange(values: number[]) {
12
- const points = values.map((y, i) => ({ x: i * 50, y }))
13
- const ys = pathYs(smoothCurvePath(points))
14
- const lo = Math.min(...values)
15
- const hi = Math.max(...values)
16
- for (const y of ys) {
17
- expect(y).toBeGreaterThanOrEqual(lo - 0.01)
18
- expect(y).toBeLessThanOrEqual(hi + 0.01)
19
- }
20
- }
21
-
22
- describe('smoothCurvePath', () => {
23
- it('stays within the data range on a sharp spike after a flat run', () => {
24
- expectWithinRange([5, 12, 45, 380, 420, 250, 35, 8])
25
- expectWithinRange([0, 0, 0, 100, 0, 0, 0])
26
- })
27
-
28
- it('stays within the data range on a flat run and alternating values', () => {
29
- expectWithinRange([10, 10, 10, 10, 10])
30
- expectWithinRange([0, 100, 0, 100, 0])
31
- })
32
-
33
- it('passes through every data point', () => {
34
- const d = smoothCurvePath([
35
- { x: 0, y: 10 },
36
- { x: 50, y: 80 },
37
- { x: 100, y: 20 },
38
- ])
39
- expect(d).toContain('50,80')
40
- expect(d.endsWith('100,20')).toBe(true)
41
- })
42
- })
@@ -1,62 +0,0 @@
1
- /**
2
- * #1243: with many categories the x-axis labels touched. The visual suite's
3
- * tolerance can't see labels moving a few pixels, so assert on the layout.
4
- */
5
- import { describe, it, expect } from 'vitest'
6
- import { parseXYChart } from '@zombie-mermaid/mermaid-parser'
7
- import { splitStatements } from '@zombie-mermaid/core'
8
- import { layoutXYChart } from '../xychart/layout.ts'
9
- import { estimateTextWidth } from '../styles.ts'
10
-
11
- function layout(src: string) {
12
- return layoutXYChart(parseXYChart(splitStatements(src)))
13
- }
14
-
15
- function shown(src: string) {
16
- return layout(src).xAxis.ticks.filter((t) => t.label !== '')
17
- }
18
-
19
- function minGap(ticks: ReturnType<typeof shown>): number {
20
- let gap = Infinity
21
- for (let i = 1; i < ticks.length; i++) {
22
- const a = ticks[i - 1]!
23
- const b = ticks[i]!
24
- const half =
25
- (estimateTextWidth(a.label, 14, 400) +
26
- estimateTextWidth(b.label, 14, 400)) /
27
- 2
28
- gap = Math.min(gap, b.labelX - a.labelX - half)
29
- }
30
- return gap
31
- }
32
-
33
- const QUARTERS = `xychart-beta
34
- x-axis [21Q1, 21Q2, 21Q3, 21Q4, 22Q1, 22Q2, 22Q3, 22Q4, 23Q1, 23Q2, 23Q3, 23Q4, 24Q1, 24Q2, 24Q3, 24Q4]
35
- bar [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]`
36
-
37
- describe('xychart x-axis label spacing (#1243)', () => {
38
- it('drops labels so adjacent shown labels do not touch', () => {
39
- const ticks = shown(QUARTERS)
40
- expect(ticks.length).toBeLessThan(16)
41
- expect(ticks.length).toBeGreaterThan(1)
42
- expect(minGap(ticks)).toBeGreaterThan(0)
43
- })
44
-
45
- it('keeps every label when they all fit', () => {
46
- const src = `xychart-beta
47
- x-axis [Q1, Q2, Q3, Q4]
48
- bar [1, 2, 3, 4]`
49
- expect(shown(src).map((t) => t.label)).toEqual(['Q1', 'Q2', 'Q3', 'Q4'])
50
- })
51
-
52
- it('does not thin a roomy axis because of one long label', () => {
53
- const src = `xychart-beta
54
- x-axis [A, B, "A Rather Long Category", D, E]
55
- bar [1, 2, 3, 4, 5]`
56
- expect(shown(src)).toHaveLength(5)
57
- })
58
-
59
- it('keeps the tick marks of dropped labels', () => {
60
- expect(layout(QUARTERS).xAxis.ticks).toHaveLength(16)
61
- })
62
- })