@zombie-mermaid/ascii-renderer 3.0.0 → 3.1.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zombie-mermaid/ascii-renderer",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "license": "MIT",
5
5
  "description": "ASCII and Unicode box-drawing renderer for zombie-mermaid diagrams.",
6
6
  "repository": {
@@ -35,7 +35,7 @@
35
35
  "LICENSE"
36
36
  ],
37
37
  "dependencies": {
38
- "@zombie-mermaid/core": "3.0.0",
39
- "@zombie-mermaid/mermaid-parser": "3.0.0"
38
+ "@zombie-mermaid/mermaid-parser": "3.1.0",
39
+ "@zombie-mermaid/core": "3.1.0"
40
40
  }
41
41
  }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Regression tests for upstream lukilabs/beautiful-mermaid#63: an LR fan-in
3
+ * followed by a fan-out (`A --> C`, `B --> C`, `C --> D`, `C --> E`).
4
+ *
5
+ * Upstream's bug: C->E was routed along B->C's corridor, so B's row rendered a
6
+ * misleading `├───┴──►E`, as if B pointed at E. The fixed shape has C->E on its
7
+ * own trunk (`┬` off C's right side, then `└────┐` down to E) while B->C ends
8
+ * at `┘` under C.
9
+ *
10
+ * This fork fixes it through findUnrelatedOverlap / createEdgeCellOwners
11
+ * (edge-cell-styles.ts) via rerouteAroundStyleConflicts (grid.ts), not by
12
+ * porting upstream's routing change; these tests pin the rendered outcome, not
13
+ * the mechanism.
14
+ */
15
+ import { describe, it, expect } from 'vitest'
16
+ import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
17
+
18
+ const lines = (s: string): string[] => s.split('\n').map((l) => l.trimEnd())
19
+
20
+ const SOURCE = `graph LR
21
+ A --> C
22
+ B --> C
23
+ C --> D
24
+ C --> E`
25
+
26
+ describe('upstream #63: LR fan-in then fan-out keeps C->E off B->C corridor', () => {
27
+ it('renders C->E on its own trunk (unicode)', () => {
28
+ expect(lines(renderMermaidASCII(SOURCE, { colorMode: 'none' }))).toEqual([
29
+ '┌───┐ ┌───┐ ┌───┐',
30
+ '│ │ │ │ │ │',
31
+ '│ A ├────►│ C ├──┬─►│ D │',
32
+ '│ │ │ │ │ │ │',
33
+ '└───┘ └───┘ │ └───┘',
34
+ ' ▲ │',
35
+ ' │ │',
36
+ ' │ └────┐',
37
+ ' │ │',
38
+ ' │ ▼',
39
+ '┌───┐ │ ┌───┐',
40
+ '│ │ │ │ │',
41
+ '│ B ├───────┘ │ E │',
42
+ '│ │ │ │',
43
+ '└───┘ └───┘',
44
+ ])
45
+ })
46
+
47
+ it('B row ends at the corner under C and never reaches E (unicode)', () => {
48
+ const out = lines(renderMermaidASCII(SOURCE, { colorMode: 'none' }))
49
+ const bRow = out.find((l) => l.includes('│ B ├'))!
50
+ // B's edge terminates at exactly one `┘`; nothing horizontal after it.
51
+ expect(bRow.match(/┘/g)).toHaveLength(1)
52
+ expect(bRow.slice(bRow.indexOf('┘') + 1)).not.toMatch(/[─├┴┬►]/)
53
+ // The misleading tee joining B's corridor to E must not appear anywhere.
54
+ expect(out.join('\n')).not.toContain('┴')
55
+ })
56
+
57
+ it('renders the same shape with the ASCII charset', () => {
58
+ expect(
59
+ lines(renderMermaidASCII(SOURCE, { colorMode: 'none', useAscii: true })),
60
+ ).toEqual([
61
+ '+---+ +---+ +---+',
62
+ '| | | | | |',
63
+ '| A +---->| C +--+->| D |',
64
+ '| | | | | | |',
65
+ '+---+ +---+ | +---+',
66
+ ' ^ |',
67
+ ' | |',
68
+ ' | +----+',
69
+ ' | |',
70
+ ' | v',
71
+ '+---+ | +---+',
72
+ '| | | | |',
73
+ '| B +-------+ | E |',
74
+ '| | | |',
75
+ '+---+ +---+',
76
+ ])
77
+ })
78
+ })
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Regression tests for upstream lukilabs/beautiful-mermaid#113 (fixing
3
+ * upstream issues #111 and #112): ASCII fan-out routing.
4
+ *
5
+ * - #111: sibling edges from one source (TB, labelled) must share a single
6
+ * horizontal trunk leaving the source's right border, each label sitting on
7
+ * its own vertical branch, instead of later edges detouring to a lower row.
8
+ * - #112: in an LR fan-out, a sibling edge's label widens the source's border
9
+ * column; the first edge's box-start connector must stay flush on the
10
+ * source box's real border rather than drifting right with the grid cell.
11
+ *
12
+ * This fork solves these through edge-bundling / draw-bundles / draw-arrows
13
+ * rather than porting upstream's pathfinder changes; these tests pin the
14
+ * rendered outcome, not the mechanism.
15
+ */
16
+ import { describe, it, expect } from 'vitest'
17
+ import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
18
+
19
+ const lines = (s: string): string[] => s.split('\n').map((l) => l.trimEnd())
20
+
21
+ const TD_FANOUT = `flowchart TB
22
+ Src["Source"]
23
+ Left["Left Target"]
24
+ Center["Center Target"]
25
+ Right["Right Target"]
26
+ Src -->|left*| Left
27
+ Src -->|center*| Center
28
+ Src -->|right*| Right`
29
+
30
+ const LR_FANOUT = `flowchart LR
31
+ Src["Source"]
32
+ Top["Top Target"]
33
+ Mid["Middle Target"]
34
+ Bot["Bottom Target"]
35
+ Src -->|top*| Top
36
+ Src -->|mid*| Mid
37
+ Src -->|bot*| Bot`
38
+
39
+ describe('upstream #111: TB fan-out siblings share one trunk', () => {
40
+ it('renders the shared trunk with labels on their own vertical branches (unicode)', () => {
41
+ const out = renderMermaidASCII(TD_FANOUT, { colorMode: 'none' })
42
+ expect(lines(out)).toEqual([
43
+ '┌─────────────┐',
44
+ '│ │',
45
+ '│ Source ├─────────────┬─────────────────────┐',
46
+ '│ │ │ │',
47
+ '└──────┬──────┘ center* right*',
48
+ ' │ │ │',
49
+ ' left* │ │',
50
+ ' │ │ │',
51
+ ' │ │ │',
52
+ ' ▼ ▼ ▼',
53
+ '┌─────────────┐ ┌───────────────┐ ┌──────────────┐',
54
+ '│ │ │ │ │ │',
55
+ '│ Left Target │ │ Center Target │ │ Right Target │',
56
+ '│ │ │ │ │ │',
57
+ '└─────────────┘ └───────────────┘ └──────────────┘',
58
+ ])
59
+ })
60
+
61
+ it('has exactly one horizontal trunk row off the source border (ASCII)', () => {
62
+ const out = lines(
63
+ renderMermaidASCII(TD_FANOUT, { colorMode: 'none', useAscii: true }),
64
+ )
65
+ // Box border rows start with '+'; only the source-border row carries a long horizontal run; the buggy
66
+ // upstream output put a second run (the "right*" detour) on a lower row.
67
+ const rowsWithRun = out.filter((l) => !l.startsWith('+') && /-{5,}/.test(l))
68
+ expect(rowsWithRun).toHaveLength(1)
69
+ expect(rowsWithRun[0]).toMatch(/^\|\s+Source\s+\+-+\+-+\+$/)
70
+ // No label is stranded on a horizontal segment.
71
+ expect(out.join('\n')).not.toMatch(/[-─]\/?(center|right)\*[-─]/)
72
+ })
73
+ })
74
+
75
+ describe('upstream #112: LR fan-out box-start connector stays on the border', () => {
76
+ it('places ├ directly under the source box right border column (unicode)', () => {
77
+ const out = lines(renderMermaidASCII(LR_FANOUT, { colorMode: 'none' }))
78
+ const sourceRow = out.find((l) => l.includes('Source'))!
79
+ const borderCol = sourceRow.indexOf('├')
80
+ expect(borderCol).toBe(out[0]!.indexOf('┐'))
81
+ // Every row of the source box (top border down to bottom border) agrees
82
+ // on the border column.
83
+ expect(out[0]![borderCol]).toBe('┐')
84
+ expect(out[4]![borderCol]).toBe('┘')
85
+ // The connector is the border glyph itself, not preceded by a gap.
86
+ expect(sourceRow[borderCol - 1]).toBe(' ')
87
+ expect(sourceRow.slice(0, borderCol)).toBe('│ Source ')
88
+ })
89
+
90
+ it('renders the full fan-out with ├ flush on the border (unicode)', () => {
91
+ const out = renderMermaidASCII(LR_FANOUT, { colorMode: 'none' })
92
+ expect(lines(out).slice(0, 6)).toEqual([
93
+ '┌────────┐ ┌───────────────┐',
94
+ '│ │ │ │',
95
+ '│ Source ├top*─►│ Top Target │',
96
+ '│ │ │ │',
97
+ '└────┬───┘ └───────────────┘',
98
+ ' │',
99
+ ])
100
+ })
101
+ })