@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.
Files changed (191) hide show
  1. package/README.md +571 -0
  2. package/dist/ansi-css-Sk5mWtdK.d.ts +119 -0
  3. package/dist/ansi-css-V6JIHGsM.d.ts +119 -0
  4. package/dist/ansi-css-_3eSEU9d.d.ts +119 -0
  5. package/dist/chunk-3EFDH7PK.js +5235 -0
  6. package/dist/chunk-3RG5ZIWI.js +10 -0
  7. package/dist/chunk-3X5IR6WE.js +884 -0
  8. package/dist/chunk-4FV5ZDCE.js +5236 -0
  9. package/dist/chunk-4OVMSF2J.js +243 -0
  10. package/dist/chunk-63FEETIS.js +4048 -0
  11. package/dist/chunk-B43KP7XJ.js +884 -0
  12. package/dist/chunk-BMTJXWUV.js +655 -0
  13. package/dist/chunk-C3SVH4N7.js +882 -0
  14. package/dist/chunk-EVWR7Y47.js +874 -0
  15. package/dist/chunk-F6A5VWUC.js +1285 -0
  16. package/dist/chunk-FD7KW7GE.js +882 -0
  17. package/dist/chunk-GBQ6UD6I.js +655 -0
  18. package/dist/chunk-GMDD3M6U.js +5227 -0
  19. package/dist/chunk-JBHRXOXM.js +1058 -0
  20. package/dist/chunk-JFOO3EYO.js +1182 -0
  21. package/dist/chunk-JQ5H3WXL.js +1291 -0
  22. package/dist/chunk-JQD5NASE.js +234 -0
  23. package/dist/chunk-KRHJP5R7.js +592 -0
  24. package/dist/chunk-KWF6WVJE.js +962 -0
  25. package/dist/chunk-LHYQVN3H.js +1038 -0
  26. package/dist/chunk-M3TLQLGC.js +1032 -0
  27. package/dist/chunk-MVW4Q5OP.js +240 -0
  28. package/dist/chunk-NXCZSWLU.js +1294 -0
  29. package/dist/chunk-O25TNRO6.js +607 -0
  30. package/dist/chunk-PNECDA2I.js +884 -0
  31. package/dist/chunk-QIHWRLJR.js +962 -0
  32. package/dist/chunk-QW5YMQ7K.js +882 -0
  33. package/dist/chunk-R5U7XKVJ.js +16 -0
  34. package/dist/chunk-RP2MVQLR.js +962 -0
  35. package/dist/chunk-TP6RXGXA.js +1087 -0
  36. package/dist/chunk-TQQSTITZ.js +655 -0
  37. package/dist/chunk-X24GWXQV.js +1281 -0
  38. package/dist/components/index.d.ts +802 -0
  39. package/dist/components/index.js +149 -0
  40. package/dist/data/index.d.ts +2554 -0
  41. package/dist/data/index.js +51 -0
  42. package/dist/forms/index.d.ts +1596 -0
  43. package/dist/forms/index.js +464 -0
  44. package/dist/index-CQRFZntR.d.ts +867 -0
  45. package/dist/index.d.ts +579 -0
  46. package/dist/index.js +786 -0
  47. package/dist/interactive-D0JkWosD.d.ts +217 -0
  48. package/dist/keyboard/index.d.ts +2 -0
  49. package/dist/keyboard/index.js +43 -0
  50. package/dist/renderers/index.d.ts +546 -0
  51. package/dist/renderers/index.js +2157 -0
  52. package/dist/storybook/index.d.ts +396 -0
  53. package/dist/storybook/index.js +641 -0
  54. package/dist/theme/index.d.ts +1339 -0
  55. package/dist/theme/index.js +123 -0
  56. package/dist/types-Bxu5PAgA.d.ts +710 -0
  57. package/dist/types-CIlop5Ji.d.ts +701 -0
  58. package/dist/types-Ca8p_p5X.d.ts +710 -0
  59. package/package.json +90 -0
  60. package/src/__tests__/components/data/card.test.ts +458 -0
  61. package/src/__tests__/components/data/list.test.ts +473 -0
  62. package/src/__tests__/components/data/metrics.test.ts +541 -0
  63. package/src/__tests__/components/data/table.test.ts +448 -0
  64. package/src/__tests__/components/input/field.test.ts +555 -0
  65. package/src/__tests__/components/input/form.test.ts +870 -0
  66. package/src/__tests__/components/input/search.test.ts +1238 -0
  67. package/src/__tests__/components/input/select.test.ts +658 -0
  68. package/src/__tests__/components/navigation/breadcrumb.test.ts +923 -0
  69. package/src/__tests__/components/navigation/command-palette.test.ts +1095 -0
  70. package/src/__tests__/components/navigation/sidebar.test.ts +1018 -0
  71. package/src/__tests__/components/navigation/tabs.test.ts +995 -0
  72. package/src/__tests__/components.test.tsx +1197 -0
  73. package/src/__tests__/core/compiler.test.ts +986 -0
  74. package/src/__tests__/core/parser.test.ts +785 -0
  75. package/src/__tests__/core/tier-switcher.test.ts +1103 -0
  76. package/src/__tests__/core/types.test.ts +1398 -0
  77. package/src/__tests__/data/collections.test.ts +1337 -0
  78. package/src/__tests__/data/db.test.ts +1265 -0
  79. package/src/__tests__/data/reactive.test.ts +1010 -0
  80. package/src/__tests__/data/sync.test.ts +1614 -0
  81. package/src/__tests__/errors.test.ts +660 -0
  82. package/src/__tests__/forms/integration.test.ts +444 -0
  83. package/src/__tests__/integration.test.ts +905 -0
  84. package/src/__tests__/keyboard.test.ts +1791 -0
  85. package/src/__tests__/renderer.test.ts +489 -0
  86. package/src/__tests__/renderers/ansi-css.test.ts +948 -0
  87. package/src/__tests__/renderers/ansi.test.ts +1366 -0
  88. package/src/__tests__/renderers/ascii.test.ts +1360 -0
  89. package/src/__tests__/renderers/interactive.test.ts +2353 -0
  90. package/src/__tests__/renderers/markdown.test.ts +1483 -0
  91. package/src/__tests__/renderers/text.test.ts +1369 -0
  92. package/src/__tests__/renderers/unicode.test.ts +1307 -0
  93. package/src/__tests__/theme.test.ts +639 -0
  94. package/src/__tests__/utils/assertions.ts +685 -0
  95. package/src/__tests__/utils/index.ts +115 -0
  96. package/src/__tests__/utils/test-renderer.ts +381 -0
  97. package/src/__tests__/utils/utils.test.ts +560 -0
  98. package/src/components/containers/card.ts +56 -0
  99. package/src/components/containers/dialog.ts +53 -0
  100. package/src/components/containers/index.ts +9 -0
  101. package/src/components/containers/panel.ts +59 -0
  102. package/src/components/feedback/badge.ts +40 -0
  103. package/src/components/feedback/index.ts +8 -0
  104. package/src/components/feedback/spinner.ts +23 -0
  105. package/src/components/helpers.ts +81 -0
  106. package/src/components/index.ts +153 -0
  107. package/src/components/layout/breadcrumb.ts +31 -0
  108. package/src/components/layout/index.ts +10 -0
  109. package/src/components/layout/list.ts +29 -0
  110. package/src/components/layout/sidebar.ts +79 -0
  111. package/src/components/layout/table.ts +62 -0
  112. package/src/components/primitives/box.ts +95 -0
  113. package/src/components/primitives/button.ts +54 -0
  114. package/src/components/primitives/index.ts +11 -0
  115. package/src/components/primitives/input.ts +88 -0
  116. package/src/components/primitives/select.ts +97 -0
  117. package/src/components/primitives/text.ts +60 -0
  118. package/src/components/render.ts +155 -0
  119. package/src/components/templates/app.ts +43 -0
  120. package/src/components/templates/index.ts +8 -0
  121. package/src/components/templates/site.ts +54 -0
  122. package/src/components/types.ts +777 -0
  123. package/src/core/compiler.ts +718 -0
  124. package/src/core/parser.ts +127 -0
  125. package/src/core/tier-switcher.ts +607 -0
  126. package/src/core/types.ts +672 -0
  127. package/src/data/collection.ts +316 -0
  128. package/src/data/collections.ts +50 -0
  129. package/src/data/context.tsx +174 -0
  130. package/src/data/db.ts +127 -0
  131. package/src/data/hooks.ts +532 -0
  132. package/src/data/index.ts +138 -0
  133. package/src/data/reactive.ts +1225 -0
  134. package/src/data/saas-collections.ts +375 -0
  135. package/src/data/sync.ts +1213 -0
  136. package/src/data/types.ts +660 -0
  137. package/src/forms/converters.ts +512 -0
  138. package/src/forms/index.ts +133 -0
  139. package/src/forms/schemas.ts +403 -0
  140. package/src/forms/types.ts +476 -0
  141. package/src/index.ts +542 -0
  142. package/src/keyboard/focus.ts +748 -0
  143. package/src/keyboard/index.ts +96 -0
  144. package/src/keyboard/integration.ts +371 -0
  145. package/src/keyboard/manager.ts +377 -0
  146. package/src/keyboard/presets.ts +90 -0
  147. package/src/renderers/ansi-css.ts +576 -0
  148. package/src/renderers/ansi.ts +802 -0
  149. package/src/renderers/ascii.ts +680 -0
  150. package/src/renderers/breadcrumb.ts +480 -0
  151. package/src/renderers/command-palette.ts +802 -0
  152. package/src/renderers/components/field.ts +210 -0
  153. package/src/renderers/components/form.ts +327 -0
  154. package/src/renderers/components/index.ts +21 -0
  155. package/src/renderers/components/search.ts +449 -0
  156. package/src/renderers/components/select.ts +222 -0
  157. package/src/renderers/index.ts +101 -0
  158. package/src/renderers/interactive/component-handlers.ts +622 -0
  159. package/src/renderers/interactive/cursor-manager.ts +147 -0
  160. package/src/renderers/interactive/focus-manager.ts +279 -0
  161. package/src/renderers/interactive/index.ts +661 -0
  162. package/src/renderers/interactive/input-handler.ts +164 -0
  163. package/src/renderers/interactive/keyboard-handler.ts +212 -0
  164. package/src/renderers/interactive/mouse-handler.ts +167 -0
  165. package/src/renderers/interactive/state-manager.ts +109 -0
  166. package/src/renderers/interactive/types.ts +338 -0
  167. package/src/renderers/interactive-string.ts +299 -0
  168. package/src/renderers/interactive.ts +59 -0
  169. package/src/renderers/markdown.ts +950 -0
  170. package/src/renderers/sidebar.ts +549 -0
  171. package/src/renderers/tabs.ts +682 -0
  172. package/src/renderers/text.ts +791 -0
  173. package/src/renderers/unicode.ts +917 -0
  174. package/src/renderers/utils.ts +942 -0
  175. package/src/router/adapters.ts +383 -0
  176. package/src/router/types.ts +140 -0
  177. package/src/router/utils.ts +452 -0
  178. package/src/schemas.ts +205 -0
  179. package/src/storybook/index.ts +91 -0
  180. package/src/storybook/interactive-decorator.tsx +659 -0
  181. package/src/storybook/keyboard-simulator.ts +501 -0
  182. package/src/theme/ansi-codes.ts +80 -0
  183. package/src/theme/box-drawing.ts +132 -0
  184. package/src/theme/color-convert.ts +254 -0
  185. package/src/theme/color-support.ts +321 -0
  186. package/src/theme/index.ts +134 -0
  187. package/src/theme/strip-ansi.ts +50 -0
  188. package/src/theme/tailwind-map.ts +469 -0
  189. package/src/theme/text-styles.ts +206 -0
  190. package/src/theme/theme-system.ts +568 -0
  191. 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
+ })