@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,159 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// ASCII sequence diagrams — `box ... end` participant grouping (#419)
|
|
3
|
+
//
|
|
4
|
+
// A non-empty box becomes a labelled bracket around its members' header
|
|
5
|
+
// boxes, and an unlabelled bracket around their footer boxes, spanning from
|
|
6
|
+
// the leftmost to the rightmost member. Colour is not representable in
|
|
7
|
+
// ASCII and is ignored (only the label, if any, is drawn).
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect } from 'vitest'
|
|
11
|
+
import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
|
|
12
|
+
|
|
13
|
+
describe('ASCII sequence diagrams – box...end', () => {
|
|
14
|
+
const source = `sequenceDiagram
|
|
15
|
+
box Aqua Group 1
|
|
16
|
+
participant A
|
|
17
|
+
participant B
|
|
18
|
+
end
|
|
19
|
+
participant C
|
|
20
|
+
A->>B: hello
|
|
21
|
+
B->>C: world`
|
|
22
|
+
|
|
23
|
+
it('draws a labelled bracket around the grouped participants (Unicode)', () => {
|
|
24
|
+
const result = renderMermaidASCII(source, { useAscii: false })
|
|
25
|
+
expect(result).toBe(
|
|
26
|
+
[
|
|
27
|
+
' ┌─ Group 1 ────────┐ ',
|
|
28
|
+
' │ ┌───┐ ┌───┐ │ ┌───┐ ',
|
|
29
|
+
' │ │ A │ │ B │ │ │ C │ ',
|
|
30
|
+
' │ └─┬─┘ └─┬─┘ │ └─┬─┘ ',
|
|
31
|
+
' └───┼──────────┼───┘ │ ',
|
|
32
|
+
' │ │ │ ',
|
|
33
|
+
' │ hello │ │ ',
|
|
34
|
+
' │──────────▶ │ ',
|
|
35
|
+
' │ │ │ ',
|
|
36
|
+
' │ │ world │ ',
|
|
37
|
+
' │ │──────────▶ ',
|
|
38
|
+
' │ │ │ ',
|
|
39
|
+
' ┌───┼──────────┼───┐ │ ',
|
|
40
|
+
' │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ',
|
|
41
|
+
' │ │ A │ │ B │ │ │ C │ ',
|
|
42
|
+
' │ └───┘ └───┘ │ └───┘ ',
|
|
43
|
+
' └──────────────────┘ ',
|
|
44
|
+
].join('\n'),
|
|
45
|
+
)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('uses the plain-ASCII cross and corners in ASCII mode', () => {
|
|
49
|
+
const result = renderMermaidASCII(source, { useAscii: true })
|
|
50
|
+
expect(result).toBe(
|
|
51
|
+
[
|
|
52
|
+
' +- Group 1 --------+ ',
|
|
53
|
+
' | +---+ +---+ | +---+ ',
|
|
54
|
+
' | | A | | B | | | C | ',
|
|
55
|
+
' | +---+ +---+ | +---+ ',
|
|
56
|
+
' +---+----------+---+ | ',
|
|
57
|
+
' | | | ',
|
|
58
|
+
' | hello | | ',
|
|
59
|
+
' |----------> | ',
|
|
60
|
+
' | | | ',
|
|
61
|
+
' | | world | ',
|
|
62
|
+
' | |----------> ',
|
|
63
|
+
' | | | ',
|
|
64
|
+
' +---+----------+---+ | ',
|
|
65
|
+
' | +---+ +---+ | +---+ ',
|
|
66
|
+
' | | A | | B | | | C | ',
|
|
67
|
+
' | +---+ +---+ | +---+ ',
|
|
68
|
+
' +------------------+ ',
|
|
69
|
+
].join('\n'),
|
|
70
|
+
)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('draws an unlabelled bracket for a box with a colour but no label', () => {
|
|
74
|
+
const result = renderMermaidASCII(
|
|
75
|
+
`sequenceDiagram
|
|
76
|
+
box Aqua
|
|
77
|
+
participant A
|
|
78
|
+
participant B
|
|
79
|
+
end
|
|
80
|
+
A->>B: hi`,
|
|
81
|
+
{ useAscii: false },
|
|
82
|
+
)
|
|
83
|
+
expect(result).toBe(
|
|
84
|
+
[
|
|
85
|
+
' ┌─────────────────┐ ',
|
|
86
|
+
' │ ┌───┐ ┌───┐ │ ',
|
|
87
|
+
' │ │ A │ │ B │ │ ',
|
|
88
|
+
' │ └─┬─┘ └─┬─┘ │ ',
|
|
89
|
+
' └───┼─────────┼───┘ ',
|
|
90
|
+
' │ │ ',
|
|
91
|
+
' │ hi │ ',
|
|
92
|
+
' │─────────▶ ',
|
|
93
|
+
' │ │ ',
|
|
94
|
+
' ┌───┼─────────┼───┐ ',
|
|
95
|
+
' │ ┌─┴─┐ ┌─┴─┐ │ ',
|
|
96
|
+
' │ │ A │ │ B │ │ ',
|
|
97
|
+
' │ └───┘ └───┘ │ ',
|
|
98
|
+
' └─────────────────┘ ',
|
|
99
|
+
].join('\n'),
|
|
100
|
+
)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('lets a loop opened inside a box nest cleanly inside the bracket', () => {
|
|
104
|
+
const result = renderMermaidASCII(
|
|
105
|
+
`sequenceDiagram
|
|
106
|
+
box G1
|
|
107
|
+
participant A
|
|
108
|
+
participant B
|
|
109
|
+
loop every day
|
|
110
|
+
A->>B: hi
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
A->>B: bye`,
|
|
114
|
+
{ useAscii: false },
|
|
115
|
+
)
|
|
116
|
+
expect(result).toBe(
|
|
117
|
+
[
|
|
118
|
+
' ┌─ G1 ────────────┐ ',
|
|
119
|
+
' │ ┌───┐ ┌───┐ │ ',
|
|
120
|
+
' │ │ A │ │ B │ │ ',
|
|
121
|
+
' │ └─┬─┘ └─┬─┘ │ ',
|
|
122
|
+
' └───┼─────────┼───┘ ',
|
|
123
|
+
' │ │ ',
|
|
124
|
+
' ┌loop [every day]─┐ ',
|
|
125
|
+
' │ │ │ │ ',
|
|
126
|
+
' │ │ hi │ │ ',
|
|
127
|
+
' │ │─────────▶ │ ',
|
|
128
|
+
' │ │ │ │ ',
|
|
129
|
+
' └─────────────────┘ ',
|
|
130
|
+
' │ │ ',
|
|
131
|
+
' │ bye │ ',
|
|
132
|
+
' │─────────▶ ',
|
|
133
|
+
' │ │ ',
|
|
134
|
+
' ┌───┼─────────┼───┐ ',
|
|
135
|
+
' │ ┌─┴─┐ ┌─┴─┐ │ ',
|
|
136
|
+
' │ │ A │ │ B │ │ ',
|
|
137
|
+
' │ └───┘ └───┘ │ ',
|
|
138
|
+
' └─────────────────┘ ',
|
|
139
|
+
].join('\n'),
|
|
140
|
+
)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('does not draw a bracket for a box with no members', () => {
|
|
144
|
+
const withEmptyBox = renderMermaidASCII(
|
|
145
|
+
`sequenceDiagram
|
|
146
|
+
box Empty
|
|
147
|
+
end
|
|
148
|
+
A->>B: hi`,
|
|
149
|
+
{ useAscii: false },
|
|
150
|
+
)
|
|
151
|
+
const withoutBox = renderMermaidASCII(
|
|
152
|
+
`sequenceDiagram
|
|
153
|
+
A->>B: hi`,
|
|
154
|
+
{ useAscii: false },
|
|
155
|
+
)
|
|
156
|
+
expect(withEmptyBox).not.toContain('Empty')
|
|
157
|
+
expect(withEmptyBox).toBe(withoutBox)
|
|
158
|
+
})
|
|
159
|
+
})
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression tests for issue #334 — sequence-diagram participant boxes and
|
|
3
|
+
* message labels don't account for double-width CJK characters, so box
|
|
4
|
+
* borders no longer line up with their content once the diagram contains
|
|
5
|
+
* Japanese/Korean/Chinese text.
|
|
6
|
+
*
|
|
7
|
+
* This is the same bug class already fixed for flowchart boxes (#66,
|
|
8
|
+
* `ascii-cjk-width.test.ts`) and class/ER boxes (#182,
|
|
9
|
+
* `ascii-multibox-cjk-width.test.ts`) — `src/ascii/sequence.ts` measured and
|
|
10
|
+
* wrote actor-box content, message labels, block headers/dividers, and note
|
|
11
|
+
* content by UTF-16 code unit (`.length` / per-code-unit `for` loops)
|
|
12
|
+
* instead of the display-width-aware helpers in `src/ascii/display-width.ts`
|
|
13
|
+
* (`displayWidth` / `toDisplayCells`), which `maxLineWidth`
|
|
14
|
+
* (`multiline-utils.ts`) already used correctly for *sizing* — only the
|
|
15
|
+
* *drawing* loops were the gap.
|
|
16
|
+
*
|
|
17
|
+
* Width is measured with `terminalDisplayWidth`, an oracle independently
|
|
18
|
+
* re-implemented from `src/ascii/display-width.ts` (see the shared helper's
|
|
19
|
+
* own doc comment), so these tests verify rendered output against an
|
|
20
|
+
* authority separate from the implementation under test.
|
|
21
|
+
*
|
|
22
|
+
* NOTE on the Unicode-charset arrowhead glyphs (▶ ◀ ◁ ▷): these are
|
|
23
|
+
* pre-existing, unrelated to #334, and out of scope here. `isWideChar`
|
|
24
|
+
* (text-metrics.ts) treats any `\p{Extended_Pictographic}` code point as
|
|
25
|
+
* double-width, which also matches these geometric-shape arrowheads even
|
|
26
|
+
* though their Unicode East Asian Width property is "Ambiguous" (narrow in
|
|
27
|
+
* a default terminal locale) — so a row containing one of them measures one
|
|
28
|
+
* column wider than an otherwise-identical row without one, in *every*
|
|
29
|
+
* Unicode-charset sequence diagram, CJK or not. `assertUniformDisplayWidth`
|
|
30
|
+
* (used unmodified by the flowchart/class/ER suites, which draw ASCII
|
|
31
|
+
* `>`/`<`/`^`/`v` arrowheads instead) would spuriously fail on any arrow row
|
|
32
|
+
* here, so this file measures specific row pairs instead of whole-diagram
|
|
33
|
+
* uniformity, and separately guards that only arrowhead-glyph rows are ever
|
|
34
|
+
* the odd one out.
|
|
35
|
+
*/
|
|
36
|
+
import { describe, it, expect } from 'vitest'
|
|
37
|
+
import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
|
|
38
|
+
import { terminalDisplayWidth } from './helpers/terminal-display-width.ts'
|
|
39
|
+
|
|
40
|
+
const ARROWHEAD_GLYPHS = ['▶', '◀', '◁', '▷']
|
|
41
|
+
|
|
42
|
+
/** True if `line` contains one of the ambiguous-width arrowhead glyphs. */
|
|
43
|
+
function hasArrowheadGlyph(line: string): boolean {
|
|
44
|
+
return ARROWHEAD_GLYPHS.some((g) => line.includes(g))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Every rendered row shares one terminal display width, *except* rows
|
|
49
|
+
* containing an arrowhead glyph (see the file-level doc comment) — those
|
|
50
|
+
* are allowed to be exactly one column wider than the rest.
|
|
51
|
+
*/
|
|
52
|
+
function assertUniformWidthIgnoringArrowheads(ascii: string): void {
|
|
53
|
+
const lines = ascii.split('\n')
|
|
54
|
+
const widths = lines.map(terminalDisplayWidth)
|
|
55
|
+
const plainWidths = widths.filter((_, i) => !hasArrowheadGlyph(lines[i]!))
|
|
56
|
+
const expected = plainWidths[0]
|
|
57
|
+
for (let i = 0; i < lines.length; i++) {
|
|
58
|
+
const allowance = hasArrowheadGlyph(lines[i]!) ? 1 : 0
|
|
59
|
+
expect(
|
|
60
|
+
widths[i],
|
|
61
|
+
`line ${i} (${JSON.stringify(lines[i])}) has display width ${widths[i]}, expected ${expected} (+${allowance} allowed for an arrowhead glyph)`,
|
|
62
|
+
).toBe(expected! + allowance)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Find the line containing `needle` and the line directly above it, and
|
|
68
|
+
* assert they occupy the same terminal display width — the direct
|
|
69
|
+
* manifestation of "border lines up with its own content" that the issue
|
|
70
|
+
* reports (border 28 columns vs. content 33 columns for the exact repro).
|
|
71
|
+
*/
|
|
72
|
+
function assertContentMatchesBorderAbove(ascii: string, needle: string): void {
|
|
73
|
+
const lines = ascii.split('\n')
|
|
74
|
+
const contentIndex = lines.findIndex((l) => l.includes(needle))
|
|
75
|
+
expect(contentIndex).toBeGreaterThan(0)
|
|
76
|
+
const contentLine = lines[contentIndex]!
|
|
77
|
+
const borderLine = lines[contentIndex - 1]!
|
|
78
|
+
expect(
|
|
79
|
+
terminalDisplayWidth(contentLine),
|
|
80
|
+
`content row ${JSON.stringify(contentLine)} vs. border row ${JSON.stringify(borderLine)}`,
|
|
81
|
+
).toBe(terminalDisplayWidth(borderLine))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
describe('ASCII sequence diagrams — CJK/wide-char box alignment (issue #334)', () => {
|
|
85
|
+
for (const useAscii of [false, true]) {
|
|
86
|
+
describe(useAscii ? 'ASCII charset' : 'Unicode charset', () => {
|
|
87
|
+
// Exact repro from the issue body.
|
|
88
|
+
const mermaid =
|
|
89
|
+
'sequenceDiagram\n participant A as アリス\n participant B as ボブ\n A->>B: こんにちは'
|
|
90
|
+
|
|
91
|
+
it('renders the issue repro with every row sharing one display width (up to arrowheads)', () => {
|
|
92
|
+
const ascii = renderMermaidASCII(mermaid, {
|
|
93
|
+
useAscii,
|
|
94
|
+
colorMode: 'none',
|
|
95
|
+
})
|
|
96
|
+
assertUniformWidthIgnoringArrowheads(ascii)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('aligns the header participant box border with its CJK content', () => {
|
|
100
|
+
const ascii = renderMermaidASCII(mermaid, {
|
|
101
|
+
useAscii,
|
|
102
|
+
colorMode: 'none',
|
|
103
|
+
})
|
|
104
|
+
assertContentMatchesBorderAbove(ascii, 'アリス')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('aligns the footer participant box border with its CJK content', () => {
|
|
108
|
+
const ascii = renderMermaidASCII(mermaid, {
|
|
109
|
+
useAscii,
|
|
110
|
+
colorMode: 'none',
|
|
111
|
+
})
|
|
112
|
+
const lines = ascii.split('\n')
|
|
113
|
+
// The footer box repeats the same label — its content row is the
|
|
114
|
+
// *last* line containing it, not the header's (first) occurrence.
|
|
115
|
+
const contentIndex = lines
|
|
116
|
+
.map((l) => l.includes('アリス'))
|
|
117
|
+
.lastIndexOf(true)
|
|
118
|
+
expect(contentIndex).toBeGreaterThan(0)
|
|
119
|
+
expect(terminalDisplayWidth(lines[contentIndex]!)).toBe(
|
|
120
|
+
terminalDisplayWidth(lines[contentIndex - 1]!),
|
|
121
|
+
)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('centers the CJK message label between lifelines without widening its row', () => {
|
|
125
|
+
const ascii = renderMermaidASCII(mermaid, {
|
|
126
|
+
useAscii,
|
|
127
|
+
colorMode: 'none',
|
|
128
|
+
})
|
|
129
|
+
const lines = ascii.split('\n')
|
|
130
|
+
const labelIndex = lines.findIndex((l) => l.includes('こんにちは'))
|
|
131
|
+
expect(labelIndex).toBeGreaterThan(0)
|
|
132
|
+
// Neighboring lifeline-only rows (blank rows immediately above/below
|
|
133
|
+
// the label) share the same fixed canvas width and contain no
|
|
134
|
+
// arrowhead glyph — a direct, quirk-free comparison.
|
|
135
|
+
const blankAbove = lines[labelIndex - 1]!
|
|
136
|
+
expect(terminalDisplayWidth(lines[labelIndex]!)).toBe(
|
|
137
|
+
terminalDisplayWidth(blankAbove),
|
|
138
|
+
)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('does not corrupt the label text itself', () => {
|
|
142
|
+
const ascii = renderMermaidASCII(mermaid, {
|
|
143
|
+
useAscii,
|
|
144
|
+
colorMode: 'none',
|
|
145
|
+
})
|
|
146
|
+
expect(ascii).toContain('アリス')
|
|
147
|
+
expect(ascii).toContain('ボブ')
|
|
148
|
+
expect(ascii).toContain('こんにちは')
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
it('handles a mixed CJK + Latin actor name', () => {
|
|
154
|
+
const mermaid =
|
|
155
|
+
'sequenceDiagram\n participant A as Aliceさん\n participant B as Bob\n A->>B: hi'
|
|
156
|
+
const ascii = renderMermaidASCII(mermaid, { colorMode: 'none' })
|
|
157
|
+
assertUniformWidthIgnoringArrowheads(ascii)
|
|
158
|
+
assertContentMatchesBorderAbove(ascii, 'Aliceさん')
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('handles a CJK self-message label without corrupting the canvas', () => {
|
|
162
|
+
const mermaid =
|
|
163
|
+
'sequenceDiagram\n participant A as アリス\n A->>A: 独り言のテスト'
|
|
164
|
+
const ascii = renderMermaidASCII(mermaid, { colorMode: 'none' })
|
|
165
|
+
assertUniformWidthIgnoringArrowheads(ascii)
|
|
166
|
+
expect(ascii).toContain('独り言のテスト')
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('handles a CJK label inside an alt block without breaking the block wall', () => {
|
|
170
|
+
const mermaid =
|
|
171
|
+
'sequenceDiagram\n participant A as アリス\n participant B as ボブ\n alt 条件が真の場合\n A->>B: メッセージ\n end'
|
|
172
|
+
const ascii = renderMermaidASCII(mermaid, { colorMode: 'none' })
|
|
173
|
+
assertUniformWidthIgnoringArrowheads(ascii)
|
|
174
|
+
expect(ascii).toContain('条件が真の場合')
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('widens the wall to fit an oversized CJK block header instead of clipping it (#352/#387)', () => {
|
|
178
|
+
// A block's wall now widens to fit its header/divider label (#352,
|
|
179
|
+
// extended by #387 to also keep the widened wall clear of any
|
|
180
|
+
// participant it doesn't enclose) rather than clipping a label longer
|
|
181
|
+
// than the messages' own lifeline span. `writeTextCells`' `exclusiveMaxX`
|
|
182
|
+
// clip (added here for #334) is still a defensive backstop — exercised
|
|
183
|
+
// by the other display-width-vs-clip-bound cases in this file — but a
|
|
184
|
+
// block header specifically no longer reaches it: `maxBlockLabelWidth`
|
|
185
|
+
// measures the label with the same CJK-aware `displayWidth` this file's
|
|
186
|
+
// other tests check, so the wall grows enough that the clip never
|
|
187
|
+
// engages. This asserts the full label renders, uncorrupted, with the
|
|
188
|
+
// wall closing immediately after it.
|
|
189
|
+
const mermaid =
|
|
190
|
+
'sequenceDiagram\n participant A as ア\n participant B as ボ\n alt 非常に長い条件のラベルがここに続きますよずっと\n A->>B: x\n end'
|
|
191
|
+
const ascii = renderMermaidASCII(mermaid, { colorMode: 'none' })
|
|
192
|
+
assertUniformWidthIgnoringArrowheads(ascii)
|
|
193
|
+
const lines = ascii.split('\n')
|
|
194
|
+
const headerLine = lines.find((l) => l.includes('alt ['))!
|
|
195
|
+
expect(headerLine).toBeDefined()
|
|
196
|
+
// Full label present, not clipped.
|
|
197
|
+
expect(headerLine).toContain(
|
|
198
|
+
'非常に長い条件のラベルがここに続きますよずっと',
|
|
199
|
+
)
|
|
200
|
+
// The wall's right corner must close immediately after the label,
|
|
201
|
+
// well-formed — proves the wall grew to fit rather than the label
|
|
202
|
+
// merely being drawn past a wall that didn't move.
|
|
203
|
+
expect(headerLine!.endsWith('┐')).toBe(true)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it('handles a CJK divider label inside an alt block', () => {
|
|
207
|
+
const mermaid =
|
|
208
|
+
'sequenceDiagram\n participant A as アリス\n participant B as ボブ\n alt 場合A\n A->>B: one\n else 場合B\n A->>B: two\n end'
|
|
209
|
+
const ascii = renderMermaidASCII(mermaid, { colorMode: 'none' })
|
|
210
|
+
assertUniformWidthIgnoringArrowheads(ascii)
|
|
211
|
+
expect(ascii).toContain('場合B')
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('aligns a CJK note box border with its content', () => {
|
|
215
|
+
const mermaid =
|
|
216
|
+
'sequenceDiagram\n participant A as アリス\n Note over A: 重要なメモです'
|
|
217
|
+
const ascii = renderMermaidASCII(mermaid, { colorMode: 'none' })
|
|
218
|
+
assertContentMatchesBorderAbove(ascii, '重要なメモです')
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('leaves the common all-ASCII case intact', () => {
|
|
222
|
+
// Guard against the fix perturbing diagrams with no wide characters —
|
|
223
|
+
// uses the ignoring-arrowheads helper since the Unicode charset still
|
|
224
|
+
// draws an ambiguous-width arrowhead glyph even for plain-ASCII labels.
|
|
225
|
+
const mermaid =
|
|
226
|
+
'sequenceDiagram\n participant Alice\n participant Bob\n Alice->>Bob: hello'
|
|
227
|
+
for (const useAscii of [false, true]) {
|
|
228
|
+
const ascii = renderMermaidASCII(mermaid, { useAscii, colorMode: 'none' })
|
|
229
|
+
assertUniformWidthIgnoringArrowheads(ascii)
|
|
230
|
+
expect(ascii).toContain('Alice')
|
|
231
|
+
expect(ascii).toContain('Bob')
|
|
232
|
+
expect(ascii).toContain('hello')
|
|
233
|
+
}
|
|
234
|
+
})
|
|
235
|
+
})
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// ASCII sequence diagrams — `create participant` / `destroy` lifecycle (#419)
|
|
3
|
+
//
|
|
4
|
+
// A created participant's box sits on the row of the message that creates
|
|
5
|
+
// it (the arrow stops at the box's edge, the lifeline starts under it); a
|
|
6
|
+
// destroyed participant's lifeline ends with a cross on the row under the
|
|
7
|
+
// destroying arrow, and it gets no footer box.
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect } from 'vitest'
|
|
11
|
+
import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
|
|
12
|
+
|
|
13
|
+
const source = `sequenceDiagram
|
|
14
|
+
A->>B: one
|
|
15
|
+
create participant C
|
|
16
|
+
A->>C: two
|
|
17
|
+
destroy B
|
|
18
|
+
A->>B: three`
|
|
19
|
+
|
|
20
|
+
describe('ASCII sequence diagrams – create/destroy', () => {
|
|
21
|
+
it('renders the created box on its creating row and the destroy cross under the destroying arrow', () => {
|
|
22
|
+
const result = renderMermaidASCII(source, { useAscii: false })
|
|
23
|
+
expect(result).toBe(
|
|
24
|
+
[
|
|
25
|
+
' ┌───┐ ┌───┐ ',
|
|
26
|
+
' │ A │ │ B │ ',
|
|
27
|
+
' └─┬─┘ └─┬─┘ ',
|
|
28
|
+
' │ │ ',
|
|
29
|
+
' │ one │ ',
|
|
30
|
+
' │──────────▶ ',
|
|
31
|
+
' │ │ ',
|
|
32
|
+
' │ two ',
|
|
33
|
+
' │ │ ┌───┐ ',
|
|
34
|
+
' │─────────────────▶│ C │ ',
|
|
35
|
+
' │ │ └─┬─┘ ',
|
|
36
|
+
' │ │ │ ',
|
|
37
|
+
' │ three │ │ ',
|
|
38
|
+
' │──────────▶ │ ',
|
|
39
|
+
' │ ✕ │ ',
|
|
40
|
+
' │ │ ',
|
|
41
|
+
' ┌─┴─┐ ┌─┴─┐ ',
|
|
42
|
+
' │ A │ │ C │ ',
|
|
43
|
+
' └───┘ └───┘ ',
|
|
44
|
+
].join('\n'),
|
|
45
|
+
)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('uses the plain-ASCII cross and corners in ASCII mode', () => {
|
|
49
|
+
const result = renderMermaidASCII(source, { useAscii: true })
|
|
50
|
+
expect(result).toBe(
|
|
51
|
+
[
|
|
52
|
+
' +---+ +---+ ',
|
|
53
|
+
' | A | | B | ',
|
|
54
|
+
' +---+ +---+ ',
|
|
55
|
+
' | | ',
|
|
56
|
+
' | one | ',
|
|
57
|
+
' |----------> ',
|
|
58
|
+
' | | ',
|
|
59
|
+
' | two ',
|
|
60
|
+
' | | +---+ ',
|
|
61
|
+
' |----------------->| C | ',
|
|
62
|
+
' | | +---+ ',
|
|
63
|
+
' | | | ',
|
|
64
|
+
' | three | | ',
|
|
65
|
+
' |----------> | ',
|
|
66
|
+
' | x | ',
|
|
67
|
+
' | | ',
|
|
68
|
+
' +---+ +---+ ',
|
|
69
|
+
' | A | | C | ',
|
|
70
|
+
' +---+ +---+ ',
|
|
71
|
+
].join('\n'),
|
|
72
|
+
)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('stops a right-to-left creating arrow at the right edge of the created box', () => {
|
|
76
|
+
const result = renderMermaidASCII(
|
|
77
|
+
`sequenceDiagram
|
|
78
|
+
create participant C
|
|
79
|
+
D->>C: hi`,
|
|
80
|
+
{ useAscii: false },
|
|
81
|
+
)
|
|
82
|
+
expect(result).toBe(
|
|
83
|
+
[
|
|
84
|
+
' ┌───┐ ',
|
|
85
|
+
' │ D │ ',
|
|
86
|
+
' └─┬─┘ ',
|
|
87
|
+
' │ ',
|
|
88
|
+
' hi │ ',
|
|
89
|
+
' ┌───┐ │ ',
|
|
90
|
+
' │ C │◀──────│ ',
|
|
91
|
+
' └─┬─┘ │ ',
|
|
92
|
+
' │ │ ',
|
|
93
|
+
' ┌─┴─┐ ┌─┴─┐ ',
|
|
94
|
+
' │ C │ │ D │ ',
|
|
95
|
+
' └───┘ └───┘ ',
|
|
96
|
+
].join('\n'),
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('keeps the alias and stick figure of a created actor, and drops nothing into the text', () => {
|
|
101
|
+
const result = renderMermaidASCII(
|
|
102
|
+
`sequenceDiagram
|
|
103
|
+
A->>B: x
|
|
104
|
+
create actor D as Donald
|
|
105
|
+
B->>D: hi`,
|
|
106
|
+
{ useAscii: false },
|
|
107
|
+
)
|
|
108
|
+
expect(result).toContain('Donald')
|
|
109
|
+
expect(result).toContain('/|\\')
|
|
110
|
+
expect(result).not.toContain('create')
|
|
111
|
+
// Donald's box is not in the header row: the first line holds only A and B
|
|
112
|
+
expect(result.split('\n')[1]).not.toContain('Donald')
|
|
113
|
+
})
|
|
114
|
+
})
|