@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,444 @@
1
+ /**
2
+ * @mdxui/terminal Forms Module Integration Tests
3
+ *
4
+ * Tests verifying that the RHF integration types, schemas, and converters
5
+ * work correctly together.
6
+ */
7
+ import { describe, it, expect } from 'vitest'
8
+ import { z } from 'zod'
9
+
10
+ // Import from forms module
11
+ import {
12
+ // Types (verify they're re-exported)
13
+ type FormValues,
14
+ type FieldConfig,
15
+ type FormState,
16
+ type SelectState,
17
+ type SearchState,
18
+
19
+ // Schemas
20
+ FieldConfigSchema,
21
+ FormStateSchema,
22
+ EmailSchema,
23
+ PasswordSchema,
24
+ createLoginSchema,
25
+ createRegistrationSchema,
26
+ createContactSchema,
27
+ createProfileSchema,
28
+ createSettingsSchema,
29
+
30
+ // Converters
31
+ convertRHFToFormState,
32
+ formStateToUINode,
33
+ fieldConfigToUINode,
34
+ selectStateToUINode,
35
+ searchStateToUINode,
36
+ extractFieldErrors,
37
+ hasFormErrors,
38
+ getFirstErrorField,
39
+ } from '../../forms'
40
+
41
+ // ============================================================================
42
+ // Schema Tests
43
+ // ============================================================================
44
+
45
+ describe('Forms Module - Schemas', () => {
46
+ describe('EmailSchema', () => {
47
+ it('validates valid emails', () => {
48
+ expect(() => EmailSchema.parse('test@example.com')).not.toThrow()
49
+ expect(() => EmailSchema.parse('user.name+tag@domain.co.uk')).not.toThrow()
50
+ })
51
+
52
+ it('rejects invalid emails', () => {
53
+ expect(() => EmailSchema.parse('notanemail')).toThrow()
54
+ expect(() => EmailSchema.parse('@nodomain')).toThrow()
55
+ expect(() => EmailSchema.parse('missing@')).toThrow()
56
+ })
57
+ })
58
+
59
+ describe('PasswordSchema', () => {
60
+ it('validates passwords with all requirements', () => {
61
+ expect(() => PasswordSchema.parse('ValidPass1')).not.toThrow()
62
+ expect(() => PasswordSchema.parse('Complex123')).not.toThrow()
63
+ })
64
+
65
+ it('rejects passwords missing requirements', () => {
66
+ // Too short
67
+ expect(() => PasswordSchema.parse('Short1')).toThrow()
68
+ // No uppercase
69
+ expect(() => PasswordSchema.parse('lowercase123')).toThrow()
70
+ // No lowercase
71
+ expect(() => PasswordSchema.parse('UPPERCASE123')).toThrow()
72
+ // No number
73
+ expect(() => PasswordSchema.parse('NoNumbers')).toThrow()
74
+ })
75
+ })
76
+
77
+ describe('FieldConfigSchema', () => {
78
+ it('validates valid field config', () => {
79
+ const config = {
80
+ name: 'email',
81
+ label: 'Email',
82
+ type: 'email',
83
+ required: true,
84
+ placeholder: 'Enter email',
85
+ }
86
+ expect(() => FieldConfigSchema.parse(config)).not.toThrow()
87
+ })
88
+
89
+ it('requires name field', () => {
90
+ expect(() => FieldConfigSchema.parse({ label: 'No Name' })).toThrow()
91
+ })
92
+
93
+ it('rejects empty name', () => {
94
+ expect(() => FieldConfigSchema.parse({ name: '', label: 'Empty' })).toThrow()
95
+ })
96
+ })
97
+
98
+ describe('createLoginSchema', () => {
99
+ it('creates basic login schema', () => {
100
+ const schema = createLoginSchema()
101
+ type LoginValues = z.infer<typeof schema>
102
+
103
+ // Valid data
104
+ const valid: LoginValues = {
105
+ email: 'test@example.com',
106
+ password: 'Password123',
107
+ }
108
+ expect(() => schema.parse(valid)).not.toThrow()
109
+
110
+ // Invalid email
111
+ expect(() => schema.parse({ email: 'bad', password: 'Password123' })).toThrow()
112
+ })
113
+
114
+ it('creates login schema with remember me', () => {
115
+ const schema = createLoginSchema({ rememberMe: true })
116
+ type LoginValues = z.infer<typeof schema>
117
+
118
+ const valid: LoginValues = {
119
+ email: 'test@example.com',
120
+ password: 'Password123',
121
+ rememberMe: true,
122
+ }
123
+ expect(() => schema.parse(valid)).not.toThrow()
124
+ })
125
+ })
126
+
127
+ describe('createRegistrationSchema', () => {
128
+ it('creates registration schema with password confirmation', () => {
129
+ const schema = createRegistrationSchema({ confirmPassword: true })
130
+
131
+ // Matching passwords
132
+ expect(() =>
133
+ schema.parse({
134
+ email: 'test@example.com',
135
+ password: 'Password123',
136
+ confirmPassword: 'Password123',
137
+ })
138
+ ).not.toThrow()
139
+
140
+ // Non-matching passwords
141
+ expect(() =>
142
+ schema.parse({
143
+ email: 'test@example.com',
144
+ password: 'Password123',
145
+ confirmPassword: 'Different123',
146
+ })
147
+ ).toThrow()
148
+ })
149
+ })
150
+
151
+ describe('createContactSchema', () => {
152
+ it('creates basic contact schema', () => {
153
+ const schema = createContactSchema()
154
+
155
+ expect(() =>
156
+ schema.parse({
157
+ name: 'John Doe',
158
+ email: 'john@example.com',
159
+ message: 'This is a test message',
160
+ })
161
+ ).not.toThrow()
162
+ })
163
+
164
+ it('requires minimum message length', () => {
165
+ const schema = createContactSchema()
166
+
167
+ expect(() =>
168
+ schema.parse({
169
+ name: 'John',
170
+ email: 'john@example.com',
171
+ message: 'Short',
172
+ })
173
+ ).toThrow()
174
+ })
175
+ })
176
+ })
177
+
178
+ // ============================================================================
179
+ // Converter Tests
180
+ // ============================================================================
181
+
182
+ describe('Forms Module - Converters', () => {
183
+ describe('convertRHFToFormState', () => {
184
+ it('converts RHF state to FormState', () => {
185
+ const rhfState = {
186
+ isSubmitting: false,
187
+ isSubmitted: false,
188
+ isValid: true,
189
+ isDirty: true,
190
+ errors: {},
191
+ touchedFields: { email: true },
192
+ dirtyFields: { email: true },
193
+ }
194
+
195
+ const fields: FieldConfig[] = [
196
+ { name: 'email', label: 'Email', type: 'email', required: true },
197
+ { name: 'password', label: 'Password', type: 'password', required: true },
198
+ ]
199
+
200
+ const result = convertRHFToFormState(rhfState, fields, {
201
+ title: 'Login',
202
+ submitLabel: 'Sign In',
203
+ values: { email: 'test@example.com', password: '' },
204
+ })
205
+
206
+ expect(result.title).toBe('Login')
207
+ expect(result.submitLabel).toBe('Sign In')
208
+ expect(result.loading).toBe(false)
209
+ expect(result.submitDisabled).toBe(false)
210
+ expect(result.fields).toHaveLength(2)
211
+ expect(result.fields?.[0].value).toBe('test@example.com')
212
+ expect(result.fields?.[0].valid).toBe(true) // touched + no error + required
213
+ })
214
+
215
+ it('merges errors from RHF state', () => {
216
+ const rhfState = {
217
+ isSubmitting: false,
218
+ isSubmitted: true,
219
+ isValid: false,
220
+ isDirty: true,
221
+ errors: {
222
+ email: { message: 'Invalid email', type: 'pattern' },
223
+ },
224
+ touchedFields: { email: true },
225
+ dirtyFields: { email: true },
226
+ }
227
+
228
+ const fields: FieldConfig[] = [
229
+ { name: 'email', label: 'Email', type: 'email' },
230
+ ]
231
+
232
+ const result = convertRHFToFormState(rhfState, fields)
233
+
234
+ expect(result.fields?.[0].error).toBe('Invalid email')
235
+ expect(result.submitDisabled).toBe(true) // Form is invalid
236
+ })
237
+ })
238
+
239
+ describe('formStateToUINode', () => {
240
+ it('converts FormState to UINode', () => {
241
+ const formState: FormState<{ email: string }> = {
242
+ title: 'Contact',
243
+ description: 'Get in touch',
244
+ fields: [
245
+ { name: 'email', label: 'Email', type: 'email' },
246
+ ],
247
+ submitLabel: 'Send',
248
+ loading: false,
249
+ }
250
+
251
+ const node = formStateToUINode(formState)
252
+
253
+ expect(node.type).toBe('form')
254
+ expect(node.props.title).toBe('Contact')
255
+ expect(node.props.description).toBe('Get in touch')
256
+ expect(node.props.submitLabel).toBe('Send')
257
+ expect(node.props.fields).toHaveLength(1)
258
+ })
259
+ })
260
+
261
+ describe('fieldConfigToUINode', () => {
262
+ it('converts FieldConfig to UINode', () => {
263
+ const field: FieldConfig = {
264
+ name: 'email',
265
+ label: 'Email Address',
266
+ type: 'email',
267
+ value: 'test@example.com',
268
+ required: true,
269
+ error: 'Invalid format',
270
+ }
271
+
272
+ const node = fieldConfigToUINode(field)
273
+
274
+ expect(node.type).toBe('field')
275
+ expect(node.props.name).toBe('email')
276
+ expect(node.props.label).toBe('Email Address')
277
+ expect(node.props.value).toBe('test@example.com')
278
+ expect(node.props.error).toBe('Invalid format')
279
+ })
280
+ })
281
+
282
+ describe('selectStateToUINode', () => {
283
+ it('converts SelectState to UINode', () => {
284
+ const state: SelectState<string> = {
285
+ options: [
286
+ { label: 'USA', value: 'us' },
287
+ { label: 'Canada', value: 'ca' },
288
+ ],
289
+ value: 'us',
290
+ label: 'Country',
291
+ open: true,
292
+ }
293
+
294
+ const node = selectStateToUINode(state)
295
+
296
+ expect(node.type).toBe('select')
297
+ expect(node.props.options).toHaveLength(2)
298
+ expect(node.props.value).toBe('us')
299
+ expect(node.props.label).toBe('Country')
300
+ })
301
+ })
302
+
303
+ describe('searchStateToUINode', () => {
304
+ it('converts SearchState to UINode', () => {
305
+ const state: SearchState = {
306
+ value: 'test query',
307
+ suggestions: [
308
+ { label: 'Test 1', value: 'test1' },
309
+ { label: 'Test 2', value: 'test2' },
310
+ ],
311
+ showSuggestions: true,
312
+ highlightedIndex: 0,
313
+ }
314
+
315
+ const node = searchStateToUINode(state)
316
+
317
+ expect(node.type).toBe('search')
318
+ expect(node.props.value).toBe('test query')
319
+ expect(node.props.suggestions).toHaveLength(2)
320
+ expect(node.props.showSuggestions).toBe(true)
321
+ })
322
+ })
323
+ })
324
+
325
+ // ============================================================================
326
+ // Validation Helper Tests
327
+ // ============================================================================
328
+
329
+ describe('Forms Module - Validation Helpers', () => {
330
+ describe('extractFieldErrors', () => {
331
+ it('extracts errors from RHF errors object', () => {
332
+ const errors = {
333
+ email: { message: 'Invalid email' },
334
+ password: { message: 'Too short' },
335
+ name: {},
336
+ }
337
+
338
+ const result = extractFieldErrors(errors)
339
+
340
+ expect(result.email).toBe('Invalid email')
341
+ expect(result.password).toBe('Too short')
342
+ expect(result.name).toBeUndefined()
343
+ })
344
+
345
+ it('filters by field names when provided', () => {
346
+ const errors = {
347
+ email: { message: 'Invalid email' },
348
+ password: { message: 'Too short' },
349
+ }
350
+
351
+ const result = extractFieldErrors(errors, ['email'])
352
+
353
+ expect(result.email).toBe('Invalid email')
354
+ expect(result.password).toBeUndefined()
355
+ })
356
+ })
357
+
358
+ describe('hasFormErrors', () => {
359
+ it('returns true when errors exist', () => {
360
+ expect(hasFormErrors({ email: { message: 'Invalid' } })).toBe(true)
361
+ })
362
+
363
+ it('returns false when no errors', () => {
364
+ expect(hasFormErrors({})).toBe(false)
365
+ })
366
+ })
367
+
368
+ describe('getFirstErrorField', () => {
369
+ it('returns first error field', () => {
370
+ const errors = {
371
+ email: { message: 'Invalid' },
372
+ password: { message: 'Too short' },
373
+ }
374
+
375
+ const result = getFirstErrorField(errors)
376
+
377
+ expect(['email', 'password']).toContain(result)
378
+ })
379
+
380
+ it('respects field order when provided', () => {
381
+ const errors = {
382
+ email: { message: 'Invalid' },
383
+ password: { message: 'Too short' },
384
+ }
385
+
386
+ const result = getFirstErrorField(errors, ['password', 'email'])
387
+
388
+ expect(result).toBe('password')
389
+ })
390
+
391
+ it('returns undefined when no errors', () => {
392
+ expect(getFirstErrorField({})).toBeUndefined()
393
+ })
394
+ })
395
+ })
396
+
397
+ // ============================================================================
398
+ // Type Export Tests
399
+ // ============================================================================
400
+
401
+ describe('Forms Module - Type Exports', () => {
402
+ it('exports FormValues type', () => {
403
+ // This is a compile-time test - if types are exported correctly, this compiles
404
+ const values: FormValues = {
405
+ email: 'test@example.com',
406
+ count: 42,
407
+ active: true,
408
+ tags: ['a', 'b'],
409
+ }
410
+ expect(values).toBeDefined()
411
+ })
412
+
413
+ it('exports FieldConfig type', () => {
414
+ const config: FieldConfig = {
415
+ name: 'test',
416
+ label: 'Test Field',
417
+ type: 'text',
418
+ }
419
+ expect(config).toBeDefined()
420
+ })
421
+
422
+ it('exports FormState type', () => {
423
+ const state: FormState = {
424
+ title: 'Test Form',
425
+ fields: [],
426
+ }
427
+ expect(state).toBeDefined()
428
+ })
429
+
430
+ it('exports SelectState type', () => {
431
+ const state: SelectState = {
432
+ options: [],
433
+ value: 'test',
434
+ }
435
+ expect(state).toBeDefined()
436
+ })
437
+
438
+ it('exports SearchState type', () => {
439
+ const state: SearchState = {
440
+ value: 'query',
441
+ }
442
+ expect(state).toBeDefined()
443
+ })
444
+ })