@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,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
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @mdxui/terminal/components/containers
3
+ *
4
+ * Container terminal components.
5
+ */
6
+
7
+ export { renderPanel, Panel } from './panel'
8
+ export { renderCard, Card } from './card'
9
+ export { renderDialog, Dialog } from './dialog'