@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,489 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mdxui/terminal OpenTUI Renderer Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* TDD RED Phase: Tests for the OpenTUI renderer integration.
|
|
5
|
+
* These tests verify the core renderer APIs work correctly:
|
|
6
|
+
* - createCliRenderer(): Creates the underlying OpenTUI terminal renderer
|
|
7
|
+
* - createRoot(): Creates a React root for terminal rendering
|
|
8
|
+
* - CLI(): High-level API combining renderer + root
|
|
9
|
+
*
|
|
10
|
+
* Tests mock @opentui/core and @opentui/react for CI environments
|
|
11
|
+
* where a real terminal is not available.
|
|
12
|
+
*/
|
|
13
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
14
|
+
import React from 'react'
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Mock Setup
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Mock renderer instance with all required methods
|
|
22
|
+
*/
|
|
23
|
+
function createMockRenderer() {
|
|
24
|
+
return {
|
|
25
|
+
width: 80,
|
|
26
|
+
height: 24,
|
|
27
|
+
start: vi.fn(),
|
|
28
|
+
stop: vi.fn(),
|
|
29
|
+
destroy: vi.fn(),
|
|
30
|
+
requestRender: vi.fn(),
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Mock root instance with render/unmount
|
|
36
|
+
*/
|
|
37
|
+
function createMockRoot() {
|
|
38
|
+
return {
|
|
39
|
+
render: vi.fn(),
|
|
40
|
+
unmount: vi.fn(),
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Mock the OpenTUI modules
|
|
45
|
+
vi.mock('@opentui/core', () => ({
|
|
46
|
+
createCliRenderer: vi.fn(async () => createMockRenderer()),
|
|
47
|
+
}))
|
|
48
|
+
|
|
49
|
+
vi.mock('@opentui/react', () => ({
|
|
50
|
+
createRoot: vi.fn(() => createMockRoot()),
|
|
51
|
+
}))
|
|
52
|
+
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// createCliRenderer Tests
|
|
55
|
+
// ============================================================================
|
|
56
|
+
|
|
57
|
+
describe('OpenTUI Renderer Integration', () => {
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
vi.resetModules()
|
|
60
|
+
vi.clearAllMocks()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
vi.restoreAllMocks()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe('createCliRenderer', () => {
|
|
68
|
+
it('returns renderer with width/height properties', async () => {
|
|
69
|
+
const { createCliRenderer } = await import('..')
|
|
70
|
+
|
|
71
|
+
const renderer = await createCliRenderer()
|
|
72
|
+
|
|
73
|
+
expect(renderer).toBeDefined()
|
|
74
|
+
expect(typeof renderer.width).toBe('number')
|
|
75
|
+
expect(typeof renderer.height).toBe('number')
|
|
76
|
+
expect(renderer.width).toBeGreaterThan(0)
|
|
77
|
+
expect(renderer.height).toBeGreaterThan(0)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('renderer has start/stop/destroy methods', async () => {
|
|
81
|
+
const { createCliRenderer } = await import('..')
|
|
82
|
+
|
|
83
|
+
const renderer = await createCliRenderer()
|
|
84
|
+
|
|
85
|
+
expect(typeof renderer.start).toBe('function')
|
|
86
|
+
expect(typeof renderer.stop).toBe('function')
|
|
87
|
+
expect(typeof renderer.destroy).toBe('function')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('renderer has requestRender method', async () => {
|
|
91
|
+
const { createCliRenderer } = await import('..')
|
|
92
|
+
|
|
93
|
+
const renderer = await createCliRenderer()
|
|
94
|
+
|
|
95
|
+
expect(typeof renderer.requestRender).toBe('function')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('start/stop/destroy methods are callable', async () => {
|
|
99
|
+
const { createCliRenderer } = await import('..')
|
|
100
|
+
|
|
101
|
+
const renderer = await createCliRenderer()
|
|
102
|
+
|
|
103
|
+
// These should not throw
|
|
104
|
+
expect(() => renderer.start()).not.toThrow()
|
|
105
|
+
expect(() => renderer.stop()).not.toThrow()
|
|
106
|
+
expect(() => renderer.requestRender()).not.toThrow()
|
|
107
|
+
expect(() => renderer.destroy()).not.toThrow()
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('accepts configuration options', async () => {
|
|
111
|
+
const { createCliRenderer } = await import('..')
|
|
112
|
+
const { createCliRenderer: mockCreate } = await import('@opentui/core')
|
|
113
|
+
|
|
114
|
+
await createCliRenderer({
|
|
115
|
+
targetFps: 30,
|
|
116
|
+
useAlternateScreen: true,
|
|
117
|
+
useMouse: false,
|
|
118
|
+
exitOnCtrlC: true,
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
expect(mockCreate).toHaveBeenCalledWith(
|
|
122
|
+
expect.objectContaining({
|
|
123
|
+
targetFps: 30,
|
|
124
|
+
useAlternateScreen: true,
|
|
125
|
+
useMouse: false,
|
|
126
|
+
exitOnCtrlC: true,
|
|
127
|
+
})
|
|
128
|
+
)
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
// ============================================================================
|
|
133
|
+
// createRoot Tests
|
|
134
|
+
// ============================================================================
|
|
135
|
+
|
|
136
|
+
describe('createRoot', () => {
|
|
137
|
+
it('returns root with render/unmount methods', async () => {
|
|
138
|
+
const { createCliRenderer, createRoot } = await import('..')
|
|
139
|
+
|
|
140
|
+
const renderer = await createCliRenderer()
|
|
141
|
+
const root = createRoot(renderer)
|
|
142
|
+
|
|
143
|
+
expect(root).toBeDefined()
|
|
144
|
+
expect(typeof root.render).toBe('function')
|
|
145
|
+
expect(typeof root.unmount).toBe('function')
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('render() accepts React elements', async () => {
|
|
149
|
+
const { createCliRenderer, createRoot, Text } = await import('..')
|
|
150
|
+
|
|
151
|
+
const renderer = await createCliRenderer()
|
|
152
|
+
const root = createRoot(renderer)
|
|
153
|
+
|
|
154
|
+
const element = React.createElement(Text, {}, 'Hello')
|
|
155
|
+
|
|
156
|
+
// render() should not throw
|
|
157
|
+
await expect(root.render(element)).resolves.not.toThrow()
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('render() can be called multiple times', async () => {
|
|
161
|
+
const { createCliRenderer, createRoot, Text } = await import('..')
|
|
162
|
+
|
|
163
|
+
const renderer = await createCliRenderer()
|
|
164
|
+
const root = createRoot(renderer)
|
|
165
|
+
|
|
166
|
+
// Multiple renders should not throw
|
|
167
|
+
await root.render(React.createElement(Text, {}, 'First'))
|
|
168
|
+
await root.render(React.createElement(Text, {}, 'Second'))
|
|
169
|
+
await root.render(React.createElement(Text, {}, 'Third'))
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('unmount() cleans up properly', async () => {
|
|
173
|
+
const { createCliRenderer, createRoot, Text } = await import('..')
|
|
174
|
+
|
|
175
|
+
const renderer = await createCliRenderer()
|
|
176
|
+
const root = createRoot(renderer)
|
|
177
|
+
|
|
178
|
+
await root.render(React.createElement(Text, {}, 'Hello'))
|
|
179
|
+
|
|
180
|
+
// unmount should not throw
|
|
181
|
+
expect(() => root.unmount()).not.toThrow()
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it('unmount() can be called before render()', async () => {
|
|
185
|
+
const { createCliRenderer, createRoot } = await import('..')
|
|
186
|
+
|
|
187
|
+
const renderer = await createCliRenderer()
|
|
188
|
+
const root = createRoot(renderer)
|
|
189
|
+
|
|
190
|
+
// unmount without render should not throw
|
|
191
|
+
expect(() => root.unmount()).not.toThrow()
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('render() reuses the same root instance on subsequent calls', async () => {
|
|
195
|
+
const { createCliRenderer, createRoot, Text } = await import('..')
|
|
196
|
+
const { createRoot: mockCreateRoot } = await import('@opentui/react')
|
|
197
|
+
|
|
198
|
+
const renderer = await createCliRenderer()
|
|
199
|
+
const root = createRoot(renderer)
|
|
200
|
+
|
|
201
|
+
// First render - should create root
|
|
202
|
+
await root.render(React.createElement(Text, {}, 'First'))
|
|
203
|
+
const callCount1 = (mockCreateRoot as ReturnType<typeof vi.fn>).mock.calls.length
|
|
204
|
+
|
|
205
|
+
// Second render - should reuse root
|
|
206
|
+
await root.render(React.createElement(Text, {}, 'Second'))
|
|
207
|
+
const callCount2 = (mockCreateRoot as ReturnType<typeof vi.fn>).mock.calls.length
|
|
208
|
+
|
|
209
|
+
// createRoot should only be called once
|
|
210
|
+
expect(callCount2).toBe(callCount1)
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
// ============================================================================
|
|
215
|
+
// CLI() High-Level API Tests
|
|
216
|
+
// ============================================================================
|
|
217
|
+
|
|
218
|
+
describe('CLI() high-level API', () => {
|
|
219
|
+
it('CLI() returns instance with render method', async () => {
|
|
220
|
+
const { CLI } = await import('..')
|
|
221
|
+
|
|
222
|
+
const cli = await CLI()
|
|
223
|
+
|
|
224
|
+
expect(cli).toBeDefined()
|
|
225
|
+
expect(typeof cli.render).toBe('function')
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('CLI() returns terminal dimensions', async () => {
|
|
229
|
+
const { CLI } = await import('..')
|
|
230
|
+
|
|
231
|
+
const cli = await CLI()
|
|
232
|
+
|
|
233
|
+
expect(typeof cli.width).toBe('number')
|
|
234
|
+
expect(typeof cli.height).toBe('number')
|
|
235
|
+
expect(cli.width).toBeGreaterThan(0)
|
|
236
|
+
expect(cli.height).toBeGreaterThan(0)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('CLI() clear() works', async () => {
|
|
240
|
+
const { CLI } = await import('..')
|
|
241
|
+
|
|
242
|
+
const cli = await CLI()
|
|
243
|
+
|
|
244
|
+
// clear() should not throw
|
|
245
|
+
expect(() => cli.clear()).not.toThrow()
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it('CLI() destroy() cleans up', async () => {
|
|
249
|
+
const { CLI } = await import('..')
|
|
250
|
+
|
|
251
|
+
const cli = await CLI()
|
|
252
|
+
|
|
253
|
+
// destroy() should not throw
|
|
254
|
+
expect(() => cli.destroy()).not.toThrow()
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('CLI() requestRender() works', async () => {
|
|
258
|
+
const { CLI } = await import('..')
|
|
259
|
+
|
|
260
|
+
const cli = await CLI()
|
|
261
|
+
|
|
262
|
+
// requestRender() should not throw
|
|
263
|
+
expect(() => cli.requestRender()).not.toThrow()
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
it('CLI() exposes underlying renderer and root', async () => {
|
|
267
|
+
const { CLI } = await import('..')
|
|
268
|
+
|
|
269
|
+
const cli = await CLI()
|
|
270
|
+
|
|
271
|
+
expect(cli.renderer).toBeDefined()
|
|
272
|
+
expect(cli.root).toBeDefined()
|
|
273
|
+
expect(typeof cli.renderer.start).toBe('function')
|
|
274
|
+
expect(typeof cli.root.render).toBe('function')
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
it('CLI() accepts options', async () => {
|
|
278
|
+
const { CLI } = await import('..')
|
|
279
|
+
const { createCliRenderer: mockCreate } = await import('@opentui/core')
|
|
280
|
+
|
|
281
|
+
await CLI({
|
|
282
|
+
colorSupport: 'truecolor',
|
|
283
|
+
targetFps: 60,
|
|
284
|
+
useAlternateScreen: true,
|
|
285
|
+
useMouse: true,
|
|
286
|
+
exitOnCtrlC: false,
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
expect(mockCreate).toHaveBeenCalledWith(
|
|
290
|
+
expect.objectContaining({
|
|
291
|
+
targetFps: 60,
|
|
292
|
+
useAlternateScreen: true,
|
|
293
|
+
useMouse: true,
|
|
294
|
+
exitOnCtrlC: false,
|
|
295
|
+
})
|
|
296
|
+
)
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it('CLI() render accepts React elements', async () => {
|
|
300
|
+
const { CLI, Text, Box } = await import('..')
|
|
301
|
+
|
|
302
|
+
const cli = await CLI()
|
|
303
|
+
|
|
304
|
+
// render should not throw with various elements
|
|
305
|
+
expect(() => cli.render(React.createElement(Text, {}, 'Hello'))).not.toThrow()
|
|
306
|
+
expect(() =>
|
|
307
|
+
cli.render(
|
|
308
|
+
React.createElement(
|
|
309
|
+
Box,
|
|
310
|
+
{ border: 'single' },
|
|
311
|
+
React.createElement(Text, {}, 'Boxed')
|
|
312
|
+
)
|
|
313
|
+
)
|
|
314
|
+
).not.toThrow()
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
// ============================================================================
|
|
319
|
+
// Component Rendering Tests
|
|
320
|
+
// ============================================================================
|
|
321
|
+
|
|
322
|
+
describe('Component rendering', () => {
|
|
323
|
+
it('Box component renders with borders', async () => {
|
|
324
|
+
const { CLI, Box, Text } = await import('..')
|
|
325
|
+
|
|
326
|
+
const cli = await CLI()
|
|
327
|
+
|
|
328
|
+
const boxElement = React.createElement(
|
|
329
|
+
Box,
|
|
330
|
+
{ border: 'single', padding: 1 },
|
|
331
|
+
React.createElement(Text, {}, 'Content')
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
// Should not throw when rendering Box with borders
|
|
335
|
+
expect(() => cli.render(boxElement)).not.toThrow()
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
it('Text component renders with colors', async () => {
|
|
339
|
+
const { CLI, Text } = await import('..')
|
|
340
|
+
|
|
341
|
+
const cli = await CLI()
|
|
342
|
+
|
|
343
|
+
const textElement = React.createElement(
|
|
344
|
+
Text,
|
|
345
|
+
{ color: 'cyan', bold: true },
|
|
346
|
+
'Styled text'
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
// Should not throw when rendering styled Text
|
|
350
|
+
expect(() => cli.render(textElement)).not.toThrow()
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
it('nested components render correctly', async () => {
|
|
354
|
+
const { CLI, Box, Text } = await import('..')
|
|
355
|
+
|
|
356
|
+
const cli = await CLI()
|
|
357
|
+
|
|
358
|
+
const nestedElement = React.createElement(
|
|
359
|
+
Box,
|
|
360
|
+
{ border: 'double', padding: 2 },
|
|
361
|
+
React.createElement(
|
|
362
|
+
Box,
|
|
363
|
+
{ border: 'single', padding: 1 },
|
|
364
|
+
React.createElement(Text, { color: 'green' }, 'Nested content')
|
|
365
|
+
)
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
// Should not throw when rendering nested components
|
|
369
|
+
expect(() => cli.render(nestedElement)).not.toThrow()
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
it('complex component trees render', async () => {
|
|
373
|
+
const { CLI, Box, Text } = await import('..')
|
|
374
|
+
|
|
375
|
+
const cli = await CLI()
|
|
376
|
+
|
|
377
|
+
// Build a dashboard-like layout
|
|
378
|
+
const dashboard = React.createElement(
|
|
379
|
+
Box,
|
|
380
|
+
{ flexDirection: 'row' },
|
|
381
|
+
// Sidebar
|
|
382
|
+
React.createElement(
|
|
383
|
+
Box,
|
|
384
|
+
{ width: 20, border: 'single', flexDirection: 'column' },
|
|
385
|
+
React.createElement(Text, { bold: true }, 'Menu'),
|
|
386
|
+
React.createElement(Text, {}, 'Item 1'),
|
|
387
|
+
React.createElement(Text, {}, 'Item 2')
|
|
388
|
+
),
|
|
389
|
+
// Content
|
|
390
|
+
React.createElement(
|
|
391
|
+
Box,
|
|
392
|
+
{ flexGrow: 1, padding: 1 },
|
|
393
|
+
React.createElement(Text, { color: 'cyan', bold: true }, 'Dashboard'),
|
|
394
|
+
React.createElement(Text, {}, 'Welcome message here')
|
|
395
|
+
)
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
// Should not throw when rendering complex trees
|
|
399
|
+
expect(() => cli.render(dashboard)).not.toThrow()
|
|
400
|
+
})
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
// ============================================================================
|
|
404
|
+
// Renderer Lifecycle Tests
|
|
405
|
+
// ============================================================================
|
|
406
|
+
|
|
407
|
+
describe('Renderer lifecycle', () => {
|
|
408
|
+
it('full lifecycle: create -> render -> clear -> destroy', async () => {
|
|
409
|
+
const { CLI, Text } = await import('..')
|
|
410
|
+
|
|
411
|
+
const cli = await CLI()
|
|
412
|
+
|
|
413
|
+
// Render content
|
|
414
|
+
cli.render(React.createElement(Text, {}, 'Hello'))
|
|
415
|
+
|
|
416
|
+
// Clear screen
|
|
417
|
+
cli.clear()
|
|
418
|
+
|
|
419
|
+
// Render again
|
|
420
|
+
cli.render(React.createElement(Text, {}, 'World'))
|
|
421
|
+
|
|
422
|
+
// Destroy
|
|
423
|
+
cli.destroy()
|
|
424
|
+
|
|
425
|
+
// After destroy, methods should still be callable (may no-op)
|
|
426
|
+
// This tests cleanup doesn't cause errors
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
it('renderer can be started and stopped', async () => {
|
|
430
|
+
const { CLI } = await import('..')
|
|
431
|
+
|
|
432
|
+
const cli = await CLI()
|
|
433
|
+
|
|
434
|
+
// Access underlying renderer for lifecycle control
|
|
435
|
+
cli.renderer.start()
|
|
436
|
+
cli.renderer.stop()
|
|
437
|
+
cli.renderer.start()
|
|
438
|
+
cli.renderer.destroy()
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
it('multiple CLI instances can coexist', async () => {
|
|
442
|
+
const { CLI, Text } = await import('..')
|
|
443
|
+
|
|
444
|
+
const cli1 = await CLI()
|
|
445
|
+
const cli2 = await CLI()
|
|
446
|
+
|
|
447
|
+
cli1.render(React.createElement(Text, {}, 'CLI 1'))
|
|
448
|
+
cli2.render(React.createElement(Text, {}, 'CLI 2'))
|
|
449
|
+
|
|
450
|
+
cli1.destroy()
|
|
451
|
+
cli2.destroy()
|
|
452
|
+
})
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
// ============================================================================
|
|
456
|
+
// Error Handling Tests
|
|
457
|
+
// ============================================================================
|
|
458
|
+
|
|
459
|
+
describe('Error handling', () => {
|
|
460
|
+
it('handles null render gracefully', async () => {
|
|
461
|
+
const { CLI } = await import('..')
|
|
462
|
+
|
|
463
|
+
const cli = await CLI()
|
|
464
|
+
|
|
465
|
+
// Rendering null should not throw (used by clear())
|
|
466
|
+
expect(() => cli.render(null)).not.toThrow()
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
it('handles undefined render gracefully', async () => {
|
|
470
|
+
const { CLI } = await import('..')
|
|
471
|
+
|
|
472
|
+
const cli = await CLI()
|
|
473
|
+
|
|
474
|
+
// Rendering undefined should not throw
|
|
475
|
+
expect(() => cli.render(undefined as any)).not.toThrow()
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
it('destroy can be called multiple times', async () => {
|
|
479
|
+
const { CLI } = await import('..')
|
|
480
|
+
|
|
481
|
+
const cli = await CLI()
|
|
482
|
+
|
|
483
|
+
cli.destroy()
|
|
484
|
+
cli.destroy()
|
|
485
|
+
cli.destroy()
|
|
486
|
+
// Should not throw
|
|
487
|
+
})
|
|
488
|
+
})
|
|
489
|
+
})
|