zombie-mermaid 4.2.2 → 4.2.3

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.
Files changed (92) hide show
  1. package/package.json +9 -7
  2. package/src/__tests__/architecture-integration.test.ts +0 -217
  3. package/src/__tests__/ascii-ansi.test.ts +0 -539
  4. package/src/__tests__/ascii-draw-bundles.test.ts +0 -849
  5. package/src/__tests__/ascii-edge-bundling-routing.test.ts +0 -214
  6. package/src/__tests__/ascii-edge-cross-style-overlap.test.ts +0 -216
  7. package/src/__tests__/ascii-grid-deep-chain.test.ts +0 -64
  8. package/src/__tests__/ascii-hyperlinks.test.ts +0 -364
  9. package/src/__tests__/ascii-nested-subgraph-sibling-order-444.test.ts +0 -108
  10. package/src/__tests__/ascii-nonmember-beside-frame-1283.test.ts +0 -113
  11. package/src/__tests__/ascii-package-boundary.test.ts +0 -291
  12. package/src/__tests__/ascii-parallel-edges-329.test.ts +0 -315
  13. package/src/__tests__/ascii-parallel-lane-border-1230.test.ts +0 -71
  14. package/src/__tests__/ascii-reverse-direction-edges-629.test.ts +0 -282
  15. package/src/__tests__/ascii-stacked-frame-geometry-1285.test.ts +0 -107
  16. package/src/__tests__/ascii-subgraph-bbox.test.ts +0 -139
  17. package/src/__tests__/ascii-subgraph-later-edgeless-301.test.ts +0 -277
  18. package/src/__tests__/browser.test.ts +0 -34
  19. package/src/__tests__/c4-integration.test.ts +0 -161
  20. package/src/__tests__/c4-orientation.test.ts +0 -162
  21. package/src/__tests__/c4-upstream-integration.test.ts +0 -211
  22. package/src/__tests__/class-click-interactions.test.ts +0 -232
  23. package/src/__tests__/class-integration.test.ts +0 -154
  24. package/src/__tests__/class-notes.test.ts +0 -317
  25. package/src/__tests__/class-parser-error-messages.test.ts +0 -162
  26. package/src/__tests__/class-parser.test.ts +0 -347
  27. package/src/__tests__/class-styling.test.ts +0 -485
  28. package/src/__tests__/cli-diagrams.test.ts +0 -106
  29. package/src/__tests__/cli-e2e.test.ts +0 -275
  30. package/src/__tests__/cli-entrypoint.test.ts +0 -146
  31. package/src/__tests__/cli-mcp-dispatch.test.ts +0 -68
  32. package/src/__tests__/cli-mcp.test.ts +0 -44
  33. package/src/__tests__/cli-png.test.ts +0 -35
  34. package/src/__tests__/cli-render.test.ts +0 -988
  35. package/src/__tests__/cli-test-helpers.ts +0 -54
  36. package/src/__tests__/cli-web-dispatch.test.ts +0 -62
  37. package/src/__tests__/cli-web.test.ts +0 -203
  38. package/src/__tests__/cli.test.ts +0 -1027
  39. package/src/__tests__/compound-flat-layout.test.ts +0 -901
  40. package/src/__tests__/direction-override.test.ts +0 -397
  41. package/src/__tests__/edge-approach-direction.test.ts +0 -295
  42. package/src/__tests__/edge-bundling-node-clearance.test.ts +0 -179
  43. package/src/__tests__/elk-layout-cache.test.ts +0 -223
  44. package/src/__tests__/er-click-unsupported.test.ts +0 -69
  45. package/src/__tests__/er-integration.test.ts +0 -531
  46. package/src/__tests__/er-parser-error-messages.test.ts +0 -82
  47. package/src/__tests__/er-parser.test.ts +0 -294
  48. package/src/__tests__/expanded-shapes.test.ts +0 -229
  49. package/src/__tests__/flowchart-back-edge-prereversal-1239.test.ts +0 -212
  50. package/src/__tests__/flowchart-syntax-gaps.test.ts +0 -371
  51. package/src/__tests__/guides-examples.test.ts +0 -181
  52. package/src/__tests__/integration.test.ts +0 -729
  53. package/src/__tests__/interactivity-config.test.ts +0 -723
  54. package/src/__tests__/interactivity-option.test.ts +0 -173
  55. package/src/__tests__/layout-disconnected.test.ts +0 -384
  56. package/src/__tests__/layout-engine.test.ts +0 -91
  57. package/src/__tests__/linkstyle.test.ts +0 -133
  58. package/src/__tests__/mcp-render-ascii-tool.test.ts +0 -95
  59. package/src/__tests__/mcp-render-svg-theme-guard.test.ts +0 -36
  60. package/src/__tests__/mcp-render-svg-tool.test.ts +0 -118
  61. package/src/__tests__/mermaid-entity-codes.test.ts +0 -114
  62. package/src/__tests__/monospace-metrics.test.ts +0 -74
  63. package/src/__tests__/multiline-labels.test.ts +0 -924
  64. package/src/__tests__/nested-subgraph-direction-crossing.test.ts +0 -322
  65. package/src/__tests__/nested-subgraph-edge-container-offset.test.ts +0 -151
  66. package/src/__tests__/package-info.test.ts +0 -51
  67. package/src/__tests__/parser.test.ts +0 -1544
  68. package/src/__tests__/render-options-overrides.test.ts +0 -257
  69. package/src/__tests__/render-options-per-type.test.ts +0 -178
  70. package/src/__tests__/renderer.test.ts +0 -743
  71. package/src/__tests__/resolve-colors.test.ts +0 -448
  72. package/src/__tests__/self-loop-edges.test.ts +0 -112
  73. package/src/__tests__/sequence-activation-check.test.ts +0 -219
  74. package/src/__tests__/sequence-integration.test.ts +0 -312
  75. package/src/__tests__/sequence-layout.test.ts +0 -1103
  76. package/src/__tests__/sequence-parser-syntax-errors.test.ts +0 -179
  77. package/src/__tests__/sequence-parser.test.ts +0 -821
  78. package/src/__tests__/start-marker-only-links-1386.test.ts +0 -104
  79. package/src/__tests__/state-composite-order-1287.test.ts +0 -132
  80. package/src/__tests__/state-flow-order-1240.test.ts +0 -113
  81. package/src/__tests__/state-styling.test.ts +0 -150
  82. package/src/__tests__/statements.test.ts +0 -264
  83. package/src/__tests__/styles.test.ts +0 -220
  84. package/src/__tests__/subgraph-title-edge-gap-1239.test.ts +0 -238
  85. package/src/__tests__/svg-accessible-name-conformance.test.ts +0 -276
  86. package/src/__tests__/svg-accessible-name.test.ts +0 -421
  87. package/src/__tests__/svg-csp-style-options.test.ts +0 -427
  88. package/src/__tests__/workspace-package-boundaries.test.ts +0 -336
  89. package/src/__tests__/xychart-integration.test.ts +0 -137
  90. package/src/__tests__/xychart-layout.test.ts +0 -205
  91. package/src/__tests__/xychart-parser-categories.test.ts +0 -78
  92. package/src/__tests__/xychart-parser-errors.test.ts +0 -95
