@zombie-mermaid/mermaid-parser 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Tests for xychart color palette generation
3
+ * (packages/mermaid-parser/src/xychart/colors.ts).
4
+ */
5
+ import { describe, it, expect } from 'vitest'
6
+ import {
7
+ CHART_ACCENT_FALLBACK,
8
+ isValidHex,
9
+ isDarkBackground,
10
+ mixHexColors,
11
+ getSeriesColor,
12
+ } from '@zombie-mermaid/mermaid-parser'
13
+
14
+ describe('isValidHex', () => {
15
+ it('accepts a well-formed 6-digit hex color', () => {
16
+ expect(isValidHex('#3b82f6')).toBe(true)
17
+ expect(isValidHex('#FFFFFF')).toBe(true)
18
+ })
19
+
20
+ it('rejects non-hex strings like CSS variable references', () => {
21
+ expect(isValidHex('var(--accent)')).toBe(false)
22
+ expect(isValidHex('#fff')).toBe(false)
23
+ expect(isValidHex('not-a-color')).toBe(false)
24
+ expect(isValidHex('')).toBe(false)
25
+ })
26
+ })
27
+
28
+ describe('isDarkBackground', () => {
29
+ it('treats black as dark', () => {
30
+ expect(isDarkBackground('#000000')).toBe(true)
31
+ })
32
+
33
+ it('treats white as light', () => {
34
+ expect(isDarkBackground('#ffffff')).toBe(false)
35
+ })
36
+
37
+ it('treats a gray just below 50% lightness as dark', () => {
38
+ expect(isDarkBackground('#7f7f7f')).toBe(true)
39
+ })
40
+
41
+ it('treats a gray at or above 50% lightness as light', () => {
42
+ expect(isDarkBackground('#808080')).toBe(false)
43
+ })
44
+ })
45
+
46
+ describe('mixHexColors', () => {
47
+ it('returns the background color unchanged at ratio 0', () => {
48
+ expect(mixHexColors('#000000', '#ffffff', 0)).toBe('#000000')
49
+ })
50
+
51
+ it('returns the foreground color unchanged at ratio 1', () => {
52
+ expect(mixHexColors('#000000', '#ffffff', 1)).toBe('#ffffff')
53
+ })
54
+
55
+ it('blends bg and fg proportionally at ratio 0.5', () => {
56
+ expect(mixHexColors('#000000', '#ffffff', 0.5)).toBe('#808080')
57
+ })
58
+
59
+ it('blends non-grayscale colors channel by channel', () => {
60
+ expect(mixHexColors('#3b82f6', '#3b82f6', 0.5)).toBe('#3b82f6')
61
+ })
62
+ })
63
+
64
+ describe('getSeriesColor', () => {
65
+ it('returns the accent color unchanged for index 0', () => {
66
+ expect(getSeriesColor(0, '#3b82f6')).toBe('#3b82f6')
67
+ })
68
+
69
+ it('falls back to the default accent when the accent is not a valid hex', () => {
70
+ const withInvalidAccent = getSeriesColor(1, 'var(--accent)')
71
+ const withFallback = getSeriesColor(1, CHART_ACCENT_FALLBACK)
72
+ expect(withInvalidAccent).toBe(withFallback)
73
+ })
74
+
75
+ it('generates distinct colors for successive series indices', () => {
76
+ const colors = [0, 1, 2, 3, 4, 5].map((i) => getSeriesColor(i, '#3b82f6'))
77
+ const unique = new Set(colors)
78
+ expect(unique.size).toBe(colors.length)
79
+ for (const c of colors) expect(isValidHex(c)).toBe(true)
80
+ })
81
+
82
+ it('produces darker shades for odd indices and lighter for even indices on a light background', () => {
83
+ const odd = getSeriesColor(1, '#3b82f6', '#ffffff')
84
+ const even = getSeriesColor(2, '#3b82f6', '#ffffff')
85
+ const [, , oddL] = hexLightness(odd)
86
+ const [, , evenL] = hexLightness(even)
87
+ expect(oddL).toBeLessThan(50)
88
+ expect(evenL).toBeGreaterThan(50)
89
+ })
90
+
91
+ it('flips shade direction on a dark background so shades stay visible', () => {
92
+ const oddOnDark = getSeriesColor(1, '#3b82f6', '#000000')
93
+ const evenOnDark = getSeriesColor(2, '#3b82f6', '#000000')
94
+ const [, , oddL] = hexLightness(oddOnDark)
95
+ const [, , evenL] = hexLightness(evenOnDark)
96
+ expect(oddL).toBeGreaterThan(50)
97
+ expect(evenL).toBeLessThan(50)
98
+ })
99
+
100
+ it('falls back to light-background shading when bgColor is not a valid hex', () => {
101
+ const withInvalidBg = getSeriesColor(1, '#3b82f6', 'var(--bg)')
102
+ const withNoBg = getSeriesColor(1, '#3b82f6')
103
+ expect(withInvalidBg).toBe(withNoBg)
104
+ })
105
+
106
+ it('produces more colors than the base palette when there are many series', () => {
107
+ const colors = Array.from({ length: 12 }, (_, i) =>
108
+ getSeriesColor(i, '#3b82f6'),
109
+ )
110
+ expect(new Set(colors).size).toBe(colors.length)
111
+ })
112
+
113
+ it('wraps hue drift around the hue wheel for low-hue accents', () => {
114
+ const lighter = getSeriesColor(2, '#ff0000')
115
+ const darker = getSeriesColor(1, '#ff0000')
116
+ expect(isValidHex(lighter)).toBe(true)
117
+ expect(isValidHex(darker)).toBe(true)
118
+ expect(lighter).not.toBe(darker)
119
+ })
120
+
121
+ it('handles accents where red is the max channel and blue exceeds green', () => {
122
+ const color = getSeriesColor(1, '#ff0080')
123
+ expect(isValidHex(color)).toBe(true)
124
+ })
125
+
126
+ it('handles accents where green is the max channel', () => {
127
+ const color = getSeriesColor(1, '#00ff00')
128
+ expect(isValidHex(color)).toBe(true)
129
+ })
130
+
131
+ it('clamps lightness so far tiers stay within a visible range', () => {
132
+ const veryOdd = getSeriesColor(99, '#3b82f6')
133
+ const veryEven = getSeriesColor(100, '#3b82f6')
134
+ expect(isValidHex(veryOdd)).toBe(true)
135
+ expect(isValidHex(veryEven)).toBe(true)
136
+ })
137
+ })
138
+
139
+ /** Minimal hex→HSL helper for assertions (mirrors colors.ts's private hexToHsl). */
140
+ function hexLightness(hex: string): [number, number, number] {
141
+ const h = hex.replace('#', '')
142
+ const r = parseInt(h.substring(0, 2), 16) / 255
143
+ const g = parseInt(h.substring(2, 4), 16) / 255
144
+ const b = parseInt(h.substring(4, 6), 16) / 255
145
+ const max = Math.max(r, g, b)
146
+ const min = Math.min(r, g, b)
147
+ const l = ((max + min) / 2) * 100
148
+ return [0, 0, l]
149
+ }
@@ -0,0 +1,15 @@
1
+ // ============================================================================
2
+ // Class member formatting — shared between the SVG (layout.ts) and ASCII
3
+ // (ascii/class-diagram.ts) renderers so method parentheses/params can't drift
4
+ // between the two again. See issue #290.
5
+ // ============================================================================
6
+
7
+ import type { ClassMember } from './types.ts'
8
+
9
+ /** Format a class member as a display string: visibility + name(+params for methods) + optional type */
10
+ export function formatClassMember(m: ClassMember): string {
11
+ const vis = m.visibility ? `${m.visibility} ` : ''
12
+ const name = m.isMethod ? `${m.name}(${m.params || ''})` : m.name
13
+ const type = m.type ? `: ${m.type}` : ''
14
+ return `${vis}${name}${type}`
15
+ }