@zombie-mermaid/mermaid-parser 4.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +9 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +100 -0
- package/dist/index.d.ts +100 -0
- package/dist/index.js +340 -179
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/flowchart-edge-label-space-before-pipe.test.ts +54 -0
- package/src/__tests__/pie-mermaid-spec.test.ts +308 -0
- package/src/__tests__/pie-parser.test.ts +452 -0
- package/src/flowchart-parser.ts +7 -4
- package/src/index.ts +3 -0
- package/src/pie/parser.ts +393 -0
- package/src/pie/types.ts +109 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zombie-mermaid/mermaid-parser",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Parsers for class, ER, sequence, and XY chart diagrams — the per-type parse functions both svg-renderer and ascii-renderer import directly.",
|
|
6
6
|
"repository": {
|
|
@@ -35,6 +35,6 @@
|
|
|
35
35
|
"LICENSE"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@zombie-mermaid/core": "4.
|
|
38
|
+
"@zombie-mermaid/core": "4.2.0"
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { parseMermaid } from '../flowchart-parser.ts'
|
|
3
|
+
|
|
4
|
+
// Upstream lukilabs/beautiful-mermaid#156: `A --> |label| B` (space before the
|
|
5
|
+
// label pipe) used to drop the target and label.
|
|
6
|
+
describe('edge label with a space before the pipe', () => {
|
|
7
|
+
const cases = [
|
|
8
|
+
['-->', 'solid', true],
|
|
9
|
+
['---', 'solid', false],
|
|
10
|
+
['-.->', 'dotted', true],
|
|
11
|
+
['==>', 'thick', true],
|
|
12
|
+
] as const
|
|
13
|
+
|
|
14
|
+
for (const [op, style, hasArrowEnd] of cases) {
|
|
15
|
+
it(`parses A ${op} |deploy| B`, () => {
|
|
16
|
+
const g = parseMermaid(`graph LR\n A ${op} |deploy| B`)
|
|
17
|
+
expect(g.edges).toHaveLength(1)
|
|
18
|
+
expect(g.edges[0]).toMatchObject({
|
|
19
|
+
source: 'A',
|
|
20
|
+
target: 'B',
|
|
21
|
+
label: 'deploy',
|
|
22
|
+
style,
|
|
23
|
+
hasArrowEnd,
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it(`parses A ${op} |deploy| B identically to the no-space form`, () => {
|
|
28
|
+
const spaced = parseMermaid(`graph LR\n A ${op} |deploy| B`)
|
|
29
|
+
const tight = parseMermaid(`graph LR\n A ${op}|deploy| B`)
|
|
30
|
+
expect(spaced.edges).toEqual(tight.edges)
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
it('still parses the no-space form', () => {
|
|
35
|
+
const g = parseMermaid('graph LR\n A -->|deploy| B')
|
|
36
|
+
expect(g.edges[0]).toMatchObject({ target: 'B', label: 'deploy' })
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('does not treat an unmarked `--`/`==` before a pipe as an arrow', () => {
|
|
40
|
+
// `--`/`==` unmarked is the text-label opener, so this must not become a
|
|
41
|
+
// labelled link with target B and label `x`.
|
|
42
|
+
for (const src of ['graph LR\n A -- |x| B', 'graph LR\n A == |x| B']) {
|
|
43
|
+
const g = parseMermaid(src)
|
|
44
|
+
expect(g.edges.some((e) => e.label === 'x' && e.target === 'B')).toBe(
|
|
45
|
+
false,
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('keeps text-embedded labels working', () => {
|
|
51
|
+
const g = parseMermaid('graph LR\n A -- Yes --> B')
|
|
52
|
+
expect(g.edges[0]).toMatchObject({ target: 'B', label: 'Yes' })
|
|
53
|
+
})
|
|
54
|
+
})
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { parsePieChart } from '../index.ts'
|
|
3
|
+
|
|
4
|
+
// Fixtures ported from Mermaid's own pie tests (mermaid-js/mermaid, MIT):
|
|
5
|
+
// - packages/mermaid/src/diagrams/pie/pie.spec.ts (parser + pieDb)
|
|
6
|
+
// - packages/parser/tests/pie.test.ts (the Langium grammar)
|
|
7
|
+
// Inputs are copied verbatim; assertions are translated from Mermaid's
|
|
8
|
+
// `db.getSections()` Map / AST shape to this package's `PieChart`. The two
|
|
9
|
+
// `config` cases in pie.spec.ts (`it.todo` setConfig/resetConfig, and
|
|
10
|
+
// getConfig defaults) test Mermaid's diagram config, not parsing, and are
|
|
11
|
+
// left for the follow-up that adds `%%{init: {"pie": …}}%%` support.
|
|
12
|
+
|
|
13
|
+
/** `db.getSections()` equivalent: label → value, in source order. */
|
|
14
|
+
const sections = (text: string): Map<string, number> =>
|
|
15
|
+
new Map(parsePieChart(text).slices.map((s) => [s.label, s.value]))
|
|
16
|
+
|
|
17
|
+
describe('mermaid pie.spec.ts: parse', () => {
|
|
18
|
+
it('should handle very simple pie', () => {
|
|
19
|
+
expect(
|
|
20
|
+
sections(`pie
|
|
21
|
+
"ash": 100
|
|
22
|
+
`).get('ash'),
|
|
23
|
+
).toBe(100)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should handle simple pie', () => {
|
|
27
|
+
const s = sections(`pie
|
|
28
|
+
"ash" : 60
|
|
29
|
+
"bat" : 40
|
|
30
|
+
`)
|
|
31
|
+
expect(s.get('ash')).toBe(60)
|
|
32
|
+
expect(s.get('bat')).toBe(40)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('should handle simple pie with showData', () => {
|
|
36
|
+
const chart = parsePieChart(`pie showData
|
|
37
|
+
"ash" : 60
|
|
38
|
+
"bat" : 40
|
|
39
|
+
`)
|
|
40
|
+
expect(chart.showData).toBe(true)
|
|
41
|
+
expect(chart.slices).toEqual([
|
|
42
|
+
{ label: 'ash', value: 60 },
|
|
43
|
+
{ label: 'bat', value: 40 },
|
|
44
|
+
])
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should handle simple pie with comments', () => {
|
|
48
|
+
const s = sections(`pie
|
|
49
|
+
%% comments
|
|
50
|
+
"ash" : 60
|
|
51
|
+
"bat" : 40
|
|
52
|
+
`)
|
|
53
|
+
expect(s.get('ash')).toBe(60)
|
|
54
|
+
expect(s.get('bat')).toBe(40)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('should handle simple pie with a title', () => {
|
|
58
|
+
const chart = parsePieChart(`pie title a 60/40 pie
|
|
59
|
+
"ash" : 60
|
|
60
|
+
"bat" : 40
|
|
61
|
+
`)
|
|
62
|
+
expect(chart.title).toBe('a 60/40 pie')
|
|
63
|
+
expect(chart.slices).toHaveLength(2)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('should handle simple pie with an acc title (accTitle)', () => {
|
|
67
|
+
const chart = parsePieChart(`pie title a neat chart
|
|
68
|
+
accTitle: a neat acc title
|
|
69
|
+
"ash" : 60
|
|
70
|
+
"bat" : 40
|
|
71
|
+
`)
|
|
72
|
+
expect(chart.title).toBe('a neat chart')
|
|
73
|
+
expect(chart.accTitle).toBe('a neat acc title')
|
|
74
|
+
expect(chart.slices).toHaveLength(2)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('should handle simple pie with an acc description (accDescr)', () => {
|
|
78
|
+
const chart = parsePieChart(`pie title a neat chart
|
|
79
|
+
accDescr: a neat description
|
|
80
|
+
"ash" : 60
|
|
81
|
+
"bat" : 40
|
|
82
|
+
`)
|
|
83
|
+
expect(chart.title).toBe('a neat chart')
|
|
84
|
+
expect(chart.accDescr).toBe('a neat description')
|
|
85
|
+
expect(chart.slices).toHaveLength(2)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('should handle simple pie with a multiline acc description (accDescr)', () => {
|
|
89
|
+
const chart = parsePieChart(`pie title a neat chart
|
|
90
|
+
accDescr {
|
|
91
|
+
a neat description
|
|
92
|
+
on multiple lines
|
|
93
|
+
}
|
|
94
|
+
"ash" : 60
|
|
95
|
+
"bat" : 40
|
|
96
|
+
`)
|
|
97
|
+
expect(chart.title).toBe('a neat chart')
|
|
98
|
+
expect(chart.accDescr).toBe('a neat description\non multiple lines')
|
|
99
|
+
expect(chart.slices).toHaveLength(2)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('should handle simple pie with positive decimal', () => {
|
|
103
|
+
const s = sections(`pie
|
|
104
|
+
"ash" : 60.67
|
|
105
|
+
"bat" : 40
|
|
106
|
+
`)
|
|
107
|
+
expect(s.get('ash')).toBe(60.67)
|
|
108
|
+
expect(s.get('bat')).toBe(40)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('should handle simple pie with negative decimal', () => {
|
|
112
|
+
expect(() =>
|
|
113
|
+
parsePieChart(`pie
|
|
114
|
+
"ash" : -60.67
|
|
115
|
+
"bat" : 40.12
|
|
116
|
+
`),
|
|
117
|
+
).toThrow()
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('should handle simple pie with zero slice value', () => {
|
|
121
|
+
const s = sections(`pie title Default text position: Animal adoption
|
|
122
|
+
accTitle: simple pie char demo
|
|
123
|
+
accDescr: pie chart with 3 sections: dogs, cats, rats. Most are dogs.
|
|
124
|
+
"dogs" : 0
|
|
125
|
+
"rats" : 40.12
|
|
126
|
+
`)
|
|
127
|
+
expect(s.get('dogs')).toBe(0)
|
|
128
|
+
expect(s.get('rats')).toBe(40.12)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('should handle simple pie with negative slice value', () => {
|
|
132
|
+
expect(() =>
|
|
133
|
+
parsePieChart(`pie title Default text position: Animal adoption
|
|
134
|
+
accTitle: simple pie char demo
|
|
135
|
+
accDescr: pie chart with 3 sections: dogs, cats, rats. Most are dogs.
|
|
136
|
+
"dogs" : -60.67
|
|
137
|
+
"rats" : 40.12
|
|
138
|
+
`),
|
|
139
|
+
).toThrow(
|
|
140
|
+
'"dogs" has invalid value: -60.67. Negative values are not allowed in pie charts. All slice values must be >= 0.',
|
|
141
|
+
)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('should handle unsafe properties', () => {
|
|
145
|
+
const chart = parsePieChart(`pie title Unsafe props test
|
|
146
|
+
"__proto__" : 386
|
|
147
|
+
"constructor" : 85
|
|
148
|
+
"prototype" : 15`)
|
|
149
|
+
expect(chart.slices.map((s) => s.label)).toEqual([
|
|
150
|
+
'__proto__',
|
|
151
|
+
'constructor',
|
|
152
|
+
'prototype',
|
|
153
|
+
])
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
describe('mermaid parser/tests/pie.test.ts: header with or without showData', () => {
|
|
158
|
+
it.each([
|
|
159
|
+
`pie`,
|
|
160
|
+
` pie `,
|
|
161
|
+
`\tpie\t`,
|
|
162
|
+
`
|
|
163
|
+
\tpie
|
|
164
|
+
`,
|
|
165
|
+
])('should handle regular pie: %j', (context) => {
|
|
166
|
+
const chart = parsePieChart(context)
|
|
167
|
+
expect(chart.showData).toBe(false)
|
|
168
|
+
expect(chart.slices).toEqual([])
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it.each([
|
|
172
|
+
`pie showData`,
|
|
173
|
+
` pie showData `,
|
|
174
|
+
`\tpie\tshowData\t`,
|
|
175
|
+
`
|
|
176
|
+
pie\tshowData
|
|
177
|
+
`,
|
|
178
|
+
])('should handle regular showData: %j', (context) => {
|
|
179
|
+
expect(parsePieChart(context).showData).toBe(true)
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
describe('mermaid parser/tests/pie.test.ts: TitleAndAccessibilities', () => {
|
|
184
|
+
it.each([
|
|
185
|
+
`pie title sample title`,
|
|
186
|
+
` pie title sample title `,
|
|
187
|
+
`\tpie\ttitle sample title\t`,
|
|
188
|
+
`pie
|
|
189
|
+
\ttitle sample title
|
|
190
|
+
`,
|
|
191
|
+
])('should handle regular pie + title in same line: %j', (context) => {
|
|
192
|
+
expect(parsePieChart(context).title).toBe('sample title')
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it.each([
|
|
196
|
+
`pie
|
|
197
|
+
title sample title`,
|
|
198
|
+
`pie
|
|
199
|
+
title sample title
|
|
200
|
+
`,
|
|
201
|
+
])('should handle regular pie + title in different line: %j', (context) => {
|
|
202
|
+
expect(parsePieChart(context).title).toBe('sample title')
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it.each([
|
|
206
|
+
`pie showData title sample title`,
|
|
207
|
+
`pie showData title sample title
|
|
208
|
+
`,
|
|
209
|
+
])('should handle regular pie + showData + title: %j', (context) => {
|
|
210
|
+
const chart = parsePieChart(context)
|
|
211
|
+
expect(chart.showData).toBe(true)
|
|
212
|
+
expect(chart.title).toBe('sample title')
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it.each([
|
|
216
|
+
`pie showData
|
|
217
|
+
title sample title`,
|
|
218
|
+
`pie showData
|
|
219
|
+
title sample title
|
|
220
|
+
`,
|
|
221
|
+
])(
|
|
222
|
+
'should handle regular showData + title in different line: %j',
|
|
223
|
+
(context) => {
|
|
224
|
+
const chart = parsePieChart(context)
|
|
225
|
+
expect(chart.showData).toBe(true)
|
|
226
|
+
expect(chart.title).toBe('sample title')
|
|
227
|
+
},
|
|
228
|
+
)
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
describe('mermaid parser/tests/pie.test.ts: sections', () => {
|
|
232
|
+
it.each([
|
|
233
|
+
`pie
|
|
234
|
+
"GitHub":100
|
|
235
|
+
"GitLab":50`,
|
|
236
|
+
`pie
|
|
237
|
+
"GitHub" : 100
|
|
238
|
+
"GitLab" : 50`,
|
|
239
|
+
`pie
|
|
240
|
+
"GitHub"\t:\t100
|
|
241
|
+
"GitLab"\t:\t50`,
|
|
242
|
+
`pie
|
|
243
|
+
\t"GitHub" \t : \t 100
|
|
244
|
+
\t"GitLab" \t : \t 50
|
|
245
|
+
`,
|
|
246
|
+
])('should handle regular sections: %j', (context) => {
|
|
247
|
+
expect(parsePieChart(context).slices).toEqual([
|
|
248
|
+
{ label: 'GitHub', value: 100 },
|
|
249
|
+
{ label: 'GitLab', value: 50 },
|
|
250
|
+
])
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it('should handle sections with showData', () => {
|
|
254
|
+
const chart = parsePieChart(`pie showData
|
|
255
|
+
"GitHub": 100
|
|
256
|
+
"GitLab": 50`)
|
|
257
|
+
expect(chart.showData).toBe(true)
|
|
258
|
+
expect(chart.slices).toEqual([
|
|
259
|
+
{ label: 'GitHub', value: 100 },
|
|
260
|
+
{ label: 'GitLab', value: 50 },
|
|
261
|
+
])
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it('should handle sections with title', () => {
|
|
265
|
+
const chart = parsePieChart(`pie title sample wow
|
|
266
|
+
"GitHub": 100
|
|
267
|
+
"GitLab": 50`)
|
|
268
|
+
expect(chart.title).toBe('sample wow')
|
|
269
|
+
expect(chart.slices).toHaveLength(2)
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
it('should handle value with positive decimal', () => {
|
|
273
|
+
expect(
|
|
274
|
+
parsePieChart(`pie
|
|
275
|
+
"ash": 60.67
|
|
276
|
+
"bat": 40`).slices,
|
|
277
|
+
).toEqual([
|
|
278
|
+
{ label: 'ash', value: 60.67 },
|
|
279
|
+
{ label: 'bat', value: 40 },
|
|
280
|
+
])
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
it('should handle sections with accTitle', () => {
|
|
284
|
+
const chart = parsePieChart(`pie accTitle: sample wow
|
|
285
|
+
"GitHub": 100
|
|
286
|
+
"GitLab": 50`)
|
|
287
|
+
expect(chart.accTitle).toBe('sample wow')
|
|
288
|
+
expect(chart.slices).toHaveLength(2)
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
it('should handle sections with single line accDescr', () => {
|
|
292
|
+
const chart = parsePieChart(`pie accDescr: sample wow
|
|
293
|
+
"GitHub": 100
|
|
294
|
+
"GitLab": 50`)
|
|
295
|
+
expect(chart.accDescr).toBe('sample wow')
|
|
296
|
+
expect(chart.slices).toHaveLength(2)
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it('should handle sections with multi line accDescr', () => {
|
|
300
|
+
const chart = parsePieChart(`pie accDescr {
|
|
301
|
+
sample wow
|
|
302
|
+
}
|
|
303
|
+
"GitHub": 100
|
|
304
|
+
"GitLab": 50`)
|
|
305
|
+
expect(chart.accDescr).toBe('sample wow')
|
|
306
|
+
expect(chart.slices).toHaveLength(2)
|
|
307
|
+
})
|
|
308
|
+
})
|