@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,986 @@
1
+ /**
2
+ * @mdxui/terminal JSX → UINode Compiler Tests (RED phase)
3
+ *
4
+ * TDD RED Phase: These tests define the contract for the JSX compiler that
5
+ * converts React element trees into UINode trees for terminal rendering.
6
+ *
7
+ * The compiler must handle:
8
+ * - Simple component elements
9
+ * - Props extraction and transformation
10
+ * - Children flattening and normalization
11
+ * - Nested component trees
12
+ * - React Fragment handling
13
+ * - Key preservation for reconciliation
14
+ * - Null/undefined children filtering
15
+ *
16
+ * NOTE: These tests are expected to FAIL until implementation is complete.
17
+ * Run: pnpm --filter @mdxui/terminal test -- --run src/__tests__/core/compiler.test.ts
18
+ */
19
+ import { describe, it, expect } from 'vitest'
20
+ import React from 'react'
21
+ import type { UINode } from '../../core/types'
22
+
23
+ // ============================================================================
24
+ // Import the compiler function that will be implemented
25
+ // ============================================================================
26
+ // NOTE: This import will fail until src/core/compiler.ts is created
27
+ import { compileJSX } from '../../core/compiler'
28
+
29
+ // ============================================================================
30
+ // Test Utilities
31
+ // ============================================================================
32
+
33
+ /**
34
+ * Helper to create JSX elements for testing
35
+ */
36
+ const el = React.createElement
37
+
38
+ /**
39
+ * Helper to verify UINode structure
40
+ */
41
+ function assertUINode(node: unknown): asserts node is UINode {
42
+ if (
43
+ typeof node !== 'object' ||
44
+ node === null ||
45
+ !('type' in node) ||
46
+ !('props' in node)
47
+ ) {
48
+ throw new Error('Invalid UINode structure')
49
+ }
50
+ }
51
+
52
+ // ============================================================================
53
+ // Simple Element Compilation Tests
54
+ // ============================================================================
55
+
56
+ describe('JSX Compiler - Simple Elements', () => {
57
+ it('compiles simple self-closing component', () => {
58
+ const jsx = el('Box')
59
+ const result = compileJSX(jsx)
60
+
61
+ expect(result).toEqual({
62
+ type: 'Box',
63
+ props: {},
64
+ })
65
+ assertUINode(result)
66
+ })
67
+
68
+ it('compiles component with uppercase name as component type', () => {
69
+ const jsx = el('Text')
70
+ const result = compileJSX(jsx)
71
+
72
+ expect(result.type).toBe('Text')
73
+ expect(result.props).toEqual({})
74
+ })
75
+
76
+ it('compiles intrinsic lowercase element name as type', () => {
77
+ const jsx = el('div')
78
+ const result = compileJSX(jsx)
79
+
80
+ expect(result.type).toBe('div')
81
+ expect(result.props).toEqual({})
82
+ })
83
+
84
+ it('returns object with type and props properties', () => {
85
+ const jsx = el('Button')
86
+ const result = compileJSX(jsx)
87
+
88
+ expect(result).toHaveProperty('type')
89
+ expect(result).toHaveProperty('props')
90
+ expect(typeof result.type).toBe('string')
91
+ expect(typeof result.props).toBe('object')
92
+ })
93
+ })
94
+
95
+ // ============================================================================
96
+ // Props Extraction Tests
97
+ // ============================================================================
98
+
99
+ describe('JSX Compiler - Props Extraction', () => {
100
+ it('extracts boolean props', () => {
101
+ const jsx = el('Text', { bold: true })
102
+ const result = compileJSX(jsx)
103
+
104
+ expect(result.props.bold).toBe(true)
105
+ })
106
+
107
+ it('extracts multiple boolean props', () => {
108
+ const jsx = el('Text', { bold: true, italic: true, underline: false })
109
+ const result = compileJSX(jsx)
110
+
111
+ expect(result.props.bold).toBe(true)
112
+ expect(result.props.italic).toBe(true)
113
+ expect(result.props.underline).toBe(false)
114
+ })
115
+
116
+ it('extracts string props', () => {
117
+ const jsx = el('Box', { title: 'My Box' })
118
+ const result = compileJSX(jsx)
119
+
120
+ expect(result.props.title).toBe('My Box')
121
+ })
122
+
123
+ it('extracts numeric props', () => {
124
+ const jsx = el('Box', { padding: 2, margin: 4 })
125
+ const result = compileJSX(jsx)
126
+
127
+ expect(result.props.padding).toBe(2)
128
+ expect(result.props.margin).toBe(4)
129
+ })
130
+
131
+ it('excludes React children prop from extracted props', () => {
132
+ // children should be handled specially, not in props
133
+ const jsx = el('Box', { children: 'test' })
134
+ const result = compileJSX(jsx)
135
+
136
+ // children should either be in props or in children field, not both
137
+ // This test documents the expected behavior
138
+ expect(result).toHaveProperty('type')
139
+ })
140
+
141
+ it('extracts array prop values', () => {
142
+ const jsx = el('List', { items: ['a', 'b', 'c'] })
143
+ const result = compileJSX(jsx)
144
+
145
+ expect(Array.isArray(result.props.items)).toBe(true)
146
+ expect(result.props.items).toEqual(['a', 'b', 'c'])
147
+ })
148
+
149
+ it('extracts object prop values', () => {
150
+ const jsx = el('Component', { config: { debug: true, level: 3 } })
151
+ const result = compileJSX(jsx)
152
+
153
+ expect(typeof result.props.config).toBe('object')
154
+ expect(result.props.config.debug).toBe(true)
155
+ expect(result.props.config.level).toBe(3)
156
+ })
157
+
158
+ it('preserves null prop values', () => {
159
+ const jsx = el('Component', { value: null })
160
+ const result = compileJSX(jsx)
161
+
162
+ expect(result.props.value).toBe(null)
163
+ })
164
+
165
+ it('handles component with empty props object', () => {
166
+ const jsx = el('Text', {})
167
+ const result = compileJSX(jsx)
168
+
169
+ expect(result.props).toEqual({})
170
+ })
171
+
172
+ it('handles component with no props argument', () => {
173
+ const jsx = el('Button')
174
+ const result = compileJSX(jsx)
175
+
176
+ expect(result.props).toEqual({})
177
+ })
178
+ })
179
+
180
+ // ============================================================================
181
+ // String Children Tests
182
+ // ============================================================================
183
+
184
+ describe('JSX Compiler - String Children', () => {
185
+ it('converts text children to children array with single string node', () => {
186
+ const jsx = el('Text', null, 'Hello')
187
+ const result = compileJSX(jsx)
188
+
189
+ expect(result.children).toBeDefined()
190
+ expect(Array.isArray(result.children)).toBe(true)
191
+ expect(result.children).toHaveLength(1)
192
+ expect(result.children![0]).toBe('Hello')
193
+ })
194
+
195
+ it('preserves multiple text nodes as separate children', () => {
196
+ const jsx = el('Box', null, 'Hello', ' ', 'World')
197
+ const result = compileJSX(jsx)
198
+
199
+ expect(result.children).toHaveLength(3)
200
+ expect(result.children![0]).toBe('Hello')
201
+ expect(result.children![1]).toBe(' ')
202
+ expect(result.children![2]).toBe('World')
203
+ })
204
+
205
+ it('converts string children with JSX props', () => {
206
+ const jsx = el('Text', { bold: true }, 'Bold text')
207
+ const result = compileJSX(jsx)
208
+
209
+ expect(result.props.bold).toBe(true)
210
+ expect(result.children).toHaveLength(1)
211
+ expect(result.children![0]).toBe('Bold text')
212
+ })
213
+ })
214
+
215
+ // ============================================================================
216
+ // Children Flattening Tests
217
+ // ============================================================================
218
+
219
+ describe('JSX Compiler - Children Flattening', () => {
220
+ it('flattens single nested element child', () => {
221
+ const jsx = el('Box', null, el('Text', null, 'Child'))
222
+ const result = compileJSX(jsx)
223
+
224
+ expect(result.children).toHaveLength(1)
225
+ expect(result.children![0]).toHaveProperty('type', 'Text')
226
+ expect(result.children![0]).toHaveProperty('props')
227
+ })
228
+
229
+ it('flattens multiple element children', () => {
230
+ const jsx = el(
231
+ 'Box',
232
+ null,
233
+ el('Text', null, 'First'),
234
+ el('Text', null, 'Second'),
235
+ el('Text', null, 'Third')
236
+ )
237
+ const result = compileJSX(jsx)
238
+
239
+ expect(result.children).toHaveLength(3)
240
+ result.children!.forEach((child) => {
241
+ assertUINode(child)
242
+ expect(child.type).toBe('Text')
243
+ })
244
+ })
245
+
246
+ it('handles mixed text and element children', () => {
247
+ const jsx = el(
248
+ 'Box',
249
+ null,
250
+ 'Before',
251
+ el('Text', null, 'Element'),
252
+ 'After'
253
+ )
254
+ const result = compileJSX(jsx)
255
+
256
+ expect(result.children).toHaveLength(3)
257
+ expect(result.children![0]).toBe('Before')
258
+ assertUINode(result.children![1])
259
+ expect(result.children![2]).toBe('After')
260
+ })
261
+
262
+ it('recursively compiles nested element children', () => {
263
+ const jsx = el(
264
+ 'Box',
265
+ null,
266
+ el('Panel', null, el('Text', null, 'Deeply nested'))
267
+ )
268
+ const result = compileJSX(jsx)
269
+
270
+ expect(result.children).toHaveLength(1)
271
+ const panelChild = result.children![0]
272
+ assertUINode(panelChild)
273
+ expect(panelChild.type).toBe('Panel')
274
+ expect(panelChild.children).toHaveLength(1)
275
+
276
+ const textChild = panelChild.children![0]
277
+ assertUINode(textChild)
278
+ expect(textChild.type).toBe('Text')
279
+ })
280
+ })
281
+
282
+ // ============================================================================
283
+ // Nested Component Trees Tests
284
+ // ============================================================================
285
+
286
+ describe('JSX Compiler - Nested Component Trees', () => {
287
+ it('compiles deeply nested component tree', () => {
288
+ const jsx = el(
289
+ 'Root',
290
+ null,
291
+ el(
292
+ 'Layout',
293
+ { type: 'grid' },
294
+ el(
295
+ 'Section',
296
+ { id: 'header' },
297
+ el('Title', null, 'Header')
298
+ ),
299
+ el(
300
+ 'Section',
301
+ { id: 'body' },
302
+ el('Content', null, 'Body content')
303
+ )
304
+ )
305
+ )
306
+ const result = compileJSX(jsx)
307
+
308
+ expect(result.type).toBe('Root')
309
+ expect(result.children).toHaveLength(1)
310
+
311
+ const layoutNode = result.children![0]
312
+ assertUINode(layoutNode)
313
+ expect(layoutNode.type).toBe('Layout')
314
+ expect(layoutNode.props.type).toBe('grid')
315
+ expect(layoutNode.children).toHaveLength(2)
316
+
317
+ const headerSection = layoutNode.children![0]
318
+ assertUINode(headerSection)
319
+ expect(headerSection.type).toBe('Section')
320
+ expect(headerSection.props.id).toBe('header')
321
+ })
322
+
323
+ it('preserves props at all nesting levels', () => {
324
+ const jsx = el(
325
+ 'Root',
326
+ { rootProp: 'root' },
327
+ el(
328
+ 'Child',
329
+ { childProp: 'child' },
330
+ el('Grandchild', { grandchildProp: 'grandchild' })
331
+ )
332
+ )
333
+ const result = compileJSX(jsx)
334
+
335
+ expect(result.props.rootProp).toBe('root')
336
+ const childNode = result.children![0]
337
+ assertUINode(childNode)
338
+ expect(childNode.props.childProp).toBe('child')
339
+
340
+ const grandchildNode = childNode.children![0]
341
+ assertUINode(grandchildNode)
342
+ expect(grandchildNode.props.grandchildProp).toBe('grandchild')
343
+ })
344
+
345
+ it('handles props and children together in nested structures', () => {
346
+ const jsx = el(
347
+ 'Box',
348
+ { padding: 2 },
349
+ el('Header', { bold: true }, 'Title'),
350
+ el('Body', null, 'Content'),
351
+ 'Footer text'
352
+ )
353
+ const result = compileJSX(jsx)
354
+
355
+ expect(result.props.padding).toBe(2)
356
+ expect(result.children).toHaveLength(3)
357
+
358
+ const headerNode = result.children![0]
359
+ assertUINode(headerNode)
360
+ expect(headerNode.props.bold).toBe(true)
361
+ expect(headerNode.children![0]).toBe('Title')
362
+ })
363
+ })
364
+
365
+ // ============================================================================
366
+ // React Fragment Handling Tests
367
+ // ============================================================================
368
+
369
+ describe('JSX Compiler - Fragment Handling', () => {
370
+ it('unwraps Fragment children into parent', () => {
371
+ const jsx = el(
372
+ 'Box',
373
+ null,
374
+ el(React.Fragment, null, el('Text', null, 'A'), el('Text', null, 'B'))
375
+ )
376
+ const result = compileJSX(jsx)
377
+
378
+ // Fragment should be unwrapped, so children are directly in Box
379
+ expect(result.children).toHaveLength(2)
380
+ expect(result.children![0].type).toBe('Text')
381
+ expect(result.children![1].type).toBe('Text')
382
+ })
383
+
384
+ it('handles multiple Fragments as siblings', () => {
385
+ const jsx = el(
386
+ 'Box',
387
+ null,
388
+ el(React.Fragment, null, el('Text', null, 'A'), el('Text', null, 'B')),
389
+ el(React.Fragment, null, el('Text', null, 'C'), el('Text', null, 'D'))
390
+ )
391
+ const result = compileJSX(jsx)
392
+
393
+ expect(result.children).toHaveLength(4)
394
+ expect(result.children!.map((c: any) => c.type)).toEqual([
395
+ 'Text',
396
+ 'Text',
397
+ 'Text',
398
+ 'Text',
399
+ ])
400
+ })
401
+
402
+ it('unwraps nested Fragments', () => {
403
+ const jsx = el(
404
+ 'Box',
405
+ null,
406
+ el(
407
+ React.Fragment,
408
+ null,
409
+ el(React.Fragment, null, el('Text', null, 'Nested'))
410
+ )
411
+ )
412
+ const result = compileJSX(jsx)
413
+
414
+ expect(result.children).toHaveLength(1)
415
+ expect(result.children![0].type).toBe('Text')
416
+ })
417
+
418
+ it('handles Fragments with text content', () => {
419
+ const jsx = el(
420
+ 'Box',
421
+ null,
422
+ el(React.Fragment, null, 'Text', el('Element', null, 'Content'))
423
+ )
424
+ const result = compileJSX(jsx)
425
+
426
+ expect(result.children).toHaveLength(2)
427
+ expect(result.children![0]).toBe('Text')
428
+ expect(result.children![1].type).toBe('Element')
429
+ })
430
+
431
+ it('preserves Fragments in nested scenarios', () => {
432
+ const jsx = el(
433
+ 'Root',
434
+ null,
435
+ el(
436
+ 'Container',
437
+ null,
438
+ el(
439
+ React.Fragment,
440
+ null,
441
+ el('Item', null, '1'),
442
+ el('Item', null, '2')
443
+ )
444
+ )
445
+ )
446
+ const result = compileJSX(jsx)
447
+
448
+ const containerNode = result.children![0]
449
+ assertUINode(containerNode)
450
+ // Fragment children are flattened into Container
451
+ expect(containerNode.children).toHaveLength(2)
452
+ expect(containerNode.children!.map((c: any) => c.type)).toEqual([
453
+ 'Item',
454
+ 'Item',
455
+ ])
456
+ })
457
+ })
458
+
459
+ // ============================================================================
460
+ // Key Preservation Tests
461
+ // ============================================================================
462
+
463
+ describe('JSX Compiler - Key Preservation', () => {
464
+ it('preserves key from element props', () => {
465
+ const jsx = el('Item', { key: 'item-1', id: 'id-1' })
466
+ const result = compileJSX(jsx)
467
+
468
+ expect(result.key).toBe('item-1')
469
+ })
470
+
471
+ it('preserves keys in children', () => {
472
+ const jsx = el(
473
+ 'List',
474
+ null,
475
+ el('Item', { key: 'item-1' }, 'One'),
476
+ el('Item', { key: 'item-2' }, 'Two'),
477
+ el('Item', { key: 'item-3' }, 'Three')
478
+ )
479
+ const result = compileJSX(jsx)
480
+
481
+ expect(result.children).toHaveLength(3)
482
+ expect(result.children![0].key).toBe('item-1')
483
+ expect(result.children![1].key).toBe('item-2')
484
+ expect(result.children![2].key).toBe('item-3')
485
+ })
486
+
487
+ it('handles missing keys gracefully', () => {
488
+ const jsx = el(
489
+ 'List',
490
+ null,
491
+ el('Item', null, 'No key'),
492
+ el('Item', { key: 'has-key' }, 'Has key')
493
+ )
494
+ const result = compileJSX(jsx)
495
+
496
+ expect(result.children).toHaveLength(2)
497
+ expect(result.children![0].key).toBeUndefined()
498
+ expect(result.children![1].key).toBe('has-key')
499
+ })
500
+
501
+ it('preserves string keys', () => {
502
+ const jsx = el('Item', { key: 'string-key-123' })
503
+ const result = compileJSX(jsx)
504
+
505
+ expect(result.key).toBe('string-key-123')
506
+ expect(typeof result.key).toBe('string')
507
+ })
508
+
509
+ it('preserves numeric keys converted to strings', () => {
510
+ // React keys are typically strings, but numeric keys get converted
511
+ const jsx = el('Item', { key: 1 })
512
+ const result = compileJSX(jsx)
513
+
514
+ expect(result.key).toBe('1')
515
+ expect(typeof result.key).toBe('string')
516
+ })
517
+
518
+ it('key not included in regular props', () => {
519
+ const jsx = el('Item', { key: 'item-1', id: 'id-1' })
520
+ const result = compileJSX(jsx)
521
+
522
+ expect(result.key).toBe('item-1')
523
+ // key should NOT be in props
524
+ expect(result.props.key).toBeUndefined()
525
+ })
526
+ })
527
+
528
+ // ============================================================================
529
+ // Null/Undefined Children Filtering Tests
530
+ // ============================================================================
531
+
532
+ describe('JSX Compiler - Null/Undefined Filtering', () => {
533
+ it('filters out null children', () => {
534
+ const jsx = el(
535
+ 'Box',
536
+ null,
537
+ 'Before',
538
+ null,
539
+ 'After'
540
+ )
541
+ const result = compileJSX(jsx)
542
+
543
+ expect(result.children).toHaveLength(2)
544
+ expect(result.children![0]).toBe('Before')
545
+ expect(result.children![1]).toBe('After')
546
+ })
547
+
548
+ it('filters out undefined children', () => {
549
+ const jsx = el(
550
+ 'Box',
551
+ null,
552
+ 'Text',
553
+ undefined,
554
+ el('Element', null, 'Content')
555
+ )
556
+ const result = compileJSX(jsx)
557
+
558
+ expect(result.children).toHaveLength(2)
559
+ expect(result.children![0]).toBe('Text')
560
+ assertUINode(result.children![1])
561
+ })
562
+
563
+ it('filters out boolean children (React false)', () => {
564
+ const jsx = el(
565
+ 'Box',
566
+ null,
567
+ 'Start',
568
+ false,
569
+ true,
570
+ 'End'
571
+ )
572
+ const result = compileJSX(jsx)
573
+
574
+ expect(result.children).toHaveLength(2)
575
+ expect(result.children![0]).toBe('Start')
576
+ expect(result.children![1]).toBe('End')
577
+ })
578
+
579
+ it('handles all null/undefined/false children', () => {
580
+ const jsx = el(
581
+ 'Box',
582
+ null,
583
+ null,
584
+ undefined,
585
+ false,
586
+ true
587
+ )
588
+ const result = compileJSX(jsx)
589
+
590
+ expect(result.children).toEqual([])
591
+ })
592
+
593
+ it('filters falsy children in nested elements', () => {
594
+ const jsx = el(
595
+ 'Box',
596
+ null,
597
+ el(
598
+ 'Container',
599
+ null,
600
+ 'Text',
601
+ null,
602
+ false,
603
+ undefined
604
+ )
605
+ )
606
+ const result = compileJSX(jsx)
607
+
608
+ const containerNode = result.children![0]
609
+ assertUINode(containerNode)
610
+ expect(containerNode.children).toHaveLength(1)
611
+ expect(containerNode.children![0]).toBe('Text')
612
+ })
613
+
614
+ it('filters null children from Fragments', () => {
615
+ const jsx = el(
616
+ 'Box',
617
+ null,
618
+ el(
619
+ React.Fragment,
620
+ null,
621
+ 'A',
622
+ null,
623
+ 'B'
624
+ )
625
+ )
626
+ const result = compileJSX(jsx)
627
+
628
+ expect(result.children).toHaveLength(2)
629
+ expect(result.children![0]).toBe('A')
630
+ expect(result.children![1]).toBe('B')
631
+ })
632
+ })
633
+
634
+ // ============================================================================
635
+ // Props and Children Interaction Tests
636
+ // ============================================================================
637
+
638
+ describe('JSX Compiler - Props and Children Together', () => {
639
+ it('includes both props and children in result', () => {
640
+ const jsx = el('Text', { bold: true, size: 12 }, 'Hello')
641
+ const result = compileJSX(jsx)
642
+
643
+ expect(result.props.bold).toBe(true)
644
+ expect(result.props.size).toBe(12)
645
+ expect(result.children).toHaveLength(1)
646
+ expect(result.children![0]).toBe('Hello')
647
+ })
648
+
649
+ it('handles component with no props but with children', () => {
650
+ const jsx = el('Box', null, 'Child content')
651
+ const result = compileJSX(jsx)
652
+
653
+ expect(result.props).toEqual({})
654
+ expect(result.children).toHaveLength(1)
655
+ })
656
+
657
+ it('handles component with props but no children', () => {
658
+ const jsx = el('Button', { label: 'Click me' })
659
+ const result = compileJSX(jsx)
660
+
661
+ expect(result.props.label).toBe('Click me')
662
+ expect(result.children).toBeUndefined()
663
+ })
664
+
665
+ it('handles component with neither props nor children', () => {
666
+ const jsx = el('Empty')
667
+ const result = compileJSX(jsx)
668
+
669
+ expect(result.props).toEqual({})
670
+ expect(result.children).toBeUndefined()
671
+ })
672
+
673
+ it('complex real-world structure', () => {
674
+ const jsx = el(
675
+ 'Panel',
676
+ { title: 'Settings', collapsible: true },
677
+ el(
678
+ 'Section',
679
+ { id: 'general' },
680
+ el('Label', null, 'Username'),
681
+ el('Input', { type: 'text', placeholder: 'Enter name' })
682
+ ),
683
+ el(
684
+ 'Section',
685
+ { id: 'advanced' },
686
+ el('Checkbox', { label: 'Enable debug mode' }),
687
+ el('Checkbox', { label: 'Enable logging' })
688
+ )
689
+ )
690
+ const result = compileJSX(jsx)
691
+
692
+ expect(result.type).toBe('Panel')
693
+ expect(result.props.title).toBe('Settings')
694
+ expect(result.props.collapsible).toBe(true)
695
+ expect(result.children).toHaveLength(2)
696
+
697
+ const firstSection = result.children![0]
698
+ assertUINode(firstSection)
699
+ expect(firstSection.type).toBe('Section')
700
+ expect(firstSection.props.id).toBe('general')
701
+ expect(firstSection.children).toHaveLength(2)
702
+ })
703
+ })
704
+
705
+ // ============================================================================
706
+ // Edge Cases Tests
707
+ // ============================================================================
708
+
709
+ describe('JSX Compiler - Edge Cases', () => {
710
+ it('handles empty string children', () => {
711
+ const jsx = el('Text', null, '')
712
+ const result = compileJSX(jsx)
713
+
714
+ expect(result.children).toHaveLength(1)
715
+ expect(result.children![0]).toBe('')
716
+ })
717
+
718
+ it('handles whitespace-only children', () => {
719
+ const jsx = el('Text', null, ' ')
720
+ const result = compileJSX(jsx)
721
+
722
+ expect(result.children).toHaveLength(1)
723
+ expect(result.children![0]).toBe(' ')
724
+ })
725
+
726
+ it('handles numeric children as strings', () => {
727
+ const jsx = el('Counter', null, 42)
728
+ const result = compileJSX(jsx)
729
+
730
+ expect(result.children).toHaveLength(1)
731
+ expect(result.children![0]).toBe('42')
732
+ })
733
+
734
+ it('handles zero as child', () => {
735
+ const jsx = el('Box', null, 0)
736
+ const result = compileJSX(jsx)
737
+
738
+ expect(result.children).toHaveLength(1)
739
+ expect(result.children![0]).toBe('0')
740
+ })
741
+
742
+ it('handles empty array children prop', () => {
743
+ const jsx = el('List', { items: [] })
744
+ const result = compileJSX(jsx)
745
+
746
+ expect(result.props.items).toEqual([])
747
+ })
748
+
749
+ it('converts number zero prop correctly', () => {
750
+ const jsx = el('Component', { count: 0 })
751
+ const result = compileJSX(jsx)
752
+
753
+ expect(result.props.count).toBe(0)
754
+ })
755
+
756
+ it('converts false prop correctly', () => {
757
+ const jsx = el('Component', { disabled: false })
758
+ const result = compileJSX(jsx)
759
+
760
+ expect(result.props.disabled).toBe(false)
761
+ })
762
+
763
+ it('handles very long prop names', () => {
764
+ const longPropName = 'a'.repeat(1000)
765
+ const jsx = el('Component', { [longPropName]: 'value' })
766
+ const result = compileJSX(jsx)
767
+
768
+ expect(result.props[longPropName]).toBe('value')
769
+ })
770
+
771
+ it('handles props with special characters in names', () => {
772
+ const jsx = el('Component', { 'data-test-id': 'test', 'aria-label': 'label' })
773
+ const result = compileJSX(jsx)
774
+
775
+ expect(result.props['data-test-id']).toBe('test')
776
+ expect(result.props['aria-label']).toBe('label')
777
+ })
778
+ })
779
+
780
+ // ============================================================================
781
+ // Type and Structure Validation Tests
782
+ // ============================================================================
783
+
784
+ describe('JSX Compiler - Output Structure Validation', () => {
785
+ it('always returns UINode-compatible object', () => {
786
+ const jsx = el('Component')
787
+ const result = compileJSX(jsx)
788
+
789
+ assertUINode(result)
790
+ })
791
+
792
+ it('result type is always a string', () => {
793
+ const jsx = el('MyComponent')
794
+ const result = compileJSX(jsx)
795
+
796
+ expect(typeof result.type).toBe('string')
797
+ })
798
+
799
+ it('result props is always an object', () => {
800
+ const jsx = el('Component', { any: 'prop' })
801
+ const result = compileJSX(jsx)
802
+
803
+ expect(typeof result.props).toBe('object')
804
+ expect(result.props !== null).toBe(true)
805
+ expect(!Array.isArray(result.props)).toBe(true)
806
+ })
807
+
808
+ it('children field is array or undefined', () => {
809
+ const withChildren = el('Box', null, 'text')
810
+ const resultWith = compileJSX(withChildren)
811
+ expect(resultWith.children === undefined || Array.isArray(resultWith.children)).toBe(true)
812
+
813
+ const noChildren = el('Box')
814
+ const resultWithout = compileJSX(noChildren)
815
+ expect(resultWithout.children === undefined || Array.isArray(resultWithout.children)).toBe(true)
816
+ })
817
+
818
+ it('key field is string or undefined', () => {
819
+ const withKey = el('Item', { key: 'test' })
820
+ const resultWith = compileJSX(withKey)
821
+ expect(resultWith.key === undefined || typeof resultWith.key === 'string').toBe(true)
822
+
823
+ const noKey = el('Item')
824
+ const resultWithout = compileJSX(noKey)
825
+ expect(resultWithout.key === undefined || typeof resultWithout.key === 'string').toBe(true)
826
+ })
827
+ })
828
+
829
+ // ============================================================================
830
+ // Deep Tree Compiler Tests (compileJSXDeep)
831
+ // ============================================================================
832
+
833
+ // Import the deep tree compiler for testing
834
+ import { compileJSXDeep } from '../../core/compiler'
835
+
836
+ describe('JSX Compiler - Deep Tree Optimization (compileJSXDeep)', () => {
837
+ it('compiles simple elements identically to compileJSX', () => {
838
+ const jsx = el('Box', { padding: 2 })
839
+ const regularResult = compileJSX(jsx)
840
+ const deepResult = compileJSXDeep(jsx)
841
+
842
+ expect(deepResult).toEqual(regularResult)
843
+ })
844
+
845
+ it('compiles elements with string children', () => {
846
+ const jsx = el('Text', { bold: true }, 'Hello World')
847
+ const regularResult = compileJSX(jsx)
848
+ const deepResult = compileJSXDeep(jsx)
849
+
850
+ expect(deepResult).toEqual(regularResult)
851
+ })
852
+
853
+ it('compiles nested elements', () => {
854
+ const jsx = el(
855
+ 'Box',
856
+ { padding: 2 },
857
+ el('Text', null, 'First'),
858
+ el('Text', null, 'Second')
859
+ )
860
+ const regularResult = compileJSX(jsx)
861
+ const deepResult = compileJSXDeep(jsx)
862
+
863
+ expect(deepResult).toEqual(regularResult)
864
+ })
865
+
866
+ it('handles Fragments correctly', () => {
867
+ const jsx = el(
868
+ 'Box',
869
+ null,
870
+ el(React.Fragment, null, el('Text', null, 'A'), el('Text', null, 'B'))
871
+ )
872
+ const regularResult = compileJSX(jsx)
873
+ const deepResult = compileJSXDeep(jsx)
874
+
875
+ expect(deepResult).toEqual(regularResult)
876
+ })
877
+
878
+ it('preserves keys', () => {
879
+ const jsx = el(
880
+ 'List',
881
+ null,
882
+ el('Item', { key: 'item-1' }, 'One'),
883
+ el('Item', { key: 'item-2' }, 'Two')
884
+ )
885
+ const regularResult = compileJSX(jsx)
886
+ const deepResult = compileJSXDeep(jsx)
887
+
888
+ expect(deepResult).toEqual(regularResult)
889
+ })
890
+
891
+ it('filters null/undefined/boolean children', () => {
892
+ const jsx = el(
893
+ 'Box',
894
+ null,
895
+ 'Before',
896
+ null,
897
+ undefined,
898
+ false,
899
+ true,
900
+ 'After'
901
+ )
902
+ const regularResult = compileJSX(jsx)
903
+ const deepResult = compileJSXDeep(jsx)
904
+
905
+ expect(deepResult).toEqual(regularResult)
906
+ })
907
+
908
+ it('converts numeric children to strings', () => {
909
+ const jsx = el('Box', null, 'Count:', 42, 0)
910
+ const regularResult = compileJSX(jsx)
911
+ const deepResult = compileJSXDeep(jsx)
912
+
913
+ expect(deepResult).toEqual(regularResult)
914
+ })
915
+
916
+ it('handles deeply nested trees without stack overflow', () => {
917
+ // Build a tree with 500 levels of nesting
918
+ // This is enough to potentially cause issues with unoptimized recursion
919
+ let deepElement: React.ReactElement = el('Leaf', null, 'content')
920
+ for (let i = 0; i < 500; i++) {
921
+ deepElement = el('Wrapper', { level: i }, deepElement)
922
+ }
923
+
924
+ // Should not throw
925
+ const result = compileJSXDeep(deepElement)
926
+
927
+ // Verify structure
928
+ expect(result.type).toBe('Wrapper')
929
+ expect(result.props.level).toBe(499)
930
+
931
+ // Traverse to the leaf
932
+ let current: UINode = result
933
+ let depth = 0
934
+ while (current.children && current.children.length > 0) {
935
+ const child = current.children[0]
936
+ if (typeof child === 'string') break
937
+ current = child
938
+ depth++
939
+ }
940
+
941
+ expect(depth).toBe(500)
942
+ expect(current.type).toBe('Leaf')
943
+ })
944
+
945
+ it('handles mixed content at all depths', () => {
946
+ const jsx = el(
947
+ 'Root',
948
+ { rootProp: 'root' },
949
+ 'Text before',
950
+ el(
951
+ 'Child',
952
+ { childProp: 'child' },
953
+ el('Grandchild', { grandchildProp: 'grandchild' }, 'Deep text')
954
+ ),
955
+ 'Text after'
956
+ )
957
+ const regularResult = compileJSX(jsx)
958
+ const deepResult = compileJSXDeep(jsx)
959
+
960
+ expect(deepResult).toEqual(regularResult)
961
+ })
962
+
963
+ it('handles nested Fragments', () => {
964
+ const jsx = el(
965
+ 'Box',
966
+ null,
967
+ el(
968
+ React.Fragment,
969
+ null,
970
+ el(React.Fragment, null, el('Text', null, 'Deeply nested'))
971
+ )
972
+ )
973
+ const regularResult = compileJSX(jsx)
974
+ const deepResult = compileJSXDeep(jsx)
975
+
976
+ expect(deepResult).toEqual(regularResult)
977
+ })
978
+
979
+ it('handles empty children array', () => {
980
+ const jsx = el('Box', { children: [] })
981
+ const regularResult = compileJSX(jsx)
982
+ const deepResult = compileJSXDeep(jsx)
983
+
984
+ expect(deepResult).toEqual(regularResult)
985
+ })
986
+ })