@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,473 @@
1
+ /**
2
+ * @mdxui/terminal List Component Tests (RED phase)
3
+ *
4
+ * TDD RED Phase: These tests define the contract for the List data component
5
+ * across all 6 render tiers (TEXT, MARKDOWN, ASCII, UNICODE, ANSI, INTERACTIVE).
6
+ *
7
+ * List Component Features:
8
+ * - Items with text content
9
+ * - Icons per item
10
+ * - Nested lists (hierarchical)
11
+ * - Item actions (click, hover)
12
+ * - Selection (single/multi)
13
+ * - Different list styles (bullet, numbered, checklist)
14
+ *
15
+ * NOTE: These tests are expected to FAIL until implementation is complete.
16
+ * Run: pnpm --filter @mdxui/terminal test -- --run src/__tests__/components/data/list.test.ts
17
+ */
18
+ import { describe, it, expect } from 'vitest'
19
+
20
+ import type { UINode, RenderContext, ThemeTokens, RenderTier } from '../../../core/types'
21
+
22
+ // ============================================================================
23
+ // Test Helpers
24
+ // ============================================================================
25
+
26
+ const ALL_TIERS: RenderTier[] = ['text', 'markdown', 'ascii', 'unicode', 'ansi', 'interactive']
27
+
28
+ function createTestTheme(): ThemeTokens {
29
+ return {
30
+ primary: '\x1b[34m',
31
+ secondary: '\x1b[36m',
32
+ muted: '\x1b[90m',
33
+ foreground: '\x1b[37m',
34
+ background: '\x1b[40m',
35
+ border: '\x1b[90m',
36
+ success: '\x1b[32m',
37
+ warning: '\x1b[33m',
38
+ error: '\x1b[31m',
39
+ info: '\x1b[34m',
40
+ }
41
+ }
42
+
43
+ function createTestContext(tier: RenderTier, overrides?: Partial<RenderContext>): RenderContext {
44
+ return {
45
+ tier,
46
+ width: 80,
47
+ height: 24,
48
+ depth: 0,
49
+ theme: createTestTheme(),
50
+ interactive: tier === 'interactive',
51
+ ...overrides,
52
+ }
53
+ }
54
+
55
+ function createListNode(props: Record<string, unknown>, children?: UINode[]): UINode {
56
+ return {
57
+ type: 'list',
58
+ props,
59
+ children,
60
+ }
61
+ }
62
+
63
+ function createListItem(content: string, props?: Record<string, unknown>, children?: UINode[]): UINode {
64
+ return {
65
+ type: 'list-item',
66
+ props: { content, ...props },
67
+ children,
68
+ }
69
+ }
70
+
71
+ // ============================================================================
72
+ // Renderer import helper - uses dynamic import for all tiers
73
+ // ============================================================================
74
+
75
+ async function renderForTier(node: UINode, tier: RenderTier, ctx?: RenderContext): Promise<string> {
76
+ const context = ctx ?? createTestContext(tier)
77
+ switch (tier) {
78
+ case 'text': {
79
+ const { renderText } = await import('../../../renderers/text')
80
+ return renderText(node)
81
+ }
82
+ case 'markdown': {
83
+ const { renderMarkdown } = await import('../../../renderers/markdown')
84
+ return renderMarkdown(node)
85
+ }
86
+ case 'ascii': {
87
+ const { renderASCII } = await import('../../../renderers/ascii')
88
+ return renderASCII(node, context)
89
+ }
90
+ case 'unicode': {
91
+ const { renderUnicode } = await import('../../../renderers/unicode')
92
+ return renderUnicode(node, context)
93
+ }
94
+ case 'ansi': {
95
+ const { renderANSI } = await import('../../../renderers/ansi')
96
+ return renderANSI(node)
97
+ }
98
+ case 'interactive': {
99
+ const { renderInteractive } = await import('../../../renderers/interactive')
100
+ return renderInteractive(node, context)
101
+ }
102
+ }
103
+ }
104
+
105
+ // ============================================================================
106
+ // Basic List Rendering Tests
107
+ // ============================================================================
108
+
109
+ describe('List Component', () => {
110
+ describe('basic rendering across all tiers', () => {
111
+ const items = ['First item', 'Second item', 'Third item']
112
+
113
+ ALL_TIERS.forEach((tier) => {
114
+ describe(`${tier.toUpperCase()} tier`, () => {
115
+ it('renders list items', async () => {
116
+ const node = createListNode({ items })
117
+ const result = await renderForTier(node, tier)
118
+
119
+ expect(result).toContain('First item')
120
+ expect(result).toContain('Second item')
121
+ expect(result).toContain('Third item')
122
+ })
123
+
124
+ it('renders list with children nodes', async () => {
125
+ const node = createListNode({}, [
126
+ createListItem('Child A'),
127
+ createListItem('Child B'),
128
+ createListItem('Child C'),
129
+ ])
130
+ const result = await renderForTier(node, tier)
131
+
132
+ expect(result).toContain('Child A')
133
+ expect(result).toContain('Child B')
134
+ expect(result).toContain('Child C')
135
+ })
136
+
137
+ it('handles empty list', async () => {
138
+ const node = createListNode({ items: [] })
139
+ const result = await renderForTier(node, tier)
140
+
141
+ expect(typeof result).toBe('string')
142
+ })
143
+
144
+ it('handles single item list', async () => {
145
+ const node = createListNode({ items: ['Only item'] })
146
+ const result = await renderForTier(node, tier)
147
+
148
+ expect(result).toContain('Only item')
149
+ })
150
+ })
151
+ })
152
+ })
153
+
154
+ // ============================================================================
155
+ // List Style Tests
156
+ // ============================================================================
157
+
158
+ describe('list styles', () => {
159
+ const items = ['Apple', 'Banana', 'Cherry']
160
+
161
+ ALL_TIERS.forEach((tier) => {
162
+ describe(`${tier.toUpperCase()} tier`, () => {
163
+ it('renders unordered list with bullet markers', async () => {
164
+ const node = createListNode({ items, style: 'unordered' })
165
+ const result = await renderForTier(node, tier)
166
+
167
+ expect(result).toContain('Apple')
168
+ expect(result).toContain('Banana')
169
+ expect(result).toContain('Cherry')
170
+ })
171
+
172
+ it('renders ordered list with numbers', async () => {
173
+ const node = createListNode({ items, style: 'ordered', numbered: true })
174
+ const result = await renderForTier(node, tier)
175
+
176
+ expect(result).toContain('Apple')
177
+ expect(result).toMatch(/1[.)]/)
178
+ expect(result).toMatch(/2[.)]/)
179
+ expect(result).toMatch(/3[.)]/)
180
+ })
181
+
182
+ it('renders checklist with checkboxes', async () => {
183
+ const checkItems = [
184
+ { text: 'Done task', checked: true },
185
+ { text: 'Pending task', checked: false },
186
+ ]
187
+ const node = createListNode({ items: checkItems, style: 'checklist', taskList: true })
188
+ const result = await renderForTier(node, tier)
189
+
190
+ expect(result).toContain('Done task')
191
+ expect(result).toContain('Pending task')
192
+ })
193
+ })
194
+ })
195
+ })
196
+
197
+ // ============================================================================
198
+ // Icons Tests
199
+ // ============================================================================
200
+
201
+ describe('items with icons', () => {
202
+ ALL_TIERS.forEach((tier) => {
203
+ describe(`${tier.toUpperCase()} tier`, () => {
204
+ it('renders items with icon property', async () => {
205
+ const itemsWithIcons = [
206
+ { text: 'Documents', icon: 'folder' },
207
+ { text: 'Images', icon: 'image' },
208
+ ]
209
+ const node = createListNode({ items: itemsWithIcons })
210
+ const result = await renderForTier(node, tier)
211
+
212
+ expect(result).toContain('Documents')
213
+ expect(result).toContain('Images')
214
+ })
215
+
216
+ it('renders items with status icon variants', async () => {
217
+ const node = createListNode({}, [
218
+ createListItem('Success item', { icon: 'success', variant: 'success' }),
219
+ createListItem('Error item', { icon: 'error', variant: 'error' }),
220
+ ])
221
+ const result = await renderForTier(node, tier)
222
+
223
+ expect(result).toContain('Success item')
224
+ expect(result).toContain('Error item')
225
+ })
226
+ })
227
+ })
228
+ })
229
+
230
+ // ============================================================================
231
+ // Nested Lists Tests
232
+ // ============================================================================
233
+
234
+ describe('nested lists', () => {
235
+ ALL_TIERS.forEach((tier) => {
236
+ describe(`${tier.toUpperCase()} tier`, () => {
237
+ it('renders two-level nested list', async () => {
238
+ const node = createListNode({}, [
239
+ createListItem('Parent 1', {}, [
240
+ createListNode({}, [
241
+ createListItem('Child 1.1'),
242
+ createListItem('Child 1.2'),
243
+ ]),
244
+ ]),
245
+ createListItem('Parent 2'),
246
+ ])
247
+ const result = await renderForTier(node, tier)
248
+
249
+ expect(result).toContain('Parent 1')
250
+ expect(result).toContain('Child 1.1')
251
+ expect(result).toContain('Child 1.2')
252
+ expect(result).toContain('Parent 2')
253
+ })
254
+
255
+ it('renders three-level nested list', async () => {
256
+ const node = createListNode({}, [
257
+ createListItem('Level 1', {}, [
258
+ createListNode({}, [
259
+ createListItem('Level 2', {}, [
260
+ createListNode({}, [
261
+ createListItem('Level 3'),
262
+ ]),
263
+ ]),
264
+ ]),
265
+ ]),
266
+ ])
267
+ const result = await renderForTier(node, tier)
268
+
269
+ expect(result).toContain('Level 1')
270
+ expect(result).toContain('Level 2')
271
+ expect(result).toContain('Level 3')
272
+ })
273
+ })
274
+ })
275
+ })
276
+
277
+ // ============================================================================
278
+ // Item Actions Tests
279
+ // ============================================================================
280
+
281
+ describe('item actions', () => {
282
+ ALL_TIERS.forEach((tier) => {
283
+ describe(`${tier.toUpperCase()} tier`, () => {
284
+ it('renders items with action handlers', async () => {
285
+ const node = createListNode({}, [
286
+ createListItem('Clickable item', { action: 'click', href: '/page1' }),
287
+ createListItem('Another action', { action: 'click', href: '/page2' }),
288
+ ])
289
+ const result = await renderForTier(node, tier)
290
+
291
+ expect(result).toContain('Clickable item')
292
+ expect(result).toContain('Another action')
293
+ })
294
+
295
+ it('renders disabled action items', async () => {
296
+ const node = createListNode({}, [
297
+ createListItem('Disabled item', { disabled: true }),
298
+ createListItem('Enabled item'),
299
+ ])
300
+ const result = await renderForTier(node, tier)
301
+
302
+ expect(result).toContain('Disabled item')
303
+ expect(result).toContain('Enabled item')
304
+ })
305
+ })
306
+ })
307
+ })
308
+
309
+ // ============================================================================
310
+ // Tier-Specific Rendering Tests
311
+ // ============================================================================
312
+
313
+ describe('tier-specific rendering', () => {
314
+ const items = ['Item 1', 'Item 2', 'Item 3']
315
+
316
+ describe('TEXT tier', () => {
317
+ it('renders with - bullet marker', async () => {
318
+ const node = createListNode({ items })
319
+ const result = await renderForTier(node, 'text')
320
+
321
+ expect(result).toContain('- Item 1')
322
+ expect(result).toContain('- Item 2')
323
+ })
324
+
325
+ it('renders numbered list with 1. 2. 3.', async () => {
326
+ const node = createListNode({ items, numbered: true })
327
+ const result = await renderForTier(node, 'text')
328
+
329
+ expect(result).toContain('1. Item 1')
330
+ expect(result).toContain('2. Item 2')
331
+ expect(result).toContain('3. Item 3')
332
+ })
333
+ })
334
+
335
+ describe('MARKDOWN tier', () => {
336
+ it('renders with markdown bullet syntax', async () => {
337
+ const node = createListNode({ items })
338
+ const result = await renderForTier(node, 'markdown')
339
+
340
+ expect(result).toMatch(/[-*]\s+Item 1/)
341
+ })
342
+
343
+ it('renders task list items with checkbox syntax', async () => {
344
+ const checkItems = [
345
+ { text: 'Done', checked: true },
346
+ { text: 'Todo', checked: false },
347
+ ]
348
+ const node = createListNode({ items: checkItems, taskList: true })
349
+ const result = await renderForTier(node, 'markdown')
350
+
351
+ expect(result).toContain('[x]')
352
+ expect(result).toContain('[ ]')
353
+ })
354
+ })
355
+
356
+ describe('ASCII tier', () => {
357
+ it('renders with * or - bullets', async () => {
358
+ const node = createListNode({ items })
359
+ const result = await renderForTier(node, 'ascii')
360
+
361
+ expect(result).toMatch(/[-*]\s+Item 1/)
362
+ })
363
+
364
+ it('renders checkbox with [x] and [ ]', async () => {
365
+ const checkItems = [
366
+ { text: 'Checked', checked: true },
367
+ { text: 'Unchecked', checked: false },
368
+ ]
369
+ const node = createListNode({ items: checkItems, taskList: true })
370
+ const result = await renderForTier(node, 'ascii')
371
+
372
+ expect(result).toContain('[x]')
373
+ expect(result).toContain('[ ]')
374
+ })
375
+ })
376
+
377
+ describe('UNICODE tier', () => {
378
+ it('renders with unicode bullet character', async () => {
379
+ const node = createListNode({ items, style: 'unordered' })
380
+ const result = await renderForTier(node, 'unicode')
381
+
382
+ expect(result).toContain('Item 1')
383
+ expect(result).toMatch(/[•◦▪▸]/)
384
+ })
385
+
386
+ it('renders checkmark for completed items', async () => {
387
+ const checkItems = [
388
+ { text: 'Done', checked: true },
389
+ { text: 'Not done', checked: false },
390
+ ]
391
+ const node = createListNode({ items: checkItems, taskList: true })
392
+ const result = await renderForTier(node, 'unicode')
393
+
394
+ expect(result).toMatch(/[✓✔☑]/)
395
+ })
396
+ })
397
+
398
+ describe('ANSI tier', () => {
399
+ it('renders with ANSI color codes', async () => {
400
+ const node = createListNode({ items })
401
+ const result = await renderForTier(node, 'ansi')
402
+
403
+ expect(result).toMatch(/\x1b\[/)
404
+ })
405
+
406
+ it('applies color to icon', async () => {
407
+ const node = createListNode({}, [
408
+ createListItem('Success', { icon: 'check', variant: 'success' }),
409
+ ])
410
+ const result = await renderForTier(node, 'ansi')
411
+
412
+ expect(result).toMatch(/\x1b\[\d+m/)
413
+ })
414
+ })
415
+
416
+ describe('INTERACTIVE tier', () => {
417
+ it('shows focus cursor on current item', async () => {
418
+ const node = createListNode({
419
+ items: ['First', 'Second', 'Third'],
420
+ focusedIndex: 1,
421
+ })
422
+ const result = await renderForTier(node, 'interactive')
423
+
424
+ expect(result).toContain('Second')
425
+ })
426
+ })
427
+ })
428
+
429
+ // ============================================================================
430
+ // Edge Cases
431
+ // ============================================================================
432
+
433
+ describe('edge cases', () => {
434
+ ALL_TIERS.forEach((tier) => {
435
+ describe(`${tier.toUpperCase()} tier`, () => {
436
+ it('handles empty string items', async () => {
437
+ const node = createListNode({ items: ['', 'Non-empty', ''] })
438
+ const result = await renderForTier(node, tier)
439
+
440
+ expect(result).toContain('Non-empty')
441
+ })
442
+
443
+ it('handles very long item text', async () => {
444
+ const longText = 'A'.repeat(200)
445
+ const node = createListNode({ items: [longText] })
446
+ const result = await renderForTier(node, tier)
447
+
448
+ expect(result).toContain('A')
449
+ })
450
+
451
+ it('handles unicode in items', async () => {
452
+ const node = createListNode({ items: ['Hello world'] })
453
+ const result = await renderForTier(node, tier)
454
+
455
+ expect(result).toContain('Hello')
456
+ })
457
+
458
+ it('handles deeply nested lists', async () => {
459
+ let innermost = createListNode({}, [createListItem('Deepest')])
460
+ for (let i = 0; i < 5; i++) {
461
+ innermost = createListNode({}, [
462
+ createListItem(`Level ${5 - i}`, {}, [innermost]),
463
+ ])
464
+ }
465
+ const result = await renderForTier(innermost, tier)
466
+
467
+ expect(result).toContain('Deepest')
468
+ expect(result).toContain('Level 1')
469
+ })
470
+ })
471
+ })
472
+ })
473
+ })