@mdxui/terminal 2.0.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/README.md +571 -0
- package/dist/ansi-css-Sk5mWtdK.d.ts +119 -0
- package/dist/ansi-css-V6JIHGsM.d.ts +119 -0
- package/dist/ansi-css-_3eSEU9d.d.ts +119 -0
- package/dist/chunk-3EFDH7PK.js +5235 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-3X5IR6WE.js +884 -0
- package/dist/chunk-4FV5ZDCE.js +5236 -0
- package/dist/chunk-4OVMSF2J.js +243 -0
- package/dist/chunk-63FEETIS.js +4048 -0
- package/dist/chunk-B43KP7XJ.js +884 -0
- package/dist/chunk-BMTJXWUV.js +655 -0
- package/dist/chunk-C3SVH4N7.js +882 -0
- package/dist/chunk-EVWR7Y47.js +874 -0
- package/dist/chunk-F6A5VWUC.js +1285 -0
- package/dist/chunk-FD7KW7GE.js +882 -0
- package/dist/chunk-GBQ6UD6I.js +655 -0
- package/dist/chunk-GMDD3M6U.js +5227 -0
- package/dist/chunk-JBHRXOXM.js +1058 -0
- package/dist/chunk-JFOO3EYO.js +1182 -0
- package/dist/chunk-JQ5H3WXL.js +1291 -0
- package/dist/chunk-JQD5NASE.js +234 -0
- package/dist/chunk-KRHJP5R7.js +592 -0
- package/dist/chunk-KWF6WVJE.js +962 -0
- package/dist/chunk-LHYQVN3H.js +1038 -0
- package/dist/chunk-M3TLQLGC.js +1032 -0
- package/dist/chunk-MVW4Q5OP.js +240 -0
- package/dist/chunk-NXCZSWLU.js +1294 -0
- package/dist/chunk-O25TNRO6.js +607 -0
- package/dist/chunk-PNECDA2I.js +884 -0
- package/dist/chunk-QIHWRLJR.js +962 -0
- package/dist/chunk-QW5YMQ7K.js +882 -0
- package/dist/chunk-R5U7XKVJ.js +16 -0
- package/dist/chunk-RP2MVQLR.js +962 -0
- package/dist/chunk-TP6RXGXA.js +1087 -0
- package/dist/chunk-TQQSTITZ.js +655 -0
- package/dist/chunk-X24GWXQV.js +1281 -0
- package/dist/components/index.d.ts +802 -0
- package/dist/components/index.js +149 -0
- package/dist/data/index.d.ts +2554 -0
- package/dist/data/index.js +51 -0
- package/dist/forms/index.d.ts +1596 -0
- package/dist/forms/index.js +464 -0
- package/dist/index-CQRFZntR.d.ts +867 -0
- package/dist/index.d.ts +579 -0
- package/dist/index.js +786 -0
- package/dist/interactive-D0JkWosD.d.ts +217 -0
- package/dist/keyboard/index.d.ts +2 -0
- package/dist/keyboard/index.js +43 -0
- package/dist/renderers/index.d.ts +546 -0
- package/dist/renderers/index.js +2157 -0
- package/dist/storybook/index.d.ts +396 -0
- package/dist/storybook/index.js +641 -0
- package/dist/theme/index.d.ts +1339 -0
- package/dist/theme/index.js +123 -0
- package/dist/types-Bxu5PAgA.d.ts +710 -0
- package/dist/types-CIlop5Ji.d.ts +701 -0
- package/dist/types-Ca8p_p5X.d.ts +710 -0
- package/package.json +90 -0
- package/src/__tests__/components/data/card.test.ts +458 -0
- package/src/__tests__/components/data/list.test.ts +473 -0
- package/src/__tests__/components/data/metrics.test.ts +541 -0
- package/src/__tests__/components/data/table.test.ts +448 -0
- package/src/__tests__/components/input/field.test.ts +555 -0
- package/src/__tests__/components/input/form.test.ts +870 -0
- package/src/__tests__/components/input/search.test.ts +1238 -0
- package/src/__tests__/components/input/select.test.ts +658 -0
- package/src/__tests__/components/navigation/breadcrumb.test.ts +923 -0
- package/src/__tests__/components/navigation/command-palette.test.ts +1095 -0
- package/src/__tests__/components/navigation/sidebar.test.ts +1018 -0
- package/src/__tests__/components/navigation/tabs.test.ts +995 -0
- package/src/__tests__/components.test.tsx +1197 -0
- package/src/__tests__/core/compiler.test.ts +986 -0
- package/src/__tests__/core/parser.test.ts +785 -0
- package/src/__tests__/core/tier-switcher.test.ts +1103 -0
- package/src/__tests__/core/types.test.ts +1398 -0
- package/src/__tests__/data/collections.test.ts +1337 -0
- package/src/__tests__/data/db.test.ts +1265 -0
- package/src/__tests__/data/reactive.test.ts +1010 -0
- package/src/__tests__/data/sync.test.ts +1614 -0
- package/src/__tests__/errors.test.ts +660 -0
- package/src/__tests__/forms/integration.test.ts +444 -0
- package/src/__tests__/integration.test.ts +905 -0
- package/src/__tests__/keyboard.test.ts +1791 -0
- package/src/__tests__/renderer.test.ts +489 -0
- package/src/__tests__/renderers/ansi-css.test.ts +948 -0
- package/src/__tests__/renderers/ansi.test.ts +1366 -0
- package/src/__tests__/renderers/ascii.test.ts +1360 -0
- package/src/__tests__/renderers/interactive.test.ts +2353 -0
- package/src/__tests__/renderers/markdown.test.ts +1483 -0
- package/src/__tests__/renderers/text.test.ts +1369 -0
- package/src/__tests__/renderers/unicode.test.ts +1307 -0
- package/src/__tests__/theme.test.ts +639 -0
- package/src/__tests__/utils/assertions.ts +685 -0
- package/src/__tests__/utils/index.ts +115 -0
- package/src/__tests__/utils/test-renderer.ts +381 -0
- package/src/__tests__/utils/utils.test.ts +560 -0
- package/src/components/containers/card.ts +56 -0
- package/src/components/containers/dialog.ts +53 -0
- package/src/components/containers/index.ts +9 -0
- package/src/components/containers/panel.ts +59 -0
- package/src/components/feedback/badge.ts +40 -0
- package/src/components/feedback/index.ts +8 -0
- package/src/components/feedback/spinner.ts +23 -0
- package/src/components/helpers.ts +81 -0
- package/src/components/index.ts +153 -0
- package/src/components/layout/breadcrumb.ts +31 -0
- package/src/components/layout/index.ts +10 -0
- package/src/components/layout/list.ts +29 -0
- package/src/components/layout/sidebar.ts +79 -0
- package/src/components/layout/table.ts +62 -0
- package/src/components/primitives/box.ts +95 -0
- package/src/components/primitives/button.ts +54 -0
- package/src/components/primitives/index.ts +11 -0
- package/src/components/primitives/input.ts +88 -0
- package/src/components/primitives/select.ts +97 -0
- package/src/components/primitives/text.ts +60 -0
- package/src/components/render.ts +155 -0
- package/src/components/templates/app.ts +43 -0
- package/src/components/templates/index.ts +8 -0
- package/src/components/templates/site.ts +54 -0
- package/src/components/types.ts +777 -0
- package/src/core/compiler.ts +718 -0
- package/src/core/parser.ts +127 -0
- package/src/core/tier-switcher.ts +607 -0
- package/src/core/types.ts +672 -0
- package/src/data/collection.ts +316 -0
- package/src/data/collections.ts +50 -0
- package/src/data/context.tsx +174 -0
- package/src/data/db.ts +127 -0
- package/src/data/hooks.ts +532 -0
- package/src/data/index.ts +138 -0
- package/src/data/reactive.ts +1225 -0
- package/src/data/saas-collections.ts +375 -0
- package/src/data/sync.ts +1213 -0
- package/src/data/types.ts +660 -0
- package/src/forms/converters.ts +512 -0
- package/src/forms/index.ts +133 -0
- package/src/forms/schemas.ts +403 -0
- package/src/forms/types.ts +476 -0
- package/src/index.ts +542 -0
- package/src/keyboard/focus.ts +748 -0
- package/src/keyboard/index.ts +96 -0
- package/src/keyboard/integration.ts +371 -0
- package/src/keyboard/manager.ts +377 -0
- package/src/keyboard/presets.ts +90 -0
- package/src/renderers/ansi-css.ts +576 -0
- package/src/renderers/ansi.ts +802 -0
- package/src/renderers/ascii.ts +680 -0
- package/src/renderers/breadcrumb.ts +480 -0
- package/src/renderers/command-palette.ts +802 -0
- package/src/renderers/components/field.ts +210 -0
- package/src/renderers/components/form.ts +327 -0
- package/src/renderers/components/index.ts +21 -0
- package/src/renderers/components/search.ts +449 -0
- package/src/renderers/components/select.ts +222 -0
- package/src/renderers/index.ts +101 -0
- package/src/renderers/interactive/component-handlers.ts +622 -0
- package/src/renderers/interactive/cursor-manager.ts +147 -0
- package/src/renderers/interactive/focus-manager.ts +279 -0
- package/src/renderers/interactive/index.ts +661 -0
- package/src/renderers/interactive/input-handler.ts +164 -0
- package/src/renderers/interactive/keyboard-handler.ts +212 -0
- package/src/renderers/interactive/mouse-handler.ts +167 -0
- package/src/renderers/interactive/state-manager.ts +109 -0
- package/src/renderers/interactive/types.ts +338 -0
- package/src/renderers/interactive-string.ts +299 -0
- package/src/renderers/interactive.ts +59 -0
- package/src/renderers/markdown.ts +950 -0
- package/src/renderers/sidebar.ts +549 -0
- package/src/renderers/tabs.ts +682 -0
- package/src/renderers/text.ts +791 -0
- package/src/renderers/unicode.ts +917 -0
- package/src/renderers/utils.ts +942 -0
- package/src/router/adapters.ts +383 -0
- package/src/router/types.ts +140 -0
- package/src/router/utils.ts +452 -0
- package/src/schemas.ts +205 -0
- package/src/storybook/index.ts +91 -0
- package/src/storybook/interactive-decorator.tsx +659 -0
- package/src/storybook/keyboard-simulator.ts +501 -0
- package/src/theme/ansi-codes.ts +80 -0
- package/src/theme/box-drawing.ts +132 -0
- package/src/theme/color-convert.ts +254 -0
- package/src/theme/color-support.ts +321 -0
- package/src/theme/index.ts +134 -0
- package/src/theme/strip-ansi.ts +50 -0
- package/src/theme/tailwind-map.ts +469 -0
- package/src/theme/text-styles.ts +206 -0
- package/src/theme/theme-system.ts +568 -0
- package/src/types.ts +103 -0
|
@@ -0,0 +1,1366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mdxui/terminal ANSI Renderer Tests
|
|
3
|
+
*
|
|
4
|
+
* TDD RED Phase: Tests for the ANSI escape code renderer.
|
|
5
|
+
* All tests should FAIL initially because renderANSI doesn't exist yet.
|
|
6
|
+
*
|
|
7
|
+
* The ANSI renderer converts UINode trees to colored terminal output strings
|
|
8
|
+
* using ANSI escape codes for styling (colors, bold, underline, etc.).
|
|
9
|
+
*
|
|
10
|
+
* This is part of the Universal Terminal UI 6-tier rendering system.
|
|
11
|
+
*/
|
|
12
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Types for ANSI Renderer (these define the contract)
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* UINode represents a node in the terminal UI tree.
|
|
20
|
+
* This is the input format for all renderers.
|
|
21
|
+
*/
|
|
22
|
+
interface UINode {
|
|
23
|
+
type: string
|
|
24
|
+
props?: Record<string, unknown>
|
|
25
|
+
children?: UINode[] | string
|
|
26
|
+
text?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* ANSI renderer options
|
|
31
|
+
*/
|
|
32
|
+
interface ANSIRenderOptions {
|
|
33
|
+
/** Color support level */
|
|
34
|
+
colorSupport?: 'none' | '16' | '256' | 'truecolor'
|
|
35
|
+
/** Theme mode */
|
|
36
|
+
theme?: 'dark' | 'light'
|
|
37
|
+
/** Terminal width for wrapping */
|
|
38
|
+
width?: number
|
|
39
|
+
/** Whether to include reset codes between elements */
|
|
40
|
+
resetBetweenElements?: boolean
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// Test Suite: Basic ANSI Output
|
|
45
|
+
// ============================================================================
|
|
46
|
+
|
|
47
|
+
describe('renderANSI', () => {
|
|
48
|
+
describe('basic text rendering', () => {
|
|
49
|
+
it('renders plain text without ANSI codes', async () => {
|
|
50
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
51
|
+
|
|
52
|
+
const node: UINode = {
|
|
53
|
+
type: 'text',
|
|
54
|
+
text: 'Hello World',
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const result = renderANSI(node)
|
|
58
|
+
expect(result).toBe('Hello World')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('renders nested text nodes', async () => {
|
|
62
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
63
|
+
|
|
64
|
+
const node: UINode = {
|
|
65
|
+
type: 'box',
|
|
66
|
+
children: [
|
|
67
|
+
{ type: 'text', text: 'Line 1' },
|
|
68
|
+
{ type: 'text', text: 'Line 2' },
|
|
69
|
+
],
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const result = renderANSI(node)
|
|
73
|
+
expect(result).toContain('Line 1')
|
|
74
|
+
expect(result).toContain('Line 2')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('returns empty string for empty node', async () => {
|
|
78
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
79
|
+
|
|
80
|
+
const node: UINode = {
|
|
81
|
+
type: 'text',
|
|
82
|
+
text: '',
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const result = renderANSI(node)
|
|
86
|
+
expect(result).toBe('')
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// Foreground Colors (Basic 16 ANSI)
|
|
92
|
+
// ============================================================================
|
|
93
|
+
|
|
94
|
+
describe('foreground colors (16 color)', () => {
|
|
95
|
+
it('applies red foreground color', async () => {
|
|
96
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
97
|
+
|
|
98
|
+
const node: UINode = {
|
|
99
|
+
type: 'text',
|
|
100
|
+
text: 'Error',
|
|
101
|
+
props: { color: 'red' },
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const result = renderANSI(node)
|
|
105
|
+
expect(result).toContain('\x1b[31m') // Red foreground
|
|
106
|
+
expect(result).toContain('Error')
|
|
107
|
+
expect(result).toContain('\x1b[0m') // Reset
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('applies green foreground color', async () => {
|
|
111
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
112
|
+
|
|
113
|
+
const node: UINode = {
|
|
114
|
+
type: 'text',
|
|
115
|
+
text: 'Success',
|
|
116
|
+
props: { color: 'green' },
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const result = renderANSI(node)
|
|
120
|
+
expect(result).toContain('\x1b[32m') // Green foreground
|
|
121
|
+
expect(result).toContain('Success')
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('applies yellow foreground color', async () => {
|
|
125
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
126
|
+
|
|
127
|
+
const node: UINode = {
|
|
128
|
+
type: 'text',
|
|
129
|
+
text: 'Warning',
|
|
130
|
+
props: { color: 'yellow' },
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const result = renderANSI(node)
|
|
134
|
+
expect(result).toContain('\x1b[33m') // Yellow foreground
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('applies blue foreground color', async () => {
|
|
138
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
139
|
+
|
|
140
|
+
const node: UINode = {
|
|
141
|
+
type: 'text',
|
|
142
|
+
text: 'Info',
|
|
143
|
+
props: { color: 'blue' },
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const result = renderANSI(node)
|
|
147
|
+
expect(result).toContain('\x1b[34m') // Blue foreground
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('applies magenta foreground color', async () => {
|
|
151
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
152
|
+
|
|
153
|
+
const node: UINode = {
|
|
154
|
+
type: 'text',
|
|
155
|
+
text: 'Highlight',
|
|
156
|
+
props: { color: 'magenta' },
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const result = renderANSI(node)
|
|
160
|
+
expect(result).toContain('\x1b[35m') // Magenta foreground
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('applies cyan foreground color', async () => {
|
|
164
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
165
|
+
|
|
166
|
+
const node: UINode = {
|
|
167
|
+
type: 'text',
|
|
168
|
+
text: 'Accent',
|
|
169
|
+
props: { color: 'cyan' },
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const result = renderANSI(node)
|
|
173
|
+
expect(result).toContain('\x1b[36m') // Cyan foreground
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('applies white foreground color', async () => {
|
|
177
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
178
|
+
|
|
179
|
+
const node: UINode = {
|
|
180
|
+
type: 'text',
|
|
181
|
+
text: 'Light',
|
|
182
|
+
props: { color: 'white' },
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const result = renderANSI(node)
|
|
186
|
+
expect(result).toContain('\x1b[37m') // White foreground
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('applies black foreground color', async () => {
|
|
190
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
191
|
+
|
|
192
|
+
const node: UINode = {
|
|
193
|
+
type: 'text',
|
|
194
|
+
text: 'Dark',
|
|
195
|
+
props: { color: 'black' },
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const result = renderANSI(node)
|
|
199
|
+
expect(result).toContain('\x1b[30m') // Black foreground
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
// ============================================================================
|
|
204
|
+
// Bright Foreground Colors
|
|
205
|
+
// ============================================================================
|
|
206
|
+
|
|
207
|
+
describe('bright foreground colors', () => {
|
|
208
|
+
it('applies bright red foreground', async () => {
|
|
209
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
210
|
+
|
|
211
|
+
const node: UINode = {
|
|
212
|
+
type: 'text',
|
|
213
|
+
text: 'Alert',
|
|
214
|
+
props: { color: 'brightRed' },
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const result = renderANSI(node)
|
|
218
|
+
expect(result).toContain('\x1b[91m') // Bright red
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('applies bright green foreground', async () => {
|
|
222
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
223
|
+
|
|
224
|
+
const node: UINode = {
|
|
225
|
+
type: 'text',
|
|
226
|
+
text: 'Done',
|
|
227
|
+
props: { color: 'brightGreen' },
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const result = renderANSI(node)
|
|
231
|
+
expect(result).toContain('\x1b[92m') // Bright green
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it('applies bright yellow foreground', async () => {
|
|
235
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
236
|
+
|
|
237
|
+
const node: UINode = {
|
|
238
|
+
type: 'text',
|
|
239
|
+
text: 'Caution',
|
|
240
|
+
props: { color: 'brightYellow' },
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const result = renderANSI(node)
|
|
244
|
+
expect(result).toContain('\x1b[93m') // Bright yellow
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it('applies bright blue foreground', async () => {
|
|
248
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
249
|
+
|
|
250
|
+
const node: UINode = {
|
|
251
|
+
type: 'text',
|
|
252
|
+
text: 'Link',
|
|
253
|
+
props: { color: 'brightBlue' },
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const result = renderANSI(node)
|
|
257
|
+
expect(result).toContain('\x1b[94m') // Bright blue
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
it('applies bright magenta foreground', async () => {
|
|
261
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
262
|
+
|
|
263
|
+
const node: UINode = {
|
|
264
|
+
type: 'text',
|
|
265
|
+
text: 'Special',
|
|
266
|
+
props: { color: 'brightMagenta' },
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const result = renderANSI(node)
|
|
270
|
+
expect(result).toContain('\x1b[95m') // Bright magenta
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
it('applies bright cyan foreground', async () => {
|
|
274
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
275
|
+
|
|
276
|
+
const node: UINode = {
|
|
277
|
+
type: 'text',
|
|
278
|
+
text: 'Primary',
|
|
279
|
+
props: { color: 'brightCyan' },
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const result = renderANSI(node)
|
|
283
|
+
expect(result).toContain('\x1b[96m') // Bright cyan
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
it('applies bright white foreground', async () => {
|
|
287
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
288
|
+
|
|
289
|
+
const node: UINode = {
|
|
290
|
+
type: 'text',
|
|
291
|
+
text: 'Bright',
|
|
292
|
+
props: { color: 'brightWhite' },
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const result = renderANSI(node)
|
|
296
|
+
expect(result).toContain('\x1b[97m') // Bright white
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it('applies bright black (gray) foreground', async () => {
|
|
300
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
301
|
+
|
|
302
|
+
const node: UINode = {
|
|
303
|
+
type: 'text',
|
|
304
|
+
text: 'Muted',
|
|
305
|
+
props: { color: 'brightBlack' },
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const result = renderANSI(node)
|
|
309
|
+
expect(result).toContain('\x1b[90m') // Bright black (gray)
|
|
310
|
+
})
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
// ============================================================================
|
|
314
|
+
// Background Colors
|
|
315
|
+
// ============================================================================
|
|
316
|
+
|
|
317
|
+
describe('background colors', () => {
|
|
318
|
+
it('applies red background color', async () => {
|
|
319
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
320
|
+
|
|
321
|
+
const node: UINode = {
|
|
322
|
+
type: 'text',
|
|
323
|
+
text: 'Error Badge',
|
|
324
|
+
props: { backgroundColor: 'red' },
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const result = renderANSI(node)
|
|
328
|
+
expect(result).toContain('\x1b[41m') // Red background
|
|
329
|
+
expect(result).toContain('Error Badge')
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
it('applies green background color', async () => {
|
|
333
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
334
|
+
|
|
335
|
+
const node: UINode = {
|
|
336
|
+
type: 'text',
|
|
337
|
+
text: 'Success Badge',
|
|
338
|
+
props: { backgroundColor: 'green' },
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const result = renderANSI(node)
|
|
342
|
+
expect(result).toContain('\x1b[42m') // Green background
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it('applies yellow background color', async () => {
|
|
346
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
347
|
+
|
|
348
|
+
const node: UINode = {
|
|
349
|
+
type: 'text',
|
|
350
|
+
text: 'Warning Badge',
|
|
351
|
+
props: { backgroundColor: 'yellow' },
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const result = renderANSI(node)
|
|
355
|
+
expect(result).toContain('\x1b[43m') // Yellow background
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
it('applies blue background color', async () => {
|
|
359
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
360
|
+
|
|
361
|
+
const node: UINode = {
|
|
362
|
+
type: 'text',
|
|
363
|
+
text: 'Info Badge',
|
|
364
|
+
props: { backgroundColor: 'blue' },
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const result = renderANSI(node)
|
|
368
|
+
expect(result).toContain('\x1b[44m') // Blue background
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
it('applies magenta background color', async () => {
|
|
372
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
373
|
+
|
|
374
|
+
const node: UINode = {
|
|
375
|
+
type: 'text',
|
|
376
|
+
text: 'Highlight',
|
|
377
|
+
props: { backgroundColor: 'magenta' },
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const result = renderANSI(node)
|
|
381
|
+
expect(result).toContain('\x1b[45m') // Magenta background
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
it('applies cyan background color', async () => {
|
|
385
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
386
|
+
|
|
387
|
+
const node: UINode = {
|
|
388
|
+
type: 'text',
|
|
389
|
+
text: 'Selected',
|
|
390
|
+
props: { backgroundColor: 'cyan' },
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const result = renderANSI(node)
|
|
394
|
+
expect(result).toContain('\x1b[46m') // Cyan background
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
it('applies white background color', async () => {
|
|
398
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
399
|
+
|
|
400
|
+
const node: UINode = {
|
|
401
|
+
type: 'text',
|
|
402
|
+
text: 'Inverted',
|
|
403
|
+
props: { backgroundColor: 'white' },
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const result = renderANSI(node)
|
|
407
|
+
expect(result).toContain('\x1b[47m') // White background
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
it('applies black background color', async () => {
|
|
411
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
412
|
+
|
|
413
|
+
const node: UINode = {
|
|
414
|
+
type: 'text',
|
|
415
|
+
text: 'Dark Mode',
|
|
416
|
+
props: { backgroundColor: 'black' },
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const result = renderANSI(node)
|
|
420
|
+
expect(result).toContain('\x1b[40m') // Black background
|
|
421
|
+
})
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
// ============================================================================
|
|
425
|
+
// Text Formatting (Bold, Italic, Underline, etc.)
|
|
426
|
+
// ============================================================================
|
|
427
|
+
|
|
428
|
+
describe('text formatting', () => {
|
|
429
|
+
it('applies bold formatting', async () => {
|
|
430
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
431
|
+
|
|
432
|
+
const node: UINode = {
|
|
433
|
+
type: 'text',
|
|
434
|
+
text: 'Important',
|
|
435
|
+
props: { bold: true },
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const result = renderANSI(node)
|
|
439
|
+
expect(result).toContain('\x1b[1m') // Bold
|
|
440
|
+
expect(result).toContain('Important')
|
|
441
|
+
expect(result).toContain('\x1b[0m') // Reset
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
it('applies italic formatting', async () => {
|
|
445
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
446
|
+
|
|
447
|
+
const node: UINode = {
|
|
448
|
+
type: 'text',
|
|
449
|
+
text: 'Emphasis',
|
|
450
|
+
props: { italic: true },
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const result = renderANSI(node)
|
|
454
|
+
expect(result).toContain('\x1b[3m') // Italic
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
it('applies underline formatting', async () => {
|
|
458
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
459
|
+
|
|
460
|
+
const node: UINode = {
|
|
461
|
+
type: 'text',
|
|
462
|
+
text: 'Link',
|
|
463
|
+
props: { underline: true },
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const result = renderANSI(node)
|
|
467
|
+
expect(result).toContain('\x1b[4m') // Underline
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
it('applies dim/faint formatting', async () => {
|
|
471
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
472
|
+
|
|
473
|
+
const node: UINode = {
|
|
474
|
+
type: 'text',
|
|
475
|
+
text: 'Subtle',
|
|
476
|
+
props: { dim: true },
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const result = renderANSI(node)
|
|
480
|
+
expect(result).toContain('\x1b[2m') // Dim
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
it('applies strikethrough formatting', async () => {
|
|
484
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
485
|
+
|
|
486
|
+
const node: UINode = {
|
|
487
|
+
type: 'text',
|
|
488
|
+
text: 'Deleted',
|
|
489
|
+
props: { strikethrough: true },
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const result = renderANSI(node)
|
|
493
|
+
expect(result).toContain('\x1b[9m') // Strikethrough
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
it('applies inverse/reverse formatting', async () => {
|
|
497
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
498
|
+
|
|
499
|
+
const node: UINode = {
|
|
500
|
+
type: 'text',
|
|
501
|
+
text: 'Inverted',
|
|
502
|
+
props: { inverse: true },
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const result = renderANSI(node)
|
|
506
|
+
expect(result).toContain('\x1b[7m') // Inverse
|
|
507
|
+
})
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
// ============================================================================
|
|
511
|
+
// Combined Styles
|
|
512
|
+
// ============================================================================
|
|
513
|
+
|
|
514
|
+
describe('combined styles', () => {
|
|
515
|
+
it('combines bold and color', async () => {
|
|
516
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
517
|
+
|
|
518
|
+
const node: UINode = {
|
|
519
|
+
type: 'text',
|
|
520
|
+
text: 'Bold Red',
|
|
521
|
+
props: { bold: true, color: 'red' },
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const result = renderANSI(node)
|
|
525
|
+
expect(result).toContain('\x1b[1m') // Bold
|
|
526
|
+
expect(result).toContain('\x1b[31m') // Red
|
|
527
|
+
expect(result).toContain('Bold Red')
|
|
528
|
+
expect(result).toContain('\x1b[0m') // Reset
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
it('combines foreground and background colors', async () => {
|
|
532
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
533
|
+
|
|
534
|
+
const node: UINode = {
|
|
535
|
+
type: 'text',
|
|
536
|
+
text: 'Badge',
|
|
537
|
+
props: { color: 'white', backgroundColor: 'blue' },
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const result = renderANSI(node)
|
|
541
|
+
expect(result).toContain('\x1b[37m') // White foreground
|
|
542
|
+
expect(result).toContain('\x1b[44m') // Blue background
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
it('combines bold, italic, and underline', async () => {
|
|
546
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
547
|
+
|
|
548
|
+
const node: UINode = {
|
|
549
|
+
type: 'text',
|
|
550
|
+
text: 'Fancy',
|
|
551
|
+
props: { bold: true, italic: true, underline: true },
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const result = renderANSI(node)
|
|
555
|
+
expect(result).toContain('\x1b[1m') // Bold
|
|
556
|
+
expect(result).toContain('\x1b[3m') // Italic
|
|
557
|
+
expect(result).toContain('\x1b[4m') // Underline
|
|
558
|
+
})
|
|
559
|
+
|
|
560
|
+
it('combines all style properties', async () => {
|
|
561
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
562
|
+
|
|
563
|
+
const node: UINode = {
|
|
564
|
+
type: 'text',
|
|
565
|
+
text: 'Everything',
|
|
566
|
+
props: {
|
|
567
|
+
bold: true,
|
|
568
|
+
italic: true,
|
|
569
|
+
underline: true,
|
|
570
|
+
color: 'cyan',
|
|
571
|
+
backgroundColor: 'black',
|
|
572
|
+
},
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const result = renderANSI(node)
|
|
576
|
+
expect(result).toContain('\x1b[1m') // Bold
|
|
577
|
+
expect(result).toContain('\x1b[3m') // Italic
|
|
578
|
+
expect(result).toContain('\x1b[4m') // Underline
|
|
579
|
+
expect(result).toContain('\x1b[36m') // Cyan
|
|
580
|
+
expect(result).toContain('\x1b[40m') // Black bg
|
|
581
|
+
expect(result).toContain('\x1b[0m') // Reset
|
|
582
|
+
})
|
|
583
|
+
})
|
|
584
|
+
|
|
585
|
+
// ============================================================================
|
|
586
|
+
// 256 Color Support
|
|
587
|
+
// ============================================================================
|
|
588
|
+
|
|
589
|
+
describe('256 color support', () => {
|
|
590
|
+
it('applies 256-color foreground using numeric code', async () => {
|
|
591
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
592
|
+
|
|
593
|
+
const node: UINode = {
|
|
594
|
+
type: 'text',
|
|
595
|
+
text: 'Extended',
|
|
596
|
+
props: { color: 196 }, // Bright red in 256 palette
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const result = renderANSI(node)
|
|
600
|
+
expect(result).toContain('\x1b[38;5;196m') // 256-color foreground
|
|
601
|
+
})
|
|
602
|
+
|
|
603
|
+
it('applies 256-color background using numeric code', async () => {
|
|
604
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
605
|
+
|
|
606
|
+
const node: UINode = {
|
|
607
|
+
type: 'text',
|
|
608
|
+
text: 'Extended BG',
|
|
609
|
+
props: { backgroundColor: 21 }, // Blue in 256 palette
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const result = renderANSI(node)
|
|
613
|
+
expect(result).toContain('\x1b[48;5;21m') // 256-color background
|
|
614
|
+
})
|
|
615
|
+
|
|
616
|
+
it('applies grayscale 256-color', async () => {
|
|
617
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
618
|
+
|
|
619
|
+
const node: UINode = {
|
|
620
|
+
type: 'text',
|
|
621
|
+
text: 'Gray',
|
|
622
|
+
props: { color: 240 }, // Mid gray in grayscale ramp (232-255)
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const result = renderANSI(node)
|
|
626
|
+
expect(result).toContain('\x1b[38;5;240m')
|
|
627
|
+
})
|
|
628
|
+
|
|
629
|
+
it('applies 256-color cube values', async () => {
|
|
630
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
631
|
+
|
|
632
|
+
// Color cube starts at 16: 6x6x6 = 216 colors (16-231)
|
|
633
|
+
const node: UINode = {
|
|
634
|
+
type: 'text',
|
|
635
|
+
text: 'Cube Color',
|
|
636
|
+
props: { color: 75 }, // Light blue in color cube
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const result = renderANSI(node)
|
|
640
|
+
expect(result).toContain('\x1b[38;5;75m')
|
|
641
|
+
})
|
|
642
|
+
})
|
|
643
|
+
|
|
644
|
+
// ============================================================================
|
|
645
|
+
// True Color (24-bit RGB) Support
|
|
646
|
+
// ============================================================================
|
|
647
|
+
|
|
648
|
+
describe('true color (24-bit) support', () => {
|
|
649
|
+
it('applies RGB foreground color', async () => {
|
|
650
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
651
|
+
|
|
652
|
+
const node: UINode = {
|
|
653
|
+
type: 'text',
|
|
654
|
+
text: 'RGB Text',
|
|
655
|
+
props: { color: { r: 59, g: 130, b: 246 } }, // Tailwind blue-500
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const result = renderANSI(node)
|
|
659
|
+
expect(result).toContain('\x1b[38;2;59;130;246m') // True color foreground
|
|
660
|
+
})
|
|
661
|
+
|
|
662
|
+
it('applies RGB background color', async () => {
|
|
663
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
664
|
+
|
|
665
|
+
const node: UINode = {
|
|
666
|
+
type: 'text',
|
|
667
|
+
text: 'RGB Background',
|
|
668
|
+
props: { backgroundColor: { r: 239, g: 68, b: 68 } }, // Tailwind red-500
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const result = renderANSI(node)
|
|
672
|
+
expect(result).toContain('\x1b[48;2;239;68;68m') // True color background
|
|
673
|
+
})
|
|
674
|
+
|
|
675
|
+
it('applies hex color foreground', async () => {
|
|
676
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
677
|
+
|
|
678
|
+
const node: UINode = {
|
|
679
|
+
type: 'text',
|
|
680
|
+
text: 'Hex Color',
|
|
681
|
+
props: { color: '#3b82f6' }, // Tailwind blue-500
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const result = renderANSI(node)
|
|
685
|
+
expect(result).toContain('\x1b[38;2;59;130;246m')
|
|
686
|
+
})
|
|
687
|
+
|
|
688
|
+
it('applies hex color background', async () => {
|
|
689
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
690
|
+
|
|
691
|
+
const node: UINode = {
|
|
692
|
+
type: 'text',
|
|
693
|
+
text: 'Hex Background',
|
|
694
|
+
props: { backgroundColor: '#10b981' }, // Tailwind emerald-500
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const result = renderANSI(node)
|
|
698
|
+
expect(result).toContain('\x1b[48;2;16;185;129m')
|
|
699
|
+
})
|
|
700
|
+
|
|
701
|
+
it('handles 3-char hex shorthand', async () => {
|
|
702
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
703
|
+
|
|
704
|
+
const node: UINode = {
|
|
705
|
+
type: 'text',
|
|
706
|
+
text: 'Short Hex',
|
|
707
|
+
props: { color: '#f00' }, // Red
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const result = renderANSI(node)
|
|
711
|
+
expect(result).toContain('\x1b[38;2;255;0;0m')
|
|
712
|
+
})
|
|
713
|
+
})
|
|
714
|
+
|
|
715
|
+
// ============================================================================
|
|
716
|
+
// Reset Codes
|
|
717
|
+
// ============================================================================
|
|
718
|
+
|
|
719
|
+
describe('reset codes', () => {
|
|
720
|
+
it('appends reset code after styled text', async () => {
|
|
721
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
722
|
+
|
|
723
|
+
const node: UINode = {
|
|
724
|
+
type: 'text',
|
|
725
|
+
text: 'Styled',
|
|
726
|
+
props: { color: 'red' },
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const result = renderANSI(node)
|
|
730
|
+
expect(result.endsWith('\x1b[0m')).toBe(true)
|
|
731
|
+
})
|
|
732
|
+
|
|
733
|
+
it('resets between sibling elements', async () => {
|
|
734
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
735
|
+
|
|
736
|
+
const node: UINode = {
|
|
737
|
+
type: 'box',
|
|
738
|
+
children: [
|
|
739
|
+
{ type: 'text', text: 'Red', props: { color: 'red' } },
|
|
740
|
+
{ type: 'text', text: 'Blue', props: { color: 'blue' } },
|
|
741
|
+
],
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const result = renderANSI(node)
|
|
745
|
+
// Red text should be followed by reset before blue
|
|
746
|
+
const redIndex = result.indexOf('\x1b[31m')
|
|
747
|
+
const resetIndex = result.indexOf('\x1b[0m', redIndex)
|
|
748
|
+
const blueIndex = result.indexOf('\x1b[34m')
|
|
749
|
+
|
|
750
|
+
expect(redIndex).toBeLessThan(resetIndex)
|
|
751
|
+
expect(resetIndex).toBeLessThan(blueIndex)
|
|
752
|
+
})
|
|
753
|
+
|
|
754
|
+
it('does not add unnecessary resets for plain text', async () => {
|
|
755
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
756
|
+
|
|
757
|
+
const node: UINode = {
|
|
758
|
+
type: 'text',
|
|
759
|
+
text: 'Plain',
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const result = renderANSI(node)
|
|
763
|
+
expect(result).not.toContain('\x1b[')
|
|
764
|
+
})
|
|
765
|
+
})
|
|
766
|
+
|
|
767
|
+
// ============================================================================
|
|
768
|
+
// Color Support Degradation
|
|
769
|
+
// ============================================================================
|
|
770
|
+
|
|
771
|
+
describe('color support degradation', () => {
|
|
772
|
+
it('degrades true color to 256 colors when colorSupport is 256', async () => {
|
|
773
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
774
|
+
|
|
775
|
+
const node: UINode = {
|
|
776
|
+
type: 'text',
|
|
777
|
+
text: 'Degraded',
|
|
778
|
+
props: { color: '#3b82f6' },
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const result = renderANSI(node, { colorSupport: '256' })
|
|
782
|
+
// Should use 256-color escape sequence instead of true color
|
|
783
|
+
expect(result).toMatch(/\x1b\[38;5;\d+m/)
|
|
784
|
+
expect(result).not.toContain('\x1b[38;2;')
|
|
785
|
+
})
|
|
786
|
+
|
|
787
|
+
it('degrades 256 color to 16 colors when colorSupport is 16', async () => {
|
|
788
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
789
|
+
|
|
790
|
+
const node: UINode = {
|
|
791
|
+
type: 'text',
|
|
792
|
+
text: 'Basic',
|
|
793
|
+
props: { color: 196 }, // 256-color red
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const result = renderANSI(node, { colorSupport: '16' })
|
|
797
|
+
// Should use basic 16-color escape sequence
|
|
798
|
+
expect(result).toMatch(/\x1b\[3[0-7]m|\x1b\[9[0-7]m/)
|
|
799
|
+
expect(result).not.toContain('\x1b[38;5;')
|
|
800
|
+
})
|
|
801
|
+
|
|
802
|
+
it('strips all colors when colorSupport is none', async () => {
|
|
803
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
804
|
+
|
|
805
|
+
const node: UINode = {
|
|
806
|
+
type: 'text',
|
|
807
|
+
text: 'No Color',
|
|
808
|
+
props: { color: 'red', backgroundColor: 'blue', bold: true },
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
const result = renderANSI(node, { colorSupport: 'none' })
|
|
812
|
+
// Should not contain any color codes, but may contain bold
|
|
813
|
+
expect(result).not.toContain('\x1b[31m')
|
|
814
|
+
expect(result).not.toContain('\x1b[44m')
|
|
815
|
+
expect(result).toContain('No Color')
|
|
816
|
+
})
|
|
817
|
+
|
|
818
|
+
it('preserves text formatting when colors are stripped', async () => {
|
|
819
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
820
|
+
|
|
821
|
+
const node: UINode = {
|
|
822
|
+
type: 'text',
|
|
823
|
+
text: 'Bold Only',
|
|
824
|
+
props: { color: 'red', bold: true },
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
const result = renderANSI(node, { colorSupport: 'none' })
|
|
828
|
+
// Bold formatting should be preserved even without colors
|
|
829
|
+
expect(result).toContain('\x1b[1m')
|
|
830
|
+
expect(result).toContain('Bold Only')
|
|
831
|
+
})
|
|
832
|
+
})
|
|
833
|
+
|
|
834
|
+
// ============================================================================
|
|
835
|
+
// Theme Support
|
|
836
|
+
// ============================================================================
|
|
837
|
+
|
|
838
|
+
describe('theme support', () => {
|
|
839
|
+
it('uses dark theme colors by default', async () => {
|
|
840
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
841
|
+
|
|
842
|
+
const node: UINode = {
|
|
843
|
+
type: 'text',
|
|
844
|
+
text: 'Default',
|
|
845
|
+
props: { color: 'primary' },
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
const result = renderANSI(node)
|
|
849
|
+
// Should use dark theme primary color (typically cyan/blue)
|
|
850
|
+
expect(result).toMatch(/\x1b\[/)
|
|
851
|
+
})
|
|
852
|
+
|
|
853
|
+
it('uses light theme colors when specified', async () => {
|
|
854
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
855
|
+
|
|
856
|
+
const node: UINode = {
|
|
857
|
+
type: 'text',
|
|
858
|
+
text: 'Light Mode',
|
|
859
|
+
props: { color: 'primary' },
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
const darkResult = renderANSI(node, { theme: 'dark' })
|
|
863
|
+
const lightResult = renderANSI(node, { theme: 'light' })
|
|
864
|
+
|
|
865
|
+
// Light and dark theme should produce different colors for primary
|
|
866
|
+
expect(darkResult).not.toBe(lightResult)
|
|
867
|
+
})
|
|
868
|
+
|
|
869
|
+
it('resolves semantic color names', async () => {
|
|
870
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
871
|
+
|
|
872
|
+
const semanticColors = ['primary', 'secondary', 'accent', 'muted', 'success', 'warning', 'error', 'info']
|
|
873
|
+
|
|
874
|
+
for (const color of semanticColors) {
|
|
875
|
+
const node: UINode = {
|
|
876
|
+
type: 'text',
|
|
877
|
+
text: color,
|
|
878
|
+
props: { color },
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
const result = renderANSI(node)
|
|
882
|
+
expect(result).toContain('\x1b[')
|
|
883
|
+
expect(result).toContain(color)
|
|
884
|
+
}
|
|
885
|
+
})
|
|
886
|
+
|
|
887
|
+
it('resolves foreground semantic color', async () => {
|
|
888
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
889
|
+
|
|
890
|
+
const node: UINode = {
|
|
891
|
+
type: 'text',
|
|
892
|
+
text: 'Foreground',
|
|
893
|
+
props: { color: 'foreground' },
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const result = renderANSI(node)
|
|
897
|
+
expect(result).toContain('\x1b[')
|
|
898
|
+
})
|
|
899
|
+
|
|
900
|
+
it('resolves background semantic color', async () => {
|
|
901
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
902
|
+
|
|
903
|
+
const node: UINode = {
|
|
904
|
+
type: 'text',
|
|
905
|
+
text: 'Background',
|
|
906
|
+
props: { backgroundColor: 'background' },
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const result = renderANSI(node)
|
|
910
|
+
expect(result).toContain('\x1b[')
|
|
911
|
+
})
|
|
912
|
+
})
|
|
913
|
+
|
|
914
|
+
// ============================================================================
|
|
915
|
+
// Component Type Rendering
|
|
916
|
+
// ============================================================================
|
|
917
|
+
|
|
918
|
+
describe('component types', () => {
|
|
919
|
+
it('renders box component with proper structure', async () => {
|
|
920
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
921
|
+
|
|
922
|
+
const node: UINode = {
|
|
923
|
+
type: 'box',
|
|
924
|
+
props: { border: 'single' },
|
|
925
|
+
children: [{ type: 'text', text: 'Content' }],
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
const result = renderANSI(node)
|
|
929
|
+
expect(result).toContain('Content')
|
|
930
|
+
})
|
|
931
|
+
|
|
932
|
+
it('renders badge component with variant colors', async () => {
|
|
933
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
934
|
+
|
|
935
|
+
const node: UINode = {
|
|
936
|
+
type: 'badge',
|
|
937
|
+
props: { variant: 'success' },
|
|
938
|
+
text: 'Active',
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
const result = renderANSI(node)
|
|
942
|
+
expect(result).toContain('\x1b[') // Should have color styling
|
|
943
|
+
expect(result).toContain('Active')
|
|
944
|
+
})
|
|
945
|
+
|
|
946
|
+
it('renders button component with focus styling', async () => {
|
|
947
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
948
|
+
|
|
949
|
+
const node: UINode = {
|
|
950
|
+
type: 'button',
|
|
951
|
+
props: { focused: true, variant: 'primary' },
|
|
952
|
+
text: 'Submit',
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
const result = renderANSI(node)
|
|
956
|
+
expect(result).toContain('Submit')
|
|
957
|
+
// Focused buttons should have distinct styling
|
|
958
|
+
expect(result).toContain('\x1b[')
|
|
959
|
+
})
|
|
960
|
+
|
|
961
|
+
it('renders spinner component with animation frame', async () => {
|
|
962
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
963
|
+
|
|
964
|
+
const node: UINode = {
|
|
965
|
+
type: 'spinner',
|
|
966
|
+
props: { label: 'Loading...' },
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
const result = renderANSI(node)
|
|
970
|
+
expect(result).toContain('Loading...')
|
|
971
|
+
})
|
|
972
|
+
|
|
973
|
+
it('renders panel component with title', async () => {
|
|
974
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
975
|
+
|
|
976
|
+
const node: UINode = {
|
|
977
|
+
type: 'panel',
|
|
978
|
+
props: { title: 'Settings', border: 'single' },
|
|
979
|
+
children: [{ type: 'text', text: 'Panel content' }],
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
const result = renderANSI(node)
|
|
983
|
+
expect(result).toContain('Settings')
|
|
984
|
+
expect(result).toContain('Panel content')
|
|
985
|
+
})
|
|
986
|
+
|
|
987
|
+
it('renders card component with title styling', async () => {
|
|
988
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
989
|
+
|
|
990
|
+
const node: UINode = {
|
|
991
|
+
type: 'card',
|
|
992
|
+
props: { title: 'Card Title' },
|
|
993
|
+
children: [{ type: 'text', text: 'Card body' }],
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
const result = renderANSI(node)
|
|
997
|
+
expect(result).toContain('Card Title')
|
|
998
|
+
expect(result).toContain('Card body')
|
|
999
|
+
})
|
|
1000
|
+
|
|
1001
|
+
it('renders input component with cursor', async () => {
|
|
1002
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1003
|
+
|
|
1004
|
+
const node: UINode = {
|
|
1005
|
+
type: 'input',
|
|
1006
|
+
props: { value: 'Hello', focused: true, cursorPosition: 5 },
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
const result = renderANSI(node)
|
|
1010
|
+
expect(result).toContain('Hello')
|
|
1011
|
+
})
|
|
1012
|
+
|
|
1013
|
+
it('renders select component with options', async () => {
|
|
1014
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1015
|
+
|
|
1016
|
+
const node: UINode = {
|
|
1017
|
+
type: 'select',
|
|
1018
|
+
props: {
|
|
1019
|
+
options: [
|
|
1020
|
+
{ label: 'Option 1', value: 1 },
|
|
1021
|
+
{ label: 'Option 2', value: 2 },
|
|
1022
|
+
],
|
|
1023
|
+
highlightedIndex: 0,
|
|
1024
|
+
},
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
const result = renderANSI(node)
|
|
1028
|
+
expect(result).toContain('Option 1')
|
|
1029
|
+
expect(result).toContain('Option 2')
|
|
1030
|
+
})
|
|
1031
|
+
|
|
1032
|
+
it('renders table component with headers and rows', async () => {
|
|
1033
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1034
|
+
|
|
1035
|
+
const node: UINode = {
|
|
1036
|
+
type: 'table',
|
|
1037
|
+
props: {
|
|
1038
|
+
columns: [
|
|
1039
|
+
{ key: 'name', header: 'Name' },
|
|
1040
|
+
{ key: 'status', header: 'Status' },
|
|
1041
|
+
],
|
|
1042
|
+
data: [
|
|
1043
|
+
{ name: 'Item 1', status: 'Active' },
|
|
1044
|
+
{ name: 'Item 2', status: 'Inactive' },
|
|
1045
|
+
],
|
|
1046
|
+
},
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
const result = renderANSI(node)
|
|
1050
|
+
expect(result).toContain('Name')
|
|
1051
|
+
expect(result).toContain('Status')
|
|
1052
|
+
expect(result).toContain('Item 1')
|
|
1053
|
+
expect(result).toContain('Item 2')
|
|
1054
|
+
})
|
|
1055
|
+
|
|
1056
|
+
it('renders list component with bullets', async () => {
|
|
1057
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1058
|
+
|
|
1059
|
+
const node: UINode = {
|
|
1060
|
+
type: 'list',
|
|
1061
|
+
props: {
|
|
1062
|
+
items: ['First', 'Second', 'Third'],
|
|
1063
|
+
bullet: '*',
|
|
1064
|
+
},
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
const result = renderANSI(node)
|
|
1068
|
+
expect(result).toContain('First')
|
|
1069
|
+
expect(result).toContain('Second')
|
|
1070
|
+
expect(result).toContain('Third')
|
|
1071
|
+
})
|
|
1072
|
+
|
|
1073
|
+
it('renders breadcrumb component with separator', async () => {
|
|
1074
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1075
|
+
|
|
1076
|
+
const node: UINode = {
|
|
1077
|
+
type: 'breadcrumb',
|
|
1078
|
+
props: {
|
|
1079
|
+
items: [
|
|
1080
|
+
{ label: 'Home', path: '/' },
|
|
1081
|
+
{ label: 'Settings', path: '/settings' },
|
|
1082
|
+
],
|
|
1083
|
+
separator: '>',
|
|
1084
|
+
},
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
const result = renderANSI(node)
|
|
1088
|
+
expect(result).toContain('Home')
|
|
1089
|
+
expect(result).toContain('>')
|
|
1090
|
+
expect(result).toContain('Settings')
|
|
1091
|
+
})
|
|
1092
|
+
})
|
|
1093
|
+
|
|
1094
|
+
// ============================================================================
|
|
1095
|
+
// Edge Cases
|
|
1096
|
+
// ============================================================================
|
|
1097
|
+
|
|
1098
|
+
describe('edge cases', () => {
|
|
1099
|
+
it('handles null children', async () => {
|
|
1100
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1101
|
+
|
|
1102
|
+
const node: UINode = {
|
|
1103
|
+
type: 'box',
|
|
1104
|
+
children: undefined,
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
const result = renderANSI(node)
|
|
1108
|
+
expect(result).toBe('')
|
|
1109
|
+
})
|
|
1110
|
+
|
|
1111
|
+
it('handles empty array children', async () => {
|
|
1112
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1113
|
+
|
|
1114
|
+
const node: UINode = {
|
|
1115
|
+
type: 'box',
|
|
1116
|
+
children: [],
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
const result = renderANSI(node)
|
|
1120
|
+
expect(result).toBe('')
|
|
1121
|
+
})
|
|
1122
|
+
|
|
1123
|
+
it('handles string children', async () => {
|
|
1124
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1125
|
+
|
|
1126
|
+
const node: UINode = {
|
|
1127
|
+
type: 'text',
|
|
1128
|
+
children: 'String child',
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
const result = renderANSI(node)
|
|
1132
|
+
expect(result).toContain('String child')
|
|
1133
|
+
})
|
|
1134
|
+
|
|
1135
|
+
it('handles deeply nested nodes', async () => {
|
|
1136
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1137
|
+
|
|
1138
|
+
const node: UINode = {
|
|
1139
|
+
type: 'box',
|
|
1140
|
+
children: [
|
|
1141
|
+
{
|
|
1142
|
+
type: 'box',
|
|
1143
|
+
children: [
|
|
1144
|
+
{
|
|
1145
|
+
type: 'box',
|
|
1146
|
+
children: [
|
|
1147
|
+
{
|
|
1148
|
+
type: 'text',
|
|
1149
|
+
text: 'Deep',
|
|
1150
|
+
props: { color: 'cyan' },
|
|
1151
|
+
},
|
|
1152
|
+
],
|
|
1153
|
+
},
|
|
1154
|
+
],
|
|
1155
|
+
},
|
|
1156
|
+
],
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
const result = renderANSI(node)
|
|
1160
|
+
expect(result).toContain('Deep')
|
|
1161
|
+
expect(result).toContain('\x1b[36m') // Cyan
|
|
1162
|
+
})
|
|
1163
|
+
|
|
1164
|
+
it('handles special characters in text', async () => {
|
|
1165
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1166
|
+
|
|
1167
|
+
const node: UINode = {
|
|
1168
|
+
type: 'text',
|
|
1169
|
+
text: 'Special: <>&"\'',
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
const result = renderANSI(node)
|
|
1173
|
+
expect(result).toContain('<>&"\'')
|
|
1174
|
+
})
|
|
1175
|
+
|
|
1176
|
+
it('handles unicode characters', async () => {
|
|
1177
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1178
|
+
|
|
1179
|
+
const node: UINode = {
|
|
1180
|
+
type: 'text',
|
|
1181
|
+
text: 'Unicode: \u2713 \u2717 \u25CF \u25CB',
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
const result = renderANSI(node)
|
|
1185
|
+
expect(result).toContain('\u2713')
|
|
1186
|
+
expect(result).toContain('\u2717')
|
|
1187
|
+
})
|
|
1188
|
+
|
|
1189
|
+
it('handles emoji', async () => {
|
|
1190
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1191
|
+
|
|
1192
|
+
const node: UINode = {
|
|
1193
|
+
type: 'text',
|
|
1194
|
+
text: 'Status: OK',
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
const result = renderANSI(node)
|
|
1198
|
+
expect(result).toContain('OK')
|
|
1199
|
+
})
|
|
1200
|
+
|
|
1201
|
+
it('handles newlines in text', async () => {
|
|
1202
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1203
|
+
|
|
1204
|
+
const node: UINode = {
|
|
1205
|
+
type: 'text',
|
|
1206
|
+
text: 'Line 1\nLine 2\nLine 3',
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
const result = renderANSI(node)
|
|
1210
|
+
expect(result).toContain('Line 1')
|
|
1211
|
+
expect(result).toContain('Line 2')
|
|
1212
|
+
expect(result).toContain('Line 3')
|
|
1213
|
+
})
|
|
1214
|
+
|
|
1215
|
+
it('handles undefined props', async () => {
|
|
1216
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1217
|
+
|
|
1218
|
+
const node: UINode = {
|
|
1219
|
+
type: 'text',
|
|
1220
|
+
text: 'No props',
|
|
1221
|
+
props: undefined,
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
const result = renderANSI(node)
|
|
1225
|
+
expect(result).toBe('No props')
|
|
1226
|
+
})
|
|
1227
|
+
|
|
1228
|
+
it('handles unknown node types gracefully', async () => {
|
|
1229
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1230
|
+
|
|
1231
|
+
const node: UINode = {
|
|
1232
|
+
type: 'unknown-component',
|
|
1233
|
+
text: 'Unknown',
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// Should not throw, may render text content or empty
|
|
1237
|
+
expect(() => renderANSI(node)).not.toThrow()
|
|
1238
|
+
})
|
|
1239
|
+
|
|
1240
|
+
it('handles invalid color values gracefully', async () => {
|
|
1241
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1242
|
+
|
|
1243
|
+
const node: UINode = {
|
|
1244
|
+
type: 'text',
|
|
1245
|
+
text: 'Invalid',
|
|
1246
|
+
props: { color: 'not-a-color' },
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// Should not throw, should render text without that color
|
|
1250
|
+
expect(() => renderANSI(node)).not.toThrow()
|
|
1251
|
+
const result = renderANSI(node)
|
|
1252
|
+
expect(result).toContain('Invalid')
|
|
1253
|
+
})
|
|
1254
|
+
})
|
|
1255
|
+
|
|
1256
|
+
// ============================================================================
|
|
1257
|
+
// Terminal Width Handling
|
|
1258
|
+
// ============================================================================
|
|
1259
|
+
|
|
1260
|
+
describe('terminal width', () => {
|
|
1261
|
+
it('respects width option for text wrapping context', async () => {
|
|
1262
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1263
|
+
|
|
1264
|
+
const node: UINode = {
|
|
1265
|
+
type: 'text',
|
|
1266
|
+
text: 'This is a long text that might need wrapping',
|
|
1267
|
+
props: { wrap: 'wrap' },
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
const result = renderANSI(node, { width: 20 })
|
|
1271
|
+
// Result should be aware of width for layout purposes
|
|
1272
|
+
expect(result).toContain('This is')
|
|
1273
|
+
})
|
|
1274
|
+
})
|
|
1275
|
+
|
|
1276
|
+
// ============================================================================
|
|
1277
|
+
// Performance / Output Size
|
|
1278
|
+
// ============================================================================
|
|
1279
|
+
|
|
1280
|
+
describe('output optimization', () => {
|
|
1281
|
+
it('does not duplicate reset codes unnecessarily', async () => {
|
|
1282
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1283
|
+
|
|
1284
|
+
const node: UINode = {
|
|
1285
|
+
type: 'box',
|
|
1286
|
+
children: [
|
|
1287
|
+
{ type: 'text', text: 'A', props: { color: 'red' } },
|
|
1288
|
+
{ type: 'text', text: 'B', props: { color: 'red' } },
|
|
1289
|
+
],
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
const result = renderANSI(node)
|
|
1293
|
+
// Should not have consecutive reset codes
|
|
1294
|
+
expect(result).not.toContain('\x1b[0m\x1b[0m')
|
|
1295
|
+
})
|
|
1296
|
+
|
|
1297
|
+
it('combines adjacent ANSI codes efficiently', async () => {
|
|
1298
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1299
|
+
|
|
1300
|
+
const node: UINode = {
|
|
1301
|
+
type: 'text',
|
|
1302
|
+
text: 'Optimized',
|
|
1303
|
+
props: { bold: true, color: 'red' },
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
const result = renderANSI(node)
|
|
1307
|
+
// Both codes should be present
|
|
1308
|
+
expect(result).toContain('\x1b[1m')
|
|
1309
|
+
expect(result).toContain('\x1b[31m')
|
|
1310
|
+
// Only one reset at the end
|
|
1311
|
+
expect(result.match(/\x1b\[0m/g)?.length).toBe(1)
|
|
1312
|
+
})
|
|
1313
|
+
})
|
|
1314
|
+
})
|
|
1315
|
+
|
|
1316
|
+
// ============================================================================
|
|
1317
|
+
// Integration with Theme System
|
|
1318
|
+
// ============================================================================
|
|
1319
|
+
|
|
1320
|
+
describe('renderANSI theme integration', () => {
|
|
1321
|
+
it('accepts theme colors from createTerminalTheme', async () => {
|
|
1322
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1323
|
+
const { createTerminalTheme } = await import('../../theme')
|
|
1324
|
+
|
|
1325
|
+
const theme = createTerminalTheme({ mode: 'dark' })
|
|
1326
|
+
|
|
1327
|
+
const node: UINode = {
|
|
1328
|
+
type: 'text',
|
|
1329
|
+
text: 'Themed',
|
|
1330
|
+
props: { color: 'primary' },
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// Should be able to pass theme colors somehow
|
|
1334
|
+
const result = renderANSI(node, { theme: 'dark' })
|
|
1335
|
+
expect(result).toContain('Themed')
|
|
1336
|
+
expect(result).toContain('\x1b[')
|
|
1337
|
+
})
|
|
1338
|
+
|
|
1339
|
+
it('uses theme error color for error variant badge', async () => {
|
|
1340
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1341
|
+
|
|
1342
|
+
const node: UINode = {
|
|
1343
|
+
type: 'badge',
|
|
1344
|
+
props: { variant: 'error' },
|
|
1345
|
+
text: 'Error',
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
const result = renderANSI(node)
|
|
1349
|
+
// Should use red-ish error color from theme
|
|
1350
|
+
expect(result).toMatch(/\x1b\[.*31m|\x1b\[.*91m|\x1b\[38;5;\d+m|\x1b\[38;2;\d+;\d+;\d+m/)
|
|
1351
|
+
})
|
|
1352
|
+
|
|
1353
|
+
it('uses theme success color for success variant badge', async () => {
|
|
1354
|
+
const { renderANSI } = await import('../../renderers/ansi')
|
|
1355
|
+
|
|
1356
|
+
const node: UINode = {
|
|
1357
|
+
type: 'badge',
|
|
1358
|
+
props: { variant: 'success' },
|
|
1359
|
+
text: 'Success',
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
const result = renderANSI(node)
|
|
1363
|
+
// Should use green-ish success color from theme
|
|
1364
|
+
expect(result).toMatch(/\x1b\[.*32m|\x1b\[.*92m|\x1b\[38;5;\d+m|\x1b\[38;2;\d+;\d+;\d+m/)
|
|
1365
|
+
})
|
|
1366
|
+
})
|