zombie-mermaid 2.2.1 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -0
- package/README.md +7 -2
- package/dist/ascii.cjs +9 -9
- package/dist/ascii.cjs.map +1 -1
- package/dist/ascii.js +4069 -3969
- package/dist/ascii.js.map +1 -1
- package/dist/cli.js +7605 -7381
- package/dist/index.cjs +52 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7613 -7459
- package/dist/index.js.map +1 -1
- package/dist/mcp.cjs +14142 -13717
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.js +14142 -13717
- package/dist/mcp.js.map +1 -1
- package/package.json +27 -5
- package/packages/ascii-renderer/LICENSE +22 -0
- package/{src/ascii → packages/ascii-renderer/src}/class-diagram.ts +277 -57
- package/{src/ascii → packages/ascii-renderer/src}/er-diagram.ts +2 -2
- package/packages/ascii-renderer/src/flowchart.ts +94 -0
- package/{src/ascii → packages/ascii-renderer/src}/grid.ts +23 -0
- package/{src/ascii → packages/ascii-renderer/src}/index.ts +13 -84
- package/{src/ascii → packages/ascii-renderer/src}/registry.ts +23 -12
- package/{src/ascii → packages/ascii-renderer/src}/sequence.ts +55 -8
- package/{src/ascii → packages/ascii-renderer/src}/xychart.ts +6 -3
- package/packages/core/LICENSE +22 -0
- package/packages/core/src/click-directive.ts +106 -0
- package/packages/core/src/color-utils.ts +205 -0
- package/packages/core/src/diagram-type.ts +35 -0
- package/packages/core/src/direction-override.ts +46 -0
- package/packages/core/src/direction.ts +56 -0
- package/packages/core/src/index.ts +34 -0
- package/packages/core/src/init-directive.ts +248 -0
- package/packages/core/src/multiline-utils.ts +275 -0
- package/packages/core/src/statements.ts +197 -0
- package/packages/core/src/style-directives.ts +214 -0
- package/packages/core/src/text-metrics.ts +498 -0
- package/packages/core/src/theme.ts +696 -0
- package/packages/core/src/types.ts +670 -0
- package/packages/mcp/LICENSE +22 -0
- package/{src/mcp → packages/mcp/src}/server.ts +34 -2
- package/{src/mcp → packages/mcp/src}/tools/check-sequence-activations.ts +8 -5
- package/packages/mcp/src/tools/fix-sequence-activations.ts +77 -0
- package/{src/mcp → packages/mcp/src}/tools/render-ascii.ts +2 -2
- package/{src/mcp → packages/mcp/src}/tools/render-svg.ts +1 -1
- package/packages/mermaid-parser/LICENSE +22 -0
- package/{src → packages/mermaid-parser/src}/class/parser.ts +86 -8
- package/{src → packages/mermaid-parser/src}/er/parser.ts +59 -26
- package/packages/mermaid-parser/src/index.ts +48 -0
- package/{src → packages/mermaid-parser/src}/sequence/activation-check.ts +4 -3
- package/packages/mermaid-parser/src/sequence/activation-fix.ts +112 -0
- package/{src → packages/mermaid-parser/src}/sequence/parser.ts +74 -16
- package/{src → packages/mermaid-parser/src}/xychart/parser.ts +84 -6
- package/packages/svg-renderer/LICENSE +22 -0
- package/{src → packages/svg-renderer/src}/class/layout.ts +8 -4
- package/{src → packages/svg-renderer/src}/class/renderer.ts +7 -6
- package/packages/svg-renderer/src/edge-curves.ts +204 -0
- package/packages/svg-renderer/src/elk-instance.ts +292 -0
- package/{src → packages/svg-renderer/src}/er/layout.ts +6 -4
- package/{src → packages/svg-renderer/src}/er/renderer.ts +4 -4
- package/packages/svg-renderer/src/index.ts +58 -0
- package/packages/svg-renderer/src/layout-engine/constants.ts +19 -0
- package/packages/svg-renderer/src/layout-engine/edge-bundling.ts +379 -0
- package/packages/svg-renderer/src/layout-engine/elk-adapter-utils.ts +81 -0
- package/packages/svg-renderer/src/layout-engine/elk-graph-builder.ts +240 -0
- package/packages/svg-renderer/src/layout-engine/from-elk.ts +685 -0
- package/packages/svg-renderer/src/layout-engine/layer-alignment.ts +174 -0
- package/packages/svg-renderer/src/layout-engine/to-elk.ts +695 -0
- package/packages/svg-renderer/src/layout-engine.ts +74 -0
- package/packages/svg-renderer/src/layout.ts +8 -0
- package/packages/svg-renderer/src/renderer.ts +1485 -0
- package/packages/svg-renderer/src/resolve-colors.ts +339 -0
- package/{src → packages/svg-renderer/src}/sequence/layout.ts +2 -6
- package/{src → packages/svg-renderer/src}/sequence/renderer.ts +4 -4
- package/packages/svg-renderer/src/shape-clipping.ts +197 -0
- package/packages/svg-renderer/src/styles.ts +118 -0
- package/{src → packages/svg-renderer/src}/xychart/layout.ts +2 -2
- package/{src → packages/svg-renderer/src}/xychart/renderer.ts +6 -6
- package/src/__tests__/ascii-ansi.test.ts +6 -2
- package/src/__tests__/ascii-canvas-write.test.ts +2 -2
- package/src/__tests__/ascii-charset-border-junctions.test.ts +1 -1
- package/src/__tests__/ascii-cjk-width.test.ts +1 -1
- package/src/__tests__/ascii-class-box-occupancy.test.ts +147 -45
- package/src/__tests__/ascii-class-column-width-488-489.test.ts +1 -1
- package/src/__tests__/ascii-class-cross-level-jog-corruption.test.ts +187 -0
- package/src/__tests__/ascii-class-detour-label-routing-487.test.ts +1 -1
- package/src/__tests__/ascii-class-diagram-compartments.test.ts +1 -1
- package/src/__tests__/ascii-class-label-box-collision.test.ts +1 -1
- package/src/__tests__/ascii-class-label-row-collision-531.test.ts +1 -1
- package/src/__tests__/ascii-class-label-territory-row-awareness.test.ts +1 -1
- package/src/__tests__/ascii-class-padding.test.ts +1 -1
- package/src/__tests__/ascii-class-reciprocal-relationships-448.test.ts +86 -1
- package/src/__tests__/ascii-combining-mark-width.test.ts +2 -2
- package/src/__tests__/ascii-coords-overlay.test.ts +2 -2
- package/src/__tests__/ascii-decision-lr-box-start.test.ts +2 -2
- package/src/__tests__/ascii-display-width-unit.test.ts +1 -1
- package/src/__tests__/ascii-draw-arrows-coverage.test.ts +1 -1
- package/src/__tests__/ascii-draw-arrows-single-point-path.test.ts +12 -5
- package/src/__tests__/ascii-draw-bundles.test.ts +5 -5
- package/src/__tests__/ascii-edge-bundling-rank-violation-454.test.ts +1 -1
- package/src/__tests__/ascii-edge-bundling-routing.test.ts +9 -5
- package/src/__tests__/ascii-edge-cross-style-overlap.test.ts +10 -6
- package/src/__tests__/ascii-edge-ending-glyphs.test.ts +1 -1
- package/src/__tests__/ascii-edge-label-diagonal-fallback-418.test.ts +2 -2
- package/src/__tests__/ascii-edge-routing-fixes.test.ts +1 -1
- package/src/__tests__/ascii-edge-routing-single-point-path.test.ts +8 -4
- package/src/__tests__/ascii-edge-styles.test.ts +1 -1
- package/src/__tests__/ascii-emoji-cluster-width.test.ts +2 -2
- package/src/__tests__/ascii-er-box-occupancy.test.ts +1 -1
- package/src/__tests__/ascii-er-cardinality.test.ts +1 -1
- package/src/__tests__/ascii-er-corner-glyphs.test.ts +1 -1
- package/src/__tests__/ascii-er-jog-stray-line.test.ts +1 -1
- package/src/__tests__/ascii-er-label-padding.test.ts +1 -1
- package/src/__tests__/ascii-er-padding.test.ts +1 -1
- package/src/__tests__/ascii-er-relationship-label-corruption-350.test.ts +1 -1
- package/src/__tests__/ascii-er-relationship-overwrite.test.ts +1 -1
- package/src/__tests__/ascii-er-stray-connectors.test.ts +7 -4
- package/src/__tests__/ascii-er-unrelated-stem-separation-411.test.ts +1 -1
- package/src/__tests__/ascii-er-vertical-one-marker.test.ts +1 -1
- package/src/__tests__/ascii-grid-deep-chain.test.ts +4 -4
- package/src/__tests__/ascii-hyperlinks.test.ts +9 -6
- package/src/__tests__/ascii-label-line-terminal-fallback.test.ts +8 -4
- package/src/__tests__/ascii-lane-search.test.ts +1 -1
- package/src/__tests__/ascii-multibox-cjk-width.test.ts +1 -1
- package/src/__tests__/ascii-multiline.test.ts +6 -3
- package/src/__tests__/ascii-nested-subgraph-sibling-order-444.test.ts +8 -5
- package/src/__tests__/ascii-package-boundary.test.ts +47 -37
- package/src/__tests__/ascii-padding-edge-cases.test.ts +1 -1
- package/src/__tests__/ascii-parallel-edges-329.test.ts +8 -5
- package/src/__tests__/ascii-pathfinder-route-edge.test.ts +14 -4
- package/src/__tests__/ascii-reverse-direction-edges-629.test.ts +9 -5
- package/src/__tests__/ascii-sequence-alt-else-label.test.ts +1 -1
- package/src/__tests__/ascii-sequence-block-wall-clearance.test.ts +1 -1
- package/src/__tests__/ascii-sequence-box-group.test.ts +1 -1
- package/src/__tests__/ascii-sequence-cjk-width.test.ts +1 -1
- package/src/__tests__/ascii-sequence-create-destroy.test.ts +1 -1
- package/src/__tests__/ascii-sequence-form-invariants.test.ts +48 -50
- package/src/__tests__/ascii-sequence-mermaid-parity.test.ts +1 -1
- package/src/__tests__/ascii-sequence-notes.test.ts +1 -1
- package/src/__tests__/ascii-sequence-padding.test.ts +1 -1
- package/src/__tests__/ascii-sequence-self-arrow.test.ts +1 -1
- package/src/__tests__/ascii-shape-diamond.test.ts +3 -3
- package/src/__tests__/ascii-shape-rectangle.test.ts +5 -5
- package/src/__tests__/ascii-shape-rounded.test.ts +3 -3
- package/src/__tests__/ascii-shapes-circle.test.ts +3 -3
- package/src/__tests__/ascii-shapes-hexagon.test.ts +3 -3
- package/src/__tests__/ascii-shapes-special.test.ts +10 -5
- package/src/__tests__/ascii-shapes-stadium.test.ts +3 -3
- package/src/__tests__/ascii-shapes-state.test.ts +15 -5
- package/src/__tests__/ascii-state-bidirectional-label-swap-530.test.ts +1 -1
- package/src/__tests__/ascii-subgraph-bbox.test.ts +8 -4
- package/src/__tests__/ascii-subgraph-direction-honored-445.test.ts +1 -1
- package/src/__tests__/ascii-subgraph-label-border-clip.test.ts +88 -25
- package/src/__tests__/ascii-subgraph-later-edgeless-301.test.ts +8 -4
- package/src/__tests__/ascii-subgraph-title-padding.test.ts +1 -1
- package/src/__tests__/ascii-territory-unit.test.ts +2 -2
- package/src/__tests__/ascii-validate.test.ts +1 -1
- package/src/__tests__/ascii.test.ts +5 -2
- package/src/__tests__/box-color.test.ts +1 -1
- package/src/__tests__/class-arrow-directions.test.ts +1 -1
- package/src/__tests__/class-click-interactions.test.ts +3 -6
- package/src/__tests__/class-integration.test.ts +1 -1
- package/src/__tests__/class-notes.test.ts +3 -3
- package/src/__tests__/class-parser-error-messages.test.ts +162 -0
- package/src/__tests__/class-parser.test.ts +6 -6
- package/src/__tests__/class-styling.test.ts +2 -2
- package/src/__tests__/cli-render.test.ts +16 -1
- package/src/__tests__/direction-override.test.ts +1 -1
- package/src/__tests__/draw-lines.test.ts +2 -2
- package/src/__tests__/edge-cell-styles.test.ts +5 -2
- package/src/__tests__/elk-layout-cache.test.ts +6 -4
- package/src/__tests__/er-click-unsupported.test.ts +5 -8
- package/src/__tests__/er-parser-error-messages.test.ts +82 -0
- package/src/__tests__/er-parser.test.ts +3 -6
- package/src/__tests__/expanded-shapes.test.ts +1 -1
- package/src/__tests__/grid-occupancy.test.ts +1 -1
- package/src/__tests__/mcp-check-sequence-activations-tool.test.ts +1 -1
- package/src/__tests__/mcp-fix-sequence-activations-tool.test.ts +134 -0
- package/src/__tests__/mcp-render-ascii-tool.test.ts +5 -4
- package/src/__tests__/mcp-render-svg-theme-guard.test.ts +3 -3
- package/src/__tests__/mcp-render-svg-tool.test.ts +1 -1
- package/src/__tests__/mcp-server.test.ts +30 -1
- package/src/__tests__/multiline-labels.test.ts +29 -18
- package/src/__tests__/parser.test.ts +117 -5
- package/src/__tests__/pathfinder.test.ts +11 -4
- package/src/__tests__/render-options-overrides.test.ts +28 -21
- package/src/__tests__/render-options-per-type.test.ts +13 -9
- package/src/__tests__/resolve-colors.test.ts +1 -1
- package/src/__tests__/self-loop-edges.test.ts +112 -0
- package/src/__tests__/sequence-activation-check.test.ts +7 -8
- package/src/__tests__/sequence-activation-fix.test.ts +91 -0
- package/src/__tests__/sequence-layout.test.ts +4 -7
- package/src/__tests__/sequence-parser-syntax-errors.test.ts +179 -0
- package/src/__tests__/sequence-parser.test.ts +3 -6
- package/src/__tests__/statements.test.ts +53 -35
- package/src/__tests__/testdata/ascii/subgraph_direction_override.txt +21 -21
- package/src/__tests__/theme-palette-contrast.test.ts +84 -0
- package/src/__tests__/workspace-package-boundaries.test.ts +199 -25
- package/src/__tests__/xychart-ascii.test.ts +1 -1
- package/src/__tests__/xychart-colors.test.ts +3 -2
- package/src/__tests__/xychart-layout.test.ts +1 -1
- package/src/__tests__/xychart-parser-errors.test.ts +95 -0
- package/src/browser.ts +8 -2
- package/src/cli/mcp.ts +1 -1
- package/src/cli/render.ts +59 -25
- package/src/cli/web.ts +1 -1
- package/src/diagram-registry.ts +190 -25
- package/src/index.ts +34 -131
- package/src/parser.ts +102 -40
- /package/{src/ascii → packages/ascii-renderer/src}/ansi.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/canvas.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/converter.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/coords.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/display-width.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/draw-arrows.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/draw-boxes.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/draw-bundles.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/draw-lines.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/draw-subgraphs.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/draw.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/edge-bundling.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/edge-cell-styles.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/edge-routing.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/grid-occupancy.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/hyperlinks.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/lane-search.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/multiline-utils.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/pathfinder.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/circle.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/corners.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/diamond.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/hexagon.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/index.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/rectangle.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/rounded.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/special.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/stadium.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/state.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/types.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/territory.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/types.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/validate.ts +0 -0
- /package/{src/mcp → packages/mcp/src}/index.ts +0 -0
- /package/{src → packages/mermaid-parser/src}/class/format.ts +0 -0
- /package/{src → packages/mermaid-parser/src}/class/types.ts +0 -0
- /package/{src → packages/mermaid-parser/src}/er/types.ts +0 -0
- /package/{src → packages/mermaid-parser/src}/expanded-shapes.ts +0 -0
- /package/{src → packages/mermaid-parser/src}/sequence/box-color.ts +0 -0
- /package/{src → packages/mermaid-parser/src}/sequence/types.ts +0 -0
- /package/{src → packages/mermaid-parser/src}/xychart/colors.ts +0 -0
- /package/{src → packages/mermaid-parser/src}/xychart/types.ts +0 -0
|
@@ -15,13 +15,18 @@ import { describe, it, expect } from 'vitest'
|
|
|
15
15
|
import { splitStatements, detectDiagramType } from '@zombie-mermaid/core'
|
|
16
16
|
import { renderMermaidASCII, renderMermaidSVG } from '../index.ts'
|
|
17
17
|
|
|
18
|
+
/** Project a `splitStatements` result down to just the text, for assertions
|
|
19
|
+
* that don't care about position (see the dedicated `line` tests below for
|
|
20
|
+
* position coverage). */
|
|
21
|
+
const texts = (source: string) => splitStatements(source).map((s) => s.text)
|
|
22
|
+
|
|
18
23
|
describe('splitStatements (issue #181)', () => {
|
|
19
24
|
it('splits on newlines', () => {
|
|
20
|
-
expect(
|
|
25
|
+
expect(texts('graph TD\nA-->B')).toEqual(['graph TD', 'A-->B'])
|
|
21
26
|
})
|
|
22
27
|
|
|
23
28
|
it('splits on semicolons', () => {
|
|
24
|
-
expect(
|
|
29
|
+
expect(texts('graph TD;A-->B;B-->C')).toEqual([
|
|
25
30
|
'graph TD',
|
|
26
31
|
'A-->B',
|
|
27
32
|
'B-->C',
|
|
@@ -29,59 +34,53 @@ describe('splitStatements (issue #181)', () => {
|
|
|
29
34
|
})
|
|
30
35
|
|
|
31
36
|
it('drops empty statements from trailing or repeated separators', () => {
|
|
32
|
-
expect(
|
|
33
|
-
expect(
|
|
34
|
-
expect(
|
|
35
|
-
'graph TD',
|
|
36
|
-
'A-->B',
|
|
37
|
-
])
|
|
37
|
+
expect(texts('graph TD;A-->B;')).toEqual(['graph TD', 'A-->B'])
|
|
38
|
+
expect(texts('graph TD;;;A-->B')).toEqual(['graph TD', 'A-->B'])
|
|
39
|
+
expect(texts('graph TD\n\n\nA-->B')).toEqual(['graph TD', 'A-->B'])
|
|
38
40
|
})
|
|
39
41
|
|
|
40
42
|
it('trims whitespace around each statement', () => {
|
|
41
|
-
expect(
|
|
42
|
-
'graph TD',
|
|
43
|
-
'A-->B',
|
|
44
|
-
])
|
|
43
|
+
expect(texts(' graph TD ; A-->B ')).toEqual(['graph TD', 'A-->B'])
|
|
45
44
|
})
|
|
46
45
|
|
|
47
46
|
describe('does not split a semicolon that is not a separator', () => {
|
|
48
47
|
it('inside a double-quoted label', () => {
|
|
49
|
-
expect(
|
|
48
|
+
expect(texts('graph TD\nA["a; b"]-->B')).toEqual([
|
|
50
49
|
'graph TD',
|
|
51
50
|
'A["a; b"]-->B',
|
|
52
51
|
])
|
|
53
52
|
})
|
|
54
53
|
|
|
55
54
|
it('inside a single-quoted label', () => {
|
|
56
|
-
expect(
|
|
55
|
+
expect(texts("graph TD\nA['x; y']-->B")).toEqual([
|
|
57
56
|
'graph TD',
|
|
58
57
|
"A['x; y']-->B",
|
|
59
58
|
])
|
|
60
59
|
})
|
|
61
60
|
|
|
62
61
|
it('terminating a named character reference', () => {
|
|
63
|
-
expect(
|
|
62
|
+
expect(texts('graph TD\nA[&]-->B')).toEqual([
|
|
64
63
|
'graph TD',
|
|
65
64
|
'A[&]-->B',
|
|
66
65
|
])
|
|
67
66
|
})
|
|
68
67
|
|
|
69
68
|
it('terminating a numeric character reference', () => {
|
|
70
|
-
expect(
|
|
69
|
+
expect(texts('graph TD\nA[#]-->B')).toEqual([
|
|
71
70
|
'graph TD',
|
|
72
71
|
'A[#]-->B',
|
|
73
72
|
])
|
|
74
73
|
})
|
|
75
74
|
|
|
76
75
|
it('terminating a hex character reference', () => {
|
|
77
|
-
expect(
|
|
76
|
+
expect(texts('graph TD\nA[😀]-->B')).toEqual([
|
|
78
77
|
'graph TD',
|
|
79
78
|
'A[😀]-->B',
|
|
80
79
|
])
|
|
81
80
|
})
|
|
82
81
|
|
|
83
82
|
it('but still splits a real separator on the same line as an entity', () => {
|
|
84
|
-
expect(
|
|
83
|
+
expect(texts('graph TD;A[&]-->B;B-->C')).toEqual([
|
|
85
84
|
'graph TD',
|
|
86
85
|
'A[&]-->B',
|
|
87
86
|
'B-->C',
|
|
@@ -90,26 +89,20 @@ describe('splitStatements (issue #181)', () => {
|
|
|
90
89
|
|
|
91
90
|
it('and treats a lone ampersand as an ordinary character', () => {
|
|
92
91
|
// `A & B` is flowchart's parallel-link syntax, not an entity.
|
|
93
|
-
expect(
|
|
94
|
-
'graph TD',
|
|
95
|
-
'A-->B & C',
|
|
96
|
-
])
|
|
92
|
+
expect(texts('graph TD;A-->B & C')).toEqual(['graph TD', 'A-->B & C'])
|
|
97
93
|
})
|
|
98
94
|
})
|
|
99
95
|
|
|
100
96
|
describe('comments', () => {
|
|
101
97
|
it('drops whole-line comments without splitting their semicolons', () => {
|
|
102
|
-
expect(
|
|
98
|
+
expect(texts('graph TD\n%% note; not code\nA-->B')).toEqual([
|
|
103
99
|
'graph TD',
|
|
104
100
|
'A-->B',
|
|
105
101
|
])
|
|
106
102
|
})
|
|
107
103
|
|
|
108
104
|
it('drops a comment that only begins after a semicolon', () => {
|
|
109
|
-
expect(
|
|
110
|
-
'graph TD',
|
|
111
|
-
'A-->B',
|
|
112
|
-
])
|
|
105
|
+
expect(texts('graph TD\nA-->B; %% note')).toEqual(['graph TD', 'A-->B'])
|
|
113
106
|
})
|
|
114
107
|
|
|
115
108
|
it('lets the comment swallow the rest of the line', () => {
|
|
@@ -118,14 +111,14 @@ describe('splitStatements (issue #181)', () => {
|
|
|
118
111
|
* resurrect what follows. Splitting first and then discarding fragments
|
|
119
112
|
* that merely begin with `%%` left `C-->D` parsing as code.
|
|
120
113
|
*/
|
|
121
|
-
expect(
|
|
114
|
+
expect(texts('graph TD\nA-->B; %% note; C-->D')).toEqual([
|
|
122
115
|
'graph TD',
|
|
123
116
|
'A-->B',
|
|
124
117
|
])
|
|
125
118
|
})
|
|
126
119
|
|
|
127
120
|
it('treats %% inside a quoted label as text, not a comment', () => {
|
|
128
|
-
expect(
|
|
121
|
+
expect(texts('graph TD\nA["100%% done"]-->B')).toEqual([
|
|
129
122
|
'graph TD',
|
|
130
123
|
'A["100%% done"]-->B',
|
|
131
124
|
])
|
|
@@ -144,14 +137,11 @@ describe('splitStatements (issue #181)', () => {
|
|
|
144
137
|
['A[heart #9829; x]-->B', 'a symbol reference'],
|
|
145
138
|
['A[say #quot;hi#quot;]-->B', 'a named reference'],
|
|
146
139
|
])('keeps %s intact (%s)', (statement) => {
|
|
147
|
-
expect(
|
|
148
|
-
'graph TD',
|
|
149
|
-
statement,
|
|
150
|
-
])
|
|
140
|
+
expect(texts(`graph TD\n ${statement}`)).toEqual(['graph TD', statement])
|
|
151
141
|
})
|
|
152
142
|
|
|
153
143
|
it('still splits a real separator on the same line as one', () => {
|
|
154
|
-
expect(
|
|
144
|
+
expect(texts('graph TD;A[#59;]-->B;B-->C')).toEqual([
|
|
155
145
|
'graph TD',
|
|
156
146
|
'A[#59;]-->B',
|
|
157
147
|
'B-->C',
|
|
@@ -159,7 +149,7 @@ describe('splitStatements (issue #181)', () => {
|
|
|
159
149
|
})
|
|
160
150
|
|
|
161
151
|
it('leaves a bare # that is not a reference alone', () => {
|
|
162
|
-
expect(
|
|
152
|
+
expect(texts('graph TD;A[C# lang]-->B')).toEqual([
|
|
163
153
|
'graph TD',
|
|
164
154
|
'A[C# lang]-->B',
|
|
165
155
|
])
|
|
@@ -167,6 +157,34 @@ describe('splitStatements (issue #181)', () => {
|
|
|
167
157
|
})
|
|
168
158
|
})
|
|
169
159
|
|
|
160
|
+
describe('splitStatements — line numbers (issue #760)', () => {
|
|
161
|
+
it('reports the 1-based physical source line for each newline-separated statement', () => {
|
|
162
|
+
const stmts = splitStatements('graph TD\nA-->B\nB-->C')
|
|
163
|
+
expect(stmts.map((s) => s.line)).toEqual([1, 2, 3])
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('carries forward the original line number across dropped blank lines', () => {
|
|
167
|
+
const stmts = splitStatements('graph TD\n\n\nA-->B')
|
|
168
|
+
expect(stmts.map((s) => ({ text: s.text, line: s.line }))).toEqual([
|
|
169
|
+
{ text: 'graph TD', line: 1 },
|
|
170
|
+
{ text: 'A-->B', line: 4 },
|
|
171
|
+
])
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('carries forward the original line number across a dropped comment line', () => {
|
|
175
|
+
const stmts = splitStatements('graph TD\n%% a comment\nA-->B')
|
|
176
|
+
expect(stmts.map((s) => ({ text: s.text, line: s.line }))).toEqual([
|
|
177
|
+
{ text: 'graph TD', line: 1 },
|
|
178
|
+
{ text: 'A-->B', line: 3 },
|
|
179
|
+
])
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('gives every semicolon-separated statement on one physical line the same line number', () => {
|
|
183
|
+
const stmts = splitStatements('graph TD\nA-->B;B-->C;C-->D')
|
|
184
|
+
expect(stmts.map((s) => s.line)).toEqual([1, 2, 2, 2])
|
|
185
|
+
})
|
|
186
|
+
})
|
|
187
|
+
|
|
170
188
|
describe('semicolon-separated diagrams render their bodies (issue #181)', () => {
|
|
171
189
|
const cases = [
|
|
172
190
|
{
|
|
@@ -16,27 +16,27 @@ B --> Y
|
|
|
16
16
|
|
|
|
17
17
|
|
|
|
18
18
|
|
|
|
19
|
-
|
|
20
|
-
|LR
|
|
21
|
-
| |
|
|
22
|
-
| v
|
|
23
|
-
| +---+
|
|
24
|
-
| | |
|
|
25
|
-
| | A |
|
|
26
|
-
| | |
|
|
27
|
-
| +-+-+
|
|
28
|
-
| |
|
|
29
|
-
| |
|
|
30
|
-
| |
|
|
31
|
-
| |
|
|
32
|
-
| v
|
|
33
|
-
| +---+
|
|
34
|
-
| | |
|
|
35
|
-
| | B |
|
|
36
|
-
| | |
|
|
37
|
-
| +-+-+
|
|
38
|
-
| |
|
|
39
|
-
|
|
19
|
+
+---|----+
|
|
20
|
+
|LR Group|
|
|
21
|
+
| | |
|
|
22
|
+
| v |
|
|
23
|
+
| +---+ |
|
|
24
|
+
| | | |
|
|
25
|
+
| | A | |
|
|
26
|
+
| | | |
|
|
27
|
+
| +-+-+ |
|
|
28
|
+
| | |
|
|
29
|
+
| | |
|
|
30
|
+
| | |
|
|
31
|
+
| | |
|
|
32
|
+
| v |
|
|
33
|
+
| +---+ |
|
|
34
|
+
| | | |
|
|
35
|
+
| | B | |
|
|
36
|
+
| | | |
|
|
37
|
+
| +-+-+ |
|
|
38
|
+
| | |
|
|
39
|
+
+---|----+
|
|
40
40
|
|
|
|
41
41
|
|
|
|
42
42
|
v
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guards the "legible, readable contrast" acceptance criterion the #684
|
|
3
|
+
* theme-selector restoration's 16 per-theme verification sub-issues
|
|
4
|
+
* (#691-706) each individually ask for, systematically across every
|
|
5
|
+
* built-in theme at once rather than eyeballing 16 screenshots one at a
|
|
6
|
+
* time — see docs/decisions/theme-selector-shared-state.md and this
|
|
7
|
+
* repo's CLAUDE.md's "favor tooling over manual scanning" convention.
|
|
8
|
+
*
|
|
9
|
+
* Computes the actual WCAG 2.x contrast ratio (relative luminance, per
|
|
10
|
+
* https://www.w3.org/TR/WCAG21/#dfn-relative-luminance) between each
|
|
11
|
+
* theme's `bg`/`fg` pair — not a hand-picked pass/fail per theme, so a
|
|
12
|
+
* future THEMES addition with a genuinely illegible pair fails this test
|
|
13
|
+
* automatically instead of only being caught by a human noticing a
|
|
14
|
+
* screenshot looks wrong.
|
|
15
|
+
*/
|
|
16
|
+
import { describe, it, expect } from 'vitest'
|
|
17
|
+
import { THEMES } from '@zombie-mermaid/core'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The threshold every theme must clear. WCAG AA's own thresholds are 4.5:1
|
|
21
|
+
* for normal text and 3:1 for large/bold text; diagram node labels here
|
|
22
|
+
* are typically medium, semi-bold text, closer to the "large text"
|
|
23
|
+
* category than dense body copy. 4.0 sits between the two -- strict enough
|
|
24
|
+
* to catch a genuinely illegible pair, loose enough not to fail
|
|
25
|
+
* `solarized-light` (4.13:1), whose muted bg/fg pair matches Ethan
|
|
26
|
+
* Schoonover's actual published Solarized values rather than being a
|
|
27
|
+
* mistake in this repo's THEMES data -- #684's own non-goals rule out
|
|
28
|
+
* changing THEMES' colors to "fix" a curated editor-theme's real values.
|
|
29
|
+
*/
|
|
30
|
+
const MIN_CONTRAST_RATIO = 4.0
|
|
31
|
+
|
|
32
|
+
function hexToRgb(hex: string): [number, number, number] {
|
|
33
|
+
const clean = hex.replace('#', '')
|
|
34
|
+
return [
|
|
35
|
+
parseInt(clean.slice(0, 2), 16),
|
|
36
|
+
parseInt(clean.slice(2, 4), 16),
|
|
37
|
+
parseInt(clean.slice(4, 6), 16),
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** sRGB channel -> linear-light value, the WCAG relative-luminance step. */
|
|
42
|
+
function linearize(channel: number): number {
|
|
43
|
+
const c = channel / 255
|
|
44
|
+
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function relativeLuminance(hex: string): number {
|
|
48
|
+
const [r, g, b] = hexToRgb(hex)
|
|
49
|
+
return 0.2126 * linearize(r) + 0.7152 * linearize(g) + 0.0722 * linearize(b)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** WCAG contrast ratio between two colors, always >= 1 (order-independent). */
|
|
53
|
+
function contrastRatio(hexA: string, hexB: string): number {
|
|
54
|
+
const lumA = relativeLuminance(hexA)
|
|
55
|
+
const lumB = relativeLuminance(hexB)
|
|
56
|
+
const lighter = Math.max(lumA, lumB)
|
|
57
|
+
const darker = Math.min(lumA, lumB)
|
|
58
|
+
return (lighter + 0.05) / (darker + 0.05)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
describe('every built-in theme has a legible bg/fg contrast ratio', () => {
|
|
62
|
+
for (const [themeKey, colors] of Object.entries(THEMES)) {
|
|
63
|
+
it(`${themeKey}: bg/fg contrast is at least ${MIN_CONTRAST_RATIO}:1`, () => {
|
|
64
|
+
const ratio = contrastRatio(colors.bg, colors.fg)
|
|
65
|
+
expect(ratio).toBeGreaterThanOrEqual(MIN_CONTRAST_RATIO)
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
describe('contrastRatio() itself', () => {
|
|
71
|
+
it('returns 1 for identical colors', () => {
|
|
72
|
+
expect(contrastRatio('#808080', '#808080')).toBeCloseTo(1, 5)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('returns 21 for pure black vs pure white (the WCAG maximum)', () => {
|
|
76
|
+
expect(contrastRatio('#000000', '#FFFFFF')).toBeCloseTo(21, 1)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('is symmetric regardless of argument order', () => {
|
|
80
|
+
const a = contrastRatio('#1a1b26', '#a9b1d6')
|
|
81
|
+
const b = contrastRatio('#a9b1d6', '#1a1b26')
|
|
82
|
+
expect(a).toBeCloseTo(b, 10)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
@@ -14,12 +14,21 @@
|
|
|
14
14
|
//
|
|
15
15
|
// So: walk what the files actually import, and assert the shape.
|
|
16
16
|
//
|
|
17
|
-
// core
|
|
18
|
-
//
|
|
17
|
+
// core -> nothing in this repo, only `elkjs` (type-only)
|
|
18
|
+
// mermaid-parser -> `@zombie-mermaid/core`, nothing else
|
|
19
|
+
// svg-renderer -> `@zombie-mermaid/core`, `@zombie-mermaid/mermaid-parser`,
|
|
20
|
+
// and `elkjs`, nothing else
|
|
19
21
|
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
22
|
+
// `svg-renderer` depending on `mermaid-parser` (added under #624) is a new
|
|
23
|
+
// edge, not a violation of the sink property above: it is an ordinary,
|
|
24
|
+
// acyclic dependency (both `svg-renderer` and the future `ascii-renderer`
|
|
25
|
+
// depend on `mermaid-parser` directly — the scoping doc's finding 2), and
|
|
26
|
+
// `mermaid-parser` itself stays a sink exactly like `core` — it does not
|
|
27
|
+
// import `svg-renderer` back.
|
|
28
|
+
//
|
|
29
|
+
// None of the three may reach back into `src/` by relative path or by
|
|
30
|
+
// importing `zombie-mermaid` itself — the umbrella depends on them, never
|
|
31
|
+
// the reverse.
|
|
23
32
|
|
|
24
33
|
import { describe, it, expect } from 'vitest'
|
|
25
34
|
import { readdirSync, readFileSync, statSync } from 'node:fs'
|
|
@@ -43,6 +52,20 @@ const PACKAGES = resolve(REPO_ROOT, 'packages')
|
|
|
43
52
|
const SPECIFIER_RE =
|
|
44
53
|
/(?:^|\n)[ \t]*(?:import|export)\b[^'"\n]*?from[ \t]*['"]([^'"]+)['"]|(?:^|\n)[ \t]*import[ \t]*['"]([^'"]+)['"]|\bimport\([ \t]*['"]([^'"]+)['"]/g
|
|
45
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Same three shapes as `SPECIFIER_RE`'s first alternative, but split into
|
|
57
|
+
* the `import`/`export` keyword and the clause between it and `from`, so a
|
|
58
|
+
* whole-clause type-only import (`import type { X } from '...'` /
|
|
59
|
+
* `export type { X } from '...'`) can be told apart from a real value one.
|
|
60
|
+
* Used only by `valueSpecifiersOf` below — `specifiersOf`/`SPECIFIER_RE`
|
|
61
|
+
* stay untouched since the import-graph tests above are deliberately
|
|
62
|
+
* over-approximating.
|
|
63
|
+
*/
|
|
64
|
+
const FROM_CLAUSE_RE =
|
|
65
|
+
/(?:^|\n)[ \t]*(?:import|export)\b([^'"\n]*?)from[ \t]*['"]([^'"]+)['"]/g
|
|
66
|
+
const SIDE_EFFECT_IMPORT_RE = /(?:^|\n)[ \t]*import[ \t]*['"]([^'"]+)['"]/g
|
|
67
|
+
const DYNAMIC_IMPORT_RE = /\bimport\([ \t]*['"]([^'"]+)['"]/g
|
|
68
|
+
|
|
46
69
|
function tsFilesUnder(dir: string): string[] {
|
|
47
70
|
const out: string[] = []
|
|
48
71
|
for (const entry of readdirSync(dir)) {
|
|
@@ -77,12 +100,63 @@ function externalSpecifiers(pkg: string): string[] {
|
|
|
77
100
|
.sort()
|
|
78
101
|
}
|
|
79
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Every specifier named by a *value* import in `packages/<name>/src` —
|
|
105
|
+
* i.e. excluding a whole-clause `import type { X } from '...'` /
|
|
106
|
+
* `export type { X } from '...'`, which is erased before bundling and so
|
|
107
|
+
* shouldn't obligate a `dependencies` entry (#742). A per-symbol inline
|
|
108
|
+
* `import { type X, y } from '...'` still counts as a value import here,
|
|
109
|
+
* since `y` binds to a real runtime export from that module — this repo
|
|
110
|
+
* doesn't currently mix the two on one specifier, so that distinction
|
|
111
|
+
* isn't exercised, but the whole-clause check stays conservative rather
|
|
112
|
+
* than trying to parse the individual specifiers in the braces.
|
|
113
|
+
*/
|
|
114
|
+
function valueSpecifiersOf(pkg: string): Map<string, string[]> {
|
|
115
|
+
const bySpecifier = new Map<string, string[]>()
|
|
116
|
+
const record = (specifier: string, file: string) => {
|
|
117
|
+
const seen = bySpecifier.get(specifier) ?? []
|
|
118
|
+
seen.push(relative(REPO_ROOT, file))
|
|
119
|
+
bySpecifier.set(specifier, seen)
|
|
120
|
+
}
|
|
121
|
+
for (const file of tsFilesUnder(resolve(PACKAGES, pkg, 'src'))) {
|
|
122
|
+
const source = readFileSync(file, 'utf8')
|
|
123
|
+
for (const match of source.matchAll(FROM_CLAUSE_RE)) {
|
|
124
|
+
const [, clause, specifier] = match
|
|
125
|
+
if (/^\s*type\b/.test(clause ?? '')) continue
|
|
126
|
+
record(specifier!, file)
|
|
127
|
+
}
|
|
128
|
+
for (const match of source.matchAll(SIDE_EFFECT_IMPORT_RE)) {
|
|
129
|
+
record(match[1]!, file)
|
|
130
|
+
}
|
|
131
|
+
for (const match of source.matchAll(DYNAMIC_IMPORT_RE)) {
|
|
132
|
+
record(match[1]!, file)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return bySpecifier
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Non-relative value-import specifiers only — what a package's
|
|
140
|
+
* `dependencies` should actually list, since a type-only import is erased
|
|
141
|
+
* before anything is bundled and never needs a runtime package present.
|
|
142
|
+
*/
|
|
143
|
+
function externalValueSpecifiers(pkg: string): string[] {
|
|
144
|
+
return [...valueSpecifiersOf(pkg).keys()]
|
|
145
|
+
.filter((s) => !s.startsWith('.'))
|
|
146
|
+
.map((s) => (s.startsWith('@') ? s : s.split('/')[0]!))
|
|
147
|
+
.filter((s, i, all) => all.indexOf(s) === i)
|
|
148
|
+
.sort()
|
|
149
|
+
}
|
|
150
|
+
|
|
80
151
|
/**
|
|
81
152
|
* A relative specifier that climbs out of `packages/<name>/src` — i.e. a
|
|
82
153
|
* reach-around into the umbrella's `src/` or into a sibling package. The
|
|
83
|
-
*
|
|
84
|
-
* importing `../styles.ts
|
|
85
|
-
*
|
|
154
|
+
* many legitimate `../` hops inside `svg-renderer` (`layout-engine/*.ts`
|
|
155
|
+
* importing `../styles.ts`, and — since #624 — each `class/`, `er/`,
|
|
156
|
+
* `sequence/`, `xychart/` subdirectory's `layout.ts`/`renderer.ts`
|
|
157
|
+
* importing sibling modules like `../elk-instance.ts`/`../renderer.ts`)
|
|
158
|
+
* stay within the package and are resolved, not pattern-matched, so this
|
|
159
|
+
* can't be fooled by depth.
|
|
86
160
|
*/
|
|
87
161
|
function escapingRelativeImports(pkg: string): string[] {
|
|
88
162
|
const packageSrc = resolve(PACKAGES, pkg, 'src')
|
|
@@ -115,10 +189,27 @@ describe('@zombie-mermaid/core is a workspace sink', () => {
|
|
|
115
189
|
})
|
|
116
190
|
})
|
|
117
191
|
|
|
118
|
-
describe('@zombie-mermaid/
|
|
119
|
-
|
|
192
|
+
describe('@zombie-mermaid/mermaid-parser is a workspace sink', () => {
|
|
193
|
+
// Verified (#624, umbrella #620): no file under packages/mermaid-parser/src
|
|
194
|
+
// imports `elkjs`, `@zombie-mermaid/svg-renderer`, or anything from the
|
|
195
|
+
// umbrella — the property that lets both `svg-renderer` and the future
|
|
196
|
+
// `ascii-renderer` depend on it without either dragging the other in.
|
|
197
|
+
it('imports no workspace package and no npm dependency other than @zombie-mermaid/core', () => {
|
|
198
|
+
expect(externalSpecifiers('mermaid-parser')).toEqual([
|
|
199
|
+
'@zombie-mermaid/core',
|
|
200
|
+
])
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('never reaches outside its own src/ by relative path', () => {
|
|
204
|
+
expect(escapingRelativeImports('mermaid-parser')).toEqual([])
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
describe('@zombie-mermaid/svg-renderer depends only on core and mermaid-parser', () => {
|
|
209
|
+
it('imports no workspace package other than @zombie-mermaid/core and @zombie-mermaid/mermaid-parser', () => {
|
|
120
210
|
expect(externalSpecifiers('svg-renderer')).toEqual([
|
|
121
211
|
'@zombie-mermaid/core',
|
|
212
|
+
'@zombie-mermaid/mermaid-parser',
|
|
122
213
|
'elkjs',
|
|
123
214
|
])
|
|
124
215
|
})
|
|
@@ -129,27 +220,110 @@ describe('@zombie-mermaid/svg-renderer depends only on core', () => {
|
|
|
129
220
|
})
|
|
130
221
|
|
|
131
222
|
describe('workspace package manifests', () => {
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
223
|
+
// As of #769, each of the five `@zombie-mermaid/*` packages has a
|
|
224
|
+
// genuinely publishable `package.json` shape — real `name`/`exports`
|
|
225
|
+
// pointing at its own built `dist/`, `publishConfig.access: "public"`,
|
|
226
|
+
// no longer `"private": true` — and its own independent build
|
|
227
|
+
// (`packages/<name>/vite.config.ts`, via `vite.config.package.ts`).
|
|
228
|
+
//
|
|
229
|
+
// That is preparation only. The umbrella (`vite.config.lib.ts`) still
|
|
230
|
+
// bundles all five packages' source directly into its own `dist/` (they
|
|
231
|
+
// are absent from `isExternal` there, same as before #769) — none of
|
|
232
|
+
// them is a real external `dependencies` entry of `zombie-mermaid`, none
|
|
233
|
+
// is part of `.changeset/config.json`'s `fixed` group (still `[]`), and
|
|
234
|
+
// none has ever actually been published to npm. Externalizing them for
|
|
235
|
+
// real is a separate, future PR gated on completing npm's one-time
|
|
236
|
+
// trusted-publishing setup for each new package name — see
|
|
237
|
+
// RELEASING.md's "Future: multi-package publish" section.
|
|
238
|
+
it.each(['core', 'mermaid-parser', 'svg-renderer', 'ascii-renderer', 'mcp'])(
|
|
239
|
+
'%s is publish-ready in shape under the @zombie-mermaid/ scope (not yet externalized or published)',
|
|
240
|
+
(pkg) => {
|
|
241
|
+
const manifest = JSON.parse(
|
|
242
|
+
readFileSync(resolve(PACKAGES, pkg, 'package.json'), 'utf8'),
|
|
243
|
+
) as {
|
|
244
|
+
name: string
|
|
245
|
+
private?: boolean
|
|
246
|
+
publishConfig?: { access?: string }
|
|
247
|
+
}
|
|
248
|
+
expect(manifest.name).toBe(`@zombie-mermaid/${pkg}`)
|
|
249
|
+
// Not `"private": true` — a scoped package still defaults to a
|
|
250
|
+
// *private* npm publish unless `publishConfig.access` says otherwise,
|
|
251
|
+
// which is the actual thing preventing an accidental unscoped-style
|
|
252
|
+
// public leak here; `private` merely gates `pnpm publish`/`npm publish`
|
|
253
|
+
// from running against this package at all.
|
|
254
|
+
expect(manifest.private).not.toBe(true)
|
|
255
|
+
expect(manifest.publishConfig?.access).toBe('public')
|
|
256
|
+
},
|
|
257
|
+
)
|
|
144
258
|
|
|
145
|
-
|
|
146
|
-
|
|
259
|
+
// Declared `dependencies` must match actual *runtime* imports, not the
|
|
260
|
+
// over-approximating `externalSpecifiers` above — a type-only import
|
|
261
|
+
// (e.g. `mermaid-parser`'s hypothetical future one) is erased before
|
|
262
|
+
// bundling and belongs in `devDependencies` instead, or a published
|
|
263
|
+
// consumer of the package would pull in a runtime dependency it never
|
|
264
|
+
// executes (#742) — UNLESS that type is itself part of this package's own
|
|
265
|
+
// published public surface (its rolled-up `dist/index.d.ts`, built via
|
|
266
|
+
// api-extractor's `bundleTypes` — see vite.config.package.ts), in which
|
|
267
|
+
// case a real downstream `tsc` consumer needs the type resolvable and a
|
|
268
|
+
// `devDependency` (never installed for a consumer) won't do. `core` is
|
|
269
|
+
// exactly this case: `LayoutCache`'s public field types reference
|
|
270
|
+
// `elkjs`'s `ElkNode` (`import type { ElkNode } from 'elkjs'` in
|
|
271
|
+
// `types.ts`), so `elkjs` is a real `dependencies` entry there despite
|
|
272
|
+
// never being a *value* import in `core`'s own source.
|
|
273
|
+
it('declares every runtime (non-type-only) external specifier its source actually imports', () => {
|
|
274
|
+
for (const pkg of ['mermaid-parser', 'svg-renderer'] as const) {
|
|
147
275
|
const manifest = JSON.parse(
|
|
148
276
|
readFileSync(resolve(PACKAGES, pkg, 'package.json'), 'utf8'),
|
|
149
277
|
) as { dependencies?: Record<string, string> }
|
|
150
278
|
expect(Object.keys(manifest.dependencies ?? {}).sort()).toEqual(
|
|
151
|
-
|
|
279
|
+
externalValueSpecifiers(pkg),
|
|
152
280
|
)
|
|
153
281
|
}
|
|
154
282
|
})
|
|
283
|
+
|
|
284
|
+
it('core additionally declares elkjs — type-only in source, but part of its own published public types', () => {
|
|
285
|
+
const manifest = JSON.parse(
|
|
286
|
+
readFileSync(resolve(PACKAGES, 'core', 'package.json'), 'utf8'),
|
|
287
|
+
) as { dependencies?: Record<string, string> }
|
|
288
|
+
expect(Object.keys(manifest.dependencies ?? {}).sort()).toEqual([
|
|
289
|
+
'elkjs',
|
|
290
|
+
...externalValueSpecifiers('core'),
|
|
291
|
+
])
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
// `ascii-renderer` and `mcp` each bundle one (`packages/ascii-renderer/src/flowchart.ts`
|
|
295
|
+
// -> `../../../src/parser.ts`) or two (`packages/mcp/src/{server,tools/render-svg}.ts`
|
|
296
|
+
// -> `../../../src/{package-info,index}.ts`) files from *outside* their
|
|
297
|
+
// own `packages/<name>/src/` — see vite.config.package.ts's header and
|
|
298
|
+
// each package's own vite.config.ts for why (their build still compiles
|
|
299
|
+
// that source in directly, same as before #769). `externalValueSpecifiers`
|
|
300
|
+
// above only scans each package's own `src/`, so it can't see what those
|
|
301
|
+
// reach-through files themselves import — this asserts the full,
|
|
302
|
+
// hand-traced transitive set instead of extending that scanner to follow
|
|
303
|
+
// an out-of-package relative import (which `escapingRelativeImports`
|
|
304
|
+
// above deliberately treats as a violation for the other three packages).
|
|
305
|
+
it('ascii-renderer declares every specifier its reach-through into src/parser.ts needs, on top of its own', () => {
|
|
306
|
+
const manifest = JSON.parse(
|
|
307
|
+
readFileSync(resolve(PACKAGES, 'ascii-renderer', 'package.json'), 'utf8'),
|
|
308
|
+
) as { dependencies?: Record<string, string> }
|
|
309
|
+
expect(Object.keys(manifest.dependencies ?? {}).sort()).toEqual([
|
|
310
|
+
'@zombie-mermaid/core',
|
|
311
|
+
'@zombie-mermaid/mermaid-parser',
|
|
312
|
+
])
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
it('mcp declares every specifier its reach-through into src/{package-info,index}.ts needs, on top of its own', () => {
|
|
316
|
+
const manifest = JSON.parse(
|
|
317
|
+
readFileSync(resolve(PACKAGES, 'mcp', 'package.json'), 'utf8'),
|
|
318
|
+
) as { dependencies?: Record<string, string> }
|
|
319
|
+
expect(Object.keys(manifest.dependencies ?? {}).sort()).toEqual([
|
|
320
|
+
'@modelcontextprotocol/sdk',
|
|
321
|
+
'@zombie-mermaid/ascii-renderer',
|
|
322
|
+
'@zombie-mermaid/core',
|
|
323
|
+
'@zombie-mermaid/mermaid-parser',
|
|
324
|
+
'@zombie-mermaid/svg-renderer',
|
|
325
|
+
'entities',
|
|
326
|
+
'zod',
|
|
327
|
+
])
|
|
328
|
+
})
|
|
155
329
|
})
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* multi-series support, staircase line routing, and edge cases.
|
|
6
6
|
*/
|
|
7
7
|
import { describe, it, expect } from 'vitest'
|
|
8
|
-
import { renderMermaidASCII } from '
|
|
8
|
+
import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
|
|
9
9
|
|
|
10
10
|
// ============================================================================
|
|
11
11
|
// Helper — render with no colors for easy string matching
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for xychart color palette generation
|
|
2
|
+
* Tests for xychart color palette generation
|
|
3
|
+
* (packages/mermaid-parser/src/xychart/colors.ts).
|
|
3
4
|
*/
|
|
4
5
|
import { describe, it, expect } from 'vitest'
|
|
5
6
|
import {
|
|
@@ -8,7 +9,7 @@ import {
|
|
|
8
9
|
isDarkBackground,
|
|
9
10
|
mixHexColors,
|
|
10
11
|
getSeriesColor,
|
|
11
|
-
} from '
|
|
12
|
+
} from '@zombie-mermaid/mermaid-parser'
|
|
12
13
|
|
|
13
14
|
describe('isValidHex', () => {
|
|
14
15
|
it('accepts a well-formed 6-digit hex color', () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Coverage-focused tests for src/xychart/layout.ts.
|
|
2
|
+
* Coverage-focused tests for packages/svg-renderer/src/xychart/layout.ts.
|
|
3
3
|
*
|
|
4
4
|
* Exercises the horizontal layout path (previously untested), numeric
|
|
5
5
|
* x-axis ranges (category label interpolation), multi-series bar groups,
|