@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,560 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mdxui/terminal Test Utilities Tests
|
|
3
|
+
*
|
|
4
|
+
* Verify that the test utilities themselves work correctly.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect } from 'vitest'
|
|
7
|
+
import React from 'react'
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
// Test renderer
|
|
11
|
+
createTestRenderer,
|
|
12
|
+
createTestRoot,
|
|
13
|
+
renderToLines,
|
|
14
|
+
renderToString,
|
|
15
|
+
prepareSnapshot,
|
|
16
|
+
mockCreateCliRenderer,
|
|
17
|
+
mockCreateRoot,
|
|
18
|
+
// Assertions
|
|
19
|
+
stripAnsi,
|
|
20
|
+
hasAnsiCodes,
|
|
21
|
+
visibleLength,
|
|
22
|
+
extractAnsiCodes,
|
|
23
|
+
getPlainContent,
|
|
24
|
+
expectTerminalContains,
|
|
25
|
+
expectTerminalNotContains,
|
|
26
|
+
expectTerminalMatches,
|
|
27
|
+
expectLineContains,
|
|
28
|
+
expectLineCount,
|
|
29
|
+
expectMinLines,
|
|
30
|
+
expectAnsiCode,
|
|
31
|
+
expectNoAnsiCode,
|
|
32
|
+
expectStyle,
|
|
33
|
+
expectReset,
|
|
34
|
+
expectBoxDrawing,
|
|
35
|
+
expectNoBoxDrawing,
|
|
36
|
+
expectMaxWidth,
|
|
37
|
+
toSnapshot,
|
|
38
|
+
terminalOutputEquals,
|
|
39
|
+
} from './index'
|
|
40
|
+
|
|
41
|
+
import { Box, Text, Button, Panel, renderComponent } from '../../components'
|
|
42
|
+
import { ANSI, boxChars } from '../../theme'
|
|
43
|
+
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// Test Renderer Tests
|
|
46
|
+
// ============================================================================
|
|
47
|
+
|
|
48
|
+
describe('Test Renderer', () => {
|
|
49
|
+
describe('createTestRenderer', () => {
|
|
50
|
+
it('creates renderer with default dimensions', () => {
|
|
51
|
+
const renderer = createTestRenderer()
|
|
52
|
+
expect(renderer.width).toBe(80)
|
|
53
|
+
expect(renderer.height).toBe(24)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('creates renderer with custom dimensions', () => {
|
|
57
|
+
const renderer = createTestRenderer({ width: 40, height: 10 })
|
|
58
|
+
expect(renderer.width).toBe(40)
|
|
59
|
+
expect(renderer.height).toBe(10)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('renders elements to lines', () => {
|
|
63
|
+
const renderer = createTestRenderer()
|
|
64
|
+
const element = Text({ children: 'Hello World' })
|
|
65
|
+
const lines = renderer.render(element)
|
|
66
|
+
|
|
67
|
+
expect(lines).toHaveLength(1)
|
|
68
|
+
expect(stripAnsi(lines[0])).toBe('Hello World')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('clears buffer', () => {
|
|
72
|
+
const renderer = createTestRenderer()
|
|
73
|
+
const element = Text({ children: 'Test' })
|
|
74
|
+
|
|
75
|
+
renderer.render(element)
|
|
76
|
+
expect(renderer.getLastOutput()).toHaveLength(1)
|
|
77
|
+
|
|
78
|
+
renderer.clear()
|
|
79
|
+
expect(renderer.getLastOutput()).toHaveLength(0)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('can be configured after creation', () => {
|
|
83
|
+
const renderer = createTestRenderer()
|
|
84
|
+
expect(renderer.width).toBe(80)
|
|
85
|
+
|
|
86
|
+
renderer.configure({ width: 40 })
|
|
87
|
+
expect(renderer.width).toBe(40)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('lifecycle methods are callable', () => {
|
|
91
|
+
const renderer = createTestRenderer()
|
|
92
|
+
expect(() => renderer.start()).not.toThrow()
|
|
93
|
+
expect(() => renderer.stop()).not.toThrow()
|
|
94
|
+
expect(() => renderer.requestRender()).not.toThrow()
|
|
95
|
+
expect(() => renderer.destroy()).not.toThrow()
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
describe('createTestRoot', () => {
|
|
100
|
+
it('creates root with render/unmount methods', () => {
|
|
101
|
+
const renderer = createTestRenderer()
|
|
102
|
+
const root = createTestRoot(renderer)
|
|
103
|
+
|
|
104
|
+
expect(typeof root.render).toBe('function')
|
|
105
|
+
expect(typeof root.unmount).toBe('function')
|
|
106
|
+
expect(typeof root.getOutput).toBe('function')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('renders elements and tracks output', () => {
|
|
110
|
+
const renderer = createTestRenderer()
|
|
111
|
+
const root = createTestRoot(renderer)
|
|
112
|
+
|
|
113
|
+
root.render(Text({ children: 'Hello' }))
|
|
114
|
+
const output = root.getOutput()
|
|
115
|
+
|
|
116
|
+
expect(output).toHaveLength(1)
|
|
117
|
+
expect(stripAnsi(output[0])).toBe('Hello')
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('clears output on unmount', () => {
|
|
121
|
+
const renderer = createTestRenderer()
|
|
122
|
+
const root = createTestRoot(renderer)
|
|
123
|
+
|
|
124
|
+
root.render(Text({ children: 'Test' }))
|
|
125
|
+
expect(root.getOutput().length).toBeGreaterThan(0)
|
|
126
|
+
|
|
127
|
+
root.unmount()
|
|
128
|
+
expect(root.getOutput()).toHaveLength(0)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('handles null/undefined render', () => {
|
|
132
|
+
const renderer = createTestRenderer()
|
|
133
|
+
const root = createTestRoot(renderer)
|
|
134
|
+
|
|
135
|
+
root.render(null)
|
|
136
|
+
expect(root.getOutput()).toHaveLength(0)
|
|
137
|
+
|
|
138
|
+
root.render(undefined)
|
|
139
|
+
expect(root.getOutput()).toHaveLength(0)
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
describe('renderToLines', () => {
|
|
144
|
+
it('renders element to lines directly', () => {
|
|
145
|
+
const lines = renderToLines(Text({ children: 'Quick test' }))
|
|
146
|
+
expect(lines).toHaveLength(1)
|
|
147
|
+
expect(stripAnsi(lines[0])).toBe('Quick test')
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('accepts custom config', () => {
|
|
151
|
+
const lines = renderToLines(
|
|
152
|
+
Text({ children: 'Test' }),
|
|
153
|
+
{ width: 40 }
|
|
154
|
+
)
|
|
155
|
+
expect(lines).toHaveLength(1)
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
describe('renderToString', () => {
|
|
160
|
+
it('renders element to joined string', () => {
|
|
161
|
+
const output = renderToString(Text({ children: 'Test' }))
|
|
162
|
+
expect(typeof output).toBe('string')
|
|
163
|
+
expect(output).toContain('Test')
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
describe('prepareSnapshot', () => {
|
|
168
|
+
it('strips ANSI codes when requested', () => {
|
|
169
|
+
const styledOutput = `${ANSI.bold}Bold${ANSI.reset}`
|
|
170
|
+
const snapshot = prepareSnapshot(styledOutput, { stripAnsi: true })
|
|
171
|
+
expect(snapshot).toBe('Bold')
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('preserves ANSI codes by default', () => {
|
|
175
|
+
const styledOutput = `${ANSI.bold}Bold${ANSI.reset}`
|
|
176
|
+
const snapshot = prepareSnapshot(styledOutput)
|
|
177
|
+
expect(snapshot).toContain(ANSI.bold)
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('trims trailing whitespace by default', () => {
|
|
181
|
+
const output = ['Line with trailing ', 'Another line ']
|
|
182
|
+
const snapshot = prepareSnapshot(output)
|
|
183
|
+
expect(snapshot).toBe('Line with trailing\nAnother line')
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('normalizes line endings', () => {
|
|
187
|
+
const output = 'Line1\r\nLine2\rLine3'
|
|
188
|
+
const snapshot = prepareSnapshot(output)
|
|
189
|
+
expect(snapshot).toBe('Line1\nLine2\nLine3')
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
describe('mockCreateCliRenderer', () => {
|
|
194
|
+
it('creates mock renderer factory', async () => {
|
|
195
|
+
const factory = mockCreateCliRenderer({ width: 40 })
|
|
196
|
+
const renderer = await factory()
|
|
197
|
+
expect(renderer.width).toBe(40)
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
describe('mockCreateRoot', () => {
|
|
202
|
+
it('creates mock root factory', () => {
|
|
203
|
+
const factory = mockCreateRoot()
|
|
204
|
+
const renderer = createTestRenderer()
|
|
205
|
+
const root = factory(renderer)
|
|
206
|
+
expect(typeof root.render).toBe('function')
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
// ============================================================================
|
|
212
|
+
// ANSI Utilities Tests
|
|
213
|
+
// ============================================================================
|
|
214
|
+
|
|
215
|
+
describe('ANSI Utilities', () => {
|
|
216
|
+
describe('stripAnsi', () => {
|
|
217
|
+
it('removes ANSI escape codes', () => {
|
|
218
|
+
const styled = `${ANSI.bold}${ANSI.cyan}Hello${ANSI.reset}`
|
|
219
|
+
expect(stripAnsi(styled)).toBe('Hello')
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('returns unchanged string if no ANSI codes', () => {
|
|
223
|
+
expect(stripAnsi('Plain text')).toBe('Plain text')
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it('handles empty string', () => {
|
|
227
|
+
expect(stripAnsi('')).toBe('')
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
describe('hasAnsiCodes', () => {
|
|
232
|
+
it('returns true for styled text', () => {
|
|
233
|
+
expect(hasAnsiCodes(`${ANSI.bold}Bold`)).toBe(true)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
it('returns false for plain text', () => {
|
|
237
|
+
expect(hasAnsiCodes('Plain')).toBe(false)
|
|
238
|
+
})
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
describe('visibleLength', () => {
|
|
242
|
+
it('returns correct length ignoring ANSI', () => {
|
|
243
|
+
const styled = `${ANSI.bold}${ANSI.cyan}Hello${ANSI.reset}`
|
|
244
|
+
expect(visibleLength(styled)).toBe(5)
|
|
245
|
+
})
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
describe('extractAnsiCodes', () => {
|
|
249
|
+
it('extracts all ANSI codes from string', () => {
|
|
250
|
+
const styled = `${ANSI.bold}${ANSI.cyan}Hi${ANSI.reset}`
|
|
251
|
+
const codes = extractAnsiCodes(styled)
|
|
252
|
+
expect(codes).toContain(ANSI.bold)
|
|
253
|
+
expect(codes).toContain(ANSI.cyan)
|
|
254
|
+
expect(codes).toContain(ANSI.reset)
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('returns empty array for plain text', () => {
|
|
258
|
+
expect(extractAnsiCodes('Plain')).toEqual([])
|
|
259
|
+
})
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
describe('getPlainContent', () => {
|
|
263
|
+
it('joins array and strips ANSI', () => {
|
|
264
|
+
const lines = [`${ANSI.bold}Line 1${ANSI.reset}`, 'Line 2']
|
|
265
|
+
expect(getPlainContent(lines)).toBe('Line 1\nLine 2')
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
it('handles string input', () => {
|
|
269
|
+
expect(getPlainContent(`${ANSI.bold}Test${ANSI.reset}`)).toBe('Test')
|
|
270
|
+
})
|
|
271
|
+
})
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
// ============================================================================
|
|
275
|
+
// Content Assertion Tests
|
|
276
|
+
// ============================================================================
|
|
277
|
+
|
|
278
|
+
describe('Content Assertions', () => {
|
|
279
|
+
describe('expectTerminalContains', () => {
|
|
280
|
+
it('passes when text is found', () => {
|
|
281
|
+
const output = [`${ANSI.bold}Hello World${ANSI.reset}`]
|
|
282
|
+
expect(() => expectTerminalContains(output, 'Hello')).not.toThrow()
|
|
283
|
+
expect(() => expectTerminalContains(output, 'World')).not.toThrow()
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
it('fails when text is not found', () => {
|
|
287
|
+
const output = ['Hello']
|
|
288
|
+
expect(() => expectTerminalContains(output, 'Goodbye')).toThrow()
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
describe('expectTerminalNotContains', () => {
|
|
293
|
+
it('passes when text is not found', () => {
|
|
294
|
+
const output = ['Hello']
|
|
295
|
+
expect(() => expectTerminalNotContains(output, 'Secret')).not.toThrow()
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
it('fails when text is found', () => {
|
|
299
|
+
const output = ['Contains Secret']
|
|
300
|
+
expect(() => expectTerminalNotContains(output, 'Secret')).toThrow()
|
|
301
|
+
})
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
describe('expectTerminalMatches', () => {
|
|
305
|
+
it('passes when pattern matches', () => {
|
|
306
|
+
const output = ['Hello World 123']
|
|
307
|
+
expect(() => expectTerminalMatches(output, /Hello \w+/)).not.toThrow()
|
|
308
|
+
expect(() => expectTerminalMatches(output, /\d+/)).not.toThrow()
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
it('fails when pattern does not match', () => {
|
|
312
|
+
const output = ['Hello']
|
|
313
|
+
expect(() => expectTerminalMatches(output, /Goodbye/)).toThrow()
|
|
314
|
+
})
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
describe('expectLineContains', () => {
|
|
318
|
+
it('checks specific line', () => {
|
|
319
|
+
const output = ['Header', 'Content', 'Footer']
|
|
320
|
+
expect(() => expectLineContains(output, 0, 'Header')).not.toThrow()
|
|
321
|
+
expect(() => expectLineContains(output, 1, 'Content')).not.toThrow()
|
|
322
|
+
expect(() => expectLineContains(output, 2, 'Footer')).not.toThrow()
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
it('fails for wrong line content', () => {
|
|
326
|
+
const output = ['Header', 'Content']
|
|
327
|
+
expect(() => expectLineContains(output, 0, 'Wrong')).toThrow()
|
|
328
|
+
})
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
describe('expectLineCount', () => {
|
|
332
|
+
it('checks exact line count', () => {
|
|
333
|
+
expect(() => expectLineCount(['a', 'b', 'c'], 3)).not.toThrow()
|
|
334
|
+
expect(() => expectLineCount(['a', 'b', 'c'], 2)).toThrow()
|
|
335
|
+
})
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
describe('expectMinLines', () => {
|
|
339
|
+
it('checks minimum line count', () => {
|
|
340
|
+
expect(() => expectMinLines(['a', 'b', 'c'], 2)).not.toThrow()
|
|
341
|
+
expect(() => expectMinLines(['a', 'b', 'c'], 3)).not.toThrow()
|
|
342
|
+
expect(() => expectMinLines(['a', 'b'], 3)).toThrow()
|
|
343
|
+
})
|
|
344
|
+
})
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
// ============================================================================
|
|
348
|
+
// ANSI Style Assertion Tests
|
|
349
|
+
// ============================================================================
|
|
350
|
+
|
|
351
|
+
describe('ANSI Style Assertions', () => {
|
|
352
|
+
describe('expectAnsiCode', () => {
|
|
353
|
+
it('passes when code is present', () => {
|
|
354
|
+
const output = `${ANSI.bold}Bold${ANSI.reset}`
|
|
355
|
+
expect(() => expectAnsiCode(output, ANSI.bold)).not.toThrow()
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
it('fails when code is missing', () => {
|
|
359
|
+
const output = 'Plain text'
|
|
360
|
+
expect(() => expectAnsiCode(output, ANSI.bold)).toThrow()
|
|
361
|
+
})
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
describe('expectNoAnsiCode', () => {
|
|
365
|
+
it('passes when code is missing', () => {
|
|
366
|
+
const output = 'Plain text'
|
|
367
|
+
expect(() => expectNoAnsiCode(output, ANSI.bold)).not.toThrow()
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
it('fails when code is present', () => {
|
|
371
|
+
const output = `${ANSI.bold}Bold`
|
|
372
|
+
expect(() => expectNoAnsiCode(output, ANSI.bold)).toThrow()
|
|
373
|
+
})
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
describe('expectStyle', () => {
|
|
377
|
+
it('checks for named styles', () => {
|
|
378
|
+
const boldText = `${ANSI.bold}Bold${ANSI.reset}`
|
|
379
|
+
expect(() => expectStyle(boldText, 'bold')).not.toThrow()
|
|
380
|
+
|
|
381
|
+
const dimText = `${ANSI.dim}Dim${ANSI.reset}`
|
|
382
|
+
expect(() => expectStyle(dimText, 'dim')).not.toThrow()
|
|
383
|
+
})
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
describe('expectReset', () => {
|
|
387
|
+
it('checks for reset code', () => {
|
|
388
|
+
const styled = `${ANSI.bold}Text${ANSI.reset}`
|
|
389
|
+
expect(() => expectReset(styled)).not.toThrow()
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
it('fails without reset', () => {
|
|
393
|
+
const styled = `${ANSI.bold}Text`
|
|
394
|
+
expect(() => expectReset(styled)).toThrow()
|
|
395
|
+
})
|
|
396
|
+
})
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
// ============================================================================
|
|
400
|
+
// Box Drawing Assertion Tests
|
|
401
|
+
// ============================================================================
|
|
402
|
+
|
|
403
|
+
describe('Box Drawing Assertions', () => {
|
|
404
|
+
describe('expectBoxDrawing', () => {
|
|
405
|
+
it('checks for single border characters', () => {
|
|
406
|
+
const element = Box({ border: 'single', width: 10, height: 3, children: '' })
|
|
407
|
+
const lines = renderComponent(element)
|
|
408
|
+
|
|
409
|
+
expect(() => expectBoxDrawing(lines, 'single')).not.toThrow()
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
it('checks for double border characters', () => {
|
|
413
|
+
const element = Box({ border: 'double', width: 10, height: 3, children: '' })
|
|
414
|
+
const lines = renderComponent(element)
|
|
415
|
+
|
|
416
|
+
expect(() => expectBoxDrawing(lines, 'double')).not.toThrow()
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
it('checks for rounded border characters', () => {
|
|
420
|
+
const element = Box({ border: 'rounded', width: 10, height: 3, children: '' })
|
|
421
|
+
const lines = renderComponent(element)
|
|
422
|
+
|
|
423
|
+
expect(() => expectBoxDrawing(lines, 'rounded')).not.toThrow()
|
|
424
|
+
})
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
describe('expectNoBoxDrawing', () => {
|
|
428
|
+
it('passes for text without borders', () => {
|
|
429
|
+
const lines = ['Plain text']
|
|
430
|
+
expect(() => expectNoBoxDrawing(lines)).not.toThrow()
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
it('fails when borders are present', () => {
|
|
434
|
+
const element = Box({ border: 'single', width: 10, height: 3, children: '' })
|
|
435
|
+
const lines = renderComponent(element)
|
|
436
|
+
|
|
437
|
+
expect(() => expectNoBoxDrawing(lines)).toThrow()
|
|
438
|
+
})
|
|
439
|
+
})
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
// ============================================================================
|
|
443
|
+
// Dimension Assertion Tests
|
|
444
|
+
// ============================================================================
|
|
445
|
+
|
|
446
|
+
describe('Dimension Assertions', () => {
|
|
447
|
+
describe('expectMaxWidth', () => {
|
|
448
|
+
it('passes when all lines within width', () => {
|
|
449
|
+
const lines = ['Short', 'Also short']
|
|
450
|
+
expect(() => expectMaxWidth(lines, 20)).not.toThrow()
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
it('fails when line exceeds width', () => {
|
|
454
|
+
const lines = ['This is a much longer line']
|
|
455
|
+
expect(() => expectMaxWidth(lines, 10)).toThrow()
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
it('ignores ANSI codes when measuring', () => {
|
|
459
|
+
const lines = [`${ANSI.bold}${ANSI.cyan}Short${ANSI.reset}`]
|
|
460
|
+
expect(() => expectMaxWidth(lines, 10)).not.toThrow()
|
|
461
|
+
})
|
|
462
|
+
})
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
// ============================================================================
|
|
466
|
+
// Snapshot Utility Tests
|
|
467
|
+
// ============================================================================
|
|
468
|
+
|
|
469
|
+
describe('Snapshot Utilities', () => {
|
|
470
|
+
describe('toSnapshot', () => {
|
|
471
|
+
it('creates snapshot-ready string', () => {
|
|
472
|
+
const lines = ['Line 1 ', 'Line 2 ']
|
|
473
|
+
const snapshot = toSnapshot(lines)
|
|
474
|
+
expect(snapshot).toBe('Line 1\nLine 2')
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
it('can strip ANSI codes', () => {
|
|
478
|
+
const lines = [`${ANSI.bold}Bold${ANSI.reset}`]
|
|
479
|
+
const snapshot = toSnapshot(lines, { stripAnsi: true })
|
|
480
|
+
expect(snapshot).toBe('Bold')
|
|
481
|
+
})
|
|
482
|
+
})
|
|
483
|
+
|
|
484
|
+
describe('terminalOutputEquals', () => {
|
|
485
|
+
it('compares plain content', () => {
|
|
486
|
+
const a = [`${ANSI.bold}Hello${ANSI.reset}`]
|
|
487
|
+
const b = [`${ANSI.dim}Hello${ANSI.reset}`]
|
|
488
|
+
|
|
489
|
+
expect(terminalOutputEquals(a, b)).toBe(true)
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
it('detects content differences', () => {
|
|
493
|
+
const a = ['Hello']
|
|
494
|
+
const b = ['Goodbye']
|
|
495
|
+
|
|
496
|
+
expect(terminalOutputEquals(a, b)).toBe(false)
|
|
497
|
+
})
|
|
498
|
+
})
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
// ============================================================================
|
|
502
|
+
// Integration Tests with Real Components
|
|
503
|
+
// ============================================================================
|
|
504
|
+
|
|
505
|
+
describe('Integration with Components', () => {
|
|
506
|
+
it('works with Box component', () => {
|
|
507
|
+
const renderer = createTestRenderer()
|
|
508
|
+
const element = Box({ border: 'single', width: 20, height: 3, children: 'Test' })
|
|
509
|
+
const lines = renderer.render(element)
|
|
510
|
+
|
|
511
|
+
expectTerminalContains(lines, 'Test')
|
|
512
|
+
expectBoxDrawing(lines, 'single')
|
|
513
|
+
expectLineCount(lines, 3)
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
it('works with Text component', () => {
|
|
517
|
+
const renderer = createTestRenderer()
|
|
518
|
+
const element = Text({ bold: true, color: 'cyan', children: 'Styled' })
|
|
519
|
+
const lines = renderer.render(element)
|
|
520
|
+
|
|
521
|
+
expectTerminalContains(lines, 'Styled')
|
|
522
|
+
expectStyle(lines.join(''), 'bold')
|
|
523
|
+
expectAnsiCode(lines.join(''), ANSI.cyan)
|
|
524
|
+
})
|
|
525
|
+
|
|
526
|
+
it('works with Button component', () => {
|
|
527
|
+
const renderer = createTestRenderer()
|
|
528
|
+
const element = Button({ variant: 'primary', onPress: () => {}, children: 'Click Me' })
|
|
529
|
+
const lines = renderer.render(element)
|
|
530
|
+
|
|
531
|
+
expectTerminalContains(lines, 'Click Me')
|
|
532
|
+
expectStyle(lines.join(''), 'bold')
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
it('works with Panel component', () => {
|
|
536
|
+
const renderer = createTestRenderer()
|
|
537
|
+
const element = Panel({ title: 'Settings', children: 'Content' })
|
|
538
|
+
const lines = renderer.render(element)
|
|
539
|
+
|
|
540
|
+
expectTerminalContains(lines, 'Settings')
|
|
541
|
+
expectTerminalContains(lines, 'Content')
|
|
542
|
+
expectBoxDrawing(lines, 'single')
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
it('creates snapshots from component output', () => {
|
|
546
|
+
const element = Box({
|
|
547
|
+
border: 'single',
|
|
548
|
+
width: 15,
|
|
549
|
+
height: 3,
|
|
550
|
+
children: 'Hello'
|
|
551
|
+
})
|
|
552
|
+
const lines = renderComponent(element)
|
|
553
|
+
const snapshot = toSnapshot(lines, { stripAnsi: true })
|
|
554
|
+
|
|
555
|
+
// Snapshot should contain box and content
|
|
556
|
+
expect(snapshot).toContain(boxChars.single.topLeft)
|
|
557
|
+
expect(snapshot).toContain('Hello')
|
|
558
|
+
expect(snapshot).toContain(boxChars.single.bottomRight)
|
|
559
|
+
})
|
|
560
|
+
})
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mdxui/terminal/components/containers/card
|
|
3
|
+
*
|
|
4
|
+
* Card component for terminal rendering.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { type ReactElement } from 'react'
|
|
8
|
+
import { ANSI, boxChars } from '../../theme'
|
|
9
|
+
import type { CardProps, RenderContext } from '../types'
|
|
10
|
+
import { extractChildren } from '../helpers'
|
|
11
|
+
|
|
12
|
+
export function renderCard(props: CardProps, context: RenderContext): string[] {
|
|
13
|
+
const { children, title, border = 'single' } = props
|
|
14
|
+
|
|
15
|
+
const lines: string[] = []
|
|
16
|
+
const chars = border === 'double' ? boxChars.double : boxChars.single
|
|
17
|
+
const cardWidth = Math.min(context.width, 60) // Cards have max width
|
|
18
|
+
|
|
19
|
+
// Top border
|
|
20
|
+
if (border !== 'none') {
|
|
21
|
+
lines.push(chars.topLeft + chars.horizontal.repeat(cardWidth - 2) + chars.topRight)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Title if present
|
|
25
|
+
if (title) {
|
|
26
|
+
const titleLine = ' ' + ANSI.bold + title + ANSI.reset
|
|
27
|
+
if (border !== 'none') {
|
|
28
|
+
lines.push(chars.vertical + titleLine.padEnd(cardWidth - 2 + ANSI.bold.length + ANSI.reset.length) + chars.vertical)
|
|
29
|
+
lines.push(chars.teeLeft + chars.horizontal.repeat(cardWidth - 2) + chars.teeRight)
|
|
30
|
+
} else {
|
|
31
|
+
lines.push(ANSI.bold + title + ANSI.reset)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Content
|
|
36
|
+
const content = extractChildren(children)
|
|
37
|
+
const contentLines = content.split('\n')
|
|
38
|
+
for (const line of contentLines) {
|
|
39
|
+
if (border !== 'none') {
|
|
40
|
+
lines.push(chars.vertical + ' ' + line.padEnd(cardWidth - 4) + ' ' + chars.vertical)
|
|
41
|
+
} else {
|
|
42
|
+
lines.push(line)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Bottom border
|
|
47
|
+
if (border !== 'none') {
|
|
48
|
+
lines.push(chars.bottomLeft + chars.horizontal.repeat(cardWidth - 2) + chars.bottomRight)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return lines
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function Card(props: CardProps): ReactElement {
|
|
55
|
+
return React.createElement('card', props)
|
|
56
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mdxui/terminal/components/containers/dialog
|
|
3
|
+
*
|
|
4
|
+
* Dialog component for terminal rendering.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { type ReactElement } from 'react'
|
|
8
|
+
import { ANSI, boxChars } from '../../theme'
|
|
9
|
+
import type { DialogProps, RenderContext } from '../types'
|
|
10
|
+
import { extractChildren } from '../helpers'
|
|
11
|
+
|
|
12
|
+
export function renderDialog(props: DialogProps, context: RenderContext): string[] {
|
|
13
|
+
const { open, title, children } = props
|
|
14
|
+
|
|
15
|
+
if (!open) {
|
|
16
|
+
return ['']
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const dialogWidth = 40
|
|
20
|
+
const padding = Math.floor((context.width - dialogWidth) / 2)
|
|
21
|
+
const paddingStr = ' '.repeat(Math.max(0, padding))
|
|
22
|
+
|
|
23
|
+
const lines: string[] = []
|
|
24
|
+
const chars = boxChars.single
|
|
25
|
+
|
|
26
|
+
// Top border
|
|
27
|
+
lines.push(paddingStr + ANSI.cyan + chars.topLeft + chars.horizontal.repeat(dialogWidth - 2) + chars.topRight + ANSI.reset)
|
|
28
|
+
|
|
29
|
+
// Title if present
|
|
30
|
+
if (title) {
|
|
31
|
+
const titlePadded = (' ' + title).padEnd(dialogWidth - 2)
|
|
32
|
+
lines.push(paddingStr + ANSI.cyan + chars.vertical + ANSI.reset + ANSI.bold + titlePadded + ANSI.reset + ANSI.cyan + chars.vertical + ANSI.reset)
|
|
33
|
+
// Title separator
|
|
34
|
+
lines.push(paddingStr + ANSI.cyan + chars.teeLeft + chars.horizontal.repeat(dialogWidth - 2) + chars.teeRight + ANSI.reset)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Content
|
|
38
|
+
const content = extractChildren(children)
|
|
39
|
+
const contentLines = content.split('\n')
|
|
40
|
+
for (const contentLine of contentLines) {
|
|
41
|
+
const paddedContent = (' ' + contentLine).padEnd(dialogWidth - 2)
|
|
42
|
+
lines.push(paddingStr + ANSI.cyan + chars.vertical + ANSI.reset + paddedContent + ANSI.cyan + chars.vertical + ANSI.reset)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Bottom border
|
|
46
|
+
lines.push(paddingStr + ANSI.cyan + chars.bottomLeft + chars.horizontal.repeat(dialogWidth - 2) + chars.bottomRight + ANSI.reset)
|
|
47
|
+
|
|
48
|
+
return lines
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function Dialog(props: DialogProps): ReactElement {
|
|
52
|
+
return React.createElement('dialog', props)
|
|
53
|
+
}
|