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.
- package/package.json +9 -7
- package/src/__tests__/architecture-integration.test.ts +0 -217
- package/src/__tests__/ascii-ansi.test.ts +0 -539
- package/src/__tests__/ascii-draw-bundles.test.ts +0 -849
- package/src/__tests__/ascii-edge-bundling-routing.test.ts +0 -214
- package/src/__tests__/ascii-edge-cross-style-overlap.test.ts +0 -216
- package/src/__tests__/ascii-grid-deep-chain.test.ts +0 -64
- package/src/__tests__/ascii-hyperlinks.test.ts +0 -364
- package/src/__tests__/ascii-nested-subgraph-sibling-order-444.test.ts +0 -108
- package/src/__tests__/ascii-nonmember-beside-frame-1283.test.ts +0 -113
- package/src/__tests__/ascii-package-boundary.test.ts +0 -291
- package/src/__tests__/ascii-parallel-edges-329.test.ts +0 -315
- package/src/__tests__/ascii-parallel-lane-border-1230.test.ts +0 -71
- package/src/__tests__/ascii-reverse-direction-edges-629.test.ts +0 -282
- package/src/__tests__/ascii-stacked-frame-geometry-1285.test.ts +0 -107
- package/src/__tests__/ascii-subgraph-bbox.test.ts +0 -139
- package/src/__tests__/ascii-subgraph-later-edgeless-301.test.ts +0 -277
- package/src/__tests__/browser.test.ts +0 -34
- package/src/__tests__/c4-integration.test.ts +0 -161
- package/src/__tests__/c4-orientation.test.ts +0 -162
- package/src/__tests__/c4-upstream-integration.test.ts +0 -211
- package/src/__tests__/class-click-interactions.test.ts +0 -232
- package/src/__tests__/class-integration.test.ts +0 -154
- package/src/__tests__/class-notes.test.ts +0 -317
- package/src/__tests__/class-parser-error-messages.test.ts +0 -162
- package/src/__tests__/class-parser.test.ts +0 -347
- package/src/__tests__/class-styling.test.ts +0 -485
- package/src/__tests__/cli-diagrams.test.ts +0 -106
- package/src/__tests__/cli-e2e.test.ts +0 -275
- package/src/__tests__/cli-entrypoint.test.ts +0 -146
- package/src/__tests__/cli-mcp-dispatch.test.ts +0 -68
- package/src/__tests__/cli-mcp.test.ts +0 -44
- package/src/__tests__/cli-png.test.ts +0 -35
- package/src/__tests__/cli-render.test.ts +0 -988
- package/src/__tests__/cli-test-helpers.ts +0 -54
- package/src/__tests__/cli-web-dispatch.test.ts +0 -62
- package/src/__tests__/cli-web.test.ts +0 -203
- package/src/__tests__/cli.test.ts +0 -1027
- package/src/__tests__/compound-flat-layout.test.ts +0 -901
- package/src/__tests__/direction-override.test.ts +0 -397
- package/src/__tests__/edge-approach-direction.test.ts +0 -295
- package/src/__tests__/edge-bundling-node-clearance.test.ts +0 -179
- package/src/__tests__/elk-layout-cache.test.ts +0 -223
- package/src/__tests__/er-click-unsupported.test.ts +0 -69
- package/src/__tests__/er-integration.test.ts +0 -531
- package/src/__tests__/er-parser-error-messages.test.ts +0 -82
- package/src/__tests__/er-parser.test.ts +0 -294
- package/src/__tests__/expanded-shapes.test.ts +0 -229
- package/src/__tests__/flowchart-back-edge-prereversal-1239.test.ts +0 -212
- package/src/__tests__/flowchart-syntax-gaps.test.ts +0 -371
- package/src/__tests__/guides-examples.test.ts +0 -181
- package/src/__tests__/integration.test.ts +0 -729
- package/src/__tests__/interactivity-config.test.ts +0 -723
- package/src/__tests__/interactivity-option.test.ts +0 -173
- package/src/__tests__/layout-disconnected.test.ts +0 -384
- package/src/__tests__/layout-engine.test.ts +0 -91
- package/src/__tests__/linkstyle.test.ts +0 -133
- package/src/__tests__/mcp-render-ascii-tool.test.ts +0 -95
- package/src/__tests__/mcp-render-svg-theme-guard.test.ts +0 -36
- package/src/__tests__/mcp-render-svg-tool.test.ts +0 -118
- package/src/__tests__/mermaid-entity-codes.test.ts +0 -114
- package/src/__tests__/monospace-metrics.test.ts +0 -74
- package/src/__tests__/multiline-labels.test.ts +0 -924
- package/src/__tests__/nested-subgraph-direction-crossing.test.ts +0 -322
- package/src/__tests__/nested-subgraph-edge-container-offset.test.ts +0 -151
- package/src/__tests__/package-info.test.ts +0 -51
- package/src/__tests__/parser.test.ts +0 -1544
- package/src/__tests__/render-options-overrides.test.ts +0 -257
- package/src/__tests__/render-options-per-type.test.ts +0 -178
- package/src/__tests__/renderer.test.ts +0 -743
- package/src/__tests__/resolve-colors.test.ts +0 -448
- package/src/__tests__/self-loop-edges.test.ts +0 -112
- package/src/__tests__/sequence-activation-check.test.ts +0 -219
- package/src/__tests__/sequence-integration.test.ts +0 -312
- package/src/__tests__/sequence-layout.test.ts +0 -1103
- package/src/__tests__/sequence-parser-syntax-errors.test.ts +0 -179
- package/src/__tests__/sequence-parser.test.ts +0 -821
- package/src/__tests__/start-marker-only-links-1386.test.ts +0 -104
- package/src/__tests__/state-composite-order-1287.test.ts +0 -132
- package/src/__tests__/state-flow-order-1240.test.ts +0 -113
- package/src/__tests__/state-styling.test.ts +0 -150
- package/src/__tests__/statements.test.ts +0 -264
- package/src/__tests__/styles.test.ts +0 -220
- package/src/__tests__/subgraph-title-edge-gap-1239.test.ts +0 -238
- package/src/__tests__/svg-accessible-name-conformance.test.ts +0 -276
- package/src/__tests__/svg-accessible-name.test.ts +0 -421
- package/src/__tests__/svg-csp-style-options.test.ts +0 -427
- package/src/__tests__/workspace-package-boundaries.test.ts +0 -336
- package/src/__tests__/xychart-integration.test.ts +0 -137
- package/src/__tests__/xychart-layout.test.ts +0 -205
- package/src/__tests__/xychart-parser-categories.test.ts +0 -78
- package/src/__tests__/xychart-parser-errors.test.ts +0 -95
|
@@ -1,539 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for the ANSI/HTML color utilities in src/ascii/ansi.ts.
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect, afterEach, vi } from 'vitest'
|
|
5
|
-
import {
|
|
6
|
-
DEFAULT_ASCII_THEME,
|
|
7
|
-
diagramColorsToAsciiTheme,
|
|
8
|
-
detectColorMode,
|
|
9
|
-
getAnsiColor,
|
|
10
|
-
getAnsiReset,
|
|
11
|
-
colorizeChar,
|
|
12
|
-
colorizeLine,
|
|
13
|
-
colorizeText,
|
|
14
|
-
} from '../../packages/ascii-renderer/src/ansi.ts'
|
|
15
|
-
import type {
|
|
16
|
-
CharRole,
|
|
17
|
-
AsciiTheme,
|
|
18
|
-
ColorMode,
|
|
19
|
-
} from '../../packages/ascii-renderer/src/types.ts'
|
|
20
|
-
import type { DiagramColors } from '@zombie-mermaid/core'
|
|
21
|
-
|
|
22
|
-
const RESET = '\x1b[0m'
|
|
23
|
-
|
|
24
|
-
const theme: AsciiTheme = {
|
|
25
|
-
fg: '#27272a',
|
|
26
|
-
border: '#a1a1aa',
|
|
27
|
-
line: '#71717a',
|
|
28
|
-
arrow: '#52525b',
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
describe('diagramColorsToAsciiTheme', () => {
|
|
32
|
-
it('derives line/border/arrow via color-mix when no enrichment colors are set', () => {
|
|
33
|
-
const colors: DiagramColors = { bg: '#ffffff', fg: '#000000' }
|
|
34
|
-
const result = diagramColorsToAsciiTheme(colors)
|
|
35
|
-
expect(result).toEqual({
|
|
36
|
-
fg: '#000000',
|
|
37
|
-
// mixColors(fg=#000000, bg=#ffffff, 50%) -> 0*.5 + 255*.5 = 128 = 0x80
|
|
38
|
-
border: '#cccccc',
|
|
39
|
-
line: '#808080',
|
|
40
|
-
// mixColors(fg=#000000, bg=#ffffff, 85%) -> 0*.85 + 255*.15 = 38.25 -> 38 = 0x26
|
|
41
|
-
arrow: '#262626',
|
|
42
|
-
accent: undefined,
|
|
43
|
-
bg: '#ffffff',
|
|
44
|
-
corner: '#808080',
|
|
45
|
-
junction: '#cccccc',
|
|
46
|
-
})
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('uses explicit enrichment colors (line/border/accent) directly, bypassing color-mix', () => {
|
|
50
|
-
const colors: DiagramColors = {
|
|
51
|
-
bg: '#ffffff',
|
|
52
|
-
fg: '#000000',
|
|
53
|
-
line: '#111111',
|
|
54
|
-
border: '#222222',
|
|
55
|
-
accent: '#333333',
|
|
56
|
-
}
|
|
57
|
-
const result = diagramColorsToAsciiTheme(colors)
|
|
58
|
-
expect(result).toEqual({
|
|
59
|
-
fg: '#000000',
|
|
60
|
-
border: '#222222',
|
|
61
|
-
line: '#111111',
|
|
62
|
-
arrow: '#333333',
|
|
63
|
-
accent: '#333333',
|
|
64
|
-
bg: '#ffffff',
|
|
65
|
-
corner: '#111111',
|
|
66
|
-
junction: '#222222',
|
|
67
|
-
})
|
|
68
|
-
})
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
describe('DEFAULT_ASCII_THEME', () => {
|
|
72
|
-
it('matches the documented zinc palette', () => {
|
|
73
|
-
expect(DEFAULT_ASCII_THEME).toEqual({
|
|
74
|
-
fg: '#27272a',
|
|
75
|
-
border: '#a1a1aa',
|
|
76
|
-
line: '#71717a',
|
|
77
|
-
arrow: '#52525b',
|
|
78
|
-
corner: '#71717a',
|
|
79
|
-
junction: '#a1a1aa',
|
|
80
|
-
})
|
|
81
|
-
})
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
describe('detectColorMode', () => {
|
|
85
|
-
const originalIsTTY = process.stdout.isTTY
|
|
86
|
-
|
|
87
|
-
afterEach(() => {
|
|
88
|
-
Object.defineProperty(process.stdout, 'isTTY', {
|
|
89
|
-
value: originalIsTTY,
|
|
90
|
-
configurable: true,
|
|
91
|
-
})
|
|
92
|
-
vi.unstubAllEnvs()
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
function setTTY(value: boolean | undefined) {
|
|
96
|
-
Object.defineProperty(process.stdout, 'isTTY', {
|
|
97
|
-
value,
|
|
98
|
-
configurable: true,
|
|
99
|
-
})
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
it('returns none when stdout is not a TTY (piped/redirected)', () => {
|
|
103
|
-
setTTY(false)
|
|
104
|
-
expect(detectColorMode()).toBe('none')
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('returns truecolor when COLORTERM=truecolor', () => {
|
|
108
|
-
setTTY(true)
|
|
109
|
-
vi.stubEnv('COLORTERM', 'truecolor')
|
|
110
|
-
vi.stubEnv('TERM', 'xterm')
|
|
111
|
-
expect(detectColorMode()).toBe('truecolor')
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
it('returns truecolor for COLORTERM=24bit, case-insensitively', () => {
|
|
115
|
-
setTTY(true)
|
|
116
|
-
vi.stubEnv('COLORTERM', '24BIT')
|
|
117
|
-
vi.stubEnv('TERM', 'xterm')
|
|
118
|
-
expect(detectColorMode()).toBe('truecolor')
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
it('returns ansi256 when TERM contains "256color"', () => {
|
|
122
|
-
setTTY(true)
|
|
123
|
-
vi.stubEnv('COLORTERM', '')
|
|
124
|
-
vi.stubEnv('TERM', 'xterm-256color')
|
|
125
|
-
expect(detectColorMode()).toBe('ansi256')
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
it('returns ansi256 when TERM contains "256" without a "color" suffix', () => {
|
|
129
|
-
setTTY(true)
|
|
130
|
-
vi.stubEnv('COLORTERM', '')
|
|
131
|
-
vi.stubEnv('TERM', 'screen-256')
|
|
132
|
-
expect(detectColorMode()).toBe('ansi256')
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
it('returns ansi16 for a basic, non-dumb TERM', () => {
|
|
136
|
-
setTTY(true)
|
|
137
|
-
vi.stubEnv('COLORTERM', '')
|
|
138
|
-
vi.stubEnv('TERM', 'xterm')
|
|
139
|
-
expect(detectColorMode()).toBe('ansi16')
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
it('returns none when TERM=dumb', () => {
|
|
143
|
-
setTTY(true)
|
|
144
|
-
vi.stubEnv('COLORTERM', '')
|
|
145
|
-
vi.stubEnv('TERM', 'dumb')
|
|
146
|
-
expect(detectColorMode()).toBe('none')
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
it('returns none when TERM is unset', () => {
|
|
150
|
-
setTTY(true)
|
|
151
|
-
vi.stubEnv('COLORTERM', '')
|
|
152
|
-
vi.stubEnv('TERM', '')
|
|
153
|
-
expect(detectColorMode()).toBe('none')
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
it('returns none when COLORTERM and TERM are entirely absent from env (not just empty)', () => {
|
|
157
|
-
setTTY(true)
|
|
158
|
-
const hadColorTerm = 'COLORTERM' in process.env
|
|
159
|
-
const hadTerm = 'TERM' in process.env
|
|
160
|
-
const prevColorTerm = process.env.COLORTERM
|
|
161
|
-
const prevTerm = process.env.TERM
|
|
162
|
-
delete process.env.COLORTERM
|
|
163
|
-
delete process.env.TERM
|
|
164
|
-
try {
|
|
165
|
-
expect(detectColorMode()).toBe('none')
|
|
166
|
-
} finally {
|
|
167
|
-
if (hadColorTerm) process.env.COLORTERM = prevColorTerm
|
|
168
|
-
if (hadTerm) process.env.TERM = prevTerm
|
|
169
|
-
}
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
it('returns html when no process object exists but document is defined', () => {
|
|
173
|
-
// Vitest's own RPC layer relies on the real `process` object (via
|
|
174
|
-
// process.nextTick) outside this synchronous callback, so it must be
|
|
175
|
-
// restored before this test body returns rather than in afterEach.
|
|
176
|
-
const realProcess = globalThis.process
|
|
177
|
-
// @ts-expect-error - simulating a non-Node (browser) environment
|
|
178
|
-
globalThis.process = undefined
|
|
179
|
-
// @ts-expect-error - `document` is not declared in this Node test env
|
|
180
|
-
globalThis.document = {}
|
|
181
|
-
try {
|
|
182
|
-
expect(detectColorMode()).toBe('html')
|
|
183
|
-
} finally {
|
|
184
|
-
globalThis.process = realProcess
|
|
185
|
-
// @ts-expect-error - clean up the simulated global
|
|
186
|
-
delete globalThis.document
|
|
187
|
-
}
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
it('returns none when neither process nor document exist', () => {
|
|
191
|
-
const realProcess = globalThis.process
|
|
192
|
-
// @ts-expect-error - simulating an environment with neither process nor document
|
|
193
|
-
globalThis.process = undefined
|
|
194
|
-
try {
|
|
195
|
-
expect(detectColorMode()).toBe('none')
|
|
196
|
-
} finally {
|
|
197
|
-
globalThis.process = realProcess
|
|
198
|
-
}
|
|
199
|
-
})
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
describe('getAnsiColor', () => {
|
|
203
|
-
it('returns an empty string for mode "none"', () => {
|
|
204
|
-
expect(getAnsiColor('text', theme, 'none')).toBe('')
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
it('generates a 24-bit truecolor escape sequence', () => {
|
|
208
|
-
expect(getAnsiColor('text', theme, 'truecolor')).toBe('\x1b[38;2;39;39;42m')
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
it('resolves the "arrow" role to theme.arrow', () => {
|
|
212
|
-
expect(getAnsiColor('arrow', theme, 'truecolor')).toBe(
|
|
213
|
-
getAnsiColor('text', { ...theme, fg: theme.arrow }, 'truecolor'),
|
|
214
|
-
)
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
it('supports 3-digit shorthand hex colors', () => {
|
|
218
|
-
expect(getAnsiColor('text', { ...theme, fg: '#fff' }, 'truecolor')).toBe(
|
|
219
|
-
'\x1b[38;2;255;255;255m',
|
|
220
|
-
)
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
it('generates a 256-color escape sequence using the 6x6x6 color cube', () => {
|
|
224
|
-
// #ff0000 is far from grayscale, so it uses the cube: 16 + 36*5 + 6*0 + 0 = 196
|
|
225
|
-
expect(getAnsiColor('text', { ...theme, fg: '#ff0000' }, 'ansi256')).toBe(
|
|
226
|
-
'\x1b[38;5;196m',
|
|
227
|
-
)
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
it('generates a 256-color escape sequence for a channel in the mid cube range [48,115)', () => {
|
|
231
|
-
// #500000: r=80 falls in the [48,115) bucket -> index 1; g=b=0 -> index 0
|
|
232
|
-
// index = 16 + 36*1 + 6*0 + 0 = 52
|
|
233
|
-
expect(getAnsiColor('text', { ...theme, fg: '#500000' }, 'ansi256')).toBe(
|
|
234
|
-
'\x1b[38;5;52m',
|
|
235
|
-
)
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
it('generates a 256-color grayscale escape sequence when r/g/b are close', () => {
|
|
239
|
-
// #808080 is grayscale: round((128/255)*23) = 12 -> 232 + 12 = 244
|
|
240
|
-
expect(getAnsiColor('text', { ...theme, fg: '#808080' }, 'ansi256')).toBe(
|
|
241
|
-
'\x1b[38;5;244m',
|
|
242
|
-
)
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
it('clamps the 256-color grayscale ramp at white and black', () => {
|
|
246
|
-
expect(getAnsiColor('text', { ...theme, fg: '#ffffff' }, 'ansi256')).toBe(
|
|
247
|
-
'\x1b[38;5;255m',
|
|
248
|
-
)
|
|
249
|
-
expect(getAnsiColor('text', { ...theme, fg: '#000000' }, 'ansi256')).toBe(
|
|
250
|
-
'\x1b[38;5;232m',
|
|
251
|
-
)
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
it('maps saturated colors to the matching 16-color ANSI code', () => {
|
|
255
|
-
expect(getAnsiColor('text', { ...theme, fg: '#ff0000' }, 'ansi16')).toBe(
|
|
256
|
-
'\x1b[91m', // red, bright (luma < 100)
|
|
257
|
-
)
|
|
258
|
-
expect(getAnsiColor('text', { ...theme, fg: '#00ff00' }, 'ansi16')).toBe(
|
|
259
|
-
'\x1b[32m', // green, not bright (luma > 100)
|
|
260
|
-
)
|
|
261
|
-
expect(getAnsiColor('text', { ...theme, fg: '#ffff00' }, 'ansi16')).toBe(
|
|
262
|
-
'\x1b[33m', // yellow
|
|
263
|
-
)
|
|
264
|
-
expect(getAnsiColor('text', { ...theme, fg: '#0000ff' }, 'ansi16')).toBe(
|
|
265
|
-
'\x1b[94m', // blue, bright
|
|
266
|
-
)
|
|
267
|
-
expect(getAnsiColor('text', { ...theme, fg: '#ff00ff' }, 'ansi16')).toBe(
|
|
268
|
-
'\x1b[35m', // magenta
|
|
269
|
-
)
|
|
270
|
-
expect(getAnsiColor('text', { ...theme, fg: '#00ffff' }, 'ansi16')).toBe(
|
|
271
|
-
'\x1b[36m', // cyan
|
|
272
|
-
)
|
|
273
|
-
})
|
|
274
|
-
|
|
275
|
-
it('maps white to bright white and black to bright black in 16-color mode', () => {
|
|
276
|
-
expect(getAnsiColor('text', { ...theme, fg: '#ffffff' }, 'ansi16')).toBe(
|
|
277
|
-
'\x1b[37m',
|
|
278
|
-
)
|
|
279
|
-
expect(getAnsiColor('text', { ...theme, fg: '#000000' }, 'ansi16')).toBe(
|
|
280
|
-
'\x1b[90m',
|
|
281
|
-
)
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
it('defaults an ambiguous gray to white in 16-color mode', () => {
|
|
285
|
-
expect(getAnsiColor('text', { ...theme, fg: '#808080' }, 'ansi16')).toBe(
|
|
286
|
-
'\x1b[37m',
|
|
287
|
-
)
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
it('returns an empty string for an unhandled mode (html)', () => {
|
|
291
|
-
expect(getAnsiColor('text', theme, 'html')).toBe('')
|
|
292
|
-
})
|
|
293
|
-
|
|
294
|
-
it('accepts a bare hex digit string with no leading # (backward compatibility)', () => {
|
|
295
|
-
expect(getAnsiColor('text', { ...theme, fg: 'ff0000' }, 'truecolor')).toBe(
|
|
296
|
-
'\x1b[38;2;255;0;0m',
|
|
297
|
-
)
|
|
298
|
-
})
|
|
299
|
-
|
|
300
|
-
it('falls back to black for a color that is neither a valid hex nor #-prefixed', () => {
|
|
301
|
-
// parseHex accepts a bare hex digit string for backward compatibility
|
|
302
|
-
// (prepending '#' itself), but something that isn't valid hex either
|
|
303
|
-
// way must fall back to black rather than emit a NaN escape sequence.
|
|
304
|
-
expect(getAnsiColor('text', { ...theme, fg: 'zzzzzz' }, 'truecolor')).toBe(
|
|
305
|
-
'\x1b[38;2;0;0;0m',
|
|
306
|
-
)
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
it('falls back to line/border for corner/junction roles when unset', () => {
|
|
310
|
-
const noCornerJunction: AsciiTheme = {
|
|
311
|
-
fg: '#27272a',
|
|
312
|
-
border: '#a1a1aa',
|
|
313
|
-
line: '#71717a',
|
|
314
|
-
arrow: '#52525b',
|
|
315
|
-
}
|
|
316
|
-
expect(getAnsiColor('corner', noCornerJunction, 'truecolor')).toBe(
|
|
317
|
-
getAnsiColor('line', noCornerJunction, 'truecolor'),
|
|
318
|
-
)
|
|
319
|
-
expect(getAnsiColor('junction', noCornerJunction, 'truecolor')).toBe(
|
|
320
|
-
getAnsiColor('border', noCornerJunction, 'truecolor'),
|
|
321
|
-
)
|
|
322
|
-
})
|
|
323
|
-
|
|
324
|
-
it('uses explicit corner/junction colors when set', () => {
|
|
325
|
-
const withCornerJunction: AsciiTheme = {
|
|
326
|
-
...theme,
|
|
327
|
-
corner: '#111111',
|
|
328
|
-
junction: '#222222',
|
|
329
|
-
}
|
|
330
|
-
expect(getAnsiColor('corner', withCornerJunction, 'truecolor')).toBe(
|
|
331
|
-
'\x1b[38;2;17;17;17m',
|
|
332
|
-
)
|
|
333
|
-
expect(getAnsiColor('junction', withCornerJunction, 'truecolor')).toBe(
|
|
334
|
-
'\x1b[38;2;34;34;34m',
|
|
335
|
-
)
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
it('falls back to fg for an unrecognized role', () => {
|
|
339
|
-
// Deliberately out-of-domain: CharRole is a closed union, so this cast
|
|
340
|
-
// is the only way to exercise the runtime fallback for a bad value.
|
|
341
|
-
expect(getAnsiColor('bogus' as CharRole, theme, 'truecolor')).toBe(
|
|
342
|
-
getAnsiColor('text', theme, 'truecolor'),
|
|
343
|
-
)
|
|
344
|
-
})
|
|
345
|
-
})
|
|
346
|
-
|
|
347
|
-
describe('getAnsiReset', () => {
|
|
348
|
-
it('returns an empty string for mode "none"', () => {
|
|
349
|
-
expect(getAnsiReset('none')).toBe('')
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
it('returns the ANSI reset sequence for color modes', () => {
|
|
353
|
-
expect(getAnsiReset('truecolor')).toBe(RESET)
|
|
354
|
-
expect(getAnsiReset('ansi16')).toBe(RESET)
|
|
355
|
-
})
|
|
356
|
-
})
|
|
357
|
-
|
|
358
|
-
describe('colorizeChar', () => {
|
|
359
|
-
it('returns the character unchanged for mode "none"', () => {
|
|
360
|
-
expect(colorizeChar('A', 'text', theme, 'none')).toBe('A')
|
|
361
|
-
})
|
|
362
|
-
|
|
363
|
-
it('returns the character unchanged when role is null', () => {
|
|
364
|
-
expect(colorizeChar('A', null, theme, 'truecolor')).toBe('A')
|
|
365
|
-
})
|
|
366
|
-
|
|
367
|
-
it('returns whitespace unchanged even with a valid role and mode', () => {
|
|
368
|
-
expect(colorizeChar(' ', 'text', theme, 'truecolor')).toBe(' ')
|
|
369
|
-
})
|
|
370
|
-
|
|
371
|
-
it('wraps a character with its role color and a reset', () => {
|
|
372
|
-
expect(colorizeChar('A', 'text', theme, 'truecolor')).toBe(
|
|
373
|
-
`\x1b[38;2;39;39;42mA${RESET}`,
|
|
374
|
-
)
|
|
375
|
-
})
|
|
376
|
-
})
|
|
377
|
-
|
|
378
|
-
describe('colorizeLine', () => {
|
|
379
|
-
it('joins characters unchanged for mode "none"', () => {
|
|
380
|
-
expect(
|
|
381
|
-
colorizeLine(['A', 'B', 'C'], ['text', 'text', 'border'], theme, 'none'),
|
|
382
|
-
).toBe('ABC')
|
|
383
|
-
})
|
|
384
|
-
|
|
385
|
-
it('returns an empty string for an empty line', () => {
|
|
386
|
-
expect(colorizeLine([], [], theme, 'truecolor')).toBe('')
|
|
387
|
-
})
|
|
388
|
-
|
|
389
|
-
it('groups consecutive same-role characters into a single escape sequence', () => {
|
|
390
|
-
const result = colorizeLine(
|
|
391
|
-
['A', 'B', ' ', 'C'],
|
|
392
|
-
['text', 'text', null, 'border'],
|
|
393
|
-
theme,
|
|
394
|
-
'truecolor',
|
|
395
|
-
)
|
|
396
|
-
expect(result).toBe(
|
|
397
|
-
`\x1b[38;2;39;39;42mAB${RESET} \x1b[38;2;161;161;170mC${RESET}`,
|
|
398
|
-
)
|
|
399
|
-
})
|
|
400
|
-
|
|
401
|
-
it('starts a new group when the role changes without whitespace between', () => {
|
|
402
|
-
const result = colorizeLine(
|
|
403
|
-
['A', 'B'],
|
|
404
|
-
['text', 'border'],
|
|
405
|
-
theme,
|
|
406
|
-
'truecolor',
|
|
407
|
-
)
|
|
408
|
-
expect(result).toBe(
|
|
409
|
-
`\x1b[38;2;39;39;42mA${RESET}\x1b[38;2;161;161;170mB${RESET}`,
|
|
410
|
-
)
|
|
411
|
-
})
|
|
412
|
-
|
|
413
|
-
it('emits an unroled (null) run as raw text, mid-line', () => {
|
|
414
|
-
const result = colorizeLine(['X', 'Y'], [null, 'text'], theme, 'truecolor')
|
|
415
|
-
expect(result).toBe(`X\x1b[38;2;39;39;42mY${RESET}`)
|
|
416
|
-
})
|
|
417
|
-
|
|
418
|
-
it('emits a trailing unroled (null) run as raw text', () => {
|
|
419
|
-
const result = colorizeLine(['A', 'B'], ['text', null], theme, 'truecolor')
|
|
420
|
-
expect(result).toBe(`\x1b[38;2;39;39;42mA${RESET}B`)
|
|
421
|
-
})
|
|
422
|
-
|
|
423
|
-
it('emits an unroled (null) run as raw text when flushed by whitespace', () => {
|
|
424
|
-
const result = colorizeLine(
|
|
425
|
-
['X', ' ', 'Y'],
|
|
426
|
-
[null, null, 'text'],
|
|
427
|
-
theme,
|
|
428
|
-
'truecolor',
|
|
429
|
-
)
|
|
430
|
-
expect(result).toBe(`X \x1b[38;2;39;39;42mY${RESET}`)
|
|
431
|
-
})
|
|
432
|
-
|
|
433
|
-
it('handles leading/consecutive whitespace with nothing buffered yet', () => {
|
|
434
|
-
const result = colorizeLine(
|
|
435
|
-
[' ', ' ', 'A'],
|
|
436
|
-
[null, null, 'text'],
|
|
437
|
-
theme,
|
|
438
|
-
'truecolor',
|
|
439
|
-
)
|
|
440
|
-
expect(result).toBe(` \x1b[38;2;39;39;42mA${RESET}`)
|
|
441
|
-
})
|
|
442
|
-
|
|
443
|
-
it('delegates to HTML rendering for mode "html"', () => {
|
|
444
|
-
const result = colorizeLine(['<', '>'], [null, null], theme, 'html')
|
|
445
|
-
expect(result).toBe('<>')
|
|
446
|
-
})
|
|
447
|
-
})
|
|
448
|
-
|
|
449
|
-
describe('colorizeLine — HTML mode', () => {
|
|
450
|
-
it('wraps a colored run in a <span> with an inline color style', () => {
|
|
451
|
-
const result = colorizeLine(['A'], ['border'], theme, 'html')
|
|
452
|
-
expect(result).toBe(`<span style="color:${theme.border}">A</span>`)
|
|
453
|
-
})
|
|
454
|
-
|
|
455
|
-
it('escapes HTML-significant characters in both colored and uncolored runs', () => {
|
|
456
|
-
const colored = colorizeLine(['&'], ['text'], theme, 'html')
|
|
457
|
-
expect(colored).toBe(`<span style="color:${theme.fg}">&</span>`)
|
|
458
|
-
|
|
459
|
-
const uncolored = colorizeLine(
|
|
460
|
-
['<', '&', '>'],
|
|
461
|
-
[null, null, null],
|
|
462
|
-
theme,
|
|
463
|
-
'html',
|
|
464
|
-
)
|
|
465
|
-
expect(uncolored).toBe('<&>')
|
|
466
|
-
})
|
|
467
|
-
|
|
468
|
-
it('emits whitespace bare, breaking runs without wrapping the space itself', () => {
|
|
469
|
-
const result = colorizeLine(
|
|
470
|
-
['a', ' ', 'b'],
|
|
471
|
-
['text', 'text', 'text'],
|
|
472
|
-
theme,
|
|
473
|
-
'html',
|
|
474
|
-
)
|
|
475
|
-
expect(result).toBe(
|
|
476
|
-
`<span style="color:${theme.fg}">a</span> <span style="color:${theme.fg}">b</span>`,
|
|
477
|
-
)
|
|
478
|
-
})
|
|
479
|
-
|
|
480
|
-
it('backs a run of full blocks with its own color so abutting glyphs cannot seam', () => {
|
|
481
|
-
const result = colorizeLine(
|
|
482
|
-
['█', '█', '█'],
|
|
483
|
-
['border', 'border', 'border'],
|
|
484
|
-
theme,
|
|
485
|
-
'html',
|
|
486
|
-
)
|
|
487
|
-
expect(result).toBe(
|
|
488
|
-
`<span style="color:${theme.border};background:${theme.border}">███</span>`,
|
|
489
|
-
)
|
|
490
|
-
})
|
|
491
|
-
|
|
492
|
-
it('does not back a run that mixes blocks with other characters', () => {
|
|
493
|
-
const result = colorizeLine(['█', 'a'], ['border', 'border'], theme, 'html')
|
|
494
|
-
expect(result).toBe(`<span style="color:${theme.border}">█a</span>`)
|
|
495
|
-
})
|
|
496
|
-
|
|
497
|
-
it('does not back half blocks, which must not fill the whole cell', () => {
|
|
498
|
-
const result = colorizeLine(['▄'], ['border'], theme, 'html')
|
|
499
|
-
expect(result).toBe(`<span style="color:${theme.border}">▄</span>`)
|
|
500
|
-
})
|
|
501
|
-
})
|
|
502
|
-
|
|
503
|
-
describe('colorizeText', () => {
|
|
504
|
-
it('returns text unchanged for mode "none"', () => {
|
|
505
|
-
expect(colorizeText('hello', '#ff0000', 'none')).toBe('hello')
|
|
506
|
-
})
|
|
507
|
-
|
|
508
|
-
it('returns an empty string unchanged regardless of mode', () => {
|
|
509
|
-
expect(colorizeText('', '#ff0000', 'truecolor')).toBe('')
|
|
510
|
-
})
|
|
511
|
-
|
|
512
|
-
it('wraps text in an HTML span for mode "html"', () => {
|
|
513
|
-
expect(colorizeText('hi', '#ff0000', 'html')).toBe(
|
|
514
|
-
'<span style="color:#ff0000">hi</span>',
|
|
515
|
-
)
|
|
516
|
-
})
|
|
517
|
-
|
|
518
|
-
it('wraps text with a truecolor escape sequence and reset', () => {
|
|
519
|
-
expect(colorizeText('hi', '#27272a', 'truecolor')).toBe(
|
|
520
|
-
`\x1b[38;2;39;39;42mhi${RESET}`,
|
|
521
|
-
)
|
|
522
|
-
})
|
|
523
|
-
|
|
524
|
-
it('wraps text with a 256-color escape sequence and reset', () => {
|
|
525
|
-
expect(colorizeText('hi', '#ff0000', 'ansi256')).toBe(
|
|
526
|
-
`\x1b[38;5;196mhi${RESET}`,
|
|
527
|
-
)
|
|
528
|
-
})
|
|
529
|
-
|
|
530
|
-
it('wraps text with a 16-color escape sequence and reset', () => {
|
|
531
|
-
expect(colorizeText('hi', '#ff0000', 'ansi16')).toBe(`\x1b[91mhi${RESET}`)
|
|
532
|
-
})
|
|
533
|
-
|
|
534
|
-
it('returns text unchanged for an unhandled mode', () => {
|
|
535
|
-
// Deliberately out-of-domain: ColorMode is a closed union, so this cast
|
|
536
|
-
// is the only way to exercise the runtime fallback for a bad value.
|
|
537
|
-
expect(colorizeText('hi', '#ff0000', 'bogus' as ColorMode)).toBe('hi')
|
|
538
|
-
})
|
|
539
|
-
})
|