@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.
- package/README.md +571 -0
- package/dist/ansi-css-Sk5mWtdK.d.ts +119 -0
- package/dist/ansi-css-V6JIHGsM.d.ts +119 -0
- package/dist/ansi-css-_3eSEU9d.d.ts +119 -0
- package/dist/chunk-3EFDH7PK.js +5235 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-3X5IR6WE.js +884 -0
- package/dist/chunk-4FV5ZDCE.js +5236 -0
- package/dist/chunk-4OVMSF2J.js +243 -0
- package/dist/chunk-63FEETIS.js +4048 -0
- package/dist/chunk-B43KP7XJ.js +884 -0
- package/dist/chunk-BMTJXWUV.js +655 -0
- package/dist/chunk-C3SVH4N7.js +882 -0
- package/dist/chunk-EVWR7Y47.js +874 -0
- package/dist/chunk-F6A5VWUC.js +1285 -0
- package/dist/chunk-FD7KW7GE.js +882 -0
- package/dist/chunk-GBQ6UD6I.js +655 -0
- package/dist/chunk-GMDD3M6U.js +5227 -0
- package/dist/chunk-JBHRXOXM.js +1058 -0
- package/dist/chunk-JFOO3EYO.js +1182 -0
- package/dist/chunk-JQ5H3WXL.js +1291 -0
- package/dist/chunk-JQD5NASE.js +234 -0
- package/dist/chunk-KRHJP5R7.js +592 -0
- package/dist/chunk-KWF6WVJE.js +962 -0
- package/dist/chunk-LHYQVN3H.js +1038 -0
- package/dist/chunk-M3TLQLGC.js +1032 -0
- package/dist/chunk-MVW4Q5OP.js +240 -0
- package/dist/chunk-NXCZSWLU.js +1294 -0
- package/dist/chunk-O25TNRO6.js +607 -0
- package/dist/chunk-PNECDA2I.js +884 -0
- package/dist/chunk-QIHWRLJR.js +962 -0
- package/dist/chunk-QW5YMQ7K.js +882 -0
- package/dist/chunk-R5U7XKVJ.js +16 -0
- package/dist/chunk-RP2MVQLR.js +962 -0
- package/dist/chunk-TP6RXGXA.js +1087 -0
- package/dist/chunk-TQQSTITZ.js +655 -0
- package/dist/chunk-X24GWXQV.js +1281 -0
- package/dist/components/index.d.ts +802 -0
- package/dist/components/index.js +149 -0
- package/dist/data/index.d.ts +2554 -0
- package/dist/data/index.js +51 -0
- package/dist/forms/index.d.ts +1596 -0
- package/dist/forms/index.js +464 -0
- package/dist/index-CQRFZntR.d.ts +867 -0
- package/dist/index.d.ts +579 -0
- package/dist/index.js +786 -0
- package/dist/interactive-D0JkWosD.d.ts +217 -0
- package/dist/keyboard/index.d.ts +2 -0
- package/dist/keyboard/index.js +43 -0
- package/dist/renderers/index.d.ts +546 -0
- package/dist/renderers/index.js +2157 -0
- package/dist/storybook/index.d.ts +396 -0
- package/dist/storybook/index.js +641 -0
- package/dist/theme/index.d.ts +1339 -0
- package/dist/theme/index.js +123 -0
- package/dist/types-Bxu5PAgA.d.ts +710 -0
- package/dist/types-CIlop5Ji.d.ts +701 -0
- package/dist/types-Ca8p_p5X.d.ts +710 -0
- package/package.json +90 -0
- package/src/__tests__/components/data/card.test.ts +458 -0
- package/src/__tests__/components/data/list.test.ts +473 -0
- package/src/__tests__/components/data/metrics.test.ts +541 -0
- package/src/__tests__/components/data/table.test.ts +448 -0
- package/src/__tests__/components/input/field.test.ts +555 -0
- package/src/__tests__/components/input/form.test.ts +870 -0
- package/src/__tests__/components/input/search.test.ts +1238 -0
- package/src/__tests__/components/input/select.test.ts +658 -0
- package/src/__tests__/components/navigation/breadcrumb.test.ts +923 -0
- package/src/__tests__/components/navigation/command-palette.test.ts +1095 -0
- package/src/__tests__/components/navigation/sidebar.test.ts +1018 -0
- package/src/__tests__/components/navigation/tabs.test.ts +995 -0
- package/src/__tests__/components.test.tsx +1197 -0
- package/src/__tests__/core/compiler.test.ts +986 -0
- package/src/__tests__/core/parser.test.ts +785 -0
- package/src/__tests__/core/tier-switcher.test.ts +1103 -0
- package/src/__tests__/core/types.test.ts +1398 -0
- package/src/__tests__/data/collections.test.ts +1337 -0
- package/src/__tests__/data/db.test.ts +1265 -0
- package/src/__tests__/data/reactive.test.ts +1010 -0
- package/src/__tests__/data/sync.test.ts +1614 -0
- package/src/__tests__/errors.test.ts +660 -0
- package/src/__tests__/forms/integration.test.ts +444 -0
- package/src/__tests__/integration.test.ts +905 -0
- package/src/__tests__/keyboard.test.ts +1791 -0
- package/src/__tests__/renderer.test.ts +489 -0
- package/src/__tests__/renderers/ansi-css.test.ts +948 -0
- package/src/__tests__/renderers/ansi.test.ts +1366 -0
- package/src/__tests__/renderers/ascii.test.ts +1360 -0
- package/src/__tests__/renderers/interactive.test.ts +2353 -0
- package/src/__tests__/renderers/markdown.test.ts +1483 -0
- package/src/__tests__/renderers/text.test.ts +1369 -0
- package/src/__tests__/renderers/unicode.test.ts +1307 -0
- package/src/__tests__/theme.test.ts +639 -0
- package/src/__tests__/utils/assertions.ts +685 -0
- package/src/__tests__/utils/index.ts +115 -0
- package/src/__tests__/utils/test-renderer.ts +381 -0
- package/src/__tests__/utils/utils.test.ts +560 -0
- package/src/components/containers/card.ts +56 -0
- package/src/components/containers/dialog.ts +53 -0
- package/src/components/containers/index.ts +9 -0
- package/src/components/containers/panel.ts +59 -0
- package/src/components/feedback/badge.ts +40 -0
- package/src/components/feedback/index.ts +8 -0
- package/src/components/feedback/spinner.ts +23 -0
- package/src/components/helpers.ts +81 -0
- package/src/components/index.ts +153 -0
- package/src/components/layout/breadcrumb.ts +31 -0
- package/src/components/layout/index.ts +10 -0
- package/src/components/layout/list.ts +29 -0
- package/src/components/layout/sidebar.ts +79 -0
- package/src/components/layout/table.ts +62 -0
- package/src/components/primitives/box.ts +95 -0
- package/src/components/primitives/button.ts +54 -0
- package/src/components/primitives/index.ts +11 -0
- package/src/components/primitives/input.ts +88 -0
- package/src/components/primitives/select.ts +97 -0
- package/src/components/primitives/text.ts +60 -0
- package/src/components/render.ts +155 -0
- package/src/components/templates/app.ts +43 -0
- package/src/components/templates/index.ts +8 -0
- package/src/components/templates/site.ts +54 -0
- package/src/components/types.ts +777 -0
- package/src/core/compiler.ts +718 -0
- package/src/core/parser.ts +127 -0
- package/src/core/tier-switcher.ts +607 -0
- package/src/core/types.ts +672 -0
- package/src/data/collection.ts +316 -0
- package/src/data/collections.ts +50 -0
- package/src/data/context.tsx +174 -0
- package/src/data/db.ts +127 -0
- package/src/data/hooks.ts +532 -0
- package/src/data/index.ts +138 -0
- package/src/data/reactive.ts +1225 -0
- package/src/data/saas-collections.ts +375 -0
- package/src/data/sync.ts +1213 -0
- package/src/data/types.ts +660 -0
- package/src/forms/converters.ts +512 -0
- package/src/forms/index.ts +133 -0
- package/src/forms/schemas.ts +403 -0
- package/src/forms/types.ts +476 -0
- package/src/index.ts +542 -0
- package/src/keyboard/focus.ts +748 -0
- package/src/keyboard/index.ts +96 -0
- package/src/keyboard/integration.ts +371 -0
- package/src/keyboard/manager.ts +377 -0
- package/src/keyboard/presets.ts +90 -0
- package/src/renderers/ansi-css.ts +576 -0
- package/src/renderers/ansi.ts +802 -0
- package/src/renderers/ascii.ts +680 -0
- package/src/renderers/breadcrumb.ts +480 -0
- package/src/renderers/command-palette.ts +802 -0
- package/src/renderers/components/field.ts +210 -0
- package/src/renderers/components/form.ts +327 -0
- package/src/renderers/components/index.ts +21 -0
- package/src/renderers/components/search.ts +449 -0
- package/src/renderers/components/select.ts +222 -0
- package/src/renderers/index.ts +101 -0
- package/src/renderers/interactive/component-handlers.ts +622 -0
- package/src/renderers/interactive/cursor-manager.ts +147 -0
- package/src/renderers/interactive/focus-manager.ts +279 -0
- package/src/renderers/interactive/index.ts +661 -0
- package/src/renderers/interactive/input-handler.ts +164 -0
- package/src/renderers/interactive/keyboard-handler.ts +212 -0
- package/src/renderers/interactive/mouse-handler.ts +167 -0
- package/src/renderers/interactive/state-manager.ts +109 -0
- package/src/renderers/interactive/types.ts +338 -0
- package/src/renderers/interactive-string.ts +299 -0
- package/src/renderers/interactive.ts +59 -0
- package/src/renderers/markdown.ts +950 -0
- package/src/renderers/sidebar.ts +549 -0
- package/src/renderers/tabs.ts +682 -0
- package/src/renderers/text.ts +791 -0
- package/src/renderers/unicode.ts +917 -0
- package/src/renderers/utils.ts +942 -0
- package/src/router/adapters.ts +383 -0
- package/src/router/types.ts +140 -0
- package/src/router/utils.ts +452 -0
- package/src/schemas.ts +205 -0
- package/src/storybook/index.ts +91 -0
- package/src/storybook/interactive-decorator.tsx +659 -0
- package/src/storybook/keyboard-simulator.ts +501 -0
- package/src/theme/ansi-codes.ts +80 -0
- package/src/theme/box-drawing.ts +132 -0
- package/src/theme/color-convert.ts +254 -0
- package/src/theme/color-support.ts +321 -0
- package/src/theme/index.ts +134 -0
- package/src/theme/strip-ansi.ts +50 -0
- package/src/theme/tailwind-map.ts +469 -0
- package/src/theme/text-styles.ts +206 -0
- package/src/theme/theme-system.ts +568 -0
- 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
|
+
})
|