@zombie-mermaid/ascii-renderer 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/dist/index.cjs +8 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +146 -0
- package/dist/index.d.ts +146 -0
- package/dist/index.js +5392 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -0
- package/src/__tests__/ascii-arrowhead-direction-1083.test.ts +51 -0
- package/src/__tests__/ascii-canvas-first-claim-wins-1093.test.ts +89 -0
- package/src/__tests__/ascii-canvas-size-offset-1093.test.ts +95 -0
- package/src/__tests__/ascii-canvas-write.test.ts +132 -0
- package/src/__tests__/ascii-chain-edge-overlap-1067.test.ts +100 -0
- package/src/__tests__/ascii-charset-border-junctions.test.ts +63 -0
- package/src/__tests__/ascii-cjk-width.test.ts +150 -0
- package/src/__tests__/ascii-class-box-occupancy.test.ts +462 -0
- package/src/__tests__/ascii-class-column-width-488-489.test.ts +639 -0
- package/src/__tests__/ascii-class-cross-level-jog-corruption.test.ts +187 -0
- package/src/__tests__/ascii-class-detour-label-routing-487.test.ts +114 -0
- package/src/__tests__/ascii-class-diagram-compartments.test.ts +70 -0
- package/src/__tests__/ascii-class-label-box-collision.test.ts +60 -0
- package/src/__tests__/ascii-class-label-row-collision-531.test.ts +135 -0
- package/src/__tests__/ascii-class-label-territory-row-awareness.test.ts +57 -0
- package/src/__tests__/ascii-class-padding.test.ts +104 -0
- package/src/__tests__/ascii-class-parent-alignment-971.test.ts +139 -0
- package/src/__tests__/ascii-class-parent-alignment-972.test.ts +208 -0
- package/src/__tests__/ascii-class-reciprocal-relationships-448.test.ts +169 -0
- package/src/__tests__/ascii-combining-mark-width.test.ts +70 -0
- package/src/__tests__/ascii-coords-overlay.test.ts +64 -0
- package/src/__tests__/ascii-decision-lr-box-start.test.ts +106 -0
- package/src/__tests__/ascii-display-width-unit.test.ts +154 -0
- package/src/__tests__/ascii-draw-arrows-coverage.test.ts +224 -0
- package/src/__tests__/ascii-draw-arrows-single-point-path.test.ts +105 -0
- package/src/__tests__/ascii-edge-bundling-rank-violation-454.test.ts +72 -0
- package/src/__tests__/ascii-edge-ending-glyphs.test.ts +206 -0
- package/src/__tests__/ascii-edge-label-diagonal-fallback-418.test.ts +95 -0
- package/src/__tests__/ascii-edge-routing-fixes.test.ts +178 -0
- package/src/__tests__/ascii-edge-routing-single-point-path.test.ts +103 -0
- package/src/__tests__/ascii-edge-style-consistency-1067.test.ts +89 -0
- package/src/__tests__/ascii-edge-styles.test.ts +149 -0
- package/src/__tests__/ascii-emoji-cluster-width.test.ts +101 -0
- package/src/__tests__/ascii-er-box-occupancy.test.ts +251 -0
- package/src/__tests__/ascii-er-cardinality.test.ts +40 -0
- package/src/__tests__/ascii-er-corner-glyphs.test.ts +249 -0
- package/src/__tests__/ascii-er-jog-stray-line.test.ts +111 -0
- package/src/__tests__/ascii-er-label-padding.test.ts +118 -0
- package/src/__tests__/ascii-er-padding.test.ts +114 -0
- package/src/__tests__/ascii-er-relationship-label-corruption-350.test.ts +458 -0
- package/src/__tests__/ascii-er-relationship-overwrite.test.ts +325 -0
- package/src/__tests__/ascii-er-stray-connectors.test.ts +344 -0
- package/src/__tests__/ascii-er-unrelated-stem-separation-411.test.ts +98 -0
- package/src/__tests__/ascii-er-vertical-one-marker.test.ts +110 -0
- package/src/__tests__/ascii-label-line-terminal-fallback.test.ts +168 -0
- package/src/__tests__/ascii-lane-search.test.ts +207 -0
- package/src/__tests__/ascii-multibox-cjk-width.test.ts +184 -0
- package/src/__tests__/ascii-multiline.test.ts +288 -0
- package/src/__tests__/ascii-padding-edge-cases.test.ts +154 -0
- package/src/__tests__/ascii-pathfinder-route-edge.test.ts +184 -0
- package/src/__tests__/ascii-sequence-alt-else-label.test.ts +236 -0
- package/src/__tests__/ascii-sequence-block-wall-clearance.test.ts +217 -0
- package/src/__tests__/ascii-sequence-box-group.test.ts +159 -0
- package/src/__tests__/ascii-sequence-cjk-width.test.ts +235 -0
- package/src/__tests__/ascii-sequence-create-destroy.test.ts +114 -0
- package/src/__tests__/ascii-sequence-form-invariants.test.ts +432 -0
- package/src/__tests__/ascii-sequence-mermaid-parity.test.ts +219 -0
- package/src/__tests__/ascii-sequence-notes.test.ts +61 -0
- package/src/__tests__/ascii-sequence-padding.test.ts +119 -0
- package/src/__tests__/ascii-sequence-self-arrow.test.ts +206 -0
- package/src/__tests__/ascii-shape-diamond.test.ts +38 -0
- package/src/__tests__/ascii-shape-rectangle.test.ts +258 -0
- package/src/__tests__/ascii-shape-rounded.test.ts +36 -0
- package/src/__tests__/ascii-shapes-circle.test.ts +41 -0
- package/src/__tests__/ascii-shapes-hexagon.test.ts +42 -0
- package/src/__tests__/ascii-shapes-special.test.ts +344 -0
- package/src/__tests__/ascii-shapes-stadium.test.ts +217 -0
- package/src/__tests__/ascii-shapes-state.test.ts +224 -0
- package/src/__tests__/ascii-state-bidirectional-label-swap-530.test.ts +130 -0
- package/src/__tests__/ascii-subgraph-direction-honored-445.test.ts +90 -0
- package/src/__tests__/ascii-subgraph-label-border-clip.test.ts +152 -0
- package/src/__tests__/ascii-subgraph-title-padding.test.ts +77 -0
- package/src/__tests__/ascii-territory-unit.test.ts +219 -0
- package/src/__tests__/ascii-validate.test.ts +220 -0
- package/src/__tests__/ascii.test.ts +325 -0
- package/src/__tests__/class-arrow-directions.test.ts +505 -0
- package/src/__tests__/draw-lines.test.ts +93 -0
- package/src/__tests__/edge-cell-styles.test.ts +278 -0
- package/src/__tests__/grid-occupancy.test.ts +240 -0
- package/src/__tests__/helpers/ascii-form.ts +142 -0
- package/src/__tests__/helpers/terminal-display-width.ts +74 -0
- package/src/__tests__/pathfinder.test.ts +239 -0
- package/src/__tests__/testdata/ascii/ampersand_lhs.txt +18 -0
- package/src/__tests__/testdata/ascii/ampersand_lhs_and_rhs.txt +18 -0
- package/src/__tests__/testdata/ascii/ampersand_rhs.txt +18 -0
- package/src/__tests__/testdata/ascii/ampersand_td_fanin.txt +18 -0
- package/src/__tests__/testdata/ascii/ampersand_td_fanout.txt +18 -0
- package/src/__tests__/testdata/ascii/ampersand_without_edge.txt +18 -0
- package/src/__tests__/testdata/ascii/back_reference_from_child.txt +10 -0
- package/src/__tests__/testdata/ascii/backlink_from_bottom.txt +22 -0
- package/src/__tests__/testdata/ascii/backlink_from_top.txt +22 -0
- package/src/__tests__/testdata/ascii/backlink_with_short_y_padding.txt +20 -0
- package/src/__tests__/testdata/ascii/cls_all_relationships.txt +19 -0
- package/src/__tests__/testdata/ascii/cls_annotation.txt +29 -0
- package/src/__tests__/testdata/ascii/cls_association.txt +14 -0
- package/src/__tests__/testdata/ascii/cls_basic.txt +15 -0
- package/src/__tests__/testdata/ascii/cls_dependency.txt +14 -0
- package/src/__tests__/testdata/ascii/cls_inheritance.txt +20 -0
- package/src/__tests__/testdata/ascii/cls_methods.txt +21 -0
- package/src/__tests__/testdata/ascii/comments.txt +23 -0
- package/src/__tests__/testdata/ascii/custom_padding.txt +10 -0
- package/src/__tests__/testdata/ascii/duplicate_labels.txt +19 -0
- package/src/__tests__/testdata/ascii/er_attributes.txt +21 -0
- package/src/__tests__/testdata/ascii/er_basic.txt +8 -0
- package/src/__tests__/testdata/ascii/er_identifying.txt +18 -0
- package/src/__tests__/testdata/ascii/flowchart_tb_simple.txt +29 -0
- package/src/__tests__/testdata/ascii/graph_bt_direction.txt +28 -0
- package/src/__tests__/testdata/ascii/graph_tb_direction.txt +26 -0
- package/src/__tests__/testdata/ascii/nested_subgraphs_with_labels.txt +36 -0
- package/src/__tests__/testdata/ascii/preserve_order_of_definition.txt +23 -0
- package/src/__tests__/testdata/ascii/self_reference.txt +10 -0
- package/src/__tests__/testdata/ascii/self_reference_with_edge.txt +10 -0
- package/src/__tests__/testdata/ascii/seq_basic.txt +17 -0
- package/src/__tests__/testdata/ascii/seq_multiple_messages.txt +25 -0
- package/src/__tests__/testdata/ascii/seq_self_message.txt +18 -0
- package/src/__tests__/testdata/ascii/single_node.txt +8 -0
- package/src/__tests__/testdata/ascii/single_node_longer_name.txt +8 -0
- package/src/__tests__/testdata/ascii/subgraph_complex_mixed.txt +38 -0
- package/src/__tests__/testdata/ascii/subgraph_complex_nested.txt +49 -0
- package/src/__tests__/testdata/ascii/subgraph_direction_override.txt +47 -0
- package/src/__tests__/testdata/ascii/subgraph_empty.txt +10 -0
- package/src/__tests__/testdata/ascii/subgraph_mixed_nodes.txt +20 -0
- package/src/__tests__/testdata/ascii/subgraph_mixed_nodes_td.txt +48 -0
- package/src/__tests__/testdata/ascii/subgraph_multiple_edges.txt +32 -0
- package/src/__tests__/testdata/ascii/subgraph_multiple_nodes.txt +16 -0
- package/src/__tests__/testdata/ascii/subgraph_nested.txt +24 -0
- package/src/__tests__/testdata/ascii/subgraph_nested_with_external.txt +30 -0
- package/src/__tests__/testdata/ascii/subgraph_node_outside_lr.txt +17 -0
- package/src/__tests__/testdata/ascii/subgraph_single_node.txt +16 -0
- package/src/__tests__/testdata/ascii/subgraph_td_direction.txt +26 -0
- package/src/__tests__/testdata/ascii/subgraph_td_multiple.txt +44 -0
- package/src/__tests__/testdata/ascii/subgraph_td_multiple_paddingy.txt +42 -0
- package/src/__tests__/testdata/ascii/subgraph_three_levels_nested.txt +32 -0
- package/src/__tests__/testdata/ascii/subgraph_three_separate.txt +24 -0
- package/src/__tests__/testdata/ascii/subgraph_two_separate.txt +20 -0
- package/src/__tests__/testdata/ascii/subgraph_with_labels.txt +20 -0
- package/src/__tests__/testdata/ascii/three_nodes.txt +9 -0
- package/src/__tests__/testdata/ascii/three_nodes_single_line.txt +8 -0
- package/src/__tests__/testdata/ascii/two_layer_single_graph.txt +19 -0
- package/src/__tests__/testdata/ascii/two_layer_single_graph_longer_names.txt +19 -0
- package/src/__tests__/testdata/ascii/two_nodes_linked.txt +8 -0
- package/src/__tests__/testdata/ascii/two_nodes_longer_names.txt +8 -0
- package/src/__tests__/testdata/ascii/two_root_nodes.txt +19 -0
- package/src/__tests__/testdata/ascii/two_root_nodes_longer_names.txt +19 -0
- package/src/__tests__/testdata/ascii/two_single_root_nodes.txt +19 -0
- package/src/__tests__/testdata/unicode/ampersand_lhs.txt +18 -0
- package/src/__tests__/testdata/unicode/ampersand_lhs_and_rhs.txt +18 -0
- package/src/__tests__/testdata/unicode/ampersand_rhs.txt +18 -0
- package/src/__tests__/testdata/unicode/ampersand_without_edge.txt +18 -0
- package/src/__tests__/testdata/unicode/back_reference_from_child.txt +10 -0
- package/src/__tests__/testdata/unicode/backlink_from_bottom.txt +22 -0
- package/src/__tests__/testdata/unicode/backlink_from_top.txt +22 -0
- package/src/__tests__/testdata/unicode/cls_all_relationships.txt +19 -0
- package/src/__tests__/testdata/unicode/cls_annotation.txt +29 -0
- package/src/__tests__/testdata/unicode/cls_association.txt +14 -0
- package/src/__tests__/testdata/unicode/cls_basic.txt +15 -0
- package/src/__tests__/testdata/unicode/cls_dependency.txt +14 -0
- package/src/__tests__/testdata/unicode/cls_inheritance.txt +20 -0
- package/src/__tests__/testdata/unicode/cls_methods.txt +21 -0
- package/src/__tests__/testdata/unicode/comments.txt +23 -0
- package/src/__tests__/testdata/unicode/duplicate_labels.txt +19 -0
- package/src/__tests__/testdata/unicode/er_attributes.txt +21 -0
- package/src/__tests__/testdata/unicode/er_basic.txt +8 -0
- package/src/__tests__/testdata/unicode/er_identifying.txt +18 -0
- package/src/__tests__/testdata/unicode/graph_bt_direction.txt +28 -0
- package/src/__tests__/testdata/unicode/preserve_order_of_definition.txt +23 -0
- package/src/__tests__/testdata/unicode/self_reference.txt +10 -0
- package/src/__tests__/testdata/unicode/self_reference_with_edge.txt +10 -0
- package/src/__tests__/testdata/unicode/seq_basic.txt +17 -0
- package/src/__tests__/testdata/unicode/seq_multiple_messages.txt +25 -0
- package/src/__tests__/testdata/unicode/seq_self_message.txt +18 -0
- package/src/__tests__/testdata/unicode/single_node.txt +8 -0
- package/src/__tests__/testdata/unicode/single_node_longer_name.txt +8 -0
- package/src/__tests__/testdata/unicode/three_nodes.txt +9 -0
- package/src/__tests__/testdata/unicode/three_nodes_single_line.txt +8 -0
- package/src/__tests__/testdata/unicode/two_layer_single_graph.txt +19 -0
- package/src/__tests__/testdata/unicode/two_layer_single_graph_longer_names.txt +19 -0
- package/src/__tests__/testdata/unicode/two_nodes_linked.txt +8 -0
- package/src/__tests__/testdata/unicode/two_nodes_longer_names.txt +8 -0
- package/src/__tests__/testdata/unicode/two_root_nodes.txt +19 -0
- package/src/__tests__/testdata/unicode/two_root_nodes_longer_names.txt +19 -0
- package/src/__tests__/testdata/unicode/two_single_root_nodes.txt +19 -0
- package/src/__tests__/xychart-ascii.test.ts +376 -0
- package/src/ansi.ts +490 -0
- package/src/canvas.ts +757 -0
- package/src/class-diagram.ts +2001 -0
- package/src/converter.ts +446 -0
- package/src/coords.ts +58 -0
- package/src/display-width.ts +151 -0
- package/src/draw-arrows.ts +593 -0
- package/src/draw-boxes.ts +267 -0
- package/src/draw-bundles.ts +611 -0
- package/src/draw-lines.ts +174 -0
- package/src/draw-subgraphs.ts +108 -0
- package/src/draw.ts +350 -0
- package/src/edge-bundling.ts +435 -0
- package/src/edge-cell-styles.ts +209 -0
- package/src/edge-routing.ts +1070 -0
- package/src/er-diagram.ts +1488 -0
- package/src/flowchart.ts +94 -0
- package/src/grid-occupancy.ts +234 -0
- package/src/grid.ts +1309 -0
- package/src/hyperlinks.ts +248 -0
- package/src/index.ts +163 -0
- package/src/lane-search.ts +68 -0
- package/src/multiline-utils.ts +82 -0
- package/src/pathfinder.ts +448 -0
- package/src/registry.ts +88 -0
- package/src/sequence.ts +1318 -0
- package/src/shapes/circle.ts +31 -0
- package/src/shapes/corners.ts +273 -0
- package/src/shapes/diamond.ts +31 -0
- package/src/shapes/hexagon.ts +35 -0
- package/src/shapes/index.ts +123 -0
- package/src/shapes/rectangle.ts +199 -0
- package/src/shapes/rounded.ts +31 -0
- package/src/shapes/special.ts +360 -0
- package/src/shapes/stadium.ts +122 -0
- package/src/shapes/state.ts +204 -0
- package/src/shapes/types.ts +78 -0
- package/src/territory.ts +136 -0
- package/src/types.ts +454 -0
- package/src/validate.ts +189 -0
- package/src/xychart.ts +1085 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test for #153 — `determineLabelLine`'s final fallback assumed
|
|
3
|
+
* a routed edge's path always has at least 2 points:
|
|
4
|
+
*
|
|
5
|
+
* largestLine = segments[0]?.line ?? [edge.path[0]!, edge.path[1]!]
|
|
6
|
+
*
|
|
7
|
+
* `pathfinder.ts`'s `getPath` can legitimately return a single-point path
|
|
8
|
+
* when a routed edge's preferred from/to grid coordinates coincide (e.g.
|
|
9
|
+
* closely-spaced/adjacent nodes). With a 1-element `edge.path`, `segments`
|
|
10
|
+
* stays empty (the `for (let i = 1; i < pathLen; i++)` loop never runs),
|
|
11
|
+
* so the fallback read `edge.path[1]` — actually `undefined`, mistyped as
|
|
12
|
+
* `GridCoord` — and crashed a few lines later when it was dereferenced.
|
|
13
|
+
*
|
|
14
|
+
* Fixed by treating a path with fewer than 2 points as a degenerate
|
|
15
|
+
* zero-length line at that single point instead of indexing past the end.
|
|
16
|
+
*/
|
|
17
|
+
import { describe, it, expect } from 'vitest'
|
|
18
|
+
import { determineLabelLine } from '../edge-routing.ts'
|
|
19
|
+
import { Down } from '../types.ts'
|
|
20
|
+
import type { AsciiEdge, AsciiGraph, AsciiNode } from '../types.ts'
|
|
21
|
+
import { createGrid } from '../grid-occupancy.ts'
|
|
22
|
+
|
|
23
|
+
function makeNode(name: string, x: number, y: number): AsciiNode {
|
|
24
|
+
return {
|
|
25
|
+
name,
|
|
26
|
+
displayLabel: name,
|
|
27
|
+
shape: 'rectangle',
|
|
28
|
+
index: 0,
|
|
29
|
+
gridCoord: { x, y },
|
|
30
|
+
drawingCoord: null,
|
|
31
|
+
drawing: null,
|
|
32
|
+
drawn: false,
|
|
33
|
+
styleClassName: '',
|
|
34
|
+
styleClass: { name: '', styles: {} },
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function makeGraph(nodes: AsciiNode[]): AsciiGraph {
|
|
39
|
+
return {
|
|
40
|
+
nodes,
|
|
41
|
+
edges: [],
|
|
42
|
+
canvas: [],
|
|
43
|
+
roleCanvas: [],
|
|
44
|
+
grid: createGrid(),
|
|
45
|
+
columnWidth: new Map(),
|
|
46
|
+
rowHeight: new Map(),
|
|
47
|
+
subgraphs: [],
|
|
48
|
+
config: {
|
|
49
|
+
useAscii: false,
|
|
50
|
+
paddingX: 5,
|
|
51
|
+
paddingY: 5,
|
|
52
|
+
boxBorderPadding: 1,
|
|
53
|
+
graphDirection: 'TD',
|
|
54
|
+
},
|
|
55
|
+
offsetX: 0,
|
|
56
|
+
offsetY: 0,
|
|
57
|
+
bundles: [],
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
describe('determineLabelLine handles a single-point edge path (#153)', () => {
|
|
62
|
+
it('does not throw when edge.path has exactly one point', () => {
|
|
63
|
+
const from = makeNode('A', 0, 0)
|
|
64
|
+
const to = makeNode('B', 4, 0)
|
|
65
|
+
const graph = makeGraph([from, to])
|
|
66
|
+
|
|
67
|
+
const edge: AsciiEdge = {
|
|
68
|
+
from,
|
|
69
|
+
to,
|
|
70
|
+
text: 'label',
|
|
71
|
+
path: [{ x: 2, y: 0 }], // single-point path — the coinciding-endpoint case
|
|
72
|
+
labelLine: [],
|
|
73
|
+
startDir: Down,
|
|
74
|
+
endDir: Down,
|
|
75
|
+
style: 'solid',
|
|
76
|
+
hasArrowStart: false,
|
|
77
|
+
hasArrowEnd: true,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
expect(() => determineLabelLine(graph, edge)).not.toThrow()
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('does not throw when edge.path is empty', () => {
|
|
84
|
+
const from = makeNode('A', 0, 0)
|
|
85
|
+
const to = makeNode('B', 4, 0)
|
|
86
|
+
const graph = makeGraph([from, to])
|
|
87
|
+
|
|
88
|
+
const edge: AsciiEdge = {
|
|
89
|
+
from,
|
|
90
|
+
to,
|
|
91
|
+
text: 'label',
|
|
92
|
+
path: [],
|
|
93
|
+
labelLine: [],
|
|
94
|
+
startDir: Down,
|
|
95
|
+
endDir: Down,
|
|
96
|
+
style: 'solid',
|
|
97
|
+
hasArrowStart: false,
|
|
98
|
+
hasArrowEnd: true,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
expect(() => determineLabelLine(graph, edge)).not.toThrow()
|
|
102
|
+
})
|
|
103
|
+
})
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test for #1067 ("All Edge Styles" form-judge finding): a
|
|
3
|
+
* same-source fan-out with three distinct edge styles (`-->`, `-.->`,
|
|
4
|
+
* `==>`) could render one edge's own line as a mix of two different
|
|
5
|
+
* styles — e.g. the dotted `A -.-> C` edge's horizontal leg drawing as
|
|
6
|
+
* heavy box-drawing characters (borrowed from the thick `A ==> D` edge's
|
|
7
|
+
* overlapping route) before switching to its own dashed vertical leg.
|
|
8
|
+
*
|
|
9
|
+
* Root cause: `A ==> D`'s route fell through to `determinePath`'s Case-4
|
|
10
|
+
* direct fallback (edge-routing.ts), which used to hand back a raw
|
|
11
|
+
* two-point diagonal segment (`[prefFrom, prefTo]`) instead of the
|
|
12
|
+
* axis-aligned corner `drawLine` (draw-lines.ts) actually draws for it.
|
|
13
|
+
* That mismatch meant `edge-cell-styles.ts`'s cross-style conflict
|
|
14
|
+
* detection — which walks `edge.path` assuming it already matches the
|
|
15
|
+
* drawn geometry — never saw the real overlap with the dotted edge's own
|
|
16
|
+
* (properly routed) horizontal leg, so nothing rerouted around it and the
|
|
17
|
+
* later-drawn thick edge's canvas simply overwrote the dotted edge's
|
|
18
|
+
* glyphs where the two coincided.
|
|
19
|
+
*
|
|
20
|
+
* Two changes fix it:
|
|
21
|
+
* - `determinePath`'s Case-4 fallback now runs its diagonal segment
|
|
22
|
+
* through `expandDiagonalSegments` before assigning `edge.path`, so the
|
|
23
|
+
* recorded path always matches what `drawLine` actually draws.
|
|
24
|
+
* - `canvas.ts`'s `firstClaimWins` resolves any leftover overlap between
|
|
25
|
+
* edges' own line canvases before they're merged: whichever edge's line
|
|
26
|
+
* reaches a cell first (by `graph.edges` order) keeps it, instead of
|
|
27
|
+
* `mergeCanvases`' normal last-overlay-wins behavior silently splicing
|
|
28
|
+
* a later sibling's style into an earlier edge's own line.
|
|
29
|
+
*/
|
|
30
|
+
import { describe, it, expect } from 'vitest'
|
|
31
|
+
import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
|
|
32
|
+
|
|
33
|
+
const ALL_EDGE_STYLES = `graph TD
|
|
34
|
+
A[Source] -->|solid| B[Target 1]
|
|
35
|
+
A -.->|dotted| C[Target 2]
|
|
36
|
+
A ==>|thick| D[Target 3]`
|
|
37
|
+
|
|
38
|
+
const HEAVY_CHARS = /[━┃]/
|
|
39
|
+
const DASHED_CHARS = /[┄┆]/
|
|
40
|
+
|
|
41
|
+
describe('edge line style consistency on a same-source mixed-style fan-out (#1067)', () => {
|
|
42
|
+
const lines = renderMermaidASCII(ALL_EDGE_STYLES, { colorMode: 'none' })
|
|
43
|
+
.split('\n')
|
|
44
|
+
.map((l) => l.replace(/\s+$/, ''))
|
|
45
|
+
|
|
46
|
+
it('renders every label and does not throw', () => {
|
|
47
|
+
const out = lines.join('\n')
|
|
48
|
+
for (const label of ['Source', 'Target 1', 'Target 2', 'Target 3']) {
|
|
49
|
+
expect(out).toContain(label)
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it("keeps the dotted edge's own horizontal leg entirely dashed, never heavy", () => {
|
|
54
|
+
const sourceRow = lines.find((l) => l.includes('Source'))!
|
|
55
|
+
const afterBoxStart = sourceRow.slice(sourceRow.indexOf('├') + 1)
|
|
56
|
+
// The dotted edge's own corner is the *first* turn ('┐') on this row —
|
|
57
|
+
// the thick edge's route continues past it to a second, later corner.
|
|
58
|
+
const dottedCornerIndex = afterBoxStart.indexOf('┐')
|
|
59
|
+
expect(dottedCornerIndex).toBeGreaterThan(-1)
|
|
60
|
+
const dottedSegment = afterBoxStart.slice(0, dottedCornerIndex + 1)
|
|
61
|
+
|
|
62
|
+
expect(dottedSegment).not.toMatch(HEAVY_CHARS)
|
|
63
|
+
expect(dottedSegment).toMatch(DASHED_CHARS)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it("keeps the dotted edge's own vertical leg entirely dashed", () => {
|
|
67
|
+
const dottedLabelRow = lines.findIndex((l) => l.includes('dotted'))
|
|
68
|
+
expect(dottedLabelRow).toBeGreaterThan(-1)
|
|
69
|
+
// The vertical run is drawn on the rows immediately above and below
|
|
70
|
+
// the label.
|
|
71
|
+
for (const row of [
|
|
72
|
+
lines[dottedLabelRow - 1]!,
|
|
73
|
+
lines[dottedLabelRow + 1]!,
|
|
74
|
+
]) {
|
|
75
|
+
const col = row.indexOf('┆')
|
|
76
|
+
expect(col).toBeGreaterThan(-1)
|
|
77
|
+
expect(row[col + 1] ?? '').not.toMatch(HEAVY_CHARS)
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it("keeps the thick edge's own vertical leg entirely heavy, never dashed", () => {
|
|
82
|
+
const thickLabelRow = lines.findIndex((l) => l.includes('thick'))
|
|
83
|
+
expect(thickLabelRow).toBeGreaterThan(-1)
|
|
84
|
+
for (const row of [lines[thickLabelRow - 1]!, lines[thickLabelRow + 1]!]) {
|
|
85
|
+
const col = row.indexOf('┃')
|
|
86
|
+
expect(col).toBeGreaterThan(-1)
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
})
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// ASCII edge style tests — dotted and thick line rendering
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest'
|
|
6
|
+
import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
|
|
7
|
+
|
|
8
|
+
describe('ASCII edge styles', () => {
|
|
9
|
+
describe('solid edges (default)', () => {
|
|
10
|
+
it('renders solid edges with ─ in unicode mode', () => {
|
|
11
|
+
const result = renderMermaidASCII(`
|
|
12
|
+
graph LR
|
|
13
|
+
A --> B
|
|
14
|
+
`)
|
|
15
|
+
expect(result).toContain('─')
|
|
16
|
+
expect(result).not.toContain('┄')
|
|
17
|
+
expect(result).not.toContain('━')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('renders solid edges with - in ascii mode', () => {
|
|
21
|
+
const result = renderMermaidASCII(
|
|
22
|
+
`
|
|
23
|
+
graph LR
|
|
24
|
+
A --> B
|
|
25
|
+
`,
|
|
26
|
+
{ useAscii: true },
|
|
27
|
+
)
|
|
28
|
+
expect(result).toContain('-')
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('dotted edges (-.->)', () => {
|
|
33
|
+
it('renders dotted edges with ┄ in unicode mode', () => {
|
|
34
|
+
const result = renderMermaidASCII(`
|
|
35
|
+
graph LR
|
|
36
|
+
A -.-> B
|
|
37
|
+
`)
|
|
38
|
+
// Should contain dotted horizontal line character
|
|
39
|
+
expect(result).toContain('┄')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('renders dotted edges with . in ascii mode', () => {
|
|
43
|
+
const result = renderMermaidASCII(
|
|
44
|
+
`
|
|
45
|
+
graph LR
|
|
46
|
+
A -.-> B
|
|
47
|
+
`,
|
|
48
|
+
{ useAscii: true },
|
|
49
|
+
)
|
|
50
|
+
// Should contain dots for dotted lines
|
|
51
|
+
expect(result).toContain('.')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('renders dotted vertical edges with ┆ in unicode mode', () => {
|
|
55
|
+
const result = renderMermaidASCII(`
|
|
56
|
+
graph TD
|
|
57
|
+
A -.-> B
|
|
58
|
+
`)
|
|
59
|
+
// Should contain dotted vertical line character
|
|
60
|
+
expect(result).toContain('┆')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('renders dotted vertical edges with : in ascii mode', () => {
|
|
64
|
+
const result = renderMermaidASCII(
|
|
65
|
+
`
|
|
66
|
+
graph TD
|
|
67
|
+
A -.-> B
|
|
68
|
+
`,
|
|
69
|
+
{ useAscii: true },
|
|
70
|
+
)
|
|
71
|
+
// Should contain colons for dotted vertical lines
|
|
72
|
+
expect(result).toContain(':')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('renders dotted edges with labels', () => {
|
|
76
|
+
const result = renderMermaidASCII(`
|
|
77
|
+
graph LR
|
|
78
|
+
A -.->|optional| B
|
|
79
|
+
`)
|
|
80
|
+
expect(result).toContain('┄')
|
|
81
|
+
expect(result).toContain('optional')
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
describe('thick edges (==>)', () => {
|
|
86
|
+
it('renders thick edges with ━ in unicode mode', () => {
|
|
87
|
+
const result = renderMermaidASCII(`
|
|
88
|
+
graph LR
|
|
89
|
+
A ==> B
|
|
90
|
+
`)
|
|
91
|
+
// Should contain thick horizontal line character
|
|
92
|
+
expect(result).toContain('━')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('renders thick edges with = in ascii mode', () => {
|
|
96
|
+
const result = renderMermaidASCII(
|
|
97
|
+
`
|
|
98
|
+
graph LR
|
|
99
|
+
A ==> B
|
|
100
|
+
`,
|
|
101
|
+
{ useAscii: true },
|
|
102
|
+
)
|
|
103
|
+
// Should contain equals for thick lines
|
|
104
|
+
expect(result).toContain('=')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('renders thick vertical edges with ┃ in unicode mode', () => {
|
|
108
|
+
const result = renderMermaidASCII(`
|
|
109
|
+
graph TD
|
|
110
|
+
A ==> B
|
|
111
|
+
`)
|
|
112
|
+
// Should contain thick vertical line character
|
|
113
|
+
expect(result).toContain('┃')
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
describe('mixed edge styles', () => {
|
|
118
|
+
it('renders different styles in the same diagram', () => {
|
|
119
|
+
const result = renderMermaidASCII(`
|
|
120
|
+
graph LR
|
|
121
|
+
A --> B
|
|
122
|
+
B -.-> C
|
|
123
|
+
C ==> D
|
|
124
|
+
`)
|
|
125
|
+
// Should have all three line types
|
|
126
|
+
expect(result).toContain('─') // solid
|
|
127
|
+
expect(result).toContain('┄') // dotted
|
|
128
|
+
expect(result).toContain('━') // thick
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('renders mixed styles in ascii mode', () => {
|
|
132
|
+
const result = renderMermaidASCII(
|
|
133
|
+
`
|
|
134
|
+
graph LR
|
|
135
|
+
A --> B
|
|
136
|
+
B -.-> C
|
|
137
|
+
C ==> D
|
|
138
|
+
`,
|
|
139
|
+
{ useAscii: true },
|
|
140
|
+
)
|
|
141
|
+
// Note: ASCII mode uses - for solid, . for dotted, = for thick
|
|
142
|
+
// We just check that the diagram renders without error
|
|
143
|
+
expect(result).toContain('A')
|
|
144
|
+
expect(result).toContain('B')
|
|
145
|
+
expect(result).toContain('C')
|
|
146
|
+
expect(result).toContain('D')
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
})
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression tests for issue #214 — ASCII renderer boxes several columns too
|
|
3
|
+
* wide for composed emoji / grapheme clusters (ZWJ sequences, flags,
|
|
4
|
+
* skin-tone modifiers).
|
|
5
|
+
*
|
|
6
|
+
* `displayWidth()` used to add up "wide" columns per JS code point rather
|
|
7
|
+
* than per rendered grapheme cluster, so a multi-code-point emoji sequence —
|
|
8
|
+
* which a real terminal renders as a single glyph occupying at most 2
|
|
9
|
+
* columns — was counted once per code point:
|
|
10
|
+
*
|
|
11
|
+
* | Input | Code points | old displayWidth | actual columns |
|
|
12
|
+
* |---------------------------|------------|-------------------|-----------------|
|
|
13
|
+
* | 👨👩👧 (ZWJ family) | 5 | 8 | 2 |
|
|
14
|
+
* | 🇺🇸 (regional indicators) | 2 | 4 | 2 |
|
|
15
|
+
* | 👍🏽 (skin-tone modifier) | 2 | 4 | 2 |
|
|
16
|
+
* | 日本 (plain CJK, contrast) | 2 | 4 | 4 |
|
|
17
|
+
*
|
|
18
|
+
* `terminalDisplayWidth` re-implements the "how many terminal columns does
|
|
19
|
+
* this string occupy" calculation independently of
|
|
20
|
+
* `src/ascii/display-width.ts`, so these tests verify the *rendered output*
|
|
21
|
+
* against an authority separate from the implementation under test.
|
|
22
|
+
*/
|
|
23
|
+
import { describe, it, expect } from 'vitest'
|
|
24
|
+
import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
|
|
25
|
+
import { displayWidth } from '../display-width.ts'
|
|
26
|
+
import {
|
|
27
|
+
terminalDisplayWidth,
|
|
28
|
+
assertUniformDisplayWidth,
|
|
29
|
+
} from './helpers/terminal-display-width.ts'
|
|
30
|
+
|
|
31
|
+
// 👨👩👧 — MAN, ZWJ, WOMAN, ZWJ, GIRL (5 code points, 1 grapheme cluster).
|
|
32
|
+
const ZWJ_FAMILY = '\u{1F468}' + '' + '\u{1F469}' + '' + '\u{1F467}'
|
|
33
|
+
// 🇺🇸 — REGIONAL INDICATOR U + REGIONAL INDICATOR S (2 code points, 1 cluster).
|
|
34
|
+
const FLAG_US = '\u{1F1FA}' + '\u{1F1F8}'
|
|
35
|
+
// 👍🏽 — THUMBS UP + MEDIUM SKIN TONE modifier (2 code points, 1 cluster).
|
|
36
|
+
const THUMB_MEDIUM = '\u{1F44D}' + '\u{1F3FD}'
|
|
37
|
+
|
|
38
|
+
describe('ASCII renderer — composed emoji cluster width (issue #214)', () => {
|
|
39
|
+
it.each([
|
|
40
|
+
['ZWJ family emoji', ZWJ_FAMILY, 5, 2],
|
|
41
|
+
['regional-indicator flag', FLAG_US, 2, 2],
|
|
42
|
+
['skin-tone-modified emoji', THUMB_MEDIUM, 2, 2],
|
|
43
|
+
['plain CJK (contrast)', '日本', 2, 4],
|
|
44
|
+
])(
|
|
45
|
+
'%s: %j has %i code points and measures as %i columns',
|
|
46
|
+
(_label, text, codePointCount, expectedWidth) => {
|
|
47
|
+
expect([...text].length).toBe(codePointCount)
|
|
48
|
+
expect(displayWidth(text)).toBe(expectedWidth)
|
|
49
|
+
expect(terminalDisplayWidth(text)).toBe(expectedWidth)
|
|
50
|
+
},
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
for (const useAscii of [false, true]) {
|
|
54
|
+
describe(useAscii ? 'ASCII charset' : 'Unicode charset', () => {
|
|
55
|
+
it('renders the issue repro (family emoji + CJK) with all rows at equal terminal width', () => {
|
|
56
|
+
const mermaid = `flowchart TD\n A[${ZWJ_FAMILY} team] --> B[日本]`
|
|
57
|
+
const ascii = renderMermaidASCII(mermaid, {
|
|
58
|
+
useAscii,
|
|
59
|
+
colorMode: 'none',
|
|
60
|
+
})
|
|
61
|
+
assertUniformDisplayWidth(ascii)
|
|
62
|
+
expect(ascii).toContain(ZWJ_FAMILY)
|
|
63
|
+
expect(ascii).toContain('日本')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('sizes a flag-emoji label box to 2 columns, not 4', () => {
|
|
67
|
+
const mermaid = `flowchart TD\n A[${FLAG_US}] --> B[ok]`
|
|
68
|
+
const ascii = renderMermaidASCII(mermaid, {
|
|
69
|
+
useAscii,
|
|
70
|
+
colorMode: 'none',
|
|
71
|
+
})
|
|
72
|
+
assertUniformDisplayWidth(ascii)
|
|
73
|
+
expect(ascii).toContain(FLAG_US)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('sizes a skin-tone-modifier label box to 2 columns, not 4', () => {
|
|
77
|
+
const mermaid = `flowchart TD\n A[${THUMB_MEDIUM}] --> B[ok]`
|
|
78
|
+
const ascii = renderMermaidASCII(mermaid, {
|
|
79
|
+
useAscii,
|
|
80
|
+
colorMode: 'none',
|
|
81
|
+
})
|
|
82
|
+
assertUniformDisplayWidth(ascii)
|
|
83
|
+
expect(ascii).toContain(THUMB_MEDIUM)
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
it('renders a class diagram compartment box with a composed emoji member at uniform width', () => {
|
|
89
|
+
const mermaid = `classDiagram\n class Team {\n +String ${ZWJ_FAMILY}\n }`
|
|
90
|
+
const ascii = renderMermaidASCII(mermaid, { colorMode: 'none' })
|
|
91
|
+
assertUniformDisplayWidth(ascii)
|
|
92
|
+
expect(ascii).toContain(ZWJ_FAMILY)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('renders an ER diagram compartment box with a composed emoji attribute at uniform width', () => {
|
|
96
|
+
const mermaid = `erDiagram\n ITEM {\n string ${FLAG_US}\n }`
|
|
97
|
+
const ascii = renderMermaidASCII(mermaid, { colorMode: 'none' })
|
|
98
|
+
assertUniformDisplayWidth(ascii)
|
|
99
|
+
expect(ascii).toContain(FLAG_US)
|
|
100
|
+
})
|
|
101
|
+
})
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// ASCII ER diagram box-occupancy invariants
|
|
3
|
+
//
|
|
4
|
+
// Generalizes issue #350's fix (src/ascii/er-diagram.ts's `setCGuarded`/
|
|
5
|
+
// `boxCells`/`regionClear` occupancy guard — see
|
|
6
|
+
// ascii-er-relationship-label-corruption-350.test.ts for the original fixed
|
|
7
|
+
// repro) from a single fixed repro into a swept matrix: no two entity boxes
|
|
8
|
+
// ever overlap, and no relationship label ever lands inside an entity box it
|
|
9
|
+
// isn't attached to. svg-samples.visual.test.ts / ascii-samples.visual.test.ts
|
|
10
|
+
// only self-diff against a committed baseline screenshot, so they can't catch
|
|
11
|
+
// this shape of regression on their own — these assertions check the
|
|
12
|
+
// renderer's own occupancy guarantee directly, on any diagram shape, not just
|
|
13
|
+
// the one screenshot on file.
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
import { describe, it, expect } from 'vitest'
|
|
17
|
+
import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
|
|
18
|
+
import {
|
|
19
|
+
findBoxRect,
|
|
20
|
+
findTextRect,
|
|
21
|
+
rectsOverlap,
|
|
22
|
+
} from './helpers/ascii-form.ts'
|
|
23
|
+
|
|
24
|
+
/** Assert no two named entity boxes in `ascii` overlap. */
|
|
25
|
+
function expectNoBoxOverlap(ascii: string, names: string[]): void {
|
|
26
|
+
const rects = names.map((n) => ({ n, r: findBoxRect(ascii, n) }))
|
|
27
|
+
for (let i = 0; i < rects.length; i++) {
|
|
28
|
+
for (let j = i + 1; j < rects.length; j++) {
|
|
29
|
+
expect(
|
|
30
|
+
rectsOverlap(rects[i]!.r, rects[j]!.r),
|
|
31
|
+
`${rects[i]!.n} and ${rects[j]!.n} boxes overlap`,
|
|
32
|
+
).toBe(false)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Assert `label` doesn't land inside any of `names`' boxes. */
|
|
38
|
+
function expectLabelOutsideBoxes(
|
|
39
|
+
ascii: string,
|
|
40
|
+
label: string,
|
|
41
|
+
names: string[],
|
|
42
|
+
): void {
|
|
43
|
+
const labelRect = findTextRect(ascii, label)
|
|
44
|
+
for (const name of names) {
|
|
45
|
+
const boxRect = findBoxRect(ascii, name)
|
|
46
|
+
expect(
|
|
47
|
+
rectsOverlap(labelRect, boxRect),
|
|
48
|
+
`label "${label}" lands inside ${name}'s box`,
|
|
49
|
+
).toBe(false)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe('ASCII ER diagrams — entity boxes never overlap (issue #350 family)', () => {
|
|
54
|
+
it('2 entities, 1 direct relationship, short label', () => {
|
|
55
|
+
const src = `erDiagram
|
|
56
|
+
A ||--o{ B : owns`
|
|
57
|
+
const ascii = renderMermaidASCII(src, { useAscii: true })
|
|
58
|
+
expectNoBoxOverlap(ascii, ['A', 'B'])
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('2 entities, direct, long label', () => {
|
|
62
|
+
const src = `erDiagram
|
|
63
|
+
A ||--o{ B : ships_to_a_regional_warehouse`
|
|
64
|
+
const ascii = renderMermaidASCII(src, { useAscii: true })
|
|
65
|
+
expectNoBoxOverlap(ascii, ['A', 'B'])
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('2 entities, direct, long identifier names', () => {
|
|
69
|
+
const src = `erDiagram
|
|
70
|
+
CUSTOMER_ACCOUNT_PROFILE ||--o{ SHIPPING_ADDRESS_RECORD : has`
|
|
71
|
+
const ascii = renderMermaidASCII(src, { useAscii: true })
|
|
72
|
+
expectNoBoxOverlap(ascii, [
|
|
73
|
+
'CUSTOMER_ACCOUNT_PROFILE',
|
|
74
|
+
'SHIPPING_ADDRESS_RECORD',
|
|
75
|
+
])
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('3 entities, adjacent-only chain (smoke case)', () => {
|
|
79
|
+
const src = `erDiagram
|
|
80
|
+
A ||--o{ B : one
|
|
81
|
+
B ||--o{ C : two`
|
|
82
|
+
const ascii = renderMermaidASCII(src, { useAscii: true })
|
|
83
|
+
expectNoBoxOverlap(ascii, ['A', 'B', 'C'])
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('3 entities same row, A-C skipping B (direct issue #350 shape)', () => {
|
|
87
|
+
const src = `erDiagram
|
|
88
|
+
A ||--o{ B : one
|
|
89
|
+
A ||--o| C : skips_b`
|
|
90
|
+
const ascii = renderMermaidASCII(src, { useAscii: true })
|
|
91
|
+
expectNoBoxOverlap(ascii, ['A', 'B', 'C'])
|
|
92
|
+
expectLabelOutsideBoxes(ascii, 'skips_b', ['B'])
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('3 entities, A-C skip B, long multi-word label', () => {
|
|
96
|
+
const src = `erDiagram
|
|
97
|
+
A ||--o{ B : one
|
|
98
|
+
A ||--o| C : a_very_long_descriptive_relationship_name`
|
|
99
|
+
const ascii = renderMermaidASCII(src, { useAscii: true })
|
|
100
|
+
expectNoBoxOverlap(ascii, ['A', 'B', 'C'])
|
|
101
|
+
expectLabelOutsideBoxes(
|
|
102
|
+
ascii,
|
|
103
|
+
'a_very_long_descriptive_relationship_name',
|
|
104
|
+
['B'],
|
|
105
|
+
)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('3 entities, A-C skip B, long names', () => {
|
|
109
|
+
const src = `erDiagram
|
|
110
|
+
ALPHA_ENTITY ||--o{ BRAVO_ENTITY : one
|
|
111
|
+
ALPHA_ENTITY ||--o| CHARLIE_ENTITY : skips_b`
|
|
112
|
+
const ascii = renderMermaidASCII(src, { useAscii: true })
|
|
113
|
+
expectNoBoxOverlap(ascii, [
|
|
114
|
+
'ALPHA_ENTITY',
|
|
115
|
+
'BRAVO_ENTITY',
|
|
116
|
+
'CHARLIE_ENTITY',
|
|
117
|
+
])
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('5 entities, 3-per-row layout, A-C skip B in row 1', () => {
|
|
121
|
+
const src = `erDiagram
|
|
122
|
+
A ||--o{ B : one
|
|
123
|
+
A ||--o| C : skips_b
|
|
124
|
+
A ||--o{ D : two
|
|
125
|
+
A ||--o{ E : three`
|
|
126
|
+
const ascii = renderMermaidASCII(src, { useAscii: true })
|
|
127
|
+
expectNoBoxOverlap(ascii, ['A', 'B', 'C', 'D', 'E'])
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('5 entities, two same-row obstructed pairs sharing the row-gap band', () => {
|
|
131
|
+
const src = `erDiagram
|
|
132
|
+
A ||--o{ B : one
|
|
133
|
+
A ||--o| C : first_skip
|
|
134
|
+
D ||--o{ E : two
|
|
135
|
+
A ||--o{ D : bridge
|
|
136
|
+
D ||--o| B : second_skip`
|
|
137
|
+
const ascii = renderMermaidASCII(src, { useAscii: true })
|
|
138
|
+
expectNoBoxOverlap(ascii, ['A', 'B', 'C', 'D', 'E'])
|
|
139
|
+
const firstSkip = findTextRect(ascii, 'first_skip')
|
|
140
|
+
const secondSkip = findTextRect(ascii, 'second_skip')
|
|
141
|
+
expect(
|
|
142
|
+
rectsOverlap(firstSkip, secondSkip),
|
|
143
|
+
'first_skip and second_skip labels overlap each other',
|
|
144
|
+
).toBe(false)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('5 entities, vertical relationship whose row-mate is taller', () => {
|
|
148
|
+
const src = `erDiagram
|
|
149
|
+
A ||--o{ B : one
|
|
150
|
+
A {
|
|
151
|
+
string id
|
|
152
|
+
string a1
|
|
153
|
+
string a2
|
|
154
|
+
string a3
|
|
155
|
+
string a4
|
|
156
|
+
string a5
|
|
157
|
+
}
|
|
158
|
+
C ||--o{ D : two
|
|
159
|
+
A ||--o| D : cross_row`
|
|
160
|
+
const ascii = renderMermaidASCII(src, { useAscii: true })
|
|
161
|
+
expectNoBoxOverlap(ascii, ['A', 'B', 'C', 'D'])
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('7 entities, multi-row skip spanning 3 rows', () => {
|
|
165
|
+
const src = `erDiagram
|
|
166
|
+
A ||--o{ B : r1a
|
|
167
|
+
A ||--o{ C : r1b
|
|
168
|
+
D ||--o{ E : r2a
|
|
169
|
+
D ||--o{ F : r2b
|
|
170
|
+
G ||--o{ A : r3
|
|
171
|
+
G ||--o| D : long_skip_across_rows`
|
|
172
|
+
const ascii = renderMermaidASCII(src, { useAscii: true })
|
|
173
|
+
expectNoBoxOverlap(ascii, ['A', 'B', 'C', 'D', 'E', 'F', 'G'])
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('4 entities, mixed same-row + cross-row + diagonal relationship', () => {
|
|
177
|
+
const src = `erDiagram
|
|
178
|
+
A ||--o{ B : same_row
|
|
179
|
+
C ||--o{ D : same_row_2
|
|
180
|
+
A ||--o| D : diagonal`
|
|
181
|
+
const ascii = renderMermaidASCII(src, { useAscii: true })
|
|
182
|
+
expectNoBoxOverlap(ascii, ['A', 'B', 'C', 'D'])
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('2 entities, parallel relationships (2 labels, same pair)', () => {
|
|
186
|
+
const src = `erDiagram
|
|
187
|
+
A ||--o{ B : places_order
|
|
188
|
+
A ||--o{ B : returns_item`
|
|
189
|
+
const ascii = renderMermaidASCII(src, { useAscii: true })
|
|
190
|
+
const label1 = findTextRect(ascii, 'places_order')
|
|
191
|
+
const label2 = findTextRect(ascii, 'returns_item')
|
|
192
|
+
expect(
|
|
193
|
+
rectsOverlap(label1, label2),
|
|
194
|
+
'the two relationship labels overlap',
|
|
195
|
+
).toBe(false)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it("3 entities, two-char crow's-foot markers, minimal gap", () => {
|
|
199
|
+
const src = `erDiagram
|
|
200
|
+
A }o--o{ B : m2m
|
|
201
|
+
B }o--o{ C : m2m2`
|
|
202
|
+
const ascii = renderMermaidASCII(src, { useAscii: true })
|
|
203
|
+
expectNoBoxOverlap(ascii, ['A', 'B', 'C'])
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it('6 entities, 2 disconnected components, each with same-row obstruction', () => {
|
|
207
|
+
const src = `erDiagram
|
|
208
|
+
A ||--o{ B : one
|
|
209
|
+
A ||--o| C : skip_1
|
|
210
|
+
|
|
211
|
+
D ||--o{ E : two
|
|
212
|
+
D ||--o| F : skip_2`
|
|
213
|
+
const ascii = renderMermaidASCII(src, { useAscii: true })
|
|
214
|
+
expectNoBoxOverlap(ascii, ['A', 'B', 'C', 'D', 'E', 'F'])
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('3 entities, A-C skip B, B has zero attributes (short box)', () => {
|
|
218
|
+
const src = `erDiagram
|
|
219
|
+
A ||--o{ B : one
|
|
220
|
+
A ||--o| C : skip_short_b`
|
|
221
|
+
const ascii = renderMermaidASCII(src, { useAscii: true })
|
|
222
|
+
expectNoBoxOverlap(ascii, ['A', 'B', 'C'])
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('3 entities, A-C skip B, B has 8 attributes (tall box)', () => {
|
|
226
|
+
const src = `erDiagram
|
|
227
|
+
A ||--o{ B : one
|
|
228
|
+
B {
|
|
229
|
+
string id
|
|
230
|
+
string b1
|
|
231
|
+
string b2
|
|
232
|
+
string b3
|
|
233
|
+
string b4
|
|
234
|
+
string b5
|
|
235
|
+
string b6
|
|
236
|
+
string b7
|
|
237
|
+
}
|
|
238
|
+
A ||--o| C : skip_tall_b`
|
|
239
|
+
const ascii = renderMermaidASCII(src, { useAscii: true })
|
|
240
|
+
expectNoBoxOverlap(ascii, ['A', 'B', 'C'])
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
it('4 entities in one row, A-D skipping 2 (B and C)', () => {
|
|
244
|
+
const src = `erDiagram
|
|
245
|
+
A ||--o{ B : one
|
|
246
|
+
B ||--o{ C : two
|
|
247
|
+
A ||--o| D : skip_two`
|
|
248
|
+
const ascii = renderMermaidASCII(src, { useAscii: true })
|
|
249
|
+
expectNoBoxOverlap(ascii, ['A', 'B', 'C', 'D'])
|
|
250
|
+
})
|
|
251
|
+
})
|