@@ -1,849 +0,0 @@
1
- /**
2
- * Coverage for src/ascii/draw-bundles.ts — fan-in/fan-out edge bundle
3
- * rendering (shared trunks, junction characters, box-start connectors,
4
- * arrowheads).
5
- *
6
- * Many branches in this file are unreachable through any real Mermaid
7
- * diagram: edge-bundling.ts only creates bundles for TD graphs, and by
8
- * construction always aligns a bundle's junction/shared-trunk x (or y)
9
- * coordinate with the shared node's, so the shared trunk is always a
10
- * straight line and a fan-in edge always exits its source moving "down".
11
- * The tests below build a real bundle via the actual layout pipeline
12
- * (so canvas/grid state is authentic) and then mutate its path arrays to
13
- * exercise the geometry this file is documented to handle but that never
14
- * occurs today — verifying the drawing functions still do the right thing
15
- * with that input.
16
- */
17
- import { describe, it, expect } from 'vitest'
18
- import { parseMermaid } from '@zombie-mermaid/mermaid-parser'
19
- import { convertToAsciiGraph } from '../../packages/ascii-renderer/src/converter.ts'
20
- import { createMapping } from '../../packages/ascii-renderer/src/grid.ts'
21
- import type {
22
- AsciiConfig,
23
- AsciiGraph,
24
- EdgeBundle,
25
- GridCoord,
26
- } from '../../packages/ascii-renderer/src/types.ts'
27
- import {
28
- drawBundledEdgeSegment,
29
- drawBundleSharedPath,
30
- drawBundleArrowhead,
31
- drawBundleArrowheadStart,
32
- drawBundledEdgeArrowhead,
33
- drawBundledEdgeArrowheadStart,
34
- drawJunctionCharacter,
35
- } from '../../packages/ascii-renderer/src/draw-bundles.ts'
36
- import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
37
-
38
- function buildGraph(src: string, useAscii = false): AsciiGraph {
39
- const config: AsciiConfig = {
40
- useAscii,
41
- paddingX: 5,
42
- paddingY: 5,
43
- boxBorderPadding: 1,
44
- graphDirection: 'TD',
45
- }
46
- const graph = convertToAsciiGraph(parseMermaid(src), config)
47
- createMapping(graph)
48
- return graph
49
- }
50
-
51
- /** Every non-space character on the canvas, in scan order. */
52
- function nonSpaceChars(canvas: string[][]): string {
53
- const found: string[] = []
54
- for (const col of canvas) {
55
- for (const ch of col) {
56
- if (ch !== ' ') found.push(ch)
57
- }
58
- }
59
- return found.join('')
60
- }
61
-
62
- const FAN_IN = `flowchart TD
63
- A[One] --> C[Target]
64
- B[Two] --> C`
65
-
66
- // A plain fan-in centers its target between the sources, so the grid has no
67
- // spare columns beside the junction. The hand-built trunk paths below step a
68
- // column to either side of it; a source that feeds a second node keeps the
69
- // target under the first source (#1339), leaving that room.
70
- const FAN_IN_BESIDE = `flowchart TD
71
- A[One] --> C[Target]
72
- B[Two] --> C
73
- B --> D[Other]`
74
-
75
- const FAN_OUT = `flowchart TD
76
- C[Source] --> A[One]
77
- C --> B[Two]`
78
-
79
- const BIDIRECTIONAL_FAN_IN = `flowchart TD
80
- A[One] <--> C[Target]
81
- B[Two] <--> C`
82
-
83
- const BIDIRECTIONAL_FAN_OUT = `flowchart TD
84
- C[Source] <--> A[One]
85
- C <--> B[Two]`
86
-
87
- const MIXED_FAN_OUT = `flowchart TD
88
- C[Source] --> A[One]
89
- C[Source] <--> B[Two]`
90
-
91
- describe('draw-bundles: rendered fan-in/fan-out diagrams', () => {
92
- it('draws a fan-in bundle with a T-junction and single arrowhead (unicode)', () => {
93
- const out = renderMermaidASCII(FAN_IN, { useAscii: false })
94
- // The two sources join in one trunk whose T-junction sits over the
95
- // (centered) target.
96
- expect(out).toContain('└───────┬──────┘')
97
- expect(out).toContain('▼')
98
- expect(out.match(/▼/g)).toHaveLength(1)
99
- })
100
-
101
- it('draws a fan-in bundle box-start and junction in ASCII charset', () => {
102
- const out = renderMermaidASCII(FAN_IN, { useAscii: true })
103
- expect(out).toContain('+-------+------+')
104
- expect(out).toContain('+--+--+')
105
- expect(out).toContain('+--------+')
106
- expect(out).toContain('v')
107
- })
108
-
109
- it('draws a fan-out bundle with one arrowhead per target (unicode)', () => {
110
- const out = renderMermaidASCII(FAN_OUT, { useAscii: false })
111
- // The source sits centred over its two targets, so the shared trunk
112
- // leaves its bottom border and splits at a ┴ directly beneath it.
113
- expect(out).toMatch(/┌─+┴─+┐/)
114
- expect(out.match(/▼/g)).toHaveLength(2)
115
- })
116
-
117
- it('draws a fan-out bundle with one arrowhead per target (ASCII)', () => {
118
- const out = renderMermaidASCII(FAN_OUT, { useAscii: true })
119
- expect(out.match(/v/g)).toHaveLength(2)
120
- })
121
-
122
- it('draws a start arrowhead at each source for a bidirectional fan-in bundle', () => {
123
- const out = renderMermaidASCII(BIDIRECTIONAL_FAN_IN, { useAscii: false })
124
- // One end arrowhead at the shared target, plus one start arrowhead per source.
125
- expect(out.match(/▼/g)).toHaveLength(1)
126
- expect(out.match(/▲/g)).toHaveLength(2)
127
- })
128
-
129
- it('draws a single shared start arrowhead for a bidirectional fan-out bundle', () => {
130
- const out = renderMermaidASCII(BIDIRECTIONAL_FAN_OUT, { useAscii: false })
131
- // One end arrowhead per target, plus a single shared start arrowhead at the source.
132
- expect(out.match(/▼/g)).toHaveLength(2)
133
- expect(out.match(/▲/g)).toHaveLength(1)
134
- })
135
-
136
- it('omits the shared fan-out start arrowhead when the bundled edges disagree on hasArrowStart', () => {
137
- const out = renderMermaidASCII(MIXED_FAN_OUT, { useAscii: false })
138
- expect(out.match(/▼/g)).toHaveLength(2)
139
- expect(out).not.toContain('▲')
140
- })
141
- })
142
-
143
- describe('drawBundledEdgeSegment', () => {
144
- const graph = buildGraph(FAN_IN)
145
- const bundle = graph.bundles[0]!
146
- const junction = bundle.junctionPoint!
147
- const edgeTemplate = bundle.edges[0]!
148
-
149
- it('returns unmodified canvases when pathToJunction is empty', () => {
150
- const edge = { ...edgeTemplate, pathToJunction: [] }
151
- const result = drawBundledEdgeSegment(graph, edge, bundle)
152
- for (const canvas of result) {
153
- expect(nonSpaceChars(canvas)).toBe('')
154
- }
155
- })
156
-
157
- it('returns unmodified canvases when pathToJunction is undefined', () => {
158
- const edge = { ...edgeTemplate, pathToJunction: undefined }
159
- const result = drawBundledEdgeSegment(graph, edge, bundle)
160
- for (const canvas of result) {
161
- expect(nonSpaceChars(canvas)).toBe('')
162
- }
163
- })
164
-
165
- it.each<[string, GridCoord[], string]>([
166
- [
167
- 'left-then-down bend',
168
- [
169
- { x: junction.x + 4, y: junction.y - 1 },
170
- { x: junction.x, y: junction.y - 1 },
171
- { x: junction.x, y: junction.y },
172
- ],
173
- '┌',
174
- ],
175
- [
176
- 'up-then-right bend',
177
- [
178
- { x: junction.x, y: junction.y + 4 },
179
- { x: junction.x, y: junction.y - 1 },
180
- { x: junction.x + 4, y: junction.y - 1 },
181
- ],
182
- '┌',
183
- ],
184
- [
185
- 'down-then-right bend',
186
- [
187
- { x: junction.x, y: junction.y - 2 },
188
- { x: junction.x, y: junction.y - 1 },
189
- { x: junction.x + 4, y: junction.y - 1 },
190
- ],
191
- '└',
192
- ],
193
- [
194
- 'left-then-up bend',
195
- [
196
- { x: junction.x + 4, y: junction.y - 1 },
197
- { x: junction.x, y: junction.y - 1 },
198
- { x: junction.x, y: junction.y - 5 },
199
- ],
200
- '└',
201
- ],
202
- [
203
- 'right-then-down bend',
204
- [
205
- { x: junction.x - 4, y: junction.y - 1 },
206
- { x: junction.x, y: junction.y - 1 },
207
- { x: junction.x, y: junction.y },
208
- ],
209
- '┐',
210
- ],
211
- [
212
- 'up-then-left bend',
213
- [
214
- { x: junction.x, y: junction.y + 4 },
215
- { x: junction.x, y: junction.y - 1 },
216
- { x: junction.x - 4, y: junction.y - 1 },
217
- ],
218
- '┐',
219
- ],
220
- [
221
- 'right-then-up bend',
222
- [
223
- { x: junction.x - 4, y: junction.y - 1 },
224
- { x: junction.x, y: junction.y - 1 },
225
- { x: junction.x, y: junction.y - 5 },
226
- ],
227
- '┘',
228
- ],
229
- [
230
- 'down-then-left bend',
231
- [
232
- { x: junction.x, y: junction.y - 2 },
233
- { x: junction.x, y: junction.y - 1 },
234
- { x: junction.x - 4, y: junction.y - 1 },
235
- ],
236
- '┘',
237
- ],
238
- [
239
- 'straight run (no real turn)',
240
- [
241
- { x: junction.x, y: junction.y - 2 },
242
- { x: junction.x, y: junction.y - 1 },
243
- { x: junction.x, y: junction.y },
244
- ],
245
- '+',
246
- ],
247
- ])('draws the %s corner', (_label, path, expectedChar) => {
248
- const edge = { ...edgeTemplate, pathToJunction: path }
249
- const [, , , , cornersCanvas] = drawBundledEdgeSegment(graph, edge, bundle)
250
- expect(nonSpaceChars(cornersCanvas)).toBe(expectedChar)
251
- })
252
-
253
- it.each<[string, GridCoord[], string]>([
254
- [
255
- 'Up',
256
- [
257
- { x: junction.x, y: junction.y + 3 },
258
- { x: junction.x, y: junction.y },
259
- ],
260
- '┴',
261
- ],
262
- [
263
- 'Left',
264
- [
265
- { x: junction.x + 4, y: junction.y },
266
- { x: junction.x, y: junction.y },
267
- ],
268
- '┤',
269
- ],
270
- [
271
- 'Right',
272
- [
273
- { x: junction.x - 4, y: junction.y },
274
- { x: junction.x + 4, y: junction.y },
275
- ],
276
- '├',
277
- ],
278
- ])(
279
- 'draws a %s-direction box-start connector',
280
- (_label, path, expectedChar) => {
281
- const edge = { ...edgeTemplate, pathToJunction: path }
282
- const [, boxStartCanvas] = drawBundledEdgeSegment(graph, edge, bundle)
283
- expect(nonSpaceChars(boxStartCanvas)).toBe(expectedChar)
284
- },
285
- )
286
- })
287
-
288
- describe('drawBundleSharedPath', () => {
289
- it('returns unmodified canvases when sharedPath has fewer than 2 points', () => {
290
- const graph = buildGraph(FAN_IN_BESIDE)
291
- const bundle = graph.bundles[0]!
292
- const shortBundle = {
293
- ...bundle,
294
- sharedPath: [bundle.junctionPoint!],
295
- }
296
- const [pathCanvas, cornersCanvas] = drawBundleSharedPath(graph, shortBundle)
297
- expect(nonSpaceChars(pathCanvas)).toBe('')
298
- expect(nonSpaceChars(cornersCanvas)).toBe('')
299
- })
300
-
301
- it.each<[string, (j: GridCoord) => GridCoord[], string]>([
302
- [
303
- 'right-then-down bend',
304
- (j) => [j, { x: j.x + 4, y: j.y }, { x: j.x + 4, y: j.y + 1 }],
305
- '┐',
306
- ],
307
- [
308
- 'right-then-up bend',
309
- (j) => [
310
- { x: j.x - 4, y: j.y + 1 },
311
- { x: j.x, y: j.y + 1 },
312
- { x: j.x, y: j.y },
313
- ],
314
- '┘',
315
- ],
316
- [
317
- 'up-then-right bend',
318
- (j) => [
319
- { x: j.x, y: j.y + 2 },
320
- { x: j.x, y: j.y + 1 },
321
- { x: j.x + 4, y: j.y + 1 },
322
- ],
323
- '┌',
324
- ],
325
- [
326
- 'left-then-up bend',
327
- (j) => [{ x: j.x + 4, y: j.y }, j, { x: j.x, y: j.y - 1 }],
328
- '└',
329
- ],
330
- [
331
- 'straight run (no real turn)',
332
- (j) => [j, { x: j.x, y: j.y + 1 }, { x: j.x, y: j.y + 2 }],
333
- '+',
334
- ],
335
- ])(
336
- 'draws a %s in the shared trunk (unicode)',
337
- (_label, makePath, expectedChar) => {
338
- const graph = buildGraph(FAN_IN_BESIDE)
339
- const bundle = graph.bundles[0]!
340
- const bent = {
341
- ...bundle,
342
- sharedPath: makePath(bundle.junctionPoint!),
343
- }
344
- const [, cornersCanvas] = drawBundleSharedPath(graph, bent)
345
- expect(nonSpaceChars(cornersCanvas)).toBe(expectedChar)
346
- },
347
- )
348
-
349
- it('draws a bend in the shared trunk as "+" in ASCII charset', () => {
350
- const graph = buildGraph(FAN_IN_BESIDE, true)
351
- const bundle = graph.bundles[0]!
352
- const junction = bundle.junctionPoint!
353
- const bent = {
354
- ...bundle,
355
- sharedPath: [
356
- junction,
357
- { x: junction.x + 4, y: junction.y },
358
- { x: junction.x + 4, y: junction.y + 1 },
359
- ],
360
- }
361
- const [, cornersCanvas] = drawBundleSharedPath(graph, bent)
362
- expect(nonSpaceChars(cornersCanvas)).toBe('+')
363
- })
364
- })
365
-
366
- /** A 2-point path whose last segment arrives from the given direction. */
367
- function arrivalSharedPath(dir: 'Up' | 'Left' | 'Right'): GridCoord[] {
368
- switch (dir) {
369
- case 'Up':
370
- return [
371
- { x: 1, y: 6 },
372
- { x: 1, y: 4 },
373
- ]
374
- case 'Left':
375
- return [
376
- { x: 5, y: 4 },
377
- { x: 1, y: 4 },
378
- ]
379
- case 'Right':
380
- return [
381
- { x: 1, y: 4 },
382
- { x: 5, y: 4 },
383
- ]
384
- }
385
- }
386
-
387
- describe('drawBundleArrowhead', () => {
388
- const graph = buildGraph(FAN_IN)
389
- const bundle = graph.bundles[0]!
390
-
391
- it('returns an unmodified canvas when sharedPath has fewer than 2 points', () => {
392
- const shortBundle = {
393
- ...bundle,
394
- sharedPath: [bundle.junctionPoint!],
395
- }
396
- const canvas = drawBundleArrowhead(graph, shortBundle)
397
- expect(nonSpaceChars(canvas)).toBe('')
398
- })
399
-
400
- it.each<['Up' | 'Left' | 'Right', string]>([
401
- ['Up', '▲'],
402
- ['Left', '◄'],
403
- ['Right', '►'],
404
- ])(
405
- 'draws a %s-arriving trunk as the %s arrowhead (unicode)',
406
- (dir, expectedChar) => {
407
- const b = { ...bundle, sharedPath: arrivalSharedPath(dir) }
408
- const canvas = drawBundleArrowhead(graph, b)
409
- expect(nonSpaceChars(canvas)).toBe(expectedChar)
410
- },
411
- )
412
-
413
- it.each<['Up' | 'Left' | 'Right', string]>([
414
- ['Up', '^'],
415
- ['Left', '<'],
416
- ['Right', '>'],
417
- ])(
418
- 'draws a %s-arriving trunk as the %s arrowhead (ASCII)',
419
- (dir, expectedChar) => {
420
- const asciiGraph = buildGraph(FAN_IN, true)
421
- const asciiBundle = asciiGraph.bundles[0]!
422
- const b = {
423
- ...asciiBundle,
424
- sharedPath: arrivalSharedPath(dir),
425
- }
426
- const canvas = drawBundleArrowhead(asciiGraph, b)
427
- expect(nonSpaceChars(canvas)).toBe(expectedChar)
428
- },
429
- )
430
-
431
- it('falls back to the default arrowhead for a diagonal trunk direction (unicode)', () => {
432
- const b = {
433
- ...bundle,
434
- sharedPath: [
435
- { x: 1, y: 2 },
436
- { x: 5, y: 4 },
437
- ],
438
- }
439
- const canvas = drawBundleArrowhead(graph, b)
440
- expect(nonSpaceChars(canvas)).toBe('▼')
441
- })
442
-
443
- it('falls back to the default arrowhead for a diagonal trunk direction (ASCII)', () => {
444
- const asciiGraph = buildGraph(FAN_IN, true)
445
- const asciiBundle = asciiGraph.bundles[0]!
446
- const b = {
447
- ...asciiBundle,
448
- sharedPath: [
449
- { x: 1, y: 2 },
450
- { x: 5, y: 4 },
451
- ],
452
- }
453
- const canvas = drawBundleArrowhead(asciiGraph, b)
454
- expect(nonSpaceChars(canvas)).toBe('v')
455
- })
456
-
457
- it('offsets the arrowhead horizontally for an LR graph direction', () => {
458
- // Bundling never fires for LR graphs (see edge-bundling.ts), so this
459
- // exercises the function's LR branch directly rather than through a
460
- // real diagram. The shared node is shifted away from the canvas edge
461
- // first since its real (TD) attachment point sits at x=0, and the LR
462
- // offset (`dc.x -= 1`) would otherwise underflow the canvas.
463
- const shiftedNode = {
464
- ...bundle.sharedNode,
465
- drawingCoord: {
466
- ...bundle.sharedNode.drawingCoord!,
467
- x: bundle.sharedNode.drawingCoord!.x + 10,
468
- },
469
- }
470
- const lrGraph = {
471
- ...graph,
472
- config: { ...graph.config, graphDirection: 'LR' as const },
473
- }
474
- const b = {
475
- ...bundle,
476
- sharedNode: shiftedNode,
477
- sharedPath: [
478
- { x: 1, y: 2 },
479
- { x: 1, y: 4 },
480
- ],
481
- }
482
- const canvas = drawBundleArrowhead(lrGraph, b)
483
- expect(nonSpaceChars(canvas)).toBe('▼')
484
- })
485
- })
486
-
487
- describe('drawBundleArrowheadStart', () => {
488
- const graph = buildGraph(BIDIRECTIONAL_FAN_OUT)
489
- const bundle = graph.bundles[0]!
490
-
491
- it('returns an unmodified canvas when sharedPath has fewer than 2 points', () => {
492
- const shortBundle = {
493
- ...bundle,
494
- sharedPath: [bundle.junctionPoint!],
495
- }
496
- const canvas = drawBundleArrowheadStart(graph, shortBundle)
497
- expect(nonSpaceChars(canvas)).toBe('')
498
- })
499
-
500
- it.each<['Up' | 'Left' | 'Right', string]>([
501
- ['Up', '▼'],
502
- ['Left', '►'],
503
- ['Right', '◄'],
504
- ])(
505
- 'draws a trunk departing %s as the reversed %s arrowhead (unicode)',
506
- (dir, expectedChar) => {
507
- const b = { ...bundle, sharedPath: arrivalSharedPath(dir) }
508
- const canvas = drawBundleArrowheadStart(graph, b)
509
- expect(nonSpaceChars(canvas)).toBe(expectedChar)
510
- },
511
- )
512
-
513
- it.each<['Up' | 'Left' | 'Right', string]>([
514
- ['Up', 'v'],
515
- ['Left', '>'],
516
- ['Right', '<'],
517
- ])(
518
- 'draws a trunk departing %s as the reversed %s arrowhead (ASCII)',
519
- (dir, expectedChar) => {
520
- const asciiGraph = buildGraph(BIDIRECTIONAL_FAN_OUT, true)
521
- const asciiBundle = asciiGraph.bundles[0]!
522
- const b = {
523
- ...asciiBundle,
524
- sharedPath: arrivalSharedPath(dir),
525
- }
526
- const canvas = drawBundleArrowheadStart(asciiGraph, b)
527
- expect(nonSpaceChars(canvas)).toBe(expectedChar)
528
- },
529
- )
530
-
531
- it('falls back to the default arrowhead for a diagonal trunk direction (unicode)', () => {
532
- const b = {
533
- ...bundle,
534
- sharedPath: [
535
- { x: 1, y: 2 },
536
- { x: 5, y: 4 },
537
- ],
538
- }
539
- const canvas = drawBundleArrowheadStart(graph, b)
540
- expect(nonSpaceChars(canvas)).toBe('▲')
541
- })
542
-
543
- it('falls back to the default arrowhead for a diagonal trunk direction (ASCII)', () => {
544
- const asciiGraph = buildGraph(BIDIRECTIONAL_FAN_OUT, true)
545
- const asciiBundle = asciiGraph.bundles[0]!
546
- const b = {
547
- ...asciiBundle,
548
- sharedPath: [
549
- { x: 1, y: 2 },
550
- { x: 5, y: 4 },
551
- ],
552
- }
553
- const canvas = drawBundleArrowheadStart(asciiGraph, b)
554
- expect(nonSpaceChars(canvas)).toBe('^')
555
- })
556
-
557
- it('offsets the arrowhead horizontally for an LR graph direction', () => {
558
- // Bundling never fires for LR graphs (see edge-bundling.ts), so this
559
- // exercises the function's LR branch directly rather than through a
560
- // real diagram.
561
- const lrGraph = {
562
- ...graph,
563
- config: { ...graph.config, graphDirection: 'LR' as const },
564
- }
565
- const b = {
566
- ...bundle,
567
- sharedPath: [
568
- { x: 1, y: 2 },
569
- { x: 1, y: 4 },
570
- ],
571
- }
572
- const canvas = drawBundleArrowheadStart(lrGraph, b)
573
- expect(nonSpaceChars(canvas)).toBe('▲')
574
- })
575
- })
576
-
577
- describe('drawBundledEdgeArrowheadStart', () => {
578
- const graph = buildGraph(BIDIRECTIONAL_FAN_IN)
579
- const bundle = graph.bundles[0]!
580
- const edgeTemplate = bundle.edges[0]!
581
-
582
- it('returns an unmodified canvas when pathToJunction is undefined', () => {
583
- const edge = { ...edgeTemplate, pathToJunction: undefined }
584
- const canvas = drawBundledEdgeArrowheadStart(graph, edge)
585
- expect(nonSpaceChars(canvas)).toBe('')
586
- })
587
-
588
- it.each<['Up' | 'Left' | 'Right', string]>([
589
- ['Up', '▼'],
590
- ['Left', '►'],
591
- ['Right', '◄'],
592
- ])(
593
- 'draws an edge departing %s as the reversed %s arrowhead (unicode)',
594
- (dir, expectedChar) => {
595
- const edge = {
596
- ...edgeTemplate,
597
- pathToJunction: arrivalSharedPath(dir),
598
- }
599
- const canvas = drawBundledEdgeArrowheadStart(graph, edge)
600
- expect(nonSpaceChars(canvas)).toBe(expectedChar)
601
- },
602
- )
603
-
604
- it.each<['Up' | 'Left' | 'Right', string]>([
605
- ['Up', 'v'],
606
- ['Left', '>'],
607
- ['Right', '<'],
608
- ])(
609
- 'draws an edge departing %s as the reversed %s arrowhead (ASCII)',
610
- (dir, expectedChar) => {
611
- const asciiGraph = buildGraph(BIDIRECTIONAL_FAN_IN, true)
612
- const asciiBundle = asciiGraph.bundles[0]!
613
- const asciiEdgeTemplate = asciiBundle.edges[0]!
614
- const edge = {
615
- ...asciiEdgeTemplate,
616
- pathToJunction: arrivalSharedPath(dir),
617
- }
618
- const canvas = drawBundledEdgeArrowheadStart(asciiGraph, edge)
619
- expect(nonSpaceChars(canvas)).toBe(expectedChar)
620
- },
621
- )
622
-
623
- it('falls back to the default arrowhead for a diagonal edge direction (unicode)', () => {
624
- const edge = {
625
- ...edgeTemplate,
626
- pathToJunction: [
627
- { x: 1, y: 2 },
628
- { x: 5, y: 4 },
629
- ],
630
- }
631
- const canvas = drawBundledEdgeArrowheadStart(graph, edge)
632
- expect(nonSpaceChars(canvas)).toBe('▼')
633
- })
634
-
635
- it('falls back to the default arrowhead for a diagonal edge direction (ASCII)', () => {
636
- const asciiGraph = buildGraph(BIDIRECTIONAL_FAN_IN, true)
637
- const asciiBundle = asciiGraph.bundles[0]!
638
- const asciiEdgeTemplate = asciiBundle.edges[0]!
639
- const edge = {
640
- ...asciiEdgeTemplate,
641
- pathToJunction: [
642
- { x: 1, y: 2 },
643
- { x: 5, y: 4 },
644
- ],
645
- }
646
- const canvas = drawBundledEdgeArrowheadStart(asciiGraph, edge)
647
- expect(nonSpaceChars(canvas)).toBe('v')
648
- })
649
- })
650
-
651
- describe('drawBundledEdgeArrowhead', () => {
652
- const graph = buildGraph(FAN_OUT)
653
- const bundle = graph.bundles[0]!
654
- const edgeTemplate = bundle.edges[0]!
655
-
656
- it('returns an unmodified canvas when pathToJunction is undefined', () => {
657
- const edge = { ...edgeTemplate, pathToJunction: undefined }
658
- const canvas = drawBundledEdgeArrowhead(graph, edge)
659
- expect(nonSpaceChars(canvas)).toBe('')
660
- })
661
-
662
- it.each<['Up' | 'Left' | 'Right', string]>([
663
- ['Up', '▲'],
664
- ['Left', '◄'],
665
- ['Right', '►'],
666
- ])(
667
- 'draws a %s-arriving edge as the %s arrowhead (unicode)',
668
- (dir, expectedChar) => {
669
- const edge = {
670
- ...edgeTemplate,
671
- pathToJunction: arrivalSharedPath(dir),
672
- }
673
- const canvas = drawBundledEdgeArrowhead(graph, edge)
674
- expect(nonSpaceChars(canvas)).toBe(expectedChar)
675
- },
676
- )
677
-
678
- it.each<['Up' | 'Left' | 'Right', string]>([
679
- ['Up', '^'],
680
- ['Left', '<'],
681
- ['Right', '>'],
682
- ])(
683
- 'draws a %s-arriving edge as the %s arrowhead (ASCII)',
684
- (dir, expectedChar) => {
685
- const asciiGraph = buildGraph(FAN_OUT, true)
686
- const asciiBundle = asciiGraph.bundles[0]!
687
- const asciiEdgeTemplate = asciiBundle.edges[0]!
688
- const edge = {
689
- ...asciiEdgeTemplate,
690
- pathToJunction: arrivalSharedPath(dir),
691
- }
692
- const canvas = drawBundledEdgeArrowhead(asciiGraph, edge)
693
- expect(nonSpaceChars(canvas)).toBe(expectedChar)
694
- },
695
- )
696
-
697
- it('falls back to the default arrowhead for a diagonal edge direction (unicode)', () => {
698
- const edge = {
699
- ...edgeTemplate,
700
- pathToJunction: [
701
- { x: 1, y: 2 },
702
- { x: 5, y: 4 },
703
- ],
704
- }
705
- const canvas = drawBundledEdgeArrowhead(graph, edge)
706
- expect(nonSpaceChars(canvas)).toBe('▼')
707
- })
708
-
709
- it('falls back to the default arrowhead for a diagonal edge direction (ASCII)', () => {
710
- const asciiGraph = buildGraph(FAN_OUT, true)
711
- const asciiBundle = asciiGraph.bundles[0]!
712
- const asciiEdgeTemplate = asciiBundle.edges[0]!
713
- const edge = {
714
- ...asciiEdgeTemplate,
715
- pathToJunction: [
716
- { x: 1, y: 2 },
717
- { x: 5, y: 4 },
718
- ],
719
- }
720
- const canvas = drawBundledEdgeArrowhead(asciiGraph, edge)
721
- expect(nonSpaceChars(canvas)).toBe('v')
722
- })
723
-
724
- it('offsets the arrowhead horizontally for an LR graph direction', () => {
725
- // Same rationale as the drawBundleArrowhead LR test above: bundling
726
- // never fires for LR graphs, so this directly exercises the LR branch.
727
- const shiftedTo = {
728
- ...edgeTemplate.to,
729
- drawingCoord: {
730
- ...edgeTemplate.to.drawingCoord!,
731
- x: edgeTemplate.to.drawingCoord!.x + 10,
732
- },
733
- }
734
- const lrGraph = {
735
- ...graph,
736
- config: { ...graph.config, graphDirection: 'LR' as const },
737
- }
738
- const edge = {
739
- ...edgeTemplate,
740
- to: shiftedTo,
741
- pathToJunction: [
742
- { x: 1, y: 2 },
743
- { x: 1, y: 4 },
744
- ],
745
- }
746
- const canvas = drawBundledEdgeArrowhead(lrGraph, edge)
747
- expect(nonSpaceChars(canvas)).toBe('▼')
748
- })
749
- })
750
-
751
- describe('drawJunctionCharacter', () => {
752
- const graph = buildGraph(FAN_IN)
753
- const bundle = graph.bundles[0]!
754
- const junction = bundle.junctionPoint!
755
-
756
- type Dir = 'Down' | 'Up' | 'Left' | 'Right'
757
-
758
- /** A point that arrives at the junction moving in the given direction. */
759
- function arrivalPoint(dir: Dir): GridCoord {
760
- switch (dir) {
761
- case 'Down':
762
- return { x: junction.x, y: junction.y - 1 }
763
- case 'Up':
764
- return { x: junction.x, y: junction.y + 1 }
765
- case 'Right':
766
- return { x: junction.x - 1, y: junction.y }
767
- case 'Left':
768
- return { x: junction.x + 1, y: junction.y }
769
- }
770
- }
771
-
772
- /** A point the shared trunk continues to from the junction, in the given direction. */
773
- function sharedNextPoint(dir: Dir): GridCoord {
774
- switch (dir) {
775
- case 'Down':
776
- return { x: junction.x, y: junction.y + 1 }
777
- case 'Up':
778
- return { x: junction.x, y: junction.y - 1 }
779
- case 'Right':
780
- return { x: junction.x + 1, y: junction.y }
781
- case 'Left':
782
- return { x: junction.x - 1, y: junction.y }
783
- }
784
- }
785
-
786
- function makeBundle(sharedDir: Dir | null, arrivalDirs: Dir[]): EdgeBundle {
787
- const sharedPath = sharedDir ? [junction, sharedNextPoint(sharedDir)] : []
788
- const edges = arrivalDirs.map((d) => ({
789
- ...bundle.edges[0]!,
790
- pathToJunction: [arrivalPoint(d), junction],
791
- }))
792
- return { ...bundle, sharedPath, edges }
793
- }
794
-
795
- it.each<[string, Dir | null, Dir[], string]>([
796
- ['all four directions', 'Down', ['Down', 'Right', 'Left'], '┼'],
797
- ['down + left + right (no up)', 'Down', ['Right', 'Left'], '┬'],
798
- ['up + left + right (no down)', 'Up', ['Right', 'Left'], '┴'],
799
- ['up + down + left (no right)', 'Down', ['Down', 'Right'], '┤'],
800
- ['left + right only', null, ['Right', 'Left'], '─'],
801
- ['up + down only', 'Down', ['Down'], '│'],
802
- ['down + left only', 'Down', ['Right'], '┐'],
803
- ['up + right only', 'Up', ['Left'], '└'],
804
- ['up + left only', 'Up', ['Right'], '┘'],
805
- ['a single direction (no named shape)', 'Up', [], '┼'],
806
- ['no connecting directions at all', null, [], '┼'],
807
- [
808
- 'shared trunk exits right, edge arrives from above',
809
- 'Right',
810
- ['Down'],
811
- '└',
812
- ],
813
- ['shared trunk exits left, edge arrives from above', 'Left', ['Down'], '┘'],
814
- ])('draws %s', (_label, sharedDir, arrivalDirs, expectedChar) => {
815
- const b = makeBundle(sharedDir, arrivalDirs)
816
- const canvas = drawJunctionCharacter(graph, b)
817
- expect(nonSpaceChars(canvas)).toBe(expectedChar)
818
- })
819
-
820
- it('draws "+" for any connecting directions in ASCII charset', () => {
821
- const asciiGraph = buildGraph(FAN_IN, true)
822
- const asciiBundle = asciiGraph.bundles[0]!
823
- const asciiJunction = asciiBundle.junctionPoint!
824
- const b = {
825
- ...asciiBundle,
826
- sharedPath: [
827
- asciiJunction,
828
- { x: asciiJunction.x, y: asciiJunction.y + 1 },
829
- ],
830
- edges: [
831
- {
832
- ...asciiBundle.edges[0]!,
833
- pathToJunction: [
834
- { x: asciiJunction.x - 1, y: asciiJunction.y },
835
- asciiJunction,
836
- ],
837
- },
838
- ],
839
- }
840
- const canvas = drawJunctionCharacter(asciiGraph, b)
841
- expect(nonSpaceChars(canvas)).toBe('+')
842
- })
843
-
844
- it('returns an unmodified canvas when junctionPoint is null', () => {
845
- const b = { ...bundle, junctionPoint: null }
846
- const canvas = drawJunctionCharacter(graph, b)
847
- expect(nonSpaceChars(canvas)).toBe('')
848
- })
849
- })