@pyreon/form 0.11.4 → 0.11.6
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 +89 -89
- package/lib/devtools.js.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +3 -3
- package/package.json +16 -16
- package/src/context.ts +5 -5
- package/src/devtools.ts +6 -6
- package/src/index.ts +10 -13
- package/src/tests/devtools.test.ts +53 -53
- package/src/tests/form-additional.test.tsx +117 -117
- package/src/tests/form.test.tsx +374 -374
- package/src/types.ts +3 -3
- package/src/use-field-array.ts +2 -2
- package/src/use-field.ts +4 -4
- package/src/use-form-state.ts +3 -3
- package/src/use-form.ts +16 -16
- package/src/use-watch.ts +3 -3
package/src/tests/form.test.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { mount } from
|
|
2
|
-
import type { FormState } from
|
|
1
|
+
import { mount } from '@pyreon/runtime-dom'
|
|
2
|
+
import type { FormState } from '../index'
|
|
3
3
|
import {
|
|
4
4
|
FormProvider,
|
|
5
5
|
useField,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
useFormContext,
|
|
9
9
|
useFormState,
|
|
10
10
|
useWatch,
|
|
11
|
-
} from
|
|
11
|
+
} from '../index'
|
|
12
12
|
|
|
13
13
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
14
14
|
|
|
@@ -19,7 +19,7 @@ function Capture<T>({ fn }: { fn: () => T }) {
|
|
|
19
19
|
|
|
20
20
|
function mountWith<T>(fn: () => T): { result: T; unmount: () => void } {
|
|
21
21
|
let result: T | undefined
|
|
22
|
-
const el = document.createElement(
|
|
22
|
+
const el = document.createElement('div')
|
|
23
23
|
document.body.appendChild(el)
|
|
24
24
|
const unmount = mount(
|
|
25
25
|
<Capture
|
|
@@ -45,19 +45,19 @@ type LoginForm = {
|
|
|
45
45
|
|
|
46
46
|
// ─── useForm ─────────────────────────────────────────────────────────────────
|
|
47
47
|
|
|
48
|
-
describe(
|
|
49
|
-
it(
|
|
48
|
+
describe('useForm', () => {
|
|
49
|
+
it('initializes with correct values', () => {
|
|
50
50
|
const { result: form, unmount } = mountWith(() =>
|
|
51
51
|
useForm({
|
|
52
|
-
initialValues: { email:
|
|
52
|
+
initialValues: { email: 'test@test.com', password: '' },
|
|
53
53
|
onSubmit: () => {
|
|
54
54
|
/* noop */
|
|
55
55
|
},
|
|
56
56
|
}),
|
|
57
57
|
)
|
|
58
58
|
|
|
59
|
-
expect(form.fields.email.value()).toBe(
|
|
60
|
-
expect(form.fields.password.value()).toBe(
|
|
59
|
+
expect(form.fields.email.value()).toBe('test@test.com')
|
|
60
|
+
expect(form.fields.password.value()).toBe('')
|
|
61
61
|
expect(form.isValid()).toBe(true)
|
|
62
62
|
expect(form.isDirty()).toBe(false)
|
|
63
63
|
expect(form.isSubmitting()).toBe(false)
|
|
@@ -65,27 +65,27 @@ describe("useForm", () => {
|
|
|
65
65
|
unmount()
|
|
66
66
|
})
|
|
67
67
|
|
|
68
|
-
it(
|
|
68
|
+
it('setValue updates field value and marks dirty', () => {
|
|
69
69
|
const { result: form, unmount } = mountWith(() =>
|
|
70
70
|
useForm<LoginForm>({
|
|
71
|
-
initialValues: { email:
|
|
71
|
+
initialValues: { email: '', password: '' },
|
|
72
72
|
onSubmit: () => {
|
|
73
73
|
/* noop */
|
|
74
74
|
},
|
|
75
75
|
}),
|
|
76
76
|
)
|
|
77
77
|
|
|
78
|
-
form.fields.email.setValue(
|
|
79
|
-
expect(form.fields.email.value()).toBe(
|
|
78
|
+
form.fields.email.setValue('hello@world.com')
|
|
79
|
+
expect(form.fields.email.value()).toBe('hello@world.com')
|
|
80
80
|
expect(form.fields.email.dirty()).toBe(true)
|
|
81
81
|
expect(form.isDirty()).toBe(true)
|
|
82
82
|
unmount()
|
|
83
83
|
})
|
|
84
84
|
|
|
85
|
-
it(
|
|
85
|
+
it('setTouched marks field as touched', () => {
|
|
86
86
|
const { result: form, unmount } = mountWith(() =>
|
|
87
87
|
useForm<LoginForm>({
|
|
88
|
-
initialValues: { email:
|
|
88
|
+
initialValues: { email: '', password: '' },
|
|
89
89
|
onSubmit: () => {
|
|
90
90
|
/* noop */
|
|
91
91
|
},
|
|
@@ -98,17 +98,17 @@ describe("useForm", () => {
|
|
|
98
98
|
unmount()
|
|
99
99
|
})
|
|
100
100
|
|
|
101
|
-
it(
|
|
101
|
+
it('field-level validation on blur', async () => {
|
|
102
102
|
const { result: form, unmount } = mountWith(() =>
|
|
103
103
|
useForm<LoginForm>({
|
|
104
|
-
initialValues: { email:
|
|
104
|
+
initialValues: { email: '', password: '' },
|
|
105
105
|
validators: {
|
|
106
|
-
email: (v) => (!v ?
|
|
106
|
+
email: (v) => (!v ? 'Required' : undefined),
|
|
107
107
|
},
|
|
108
108
|
onSubmit: () => {
|
|
109
109
|
/* noop */
|
|
110
110
|
},
|
|
111
|
-
validateOn:
|
|
111
|
+
validateOn: 'blur',
|
|
112
112
|
}),
|
|
113
113
|
)
|
|
114
114
|
|
|
@@ -119,11 +119,11 @@ describe("useForm", () => {
|
|
|
119
119
|
form.fields.email.setTouched()
|
|
120
120
|
await new Promise((r) => setTimeout(r, 0))
|
|
121
121
|
|
|
122
|
-
expect(form.fields.email.error()).toBe(
|
|
122
|
+
expect(form.fields.email.error()).toBe('Required')
|
|
123
123
|
expect(form.isValid()).toBe(false)
|
|
124
124
|
|
|
125
125
|
// Fix the value
|
|
126
|
-
form.fields.email.setValue(
|
|
126
|
+
form.fields.email.setValue('test@test.com')
|
|
127
127
|
form.fields.email.setTouched()
|
|
128
128
|
await new Promise((r) => setTimeout(r, 0))
|
|
129
129
|
|
|
@@ -132,38 +132,38 @@ describe("useForm", () => {
|
|
|
132
132
|
unmount()
|
|
133
133
|
})
|
|
134
134
|
|
|
135
|
-
it(
|
|
135
|
+
it('field-level validation on change', async () => {
|
|
136
136
|
const { result: form, unmount } = mountWith(() =>
|
|
137
137
|
useForm<LoginForm>({
|
|
138
|
-
initialValues: { email:
|
|
138
|
+
initialValues: { email: '', password: '' },
|
|
139
139
|
validators: {
|
|
140
|
-
email: (v) => (!v ?
|
|
140
|
+
email: (v) => (!v ? 'Required' : undefined),
|
|
141
141
|
},
|
|
142
142
|
onSubmit: () => {
|
|
143
143
|
/* noop */
|
|
144
144
|
},
|
|
145
|
-
validateOn:
|
|
145
|
+
validateOn: 'change',
|
|
146
146
|
}),
|
|
147
147
|
)
|
|
148
148
|
|
|
149
149
|
// Error should be set immediately via effect
|
|
150
150
|
await new Promise((r) => setTimeout(r, 0))
|
|
151
|
-
expect(form.fields.email.error()).toBe(
|
|
151
|
+
expect(form.fields.email.error()).toBe('Required')
|
|
152
152
|
|
|
153
|
-
form.fields.email.setValue(
|
|
153
|
+
form.fields.email.setValue('hello')
|
|
154
154
|
await new Promise((r) => setTimeout(r, 0))
|
|
155
155
|
expect(form.fields.email.error()).toBeUndefined()
|
|
156
156
|
unmount()
|
|
157
157
|
})
|
|
158
158
|
|
|
159
|
-
it(
|
|
159
|
+
it('handleSubmit validates and calls onSubmit when valid', async () => {
|
|
160
160
|
let submitted: LoginForm | undefined
|
|
161
161
|
const { result: form, unmount } = mountWith(() =>
|
|
162
162
|
useForm<LoginForm>({
|
|
163
|
-
initialValues: { email:
|
|
163
|
+
initialValues: { email: 'a@b.com', password: '12345678' },
|
|
164
164
|
validators: {
|
|
165
|
-
email: (v) => (!v ?
|
|
166
|
-
password: (v) => (v.length < 8 ?
|
|
165
|
+
email: (v) => (!v ? 'Required' : undefined),
|
|
166
|
+
password: (v) => (v.length < 8 ? 'Too short' : undefined),
|
|
167
167
|
},
|
|
168
168
|
onSubmit: (values) => {
|
|
169
169
|
submitted = values
|
|
@@ -172,18 +172,18 @@ describe("useForm", () => {
|
|
|
172
172
|
)
|
|
173
173
|
|
|
174
174
|
await form.handleSubmit()
|
|
175
|
-
expect(submitted).toEqual({ email:
|
|
175
|
+
expect(submitted).toEqual({ email: 'a@b.com', password: '12345678' })
|
|
176
176
|
expect(form.submitCount()).toBe(1)
|
|
177
177
|
unmount()
|
|
178
178
|
})
|
|
179
179
|
|
|
180
|
-
it(
|
|
180
|
+
it('handleSubmit does not call onSubmit when invalid', async () => {
|
|
181
181
|
let called = false
|
|
182
182
|
const { result: form, unmount } = mountWith(() =>
|
|
183
183
|
useForm<LoginForm>({
|
|
184
|
-
initialValues: { email:
|
|
184
|
+
initialValues: { email: '', password: '' },
|
|
185
185
|
validators: {
|
|
186
|
-
email: (v) => (!v ?
|
|
186
|
+
email: (v) => (!v ? 'Required' : undefined),
|
|
187
187
|
},
|
|
188
188
|
onSubmit: () => {
|
|
189
189
|
called = true
|
|
@@ -194,16 +194,16 @@ describe("useForm", () => {
|
|
|
194
194
|
await form.handleSubmit()
|
|
195
195
|
expect(called).toBe(false)
|
|
196
196
|
expect(form.submitCount()).toBe(1)
|
|
197
|
-
expect(form.fields.email.error()).toBe(
|
|
197
|
+
expect(form.fields.email.error()).toBe('Required')
|
|
198
198
|
expect(form.fields.email.touched()).toBe(true)
|
|
199
199
|
unmount()
|
|
200
200
|
})
|
|
201
201
|
|
|
202
|
-
it(
|
|
202
|
+
it('handleSubmit sets isSubmitting during async onSubmit', async () => {
|
|
203
203
|
const states: boolean[] = []
|
|
204
204
|
const { result: form, unmount } = mountWith(() =>
|
|
205
205
|
useForm<LoginForm>({
|
|
206
|
-
initialValues: { email:
|
|
206
|
+
initialValues: { email: 'a@b.com', password: '12345678' },
|
|
207
207
|
onSubmit: async () => {
|
|
208
208
|
states.push(form.isSubmitting())
|
|
209
209
|
await new Promise((r) => setTimeout(r, 10))
|
|
@@ -219,15 +219,15 @@ describe("useForm", () => {
|
|
|
219
219
|
unmount()
|
|
220
220
|
})
|
|
221
221
|
|
|
222
|
-
it(
|
|
222
|
+
it('schema validation runs after field validators', async () => {
|
|
223
223
|
let submitted = false
|
|
224
224
|
const { result: form, unmount } = mountWith(() =>
|
|
225
225
|
useForm({
|
|
226
|
-
initialValues: { password:
|
|
226
|
+
initialValues: { password: '12345678', confirmPassword: '12345679' },
|
|
227
227
|
schema: (values) => {
|
|
228
|
-
const errors: Partial<Record<
|
|
228
|
+
const errors: Partial<Record<'password' | 'confirmPassword', string>> = {}
|
|
229
229
|
if (values.password !== values.confirmPassword) {
|
|
230
|
-
errors.confirmPassword =
|
|
230
|
+
errors.confirmPassword = 'Passwords must match'
|
|
231
231
|
}
|
|
232
232
|
return errors
|
|
233
233
|
},
|
|
@@ -239,41 +239,41 @@ describe("useForm", () => {
|
|
|
239
239
|
|
|
240
240
|
await form.handleSubmit()
|
|
241
241
|
expect(submitted).toBe(false)
|
|
242
|
-
expect(form.fields.confirmPassword.error()).toBe(
|
|
242
|
+
expect(form.fields.confirmPassword.error()).toBe('Passwords must match')
|
|
243
243
|
|
|
244
244
|
// Fix the value
|
|
245
|
-
form.fields.confirmPassword.setValue(
|
|
245
|
+
form.fields.confirmPassword.setValue('12345678')
|
|
246
246
|
await form.handleSubmit()
|
|
247
247
|
expect(submitted).toBe(true)
|
|
248
248
|
unmount()
|
|
249
249
|
})
|
|
250
250
|
|
|
251
|
-
it(
|
|
251
|
+
it('values() returns all current values', () => {
|
|
252
252
|
const { result: form, unmount } = mountWith(() =>
|
|
253
253
|
useForm<LoginForm>({
|
|
254
|
-
initialValues: { email:
|
|
254
|
+
initialValues: { email: 'a@b.com', password: 'secret' },
|
|
255
255
|
onSubmit: () => {
|
|
256
256
|
/* noop */
|
|
257
257
|
},
|
|
258
258
|
}),
|
|
259
259
|
)
|
|
260
260
|
|
|
261
|
-
expect(form.values()).toEqual({ email:
|
|
262
|
-
form.fields.email.setValue(
|
|
261
|
+
expect(form.values()).toEqual({ email: 'a@b.com', password: 'secret' })
|
|
262
|
+
form.fields.email.setValue('new@email.com')
|
|
263
263
|
expect(form.values()).toEqual({
|
|
264
|
-
email:
|
|
265
|
-
password:
|
|
264
|
+
email: 'new@email.com',
|
|
265
|
+
password: 'secret',
|
|
266
266
|
})
|
|
267
267
|
unmount()
|
|
268
268
|
})
|
|
269
269
|
|
|
270
|
-
it(
|
|
270
|
+
it('errors() returns all current errors', async () => {
|
|
271
271
|
const { result: form, unmount } = mountWith(() =>
|
|
272
272
|
useForm<LoginForm>({
|
|
273
|
-
initialValues: { email:
|
|
273
|
+
initialValues: { email: '', password: '' },
|
|
274
274
|
validators: {
|
|
275
|
-
email: (v) => (!v ?
|
|
276
|
-
password: (v) => (!v ?
|
|
275
|
+
email: (v) => (!v ? 'Required' : undefined),
|
|
276
|
+
password: (v) => (!v ? 'Required' : undefined),
|
|
277
277
|
},
|
|
278
278
|
onSubmit: () => {
|
|
279
279
|
/* noop */
|
|
@@ -283,18 +283,18 @@ describe("useForm", () => {
|
|
|
283
283
|
|
|
284
284
|
await form.validate()
|
|
285
285
|
expect(form.errors()).toEqual({
|
|
286
|
-
email:
|
|
287
|
-
password:
|
|
286
|
+
email: 'Required',
|
|
287
|
+
password: 'Required',
|
|
288
288
|
})
|
|
289
289
|
unmount()
|
|
290
290
|
})
|
|
291
291
|
|
|
292
|
-
it(
|
|
292
|
+
it('reset() restores initial values and clears state', async () => {
|
|
293
293
|
const { result: form, unmount } = mountWith(() =>
|
|
294
294
|
useForm<LoginForm>({
|
|
295
|
-
initialValues: { email:
|
|
295
|
+
initialValues: { email: '', password: '' },
|
|
296
296
|
validators: {
|
|
297
|
-
email: (v) => (!v ?
|
|
297
|
+
email: (v) => (!v ? 'Required' : undefined),
|
|
298
298
|
},
|
|
299
299
|
onSubmit: () => {
|
|
300
300
|
/* noop */
|
|
@@ -302,12 +302,12 @@ describe("useForm", () => {
|
|
|
302
302
|
}),
|
|
303
303
|
)
|
|
304
304
|
|
|
305
|
-
form.fields.email.setValue(
|
|
305
|
+
form.fields.email.setValue('changed')
|
|
306
306
|
form.fields.email.setTouched()
|
|
307
307
|
await form.handleSubmit()
|
|
308
308
|
|
|
309
309
|
form.reset()
|
|
310
|
-
expect(form.fields.email.value()).toBe(
|
|
310
|
+
expect(form.fields.email.value()).toBe('')
|
|
311
311
|
expect(form.fields.email.error()).toBeUndefined()
|
|
312
312
|
expect(form.fields.email.touched()).toBe(false)
|
|
313
313
|
expect(form.fields.email.dirty()).toBe(false)
|
|
@@ -315,12 +315,12 @@ describe("useForm", () => {
|
|
|
315
315
|
unmount()
|
|
316
316
|
})
|
|
317
317
|
|
|
318
|
-
it(
|
|
318
|
+
it('validate() returns true when all valid', async () => {
|
|
319
319
|
const { result: form, unmount } = mountWith(() =>
|
|
320
320
|
useForm<LoginForm>({
|
|
321
|
-
initialValues: { email:
|
|
321
|
+
initialValues: { email: 'test@test.com', password: '12345678' },
|
|
322
322
|
validators: {
|
|
323
|
-
email: (v) => (!v ?
|
|
323
|
+
email: (v) => (!v ? 'Required' : undefined),
|
|
324
324
|
},
|
|
325
325
|
onSubmit: () => {
|
|
326
326
|
/* noop */
|
|
@@ -333,14 +333,14 @@ describe("useForm", () => {
|
|
|
333
333
|
unmount()
|
|
334
334
|
})
|
|
335
335
|
|
|
336
|
-
it(
|
|
336
|
+
it('async validators work', async () => {
|
|
337
337
|
const { result: form, unmount } = mountWith(() =>
|
|
338
338
|
useForm({
|
|
339
|
-
initialValues: { username:
|
|
339
|
+
initialValues: { username: 'taken' },
|
|
340
340
|
validators: {
|
|
341
341
|
username: async (v) => {
|
|
342
342
|
await new Promise((r) => setTimeout(r, 5))
|
|
343
|
-
return v ===
|
|
343
|
+
return v === 'taken' ? 'Already taken' : undefined
|
|
344
344
|
},
|
|
345
345
|
},
|
|
346
346
|
onSubmit: () => {
|
|
@@ -351,36 +351,36 @@ describe("useForm", () => {
|
|
|
351
351
|
|
|
352
352
|
const valid = await form.validate()
|
|
353
353
|
expect(valid).toBe(false)
|
|
354
|
-
expect(form.fields.username.error()).toBe(
|
|
354
|
+
expect(form.fields.username.error()).toBe('Already taken')
|
|
355
355
|
unmount()
|
|
356
356
|
})
|
|
357
357
|
|
|
358
|
-
it(
|
|
358
|
+
it('setting value back to initial clears dirty', () => {
|
|
359
359
|
const { result: form, unmount } = mountWith(() =>
|
|
360
360
|
useForm<LoginForm>({
|
|
361
|
-
initialValues: { email:
|
|
361
|
+
initialValues: { email: 'original', password: '' },
|
|
362
362
|
onSubmit: () => {
|
|
363
363
|
/* noop */
|
|
364
364
|
},
|
|
365
365
|
}),
|
|
366
366
|
)
|
|
367
367
|
|
|
368
|
-
form.fields.email.setValue(
|
|
368
|
+
form.fields.email.setValue('changed')
|
|
369
369
|
expect(form.fields.email.dirty()).toBe(true)
|
|
370
370
|
|
|
371
|
-
form.fields.email.setValue(
|
|
371
|
+
form.fields.email.setValue('original')
|
|
372
372
|
expect(form.fields.email.dirty()).toBe(false)
|
|
373
373
|
expect(form.isDirty()).toBe(false)
|
|
374
374
|
unmount()
|
|
375
375
|
})
|
|
376
376
|
|
|
377
|
-
it(
|
|
377
|
+
it('cross-field validation — validators receive all form values', async () => {
|
|
378
378
|
const { result: form, unmount } = mountWith(() =>
|
|
379
379
|
useForm({
|
|
380
|
-
initialValues: { password:
|
|
380
|
+
initialValues: { password: 'abc123', confirmPassword: 'different' },
|
|
381
381
|
validators: {
|
|
382
382
|
confirmPassword: (value, allValues) =>
|
|
383
|
-
value !== allValues.password ?
|
|
383
|
+
value !== allValues.password ? 'Passwords must match' : undefined,
|
|
384
384
|
},
|
|
385
385
|
onSubmit: () => {
|
|
386
386
|
/* noop */
|
|
@@ -390,98 +390,98 @@ describe("useForm", () => {
|
|
|
390
390
|
|
|
391
391
|
const valid = await form.validate()
|
|
392
392
|
expect(valid).toBe(false)
|
|
393
|
-
expect(form.fields.confirmPassword.error()).toBe(
|
|
393
|
+
expect(form.fields.confirmPassword.error()).toBe('Passwords must match')
|
|
394
394
|
|
|
395
|
-
form.fields.confirmPassword.setValue(
|
|
395
|
+
form.fields.confirmPassword.setValue('abc123')
|
|
396
396
|
const valid2 = await form.validate()
|
|
397
397
|
expect(valid2).toBe(true)
|
|
398
398
|
expect(form.fields.confirmPassword.error()).toBeUndefined()
|
|
399
399
|
unmount()
|
|
400
400
|
})
|
|
401
401
|
|
|
402
|
-
it(
|
|
402
|
+
it('register() returns value signal and event handlers', () => {
|
|
403
403
|
const { result: form, unmount } = mountWith(() =>
|
|
404
404
|
useForm<LoginForm>({
|
|
405
|
-
initialValues: { email:
|
|
405
|
+
initialValues: { email: '', password: '' },
|
|
406
406
|
onSubmit: () => {
|
|
407
407
|
/* noop */
|
|
408
408
|
},
|
|
409
409
|
}),
|
|
410
410
|
)
|
|
411
411
|
|
|
412
|
-
const props = form.register(
|
|
412
|
+
const props = form.register('email')
|
|
413
413
|
expect(props.value).toBe(form.fields.email.value)
|
|
414
|
-
expect(typeof props.onInput).toBe(
|
|
415
|
-
expect(typeof props.onBlur).toBe(
|
|
414
|
+
expect(typeof props.onInput).toBe('function')
|
|
415
|
+
expect(typeof props.onBlur).toBe('function')
|
|
416
416
|
unmount()
|
|
417
417
|
})
|
|
418
418
|
|
|
419
|
-
it(
|
|
419
|
+
it('register() onInput updates field value', () => {
|
|
420
420
|
const { result: form, unmount } = mountWith(() =>
|
|
421
421
|
useForm<LoginForm>({
|
|
422
|
-
initialValues: { email:
|
|
422
|
+
initialValues: { email: '', password: '' },
|
|
423
423
|
onSubmit: () => {
|
|
424
424
|
/* noop */
|
|
425
425
|
},
|
|
426
426
|
}),
|
|
427
427
|
)
|
|
428
428
|
|
|
429
|
-
const props = form.register(
|
|
430
|
-
const fakeEvent = { target: { value:
|
|
429
|
+
const props = form.register('email')
|
|
430
|
+
const fakeEvent = { target: { value: 'test@test.com' } } as unknown as Event
|
|
431
431
|
props.onInput(fakeEvent)
|
|
432
432
|
|
|
433
|
-
expect(form.fields.email.value()).toBe(
|
|
433
|
+
expect(form.fields.email.value()).toBe('test@test.com')
|
|
434
434
|
expect(form.fields.email.dirty()).toBe(true)
|
|
435
435
|
unmount()
|
|
436
436
|
})
|
|
437
437
|
|
|
438
|
-
it(
|
|
438
|
+
it('register() onBlur marks field as touched and validates', async () => {
|
|
439
439
|
const { result: form, unmount } = mountWith(() =>
|
|
440
440
|
useForm<LoginForm>({
|
|
441
|
-
initialValues: { email:
|
|
441
|
+
initialValues: { email: '', password: '' },
|
|
442
442
|
validators: {
|
|
443
|
-
email: (v) => (!v ?
|
|
443
|
+
email: (v) => (!v ? 'Required' : undefined),
|
|
444
444
|
},
|
|
445
445
|
onSubmit: () => {
|
|
446
446
|
/* noop */
|
|
447
447
|
},
|
|
448
|
-
validateOn:
|
|
448
|
+
validateOn: 'blur',
|
|
449
449
|
}),
|
|
450
450
|
)
|
|
451
451
|
|
|
452
|
-
const props = form.register(
|
|
452
|
+
const props = form.register('email')
|
|
453
453
|
props.onBlur()
|
|
454
454
|
await new Promise((r) => setTimeout(r, 0))
|
|
455
455
|
|
|
456
456
|
expect(form.fields.email.touched()).toBe(true)
|
|
457
|
-
expect(form.fields.email.error()).toBe(
|
|
457
|
+
expect(form.fields.email.error()).toBe('Required')
|
|
458
458
|
unmount()
|
|
459
459
|
})
|
|
460
460
|
|
|
461
|
-
it(
|
|
461
|
+
it('setFieldError sets a single field error', () => {
|
|
462
462
|
const { result: form, unmount } = mountWith(() =>
|
|
463
463
|
useForm<LoginForm>({
|
|
464
|
-
initialValues: { email:
|
|
464
|
+
initialValues: { email: '', password: '' },
|
|
465
465
|
onSubmit: () => {
|
|
466
466
|
/* noop */
|
|
467
467
|
},
|
|
468
468
|
}),
|
|
469
469
|
)
|
|
470
470
|
|
|
471
|
-
form.setFieldError(
|
|
472
|
-
expect(form.fields.email.error()).toBe(
|
|
471
|
+
form.setFieldError('email', 'Server error: email taken')
|
|
472
|
+
expect(form.fields.email.error()).toBe('Server error: email taken')
|
|
473
473
|
expect(form.isValid()).toBe(false)
|
|
474
474
|
|
|
475
|
-
form.setFieldError(
|
|
475
|
+
form.setFieldError('email', undefined)
|
|
476
476
|
expect(form.fields.email.error()).toBeUndefined()
|
|
477
477
|
expect(form.isValid()).toBe(true)
|
|
478
478
|
unmount()
|
|
479
479
|
})
|
|
480
480
|
|
|
481
|
-
it(
|
|
481
|
+
it('setErrors sets multiple field errors at once', () => {
|
|
482
482
|
const { result: form, unmount } = mountWith(() =>
|
|
483
483
|
useForm<LoginForm>({
|
|
484
|
-
initialValues: { email:
|
|
484
|
+
initialValues: { email: '', password: '' },
|
|
485
485
|
onSubmit: () => {
|
|
486
486
|
/* noop */
|
|
487
487
|
},
|
|
@@ -489,30 +489,30 @@ describe("useForm", () => {
|
|
|
489
489
|
)
|
|
490
490
|
|
|
491
491
|
form.setErrors({
|
|
492
|
-
email:
|
|
493
|
-
password:
|
|
492
|
+
email: 'Invalid email',
|
|
493
|
+
password: 'Too weak',
|
|
494
494
|
})
|
|
495
|
-
expect(form.fields.email.error()).toBe(
|
|
496
|
-
expect(form.fields.password.error()).toBe(
|
|
495
|
+
expect(form.fields.email.error()).toBe('Invalid email')
|
|
496
|
+
expect(form.fields.password.error()).toBe('Too weak')
|
|
497
497
|
expect(form.isValid()).toBe(false)
|
|
498
498
|
unmount()
|
|
499
499
|
})
|
|
500
500
|
|
|
501
|
-
it(
|
|
501
|
+
it('debounceMs delays validation', async () => {
|
|
502
502
|
let callCount = 0
|
|
503
503
|
const { result: form, unmount } = mountWith(() =>
|
|
504
504
|
useForm({
|
|
505
|
-
initialValues: { name:
|
|
505
|
+
initialValues: { name: '' },
|
|
506
506
|
validators: {
|
|
507
507
|
name: (v) => {
|
|
508
508
|
callCount++
|
|
509
|
-
return !v ?
|
|
509
|
+
return !v ? 'Required' : undefined
|
|
510
510
|
},
|
|
511
511
|
},
|
|
512
512
|
onSubmit: () => {
|
|
513
513
|
/* noop */
|
|
514
514
|
},
|
|
515
|
-
validateOn:
|
|
515
|
+
validateOn: 'blur',
|
|
516
516
|
debounceMs: 50,
|
|
517
517
|
}),
|
|
518
518
|
)
|
|
@@ -529,19 +529,19 @@ describe("useForm", () => {
|
|
|
529
529
|
// After debounce period, should have validated once
|
|
530
530
|
await new Promise((r) => setTimeout(r, 60))
|
|
531
531
|
expect(callCount).toBe(1)
|
|
532
|
-
expect(form.fields.name.error()).toBe(
|
|
532
|
+
expect(form.fields.name.error()).toBe('Required')
|
|
533
533
|
unmount()
|
|
534
534
|
})
|
|
535
535
|
|
|
536
|
-
it(
|
|
536
|
+
it('validate() bypasses debounce for immediate validation', async () => {
|
|
537
537
|
let callCount = 0
|
|
538
538
|
const { result: form, unmount } = mountWith(() =>
|
|
539
539
|
useForm({
|
|
540
|
-
initialValues: { name:
|
|
540
|
+
initialValues: { name: '' },
|
|
541
541
|
validators: {
|
|
542
542
|
name: (v) => {
|
|
543
543
|
callCount++
|
|
544
|
-
return !v ?
|
|
544
|
+
return !v ? 'Required' : undefined
|
|
545
545
|
},
|
|
546
546
|
},
|
|
547
547
|
onSubmit: () => {
|
|
@@ -558,29 +558,29 @@ describe("useForm", () => {
|
|
|
558
558
|
unmount()
|
|
559
559
|
})
|
|
560
560
|
|
|
561
|
-
it(
|
|
561
|
+
it('setFieldValue sets a field value from the form level', () => {
|
|
562
562
|
const { result: form, unmount } = mountWith(() =>
|
|
563
563
|
useForm<LoginForm>({
|
|
564
|
-
initialValues: { email:
|
|
564
|
+
initialValues: { email: '', password: '' },
|
|
565
565
|
onSubmit: () => {
|
|
566
566
|
/* noop */
|
|
567
567
|
},
|
|
568
568
|
}),
|
|
569
569
|
)
|
|
570
570
|
|
|
571
|
-
form.setFieldValue(
|
|
572
|
-
expect(form.fields.email.value()).toBe(
|
|
571
|
+
form.setFieldValue('email', 'new@email.com')
|
|
572
|
+
expect(form.fields.email.value()).toBe('new@email.com')
|
|
573
573
|
expect(form.fields.email.dirty()).toBe(true)
|
|
574
574
|
unmount()
|
|
575
575
|
})
|
|
576
576
|
|
|
577
|
-
it(
|
|
577
|
+
it('clearErrors clears all field errors', async () => {
|
|
578
578
|
const { result: form, unmount } = mountWith(() =>
|
|
579
579
|
useForm<LoginForm>({
|
|
580
|
-
initialValues: { email:
|
|
580
|
+
initialValues: { email: '', password: '' },
|
|
581
581
|
validators: {
|
|
582
|
-
email: (v) => (!v ?
|
|
583
|
-
password: (v) => (!v ?
|
|
582
|
+
email: (v) => (!v ? 'Required' : undefined),
|
|
583
|
+
password: (v) => (!v ? 'Required' : undefined),
|
|
584
584
|
},
|
|
585
585
|
onSubmit: () => {
|
|
586
586
|
/* noop */
|
|
@@ -598,38 +598,38 @@ describe("useForm", () => {
|
|
|
598
598
|
unmount()
|
|
599
599
|
})
|
|
600
600
|
|
|
601
|
-
it(
|
|
601
|
+
it('resetField resets a single field without affecting others', () => {
|
|
602
602
|
const { result: form, unmount } = mountWith(() =>
|
|
603
603
|
useForm<LoginForm>({
|
|
604
|
-
initialValues: { email:
|
|
604
|
+
initialValues: { email: '', password: '' },
|
|
605
605
|
onSubmit: () => {
|
|
606
606
|
/* noop */
|
|
607
607
|
},
|
|
608
608
|
}),
|
|
609
609
|
)
|
|
610
610
|
|
|
611
|
-
form.fields.email.setValue(
|
|
612
|
-
form.fields.password.setValue(
|
|
611
|
+
form.fields.email.setValue('changed')
|
|
612
|
+
form.fields.password.setValue('changed')
|
|
613
613
|
form.fields.email.setTouched()
|
|
614
614
|
|
|
615
|
-
form.resetField(
|
|
616
|
-
expect(form.fields.email.value()).toBe(
|
|
615
|
+
form.resetField('email')
|
|
616
|
+
expect(form.fields.email.value()).toBe('')
|
|
617
617
|
expect(form.fields.email.dirty()).toBe(false)
|
|
618
618
|
expect(form.fields.email.touched()).toBe(false)
|
|
619
619
|
// Password should be unaffected
|
|
620
|
-
expect(form.fields.password.value()).toBe(
|
|
620
|
+
expect(form.fields.password.value()).toBe('changed')
|
|
621
621
|
expect(form.fields.password.dirty()).toBe(true)
|
|
622
622
|
unmount()
|
|
623
623
|
})
|
|
624
624
|
|
|
625
|
-
it(
|
|
625
|
+
it('isValidating tracks async validation state', async () => {
|
|
626
626
|
const { result: form, unmount } = mountWith(() =>
|
|
627
627
|
useForm({
|
|
628
|
-
initialValues: { name:
|
|
628
|
+
initialValues: { name: '' },
|
|
629
629
|
validators: {
|
|
630
630
|
name: async (v) => {
|
|
631
631
|
await new Promise((r) => setTimeout(r, 20))
|
|
632
|
-
return !v ?
|
|
632
|
+
return !v ? 'Required' : undefined
|
|
633
633
|
},
|
|
634
634
|
},
|
|
635
635
|
onSubmit: () => {
|
|
@@ -646,10 +646,10 @@ describe("useForm", () => {
|
|
|
646
646
|
unmount()
|
|
647
647
|
})
|
|
648
648
|
|
|
649
|
-
it(
|
|
649
|
+
it('handleSubmit calls preventDefault on event', async () => {
|
|
650
650
|
const { result: form, unmount } = mountWith(() =>
|
|
651
651
|
useForm<LoginForm>({
|
|
652
|
-
initialValues: { email:
|
|
652
|
+
initialValues: { email: 'a@b.com', password: '12345678' },
|
|
653
653
|
onSubmit: () => {
|
|
654
654
|
/* noop */
|
|
655
655
|
},
|
|
@@ -668,7 +668,7 @@ describe("useForm", () => {
|
|
|
668
668
|
unmount()
|
|
669
669
|
})
|
|
670
670
|
|
|
671
|
-
it(
|
|
671
|
+
it('register() with checkbox type uses checked property', () => {
|
|
672
672
|
const { result: form, unmount } = mountWith(() =>
|
|
673
673
|
useForm({
|
|
674
674
|
initialValues: { remember: false },
|
|
@@ -678,13 +678,13 @@ describe("useForm", () => {
|
|
|
678
678
|
}),
|
|
679
679
|
)
|
|
680
680
|
|
|
681
|
-
const props = form.register(
|
|
681
|
+
const props = form.register('remember', { type: 'checkbox' })
|
|
682
682
|
expect(props.checked).toBeDefined()
|
|
683
683
|
expect(props.checked!()).toBe(false)
|
|
684
684
|
|
|
685
685
|
// Simulate checkbox change
|
|
686
686
|
const fakeEvent = {
|
|
687
|
-
target: { checked: true, value:
|
|
687
|
+
target: { checked: true, value: 'on' },
|
|
688
688
|
} as unknown as Event
|
|
689
689
|
props.onInput(fakeEvent)
|
|
690
690
|
|
|
@@ -693,7 +693,7 @@ describe("useForm", () => {
|
|
|
693
693
|
unmount()
|
|
694
694
|
})
|
|
695
695
|
|
|
696
|
-
it(
|
|
696
|
+
it('register() with number type uses valueAsNumber when valid', () => {
|
|
697
697
|
const { result: form, unmount } = mountWith(() =>
|
|
698
698
|
useForm({
|
|
699
699
|
initialValues: { age: 0 },
|
|
@@ -703,46 +703,46 @@ describe("useForm", () => {
|
|
|
703
703
|
}),
|
|
704
704
|
)
|
|
705
705
|
|
|
706
|
-
const props = form.register(
|
|
706
|
+
const props = form.register('age', { type: 'number' })
|
|
707
707
|
|
|
708
708
|
// Simulate input with a valid number
|
|
709
709
|
const validEvent = {
|
|
710
|
-
target: { value:
|
|
710
|
+
target: { value: '25', valueAsNumber: 25 },
|
|
711
711
|
} as unknown as Event
|
|
712
712
|
props.onInput(validEvent)
|
|
713
713
|
expect(form.fields.age.value()).toBe(25)
|
|
714
714
|
|
|
715
715
|
// Simulate input with NaN (e.g. empty string) — falls back to target.value
|
|
716
716
|
const nanEvent = {
|
|
717
|
-
target: { value:
|
|
717
|
+
target: { value: '', valueAsNumber: NaN },
|
|
718
718
|
} as unknown as Event
|
|
719
719
|
props.onInput(nanEvent)
|
|
720
|
-
expect(form.fields.age.value()).toBe(
|
|
720
|
+
expect(form.fields.age.value()).toBe('')
|
|
721
721
|
unmount()
|
|
722
722
|
})
|
|
723
723
|
|
|
724
|
-
it(
|
|
724
|
+
it('register() returns same props for repeated calls (memoized)', () => {
|
|
725
725
|
const { result: form, unmount } = mountWith(() =>
|
|
726
726
|
useForm<LoginForm>({
|
|
727
|
-
initialValues: { email:
|
|
727
|
+
initialValues: { email: '', password: '' },
|
|
728
728
|
onSubmit: () => {
|
|
729
729
|
/* noop */
|
|
730
730
|
},
|
|
731
731
|
}),
|
|
732
732
|
)
|
|
733
733
|
|
|
734
|
-
const first = form.register(
|
|
735
|
-
const second = form.register(
|
|
734
|
+
const first = form.register('email')
|
|
735
|
+
const second = form.register('email')
|
|
736
736
|
expect(first).toBe(second)
|
|
737
737
|
unmount()
|
|
738
738
|
})
|
|
739
739
|
|
|
740
|
-
it(
|
|
740
|
+
it('submitError captures onSubmit errors', async () => {
|
|
741
741
|
const { result: form, unmount } = mountWith(() =>
|
|
742
742
|
useForm<LoginForm>({
|
|
743
|
-
initialValues: { email:
|
|
743
|
+
initialValues: { email: 'a@b.com', password: '12345678' },
|
|
744
744
|
onSubmit: async () => {
|
|
745
|
-
throw new Error(
|
|
745
|
+
throw new Error('Server error')
|
|
746
746
|
},
|
|
747
747
|
}),
|
|
748
748
|
)
|
|
@@ -752,7 +752,7 @@ describe("useForm", () => {
|
|
|
752
752
|
/* expected */
|
|
753
753
|
})
|
|
754
754
|
expect(form.submitError()).toBeInstanceOf(Error)
|
|
755
|
-
expect((form.submitError() as Error).message).toBe(
|
|
755
|
+
expect((form.submitError() as Error).message).toBe('Server error')
|
|
756
756
|
|
|
757
757
|
// Reset clears submitError
|
|
758
758
|
form.reset()
|
|
@@ -760,10 +760,10 @@ describe("useForm", () => {
|
|
|
760
760
|
unmount()
|
|
761
761
|
})
|
|
762
762
|
|
|
763
|
-
it(
|
|
763
|
+
it('dirty detection works for object field values', () => {
|
|
764
764
|
const { result: form, unmount } = mountWith(() =>
|
|
765
765
|
useForm({
|
|
766
|
-
initialValues: { address: { city:
|
|
766
|
+
initialValues: { address: { city: 'NYC', zip: '10001' } },
|
|
767
767
|
onSubmit: () => {
|
|
768
768
|
/* noop */
|
|
769
769
|
},
|
|
@@ -771,19 +771,19 @@ describe("useForm", () => {
|
|
|
771
771
|
)
|
|
772
772
|
|
|
773
773
|
// Same structure = not dirty
|
|
774
|
-
form.fields.address.setValue({ city:
|
|
774
|
+
form.fields.address.setValue({ city: 'NYC', zip: '10001' })
|
|
775
775
|
expect(form.fields.address.dirty()).toBe(false)
|
|
776
776
|
|
|
777
777
|
// Different structure = dirty
|
|
778
|
-
form.fields.address.setValue({ city:
|
|
778
|
+
form.fields.address.setValue({ city: 'LA', zip: '90001' })
|
|
779
779
|
expect(form.fields.address.dirty()).toBe(true)
|
|
780
780
|
unmount()
|
|
781
781
|
})
|
|
782
782
|
|
|
783
|
-
it(
|
|
783
|
+
it('dirty detection works for array field values', () => {
|
|
784
784
|
const { result: form, unmount } = mountWith(() =>
|
|
785
785
|
useForm({
|
|
786
|
-
initialValues: { tags: [
|
|
786
|
+
initialValues: { tags: ['a', 'b'] },
|
|
787
787
|
onSubmit: () => {
|
|
788
788
|
/* noop */
|
|
789
789
|
},
|
|
@@ -791,11 +791,11 @@ describe("useForm", () => {
|
|
|
791
791
|
)
|
|
792
792
|
|
|
793
793
|
// Same array = not dirty
|
|
794
|
-
form.fields.tags.setValue([
|
|
794
|
+
form.fields.tags.setValue(['a', 'b'])
|
|
795
795
|
expect(form.fields.tags.dirty()).toBe(false)
|
|
796
796
|
|
|
797
797
|
// Different array = dirty
|
|
798
|
-
form.fields.tags.setValue([
|
|
798
|
+
form.fields.tags.setValue(['a', 'b', 'c'])
|
|
799
799
|
expect(form.fields.tags.dirty()).toBe(true)
|
|
800
800
|
unmount()
|
|
801
801
|
})
|
|
@@ -803,16 +803,16 @@ describe("useForm", () => {
|
|
|
803
803
|
|
|
804
804
|
// ─── useFieldArray ───────────────────────────────────────────────────────────
|
|
805
805
|
|
|
806
|
-
describe(
|
|
807
|
-
it(
|
|
808
|
-
const { result: arr, unmount } = mountWith(() => useFieldArray([
|
|
806
|
+
describe('useFieldArray', () => {
|
|
807
|
+
it('initializes with provided values', () => {
|
|
808
|
+
const { result: arr, unmount } = mountWith(() => useFieldArray(['a', 'b', 'c']))
|
|
809
809
|
|
|
810
810
|
expect(arr.length()).toBe(3)
|
|
811
|
-
expect(arr.values()).toEqual([
|
|
811
|
+
expect(arr.values()).toEqual(['a', 'b', 'c'])
|
|
812
812
|
unmount()
|
|
813
813
|
})
|
|
814
814
|
|
|
815
|
-
it(
|
|
815
|
+
it('initializes empty by default', () => {
|
|
816
816
|
const { result: arr, unmount } = mountWith(() => useFieldArray<string>())
|
|
817
817
|
|
|
818
818
|
expect(arr.length()).toBe(0)
|
|
@@ -820,69 +820,69 @@ describe("useFieldArray", () => {
|
|
|
820
820
|
unmount()
|
|
821
821
|
})
|
|
822
822
|
|
|
823
|
-
it(
|
|
824
|
-
const { result: arr, unmount } = mountWith(() => useFieldArray([
|
|
823
|
+
it('append adds to end', () => {
|
|
824
|
+
const { result: arr, unmount } = mountWith(() => useFieldArray(['a']))
|
|
825
825
|
|
|
826
|
-
arr.append(
|
|
827
|
-
expect(arr.values()).toEqual([
|
|
826
|
+
arr.append('b')
|
|
827
|
+
expect(arr.values()).toEqual(['a', 'b'])
|
|
828
828
|
expect(arr.length()).toBe(2)
|
|
829
829
|
unmount()
|
|
830
830
|
})
|
|
831
831
|
|
|
832
|
-
it(
|
|
833
|
-
const { result: arr, unmount } = mountWith(() => useFieldArray([
|
|
832
|
+
it('prepend adds to start', () => {
|
|
833
|
+
const { result: arr, unmount } = mountWith(() => useFieldArray(['b']))
|
|
834
834
|
|
|
835
|
-
arr.prepend(
|
|
836
|
-
expect(arr.values()).toEqual([
|
|
835
|
+
arr.prepend('a')
|
|
836
|
+
expect(arr.values()).toEqual(['a', 'b'])
|
|
837
837
|
unmount()
|
|
838
838
|
})
|
|
839
839
|
|
|
840
|
-
it(
|
|
841
|
-
const { result: arr, unmount } = mountWith(() => useFieldArray([
|
|
840
|
+
it('insert at index', () => {
|
|
841
|
+
const { result: arr, unmount } = mountWith(() => useFieldArray(['a', 'c']))
|
|
842
842
|
|
|
843
|
-
arr.insert(1,
|
|
844
|
-
expect(arr.values()).toEqual([
|
|
843
|
+
arr.insert(1, 'b')
|
|
844
|
+
expect(arr.values()).toEqual(['a', 'b', 'c'])
|
|
845
845
|
unmount()
|
|
846
846
|
})
|
|
847
847
|
|
|
848
|
-
it(
|
|
849
|
-
const { result: arr, unmount } = mountWith(() => useFieldArray([
|
|
848
|
+
it('remove by index', () => {
|
|
849
|
+
const { result: arr, unmount } = mountWith(() => useFieldArray(['a', 'b', 'c']))
|
|
850
850
|
|
|
851
851
|
arr.remove(1)
|
|
852
|
-
expect(arr.values()).toEqual([
|
|
852
|
+
expect(arr.values()).toEqual(['a', 'c'])
|
|
853
853
|
unmount()
|
|
854
854
|
})
|
|
855
855
|
|
|
856
|
-
it(
|
|
857
|
-
const { result: arr, unmount } = mountWith(() => useFieldArray([
|
|
856
|
+
it('move reorders items', () => {
|
|
857
|
+
const { result: arr, unmount } = mountWith(() => useFieldArray(['a', 'b', 'c']))
|
|
858
858
|
|
|
859
859
|
arr.move(0, 2)
|
|
860
|
-
expect(arr.values()).toEqual([
|
|
860
|
+
expect(arr.values()).toEqual(['b', 'c', 'a'])
|
|
861
861
|
unmount()
|
|
862
862
|
})
|
|
863
863
|
|
|
864
|
-
it(
|
|
865
|
-
const { result: arr, unmount } = mountWith(() => useFieldArray([
|
|
864
|
+
it('swap exchanges items', () => {
|
|
865
|
+
const { result: arr, unmount } = mountWith(() => useFieldArray(['a', 'b', 'c']))
|
|
866
866
|
|
|
867
867
|
arr.swap(0, 2)
|
|
868
|
-
expect(arr.values()).toEqual([
|
|
868
|
+
expect(arr.values()).toEqual(['c', 'b', 'a'])
|
|
869
869
|
unmount()
|
|
870
870
|
})
|
|
871
871
|
|
|
872
|
-
it(
|
|
873
|
-
const { result: arr, unmount } = mountWith(() => useFieldArray([
|
|
872
|
+
it('replace replaces all items', () => {
|
|
873
|
+
const { result: arr, unmount } = mountWith(() => useFieldArray(['a', 'b']))
|
|
874
874
|
|
|
875
|
-
arr.replace([
|
|
876
|
-
expect(arr.values()).toEqual([
|
|
875
|
+
arr.replace(['x', 'y', 'z'])
|
|
876
|
+
expect(arr.values()).toEqual(['x', 'y', 'z'])
|
|
877
877
|
expect(arr.length()).toBe(3)
|
|
878
878
|
unmount()
|
|
879
879
|
})
|
|
880
880
|
|
|
881
|
-
it(
|
|
882
|
-
const { result: arr, unmount } = mountWith(() => useFieldArray([
|
|
881
|
+
it('items have stable keys', () => {
|
|
882
|
+
const { result: arr, unmount } = mountWith(() => useFieldArray(['a', 'b']))
|
|
883
883
|
|
|
884
884
|
const keysBefore = arr.items().map((i: any) => i.key)
|
|
885
|
-
arr.append(
|
|
885
|
+
arr.append('c')
|
|
886
886
|
const keysAfter = arr.items().map((i: any) => i.key)
|
|
887
887
|
|
|
888
888
|
// First two keys should be preserved
|
|
@@ -894,75 +894,75 @@ describe("useFieldArray", () => {
|
|
|
894
894
|
unmount()
|
|
895
895
|
})
|
|
896
896
|
|
|
897
|
-
it(
|
|
898
|
-
const { result: arr, unmount } = mountWith(() => useFieldArray([
|
|
897
|
+
it('individual item values are reactive signals', () => {
|
|
898
|
+
const { result: arr, unmount } = mountWith(() => useFieldArray(['a', 'b']))
|
|
899
899
|
|
|
900
900
|
const item = arr.items()[0]!
|
|
901
|
-
expect(item.value()).toBe(
|
|
902
|
-
item.value.set(
|
|
903
|
-
expect(item.value()).toBe(
|
|
901
|
+
expect(item.value()).toBe('a')
|
|
902
|
+
item.value.set('updated')
|
|
903
|
+
expect(item.value()).toBe('updated')
|
|
904
904
|
unmount()
|
|
905
905
|
})
|
|
906
906
|
|
|
907
|
-
it(
|
|
908
|
-
const { result: arr, unmount } = mountWith(() => useFieldArray([
|
|
907
|
+
it('update modifies value at index', () => {
|
|
908
|
+
const { result: arr, unmount } = mountWith(() => useFieldArray(['a', 'b', 'c']))
|
|
909
909
|
|
|
910
|
-
arr.update(1,
|
|
911
|
-
expect(arr.values()).toEqual([
|
|
910
|
+
arr.update(1, 'updated')
|
|
911
|
+
expect(arr.values()).toEqual(['a', 'updated', 'c'])
|
|
912
912
|
|
|
913
913
|
// Key should be preserved
|
|
914
914
|
const item = arr.items()[1]!
|
|
915
|
-
expect(item.value()).toBe(
|
|
915
|
+
expect(item.value()).toBe('updated')
|
|
916
916
|
unmount()
|
|
917
917
|
})
|
|
918
918
|
|
|
919
|
-
it(
|
|
920
|
-
const { result: arr, unmount } = mountWith(() => useFieldArray([
|
|
919
|
+
it('update with invalid (out-of-bounds) index is a no-op', () => {
|
|
920
|
+
const { result: arr, unmount } = mountWith(() => useFieldArray(['a', 'b']))
|
|
921
921
|
|
|
922
|
-
arr.update(99,
|
|
923
|
-
expect(arr.values()).toEqual([
|
|
922
|
+
arr.update(99, 'nope')
|
|
923
|
+
expect(arr.values()).toEqual(['a', 'b'])
|
|
924
924
|
|
|
925
|
-
arr.update(-1,
|
|
926
|
-
expect(arr.values()).toEqual([
|
|
925
|
+
arr.update(-1, 'nope')
|
|
926
|
+
expect(arr.values()).toEqual(['a', 'b'])
|
|
927
927
|
unmount()
|
|
928
928
|
})
|
|
929
929
|
|
|
930
|
-
it(
|
|
931
|
-
const { result: arr, unmount } = mountWith(() => useFieldArray([
|
|
930
|
+
it('move with invalid from index does not insert undefined', () => {
|
|
931
|
+
const { result: arr, unmount } = mountWith(() => useFieldArray(['a', 'b', 'c']))
|
|
932
932
|
|
|
933
933
|
// splice(99,1) returns [] so item is undefined → splice(to,0) is a no-op
|
|
934
934
|
arr.move(99, 0)
|
|
935
935
|
// The array should still have 3 items (the splice removed nothing, guard prevented insert)
|
|
936
936
|
// Actually splice removes nothing and returns [], item is undefined so nothing inserted
|
|
937
|
-
expect(arr.values()).toEqual([
|
|
937
|
+
expect(arr.values()).toEqual(['a', 'b', 'c'])
|
|
938
938
|
unmount()
|
|
939
939
|
})
|
|
940
940
|
|
|
941
|
-
it(
|
|
942
|
-
const { result: arr, unmount } = mountWith(() => useFieldArray([
|
|
941
|
+
it('swap with one invalid index is a no-op', () => {
|
|
942
|
+
const { result: arr, unmount } = mountWith(() => useFieldArray(['a', 'b', 'c']))
|
|
943
943
|
|
|
944
944
|
// indexA out of bounds
|
|
945
945
|
arr.swap(99, 0)
|
|
946
|
-
expect(arr.values()).toEqual([
|
|
946
|
+
expect(arr.values()).toEqual(['a', 'b', 'c'])
|
|
947
947
|
|
|
948
948
|
// indexB out of bounds
|
|
949
949
|
arr.swap(0, 99)
|
|
950
|
-
expect(arr.values()).toEqual([
|
|
950
|
+
expect(arr.values()).toEqual(['a', 'b', 'c'])
|
|
951
951
|
|
|
952
952
|
// Both out of bounds
|
|
953
953
|
arr.swap(99, 100)
|
|
954
|
-
expect(arr.values()).toEqual([
|
|
954
|
+
expect(arr.values()).toEqual(['a', 'b', 'c'])
|
|
955
955
|
unmount()
|
|
956
956
|
})
|
|
957
957
|
})
|
|
958
958
|
|
|
959
959
|
// ─── structuredEqual (via dirty tracking) ────────────────────────────────────
|
|
960
960
|
|
|
961
|
-
describe(
|
|
962
|
-
it(
|
|
961
|
+
describe('structuredEqual coverage via dirty tracking', () => {
|
|
962
|
+
it('arrays with different lengths are detected as dirty', () => {
|
|
963
963
|
const { result: form, unmount } = mountWith(() =>
|
|
964
964
|
useForm({
|
|
965
|
-
initialValues: { items: [
|
|
965
|
+
initialValues: { items: ['a', 'b'] as string[] },
|
|
966
966
|
onSubmit: () => {
|
|
967
967
|
/* noop */
|
|
968
968
|
},
|
|
@@ -970,42 +970,42 @@ describe("structuredEqual coverage via dirty tracking", () => {
|
|
|
970
970
|
)
|
|
971
971
|
|
|
972
972
|
// Longer array
|
|
973
|
-
form.fields.items.setValue([
|
|
973
|
+
form.fields.items.setValue(['a', 'b', 'c'])
|
|
974
974
|
expect(form.fields.items.dirty()).toBe(true)
|
|
975
975
|
|
|
976
976
|
// Shorter array
|
|
977
|
-
form.fields.items.setValue([
|
|
977
|
+
form.fields.items.setValue(['a'])
|
|
978
978
|
expect(form.fields.items.dirty()).toBe(true)
|
|
979
979
|
|
|
980
980
|
// Same length same elements — not dirty
|
|
981
|
-
form.fields.items.setValue([
|
|
981
|
+
form.fields.items.setValue(['a', 'b'])
|
|
982
982
|
expect(form.fields.items.dirty()).toBe(false)
|
|
983
983
|
unmount()
|
|
984
984
|
})
|
|
985
985
|
|
|
986
|
-
it(
|
|
986
|
+
it('arrays with same length but different elements are detected as dirty', () => {
|
|
987
987
|
const { result: form, unmount } = mountWith(() =>
|
|
988
988
|
useForm({
|
|
989
|
-
initialValues: { items: [
|
|
989
|
+
initialValues: { items: ['a', 'b', 'c'] as string[] },
|
|
990
990
|
onSubmit: () => {
|
|
991
991
|
/* noop */
|
|
992
992
|
},
|
|
993
993
|
}),
|
|
994
994
|
)
|
|
995
995
|
|
|
996
|
-
form.fields.items.setValue([
|
|
996
|
+
form.fields.items.setValue(['a', 'b', 'x'])
|
|
997
997
|
expect(form.fields.items.dirty()).toBe(true)
|
|
998
998
|
|
|
999
|
-
form.fields.items.setValue([
|
|
999
|
+
form.fields.items.setValue(['x', 'b', 'c'])
|
|
1000
1000
|
expect(form.fields.items.dirty()).toBe(true)
|
|
1001
1001
|
|
|
1002
1002
|
// Restoring original clears dirty
|
|
1003
|
-
form.fields.items.setValue([
|
|
1003
|
+
form.fields.items.setValue(['a', 'b', 'c'])
|
|
1004
1004
|
expect(form.fields.items.dirty()).toBe(false)
|
|
1005
1005
|
unmount()
|
|
1006
1006
|
})
|
|
1007
1007
|
|
|
1008
|
-
it(
|
|
1008
|
+
it('objects with different number of keys are detected as dirty', () => {
|
|
1009
1009
|
const { result: form, unmount } = mountWith(() =>
|
|
1010
1010
|
useForm({
|
|
1011
1011
|
initialValues: { meta: { x: 1, y: 2 } as Record<string, number> },
|
|
@@ -1029,7 +1029,7 @@ describe("structuredEqual coverage via dirty tracking", () => {
|
|
|
1029
1029
|
unmount()
|
|
1030
1030
|
})
|
|
1031
1031
|
|
|
1032
|
-
it(
|
|
1032
|
+
it('objects with same key count but different values are detected as dirty', () => {
|
|
1033
1033
|
const { result: form, unmount } = mountWith(() =>
|
|
1034
1034
|
useForm({
|
|
1035
1035
|
initialValues: { meta: { x: 1, y: 2 } as Record<string, number> },
|
|
@@ -1044,7 +1044,7 @@ describe("structuredEqual coverage via dirty tracking", () => {
|
|
|
1044
1044
|
unmount()
|
|
1045
1045
|
})
|
|
1046
1046
|
|
|
1047
|
-
it(
|
|
1047
|
+
it('null vs object is detected as dirty', () => {
|
|
1048
1048
|
const { result: form, unmount } = mountWith(() =>
|
|
1049
1049
|
useForm({
|
|
1050
1050
|
initialValues: { data: { a: 1 } as Record<string, number> | null },
|
|
@@ -1062,22 +1062,22 @@ describe("structuredEqual coverage via dirty tracking", () => {
|
|
|
1062
1062
|
|
|
1063
1063
|
// ─── validateOn: 'submit' ───────────────────────────────────────────────────
|
|
1064
1064
|
|
|
1065
|
-
describe(
|
|
1066
|
-
it(
|
|
1065
|
+
describe('validateOn: submit', () => {
|
|
1066
|
+
it('does not validate on blur', async () => {
|
|
1067
1067
|
let validatorCalls = 0
|
|
1068
1068
|
const { result: form, unmount } = mountWith(() =>
|
|
1069
1069
|
useForm({
|
|
1070
|
-
initialValues: { name:
|
|
1070
|
+
initialValues: { name: '' },
|
|
1071
1071
|
validators: {
|
|
1072
1072
|
name: (v) => {
|
|
1073
1073
|
validatorCalls++
|
|
1074
|
-
return !v ?
|
|
1074
|
+
return !v ? 'Required' : undefined
|
|
1075
1075
|
},
|
|
1076
1076
|
},
|
|
1077
1077
|
onSubmit: () => {
|
|
1078
1078
|
/* noop */
|
|
1079
1079
|
},
|
|
1080
|
-
validateOn:
|
|
1080
|
+
validateOn: 'submit',
|
|
1081
1081
|
}),
|
|
1082
1082
|
)
|
|
1083
1083
|
|
|
@@ -1088,25 +1088,25 @@ describe("validateOn: submit", () => {
|
|
|
1088
1088
|
expect(form.fields.name.error()).toBeUndefined()
|
|
1089
1089
|
|
|
1090
1090
|
// setValue should NOT trigger validation
|
|
1091
|
-
form.fields.name.setValue(
|
|
1091
|
+
form.fields.name.setValue('hello')
|
|
1092
1092
|
await new Promise((r) => setTimeout(r, 10))
|
|
1093
1093
|
expect(validatorCalls).toBe(0)
|
|
1094
1094
|
expect(form.fields.name.error()).toBeUndefined()
|
|
1095
1095
|
unmount()
|
|
1096
1096
|
})
|
|
1097
1097
|
|
|
1098
|
-
it(
|
|
1098
|
+
it('validates only when handleSubmit is called', async () => {
|
|
1099
1099
|
let submitted = false
|
|
1100
1100
|
const { result: form, unmount } = mountWith(() =>
|
|
1101
1101
|
useForm({
|
|
1102
|
-
initialValues: { name:
|
|
1102
|
+
initialValues: { name: '' },
|
|
1103
1103
|
validators: {
|
|
1104
|
-
name: (v) => (!v ?
|
|
1104
|
+
name: (v) => (!v ? 'Required' : undefined),
|
|
1105
1105
|
},
|
|
1106
1106
|
onSubmit: () => {
|
|
1107
1107
|
submitted = true
|
|
1108
1108
|
},
|
|
1109
|
-
validateOn:
|
|
1109
|
+
validateOn: 'submit',
|
|
1110
1110
|
}),
|
|
1111
1111
|
)
|
|
1112
1112
|
|
|
@@ -1118,10 +1118,10 @@ describe("validateOn: submit", () => {
|
|
|
1118
1118
|
// Submit triggers validation
|
|
1119
1119
|
await form.handleSubmit()
|
|
1120
1120
|
expect(submitted).toBe(false)
|
|
1121
|
-
expect(form.fields.name.error()).toBe(
|
|
1121
|
+
expect(form.fields.name.error()).toBe('Required')
|
|
1122
1122
|
|
|
1123
1123
|
// Fix and resubmit
|
|
1124
|
-
form.fields.name.setValue(
|
|
1124
|
+
form.fields.name.setValue('hello')
|
|
1125
1125
|
await form.handleSubmit()
|
|
1126
1126
|
expect(submitted).toBe(true)
|
|
1127
1127
|
unmount()
|
|
@@ -1130,30 +1130,30 @@ describe("validateOn: submit", () => {
|
|
|
1130
1130
|
|
|
1131
1131
|
// ─── debounceMs for field validation ─────────────────────────────────────────
|
|
1132
1132
|
|
|
1133
|
-
describe(
|
|
1134
|
-
it(
|
|
1133
|
+
describe('debounceMs field validation', () => {
|
|
1134
|
+
it('debounced validation on change mode', async () => {
|
|
1135
1135
|
let callCount = 0
|
|
1136
1136
|
const { result: form, unmount } = mountWith(() =>
|
|
1137
1137
|
useForm({
|
|
1138
|
-
initialValues: { name:
|
|
1138
|
+
initialValues: { name: '' },
|
|
1139
1139
|
validators: {
|
|
1140
1140
|
name: (v) => {
|
|
1141
1141
|
callCount++
|
|
1142
|
-
return !v ?
|
|
1142
|
+
return !v ? 'Required' : undefined
|
|
1143
1143
|
},
|
|
1144
1144
|
},
|
|
1145
1145
|
onSubmit: () => {
|
|
1146
1146
|
/* noop */
|
|
1147
1147
|
},
|
|
1148
|
-
validateOn:
|
|
1148
|
+
validateOn: 'change',
|
|
1149
1149
|
debounceMs: 50,
|
|
1150
1150
|
}),
|
|
1151
1151
|
)
|
|
1152
1152
|
|
|
1153
1153
|
// Change should trigger debounced validation
|
|
1154
|
-
form.fields.name.setValue(
|
|
1155
|
-
form.fields.name.setValue(
|
|
1156
|
-
form.fields.name.setValue(
|
|
1154
|
+
form.fields.name.setValue('a')
|
|
1155
|
+
form.fields.name.setValue('ab')
|
|
1156
|
+
form.fields.name.setValue('abc')
|
|
1157
1157
|
|
|
1158
1158
|
// Not yet validated
|
|
1159
1159
|
await new Promise((r) => setTimeout(r, 10))
|
|
@@ -1166,17 +1166,17 @@ describe("debounceMs field validation", () => {
|
|
|
1166
1166
|
unmount()
|
|
1167
1167
|
})
|
|
1168
1168
|
|
|
1169
|
-
it(
|
|
1169
|
+
it('debounced validation resolves after timer fires', async () => {
|
|
1170
1170
|
const { result: form, unmount } = mountWith(() =>
|
|
1171
1171
|
useForm({
|
|
1172
|
-
initialValues: { name:
|
|
1172
|
+
initialValues: { name: '' },
|
|
1173
1173
|
validators: {
|
|
1174
|
-
name: (v) => (!v ?
|
|
1174
|
+
name: (v) => (!v ? 'Required' : undefined),
|
|
1175
1175
|
},
|
|
1176
1176
|
onSubmit: () => {
|
|
1177
1177
|
/* noop */
|
|
1178
1178
|
},
|
|
1179
|
-
validateOn:
|
|
1179
|
+
validateOn: 'blur',
|
|
1180
1180
|
debounceMs: 30,
|
|
1181
1181
|
}),
|
|
1182
1182
|
)
|
|
@@ -1189,25 +1189,25 @@ describe("debounceMs field validation", () => {
|
|
|
1189
1189
|
|
|
1190
1190
|
// After debounce fires
|
|
1191
1191
|
await new Promise((r) => setTimeout(r, 50))
|
|
1192
|
-
expect(form.fields.name.error()).toBe(
|
|
1192
|
+
expect(form.fields.name.error()).toBe('Required')
|
|
1193
1193
|
unmount()
|
|
1194
1194
|
})
|
|
1195
1195
|
|
|
1196
|
-
it(
|
|
1196
|
+
it('reset clears pending debounce timers', async () => {
|
|
1197
1197
|
let callCount = 0
|
|
1198
1198
|
const { result: form, unmount } = mountWith(() =>
|
|
1199
1199
|
useForm({
|
|
1200
|
-
initialValues: { name:
|
|
1200
|
+
initialValues: { name: '' },
|
|
1201
1201
|
validators: {
|
|
1202
1202
|
name: (v) => {
|
|
1203
1203
|
callCount++
|
|
1204
|
-
return !v ?
|
|
1204
|
+
return !v ? 'Required' : undefined
|
|
1205
1205
|
},
|
|
1206
1206
|
},
|
|
1207
1207
|
onSubmit: () => {
|
|
1208
1208
|
/* noop */
|
|
1209
1209
|
},
|
|
1210
|
-
validateOn:
|
|
1210
|
+
validateOn: 'blur',
|
|
1211
1211
|
debounceMs: 50,
|
|
1212
1212
|
}),
|
|
1213
1213
|
)
|
|
@@ -1226,61 +1226,61 @@ describe("debounceMs field validation", () => {
|
|
|
1226
1226
|
|
|
1227
1227
|
// ─── Edge case: nonexistent field names ──────────────────────────────────────
|
|
1228
1228
|
|
|
1229
|
-
describe(
|
|
1230
|
-
it(
|
|
1229
|
+
describe('useForm nonexistent field operations', () => {
|
|
1230
|
+
it('setFieldValue with nonexistent field throws', () => {
|
|
1231
1231
|
const { result: form, unmount } = mountWith(() =>
|
|
1232
1232
|
useForm({
|
|
1233
|
-
initialValues: { name:
|
|
1233
|
+
initialValues: { name: 'Alice' },
|
|
1234
1234
|
onSubmit: () => {
|
|
1235
1235
|
/* noop */
|
|
1236
1236
|
},
|
|
1237
1237
|
}),
|
|
1238
1238
|
)
|
|
1239
1239
|
|
|
1240
|
-
expect(() => form.setFieldValue(
|
|
1240
|
+
expect(() => form.setFieldValue('nonexistent' as any, 'value')).toThrow(
|
|
1241
1241
|
'[@pyreon/form] Field "nonexistent" does not exist',
|
|
1242
1242
|
)
|
|
1243
|
-
expect(form.fields.name.value()).toBe(
|
|
1243
|
+
expect(form.fields.name.value()).toBe('Alice')
|
|
1244
1244
|
unmount()
|
|
1245
1245
|
})
|
|
1246
1246
|
|
|
1247
|
-
it(
|
|
1247
|
+
it('setFieldError with nonexistent field throws', () => {
|
|
1248
1248
|
const { result: form, unmount } = mountWith(() =>
|
|
1249
1249
|
useForm({
|
|
1250
|
-
initialValues: { name:
|
|
1250
|
+
initialValues: { name: 'Alice' },
|
|
1251
1251
|
onSubmit: () => {
|
|
1252
1252
|
/* noop */
|
|
1253
1253
|
},
|
|
1254
1254
|
}),
|
|
1255
1255
|
)
|
|
1256
1256
|
|
|
1257
|
-
expect(() => form.setFieldError(
|
|
1257
|
+
expect(() => form.setFieldError('nonexistent' as any, 'error')).toThrow(
|
|
1258
1258
|
'[@pyreon/form] Field "nonexistent" does not exist',
|
|
1259
1259
|
)
|
|
1260
1260
|
expect(form.isValid()).toBe(true)
|
|
1261
1261
|
unmount()
|
|
1262
1262
|
})
|
|
1263
1263
|
|
|
1264
|
-
it(
|
|
1264
|
+
it('resetField with nonexistent field is a no-op', () => {
|
|
1265
1265
|
const { result: form, unmount } = mountWith(() =>
|
|
1266
1266
|
useForm({
|
|
1267
|
-
initialValues: { name:
|
|
1267
|
+
initialValues: { name: 'Alice' },
|
|
1268
1268
|
onSubmit: () => {
|
|
1269
1269
|
/* noop */
|
|
1270
1270
|
},
|
|
1271
1271
|
}),
|
|
1272
1272
|
)
|
|
1273
1273
|
|
|
1274
|
-
form.resetField(
|
|
1275
|
-
expect(form.fields.name.value()).toBe(
|
|
1274
|
+
form.resetField('nonexistent' as any)
|
|
1275
|
+
expect(form.fields.name.value()).toBe('Alice')
|
|
1276
1276
|
unmount()
|
|
1277
1277
|
})
|
|
1278
1278
|
})
|
|
1279
1279
|
|
|
1280
1280
|
// ─── Edge case: structuredEqual mixed types ──────────────────────────────────
|
|
1281
1281
|
|
|
1282
|
-
describe(
|
|
1283
|
-
it(
|
|
1282
|
+
describe('dirty detection with mixed types', () => {
|
|
1283
|
+
it('number vs string is dirty', () => {
|
|
1284
1284
|
const { result: form, unmount } = mountWith(() =>
|
|
1285
1285
|
useForm({
|
|
1286
1286
|
initialValues: { value: 0 as any },
|
|
@@ -1290,7 +1290,7 @@ describe("dirty detection with mixed types", () => {
|
|
|
1290
1290
|
}),
|
|
1291
1291
|
)
|
|
1292
1292
|
|
|
1293
|
-
form.fields.value.setValue(
|
|
1293
|
+
form.fields.value.setValue('0' as any)
|
|
1294
1294
|
expect(form.fields.value.dirty()).toBe(true)
|
|
1295
1295
|
unmount()
|
|
1296
1296
|
})
|
|
@@ -1298,14 +1298,14 @@ describe("dirty detection with mixed types", () => {
|
|
|
1298
1298
|
|
|
1299
1299
|
// ─── validate() branch coverage ──────────────────────────────────────────────
|
|
1300
1300
|
|
|
1301
|
-
describe(
|
|
1302
|
-
it(
|
|
1301
|
+
describe('validate() branch coverage', () => {
|
|
1302
|
+
it('getErrors returns empty when no errors exist', async () => {
|
|
1303
1303
|
const { result: form, unmount } = mountWith(() =>
|
|
1304
1304
|
useForm({
|
|
1305
|
-
initialValues: { name:
|
|
1305
|
+
initialValues: { name: 'valid', email: 'a@b.com' },
|
|
1306
1306
|
validators: {
|
|
1307
|
-
name: (v) => (!v ?
|
|
1308
|
-
email: (v) => (!v ?
|
|
1307
|
+
name: (v) => (!v ? 'Required' : undefined),
|
|
1308
|
+
email: (v) => (!v ? 'Required' : undefined),
|
|
1309
1309
|
},
|
|
1310
1310
|
onSubmit: () => {
|
|
1311
1311
|
/* noop */
|
|
@@ -1319,11 +1319,11 @@ describe("validate() branch coverage", () => {
|
|
|
1319
1319
|
unmount()
|
|
1320
1320
|
})
|
|
1321
1321
|
|
|
1322
|
-
it(
|
|
1322
|
+
it('stale async field-level validation on blur is discarded', async () => {
|
|
1323
1323
|
const resolvers: Array<(v: string | undefined) => void> = []
|
|
1324
1324
|
const { result: form, unmount } = mountWith(() =>
|
|
1325
1325
|
useForm({
|
|
1326
|
-
initialValues: { name:
|
|
1326
|
+
initialValues: { name: '' },
|
|
1327
1327
|
validators: {
|
|
1328
1328
|
name: (_v) => {
|
|
1329
1329
|
return new Promise<string | undefined>((resolve) => {
|
|
@@ -1334,7 +1334,7 @@ describe("validate() branch coverage", () => {
|
|
|
1334
1334
|
onSubmit: () => {
|
|
1335
1335
|
/* noop */
|
|
1336
1336
|
},
|
|
1337
|
-
validateOn:
|
|
1337
|
+
validateOn: 'blur',
|
|
1338
1338
|
}),
|
|
1339
1339
|
)
|
|
1340
1340
|
|
|
@@ -1344,7 +1344,7 @@ describe("validate() branch coverage", () => {
|
|
|
1344
1344
|
form.fields.name.setTouched()
|
|
1345
1345
|
|
|
1346
1346
|
// Resolve the first (stale) result
|
|
1347
|
-
resolvers[0]!(
|
|
1347
|
+
resolvers[0]!('Stale error from blur')
|
|
1348
1348
|
await new Promise((r) => setTimeout(r, 0))
|
|
1349
1349
|
// Error should NOT be set since it's stale
|
|
1350
1350
|
expect(form.fields.name.error()).toBeUndefined()
|
|
@@ -1356,11 +1356,11 @@ describe("validate() branch coverage", () => {
|
|
|
1356
1356
|
unmount()
|
|
1357
1357
|
})
|
|
1358
1358
|
|
|
1359
|
-
it(
|
|
1359
|
+
it('stale async validation results are discarded during validate()', async () => {
|
|
1360
1360
|
const resolvers: Array<(v: string | undefined) => void> = []
|
|
1361
1361
|
const { result: form, unmount } = mountWith(() =>
|
|
1362
1362
|
useForm({
|
|
1363
|
-
initialValues: { name:
|
|
1363
|
+
initialValues: { name: '' },
|
|
1364
1364
|
validators: {
|
|
1365
1365
|
name: (_v) => {
|
|
1366
1366
|
return new Promise<string | undefined>((resolve) => {
|
|
@@ -1380,7 +1380,7 @@ describe("validate() branch coverage", () => {
|
|
|
1380
1380
|
const secondValidate = form.validate()
|
|
1381
1381
|
|
|
1382
1382
|
// Resolve the first (stale) validation — should be discarded
|
|
1383
|
-
resolvers[0]!(
|
|
1383
|
+
resolvers[0]!('Stale error')
|
|
1384
1384
|
// Resolve the second (current) validation
|
|
1385
1385
|
resolvers[1]!(undefined)
|
|
1386
1386
|
|
|
@@ -1391,13 +1391,13 @@ describe("validate() branch coverage", () => {
|
|
|
1391
1391
|
unmount()
|
|
1392
1392
|
})
|
|
1393
1393
|
|
|
1394
|
-
it(
|
|
1394
|
+
it('field-level validator throwing during validate() captures error', async () => {
|
|
1395
1395
|
const { result: form, unmount } = mountWith(() =>
|
|
1396
1396
|
useForm({
|
|
1397
|
-
initialValues: { name:
|
|
1397
|
+
initialValues: { name: 'Alice' },
|
|
1398
1398
|
validators: {
|
|
1399
1399
|
name: () => {
|
|
1400
|
-
throw new Error(
|
|
1400
|
+
throw new Error('Validator crashed')
|
|
1401
1401
|
},
|
|
1402
1402
|
},
|
|
1403
1403
|
onSubmit: () => {
|
|
@@ -1408,20 +1408,20 @@ describe("validate() branch coverage", () => {
|
|
|
1408
1408
|
|
|
1409
1409
|
const valid = await form.validate()
|
|
1410
1410
|
expect(valid).toBe(false)
|
|
1411
|
-
expect(form.fields.name.error()).toBe(
|
|
1411
|
+
expect(form.fields.name.error()).toBe('Validator crashed')
|
|
1412
1412
|
unmount()
|
|
1413
1413
|
})
|
|
1414
1414
|
|
|
1415
|
-
it(
|
|
1415
|
+
it('field-level validator throwing on blur captures error', async () => {
|
|
1416
1416
|
const { result: form, unmount } = mountWith(() =>
|
|
1417
1417
|
useForm({
|
|
1418
|
-
initialValues: { name:
|
|
1418
|
+
initialValues: { name: '' },
|
|
1419
1419
|
validators: {
|
|
1420
1420
|
name: () => {
|
|
1421
|
-
throw new Error(
|
|
1421
|
+
throw new Error('Blur validator crashed')
|
|
1422
1422
|
},
|
|
1423
1423
|
},
|
|
1424
|
-
validateOn:
|
|
1424
|
+
validateOn: 'blur',
|
|
1425
1425
|
onSubmit: () => {
|
|
1426
1426
|
/* noop */
|
|
1427
1427
|
},
|
|
@@ -1430,15 +1430,15 @@ describe("validate() branch coverage", () => {
|
|
|
1430
1430
|
|
|
1431
1431
|
form.fields.name.setTouched()
|
|
1432
1432
|
await new Promise((r) => setTimeout(r, 0))
|
|
1433
|
-
expect(form.fields.name.error()).toBe(
|
|
1433
|
+
expect(form.fields.name.error()).toBe('Blur validator crashed')
|
|
1434
1434
|
unmount()
|
|
1435
1435
|
})
|
|
1436
1436
|
|
|
1437
|
-
it(
|
|
1437
|
+
it('schema validator with keys having undefined value does not block submit', async () => {
|
|
1438
1438
|
let submitted = false
|
|
1439
1439
|
const { result: form, unmount } = mountWith(() =>
|
|
1440
1440
|
useForm({
|
|
1441
|
-
initialValues: { name:
|
|
1441
|
+
initialValues: { name: 'Alice', email: 'a@b.com' },
|
|
1442
1442
|
schema: (_values) => {
|
|
1443
1443
|
// Return an object where some keys have undefined values
|
|
1444
1444
|
return { name: undefined, email: undefined } as any
|
|
@@ -1455,12 +1455,12 @@ describe("validate() branch coverage", () => {
|
|
|
1455
1455
|
unmount()
|
|
1456
1456
|
})
|
|
1457
1457
|
|
|
1458
|
-
it(
|
|
1458
|
+
it('schema validator throwing sets submitError and returns false', async () => {
|
|
1459
1459
|
const { result: form, unmount } = mountWith(() =>
|
|
1460
1460
|
useForm({
|
|
1461
|
-
initialValues: { name:
|
|
1461
|
+
initialValues: { name: 'Alice', email: 'a@b.com' },
|
|
1462
1462
|
schema: () => {
|
|
1463
|
-
throw new Error(
|
|
1463
|
+
throw new Error('Schema exploded')
|
|
1464
1464
|
},
|
|
1465
1465
|
onSubmit: () => {
|
|
1466
1466
|
/* noop */
|
|
@@ -1471,16 +1471,16 @@ describe("validate() branch coverage", () => {
|
|
|
1471
1471
|
const valid = await form.validate()
|
|
1472
1472
|
expect(valid).toBe(false)
|
|
1473
1473
|
expect(form.submitError()).toBeInstanceOf(Error)
|
|
1474
|
-
expect((form.submitError() as Error).message).toBe(
|
|
1474
|
+
expect((form.submitError() as Error).message).toBe('Schema exploded')
|
|
1475
1475
|
unmount()
|
|
1476
1476
|
})
|
|
1477
1477
|
|
|
1478
|
-
it(
|
|
1478
|
+
it('fields without validators return undefined in validate()', async () => {
|
|
1479
1479
|
const { result: form, unmount } = mountWith(() =>
|
|
1480
1480
|
useForm({
|
|
1481
|
-
initialValues: { name:
|
|
1481
|
+
initialValues: { name: 'Alice', noValidator: 'test' },
|
|
1482
1482
|
validators: {
|
|
1483
|
-
name: (v) => (!v ?
|
|
1483
|
+
name: (v) => (!v ? 'Required' : undefined),
|
|
1484
1484
|
// noValidator field has no validator
|
|
1485
1485
|
},
|
|
1486
1486
|
onSubmit: () => {
|
|
@@ -1498,19 +1498,19 @@ describe("validate() branch coverage", () => {
|
|
|
1498
1498
|
|
|
1499
1499
|
// ─── Edge case: debounceMs + validateOn: 'change' ───────────────────────────
|
|
1500
1500
|
|
|
1501
|
-
describe(
|
|
1502
|
-
it(
|
|
1501
|
+
describe('debounceMs with validateOn change', () => {
|
|
1502
|
+
it('debounces validation on change', async () => {
|
|
1503
1503
|
let callCount = 0
|
|
1504
1504
|
const { result: form, unmount } = mountWith(() =>
|
|
1505
1505
|
useForm({
|
|
1506
|
-
initialValues: { name:
|
|
1506
|
+
initialValues: { name: '' },
|
|
1507
1507
|
validators: {
|
|
1508
1508
|
name: async (v) => {
|
|
1509
1509
|
callCount++
|
|
1510
|
-
return v.length < 3 ?
|
|
1510
|
+
return v.length < 3 ? 'Too short' : undefined
|
|
1511
1511
|
},
|
|
1512
1512
|
},
|
|
1513
|
-
validateOn:
|
|
1513
|
+
validateOn: 'change',
|
|
1514
1514
|
debounceMs: 50,
|
|
1515
1515
|
onSubmit: () => {
|
|
1516
1516
|
/* noop */
|
|
@@ -1518,9 +1518,9 @@ describe("debounceMs with validateOn change", () => {
|
|
|
1518
1518
|
}),
|
|
1519
1519
|
)
|
|
1520
1520
|
|
|
1521
|
-
form.fields.name.setValue(
|
|
1522
|
-
form.fields.name.setValue(
|
|
1523
|
-
form.fields.name.setValue(
|
|
1521
|
+
form.fields.name.setValue('a')
|
|
1522
|
+
form.fields.name.setValue('ab')
|
|
1523
|
+
form.fields.name.setValue('abc')
|
|
1524
1524
|
|
|
1525
1525
|
// None should have fired yet
|
|
1526
1526
|
expect(callCount).toBe(0)
|
|
@@ -1535,36 +1535,36 @@ describe("debounceMs with validateOn change", () => {
|
|
|
1535
1535
|
|
|
1536
1536
|
// ─── useField ────────────────────────────────────────────────────────────────
|
|
1537
1537
|
|
|
1538
|
-
describe(
|
|
1539
|
-
it(
|
|
1538
|
+
describe('useField', () => {
|
|
1539
|
+
it('extracts a single field from a form', () => {
|
|
1540
1540
|
const { result, unmount } = mountWith(() => {
|
|
1541
1541
|
const form = useForm({
|
|
1542
|
-
initialValues: { email:
|
|
1542
|
+
initialValues: { email: '', password: '' },
|
|
1543
1543
|
onSubmit: () => {
|
|
1544
1544
|
/* noop */
|
|
1545
1545
|
},
|
|
1546
1546
|
})
|
|
1547
|
-
const field = useField(form,
|
|
1547
|
+
const field = useField(form, 'email')
|
|
1548
1548
|
return { form, field }
|
|
1549
1549
|
})
|
|
1550
1550
|
|
|
1551
|
-
expect(result.field.value()).toBe(
|
|
1552
|
-
result.field.setValue(
|
|
1553
|
-
expect(result.form.fields.email.value()).toBe(
|
|
1551
|
+
expect(result.field.value()).toBe('')
|
|
1552
|
+
result.field.setValue('test@test.com')
|
|
1553
|
+
expect(result.form.fields.email.value()).toBe('test@test.com')
|
|
1554
1554
|
expect(result.field.dirty()).toBe(true)
|
|
1555
1555
|
unmount()
|
|
1556
1556
|
})
|
|
1557
1557
|
|
|
1558
|
-
it(
|
|
1558
|
+
it('hasError and showError computed correctly', async () => {
|
|
1559
1559
|
const { result, unmount } = mountWith(() => {
|
|
1560
1560
|
const form = useForm({
|
|
1561
|
-
initialValues: { email:
|
|
1562
|
-
validators: { email: (v) => (!v ?
|
|
1561
|
+
initialValues: { email: '' },
|
|
1562
|
+
validators: { email: (v) => (!v ? 'Required' : undefined) },
|
|
1563
1563
|
onSubmit: () => {
|
|
1564
1564
|
/* noop */
|
|
1565
1565
|
},
|
|
1566
1566
|
})
|
|
1567
|
-
const field = useField(form,
|
|
1567
|
+
const field = useField(form, 'email')
|
|
1568
1568
|
return { form, field }
|
|
1569
1569
|
})
|
|
1570
1570
|
|
|
@@ -1581,26 +1581,26 @@ describe("useField", () => {
|
|
|
1581
1581
|
unmount()
|
|
1582
1582
|
})
|
|
1583
1583
|
|
|
1584
|
-
it(
|
|
1584
|
+
it('register() delegates to form.register()', () => {
|
|
1585
1585
|
const { result, unmount } = mountWith(() => {
|
|
1586
1586
|
const form = useForm({
|
|
1587
|
-
initialValues: { email:
|
|
1587
|
+
initialValues: { email: '' },
|
|
1588
1588
|
onSubmit: () => {
|
|
1589
1589
|
/* noop */
|
|
1590
1590
|
},
|
|
1591
1591
|
})
|
|
1592
|
-
const field = useField(form,
|
|
1592
|
+
const field = useField(form, 'email')
|
|
1593
1593
|
return { form, field }
|
|
1594
1594
|
})
|
|
1595
1595
|
|
|
1596
1596
|
const fieldProps = result.field.register()
|
|
1597
|
-
const formProps = result.form.register(
|
|
1597
|
+
const formProps = result.form.register('email')
|
|
1598
1598
|
// Should be the same memoized object
|
|
1599
1599
|
expect(fieldProps).toBe(formProps)
|
|
1600
1600
|
unmount()
|
|
1601
1601
|
})
|
|
1602
1602
|
|
|
1603
|
-
it(
|
|
1603
|
+
it('register() with checkbox type', () => {
|
|
1604
1604
|
const { result, unmount } = mountWith(() => {
|
|
1605
1605
|
const form = useForm({
|
|
1606
1606
|
initialValues: { remember: false },
|
|
@@ -1608,35 +1608,35 @@ describe("useField", () => {
|
|
|
1608
1608
|
/* noop */
|
|
1609
1609
|
},
|
|
1610
1610
|
})
|
|
1611
|
-
const field = useField(form,
|
|
1611
|
+
const field = useField(form, 'remember')
|
|
1612
1612
|
return { form, field }
|
|
1613
1613
|
})
|
|
1614
1614
|
|
|
1615
|
-
const props = result.field.register({ type:
|
|
1615
|
+
const props = result.field.register({ type: 'checkbox' })
|
|
1616
1616
|
expect(props.checked).toBeDefined()
|
|
1617
1617
|
expect(props.checked!()).toBe(false)
|
|
1618
1618
|
unmount()
|
|
1619
1619
|
})
|
|
1620
1620
|
|
|
1621
|
-
it(
|
|
1621
|
+
it('reset delegates to field reset', () => {
|
|
1622
1622
|
const { result, unmount } = mountWith(() => {
|
|
1623
1623
|
const form = useForm({
|
|
1624
|
-
initialValues: { name:
|
|
1624
|
+
initialValues: { name: 'initial' },
|
|
1625
1625
|
onSubmit: () => {
|
|
1626
1626
|
/* noop */
|
|
1627
1627
|
},
|
|
1628
1628
|
})
|
|
1629
|
-
const field = useField(form,
|
|
1629
|
+
const field = useField(form, 'name')
|
|
1630
1630
|
return { form, field }
|
|
1631
1631
|
})
|
|
1632
1632
|
|
|
1633
|
-
result.field.setValue(
|
|
1633
|
+
result.field.setValue('changed')
|
|
1634
1634
|
result.field.setTouched()
|
|
1635
1635
|
expect(result.field.dirty()).toBe(true)
|
|
1636
1636
|
expect(result.field.touched()).toBe(true)
|
|
1637
1637
|
|
|
1638
1638
|
result.field.reset()
|
|
1639
|
-
expect(result.field.value()).toBe(
|
|
1639
|
+
expect(result.field.value()).toBe('initial')
|
|
1640
1640
|
expect(result.field.dirty()).toBe(false)
|
|
1641
1641
|
expect(result.field.touched()).toBe(false)
|
|
1642
1642
|
unmount()
|
|
@@ -1645,48 +1645,48 @@ describe("useField", () => {
|
|
|
1645
1645
|
|
|
1646
1646
|
// ─── useWatch ────────────────────────────────────────────────────────────────
|
|
1647
1647
|
|
|
1648
|
-
describe(
|
|
1649
|
-
it(
|
|
1648
|
+
describe('useWatch', () => {
|
|
1649
|
+
it('watches a single field value', () => {
|
|
1650
1650
|
const { result, unmount } = mountWith(() => {
|
|
1651
1651
|
const form = useForm({
|
|
1652
|
-
initialValues: { email:
|
|
1652
|
+
initialValues: { email: 'a@b.com', password: '' },
|
|
1653
1653
|
onSubmit: () => {
|
|
1654
1654
|
/* noop */
|
|
1655
1655
|
},
|
|
1656
1656
|
})
|
|
1657
|
-
const email = useWatch(form,
|
|
1657
|
+
const email = useWatch(form, 'email')
|
|
1658
1658
|
return { form, email }
|
|
1659
1659
|
})
|
|
1660
1660
|
|
|
1661
|
-
expect(result.email()).toBe(
|
|
1662
|
-
result.form.fields.email.setValue(
|
|
1663
|
-
expect(result.email()).toBe(
|
|
1661
|
+
expect(result.email()).toBe('a@b.com')
|
|
1662
|
+
result.form.fields.email.setValue('new@email.com')
|
|
1663
|
+
expect(result.email()).toBe('new@email.com')
|
|
1664
1664
|
unmount()
|
|
1665
1665
|
})
|
|
1666
1666
|
|
|
1667
|
-
it(
|
|
1667
|
+
it('watches multiple fields', () => {
|
|
1668
1668
|
const { result, unmount } = mountWith(() => {
|
|
1669
1669
|
const form = useForm({
|
|
1670
|
-
initialValues: { first:
|
|
1670
|
+
initialValues: { first: 'John', last: 'Doe' },
|
|
1671
1671
|
onSubmit: () => {
|
|
1672
1672
|
/* noop */
|
|
1673
1673
|
},
|
|
1674
1674
|
})
|
|
1675
|
-
const [first, last] = useWatch(form, [
|
|
1675
|
+
const [first, last] = useWatch(form, ['first', 'last'])
|
|
1676
1676
|
return { form, first, last }
|
|
1677
1677
|
})
|
|
1678
1678
|
|
|
1679
|
-
expect(result.first!()).toBe(
|
|
1680
|
-
expect(result.last!()).toBe(
|
|
1681
|
-
result.form.fields.first.setValue(
|
|
1682
|
-
expect(result.first!()).toBe(
|
|
1679
|
+
expect(result.first!()).toBe('John')
|
|
1680
|
+
expect(result.last!()).toBe('Doe')
|
|
1681
|
+
result.form.fields.first.setValue('Jane')
|
|
1682
|
+
expect(result.first!()).toBe('Jane')
|
|
1683
1683
|
unmount()
|
|
1684
1684
|
})
|
|
1685
1685
|
|
|
1686
|
-
it(
|
|
1686
|
+
it('watches all fields when no name provided', () => {
|
|
1687
1687
|
const { result, unmount } = mountWith(() => {
|
|
1688
1688
|
const form = useForm({
|
|
1689
|
-
initialValues: { email:
|
|
1689
|
+
initialValues: { email: 'a@b.com', name: 'Alice' },
|
|
1690
1690
|
onSubmit: () => {
|
|
1691
1691
|
/* noop */
|
|
1692
1692
|
},
|
|
@@ -1695,21 +1695,21 @@ describe("useWatch", () => {
|
|
|
1695
1695
|
return { form, all }
|
|
1696
1696
|
})
|
|
1697
1697
|
|
|
1698
|
-
expect(result.all()).toEqual({ email:
|
|
1699
|
-
result.form.fields.email.setValue(
|
|
1700
|
-
expect(result.all()).toEqual({ email:
|
|
1698
|
+
expect(result.all()).toEqual({ email: 'a@b.com', name: 'Alice' })
|
|
1699
|
+
result.form.fields.email.setValue('new@email.com')
|
|
1700
|
+
expect(result.all()).toEqual({ email: 'new@email.com', name: 'Alice' })
|
|
1701
1701
|
unmount()
|
|
1702
1702
|
})
|
|
1703
1703
|
})
|
|
1704
1704
|
|
|
1705
1705
|
// ─── useFormState ────────────────────────────────────────────────────────────
|
|
1706
1706
|
|
|
1707
|
-
describe(
|
|
1708
|
-
it(
|
|
1707
|
+
describe('useFormState', () => {
|
|
1708
|
+
it('returns full form state summary', async () => {
|
|
1709
1709
|
const { result, unmount } = mountWith(() => {
|
|
1710
1710
|
const form = useForm({
|
|
1711
|
-
initialValues: { email:
|
|
1712
|
-
validators: { email: (v) => (!v ?
|
|
1711
|
+
initialValues: { email: '', password: '' },
|
|
1712
|
+
validators: { email: (v) => (!v ? 'Required' : undefined) },
|
|
1713
1713
|
onSubmit: () => {
|
|
1714
1714
|
/* noop */
|
|
1715
1715
|
},
|
|
@@ -1730,7 +1730,7 @@ describe("useFormState", () => {
|
|
|
1730
1730
|
expect(s.errors).toEqual({})
|
|
1731
1731
|
|
|
1732
1732
|
// Change form state
|
|
1733
|
-
result.form.fields.email.setValue(
|
|
1733
|
+
result.form.fields.email.setValue('test')
|
|
1734
1734
|
result.form.fields.email.setTouched()
|
|
1735
1735
|
await new Promise((r) => setTimeout(r, 0))
|
|
1736
1736
|
|
|
@@ -1741,11 +1741,11 @@ describe("useFormState", () => {
|
|
|
1741
1741
|
unmount()
|
|
1742
1742
|
})
|
|
1743
1743
|
|
|
1744
|
-
it(
|
|
1744
|
+
it('works with selector for fine-grained reactivity', async () => {
|
|
1745
1745
|
const { result, unmount } = mountWith(() => {
|
|
1746
1746
|
const form = useForm({
|
|
1747
|
-
initialValues: { email:
|
|
1748
|
-
validators: { email: (v) => (!v ?
|
|
1747
|
+
initialValues: { email: '' },
|
|
1748
|
+
validators: { email: (v) => (!v ? 'Required' : undefined) },
|
|
1749
1749
|
onSubmit: () => {
|
|
1750
1750
|
/* noop */
|
|
1751
1751
|
},
|
|
@@ -1761,19 +1761,19 @@ describe("useFormState", () => {
|
|
|
1761
1761
|
expect(result.canSubmit()).toBe(false)
|
|
1762
1762
|
|
|
1763
1763
|
// Fix value
|
|
1764
|
-
result.form.fields.email.setValue(
|
|
1764
|
+
result.form.fields.email.setValue('test@test.com')
|
|
1765
1765
|
await result.form.validate()
|
|
1766
1766
|
expect(result.canSubmit()).toBe(true)
|
|
1767
1767
|
unmount()
|
|
1768
1768
|
})
|
|
1769
1769
|
|
|
1770
|
-
it(
|
|
1770
|
+
it('tracks errors in summary', async () => {
|
|
1771
1771
|
const { result, unmount } = mountWith(() => {
|
|
1772
1772
|
const form = useForm({
|
|
1773
|
-
initialValues: { email:
|
|
1773
|
+
initialValues: { email: '', name: '' },
|
|
1774
1774
|
validators: {
|
|
1775
|
-
email: (v) => (!v ?
|
|
1776
|
-
name: (v) => (!v ?
|
|
1775
|
+
email: (v) => (!v ? 'Email required' : undefined),
|
|
1776
|
+
name: (v) => (!v ? 'Name required' : undefined),
|
|
1777
1777
|
},
|
|
1778
1778
|
onSubmit: () => {
|
|
1779
1779
|
/* noop */
|
|
@@ -1785,7 +1785,7 @@ describe("useFormState", () => {
|
|
|
1785
1785
|
|
|
1786
1786
|
await result.form.validate()
|
|
1787
1787
|
const s = result.state()
|
|
1788
|
-
expect(s.errors).toEqual({ email:
|
|
1788
|
+
expect(s.errors).toEqual({ email: 'Email required', name: 'Name required' })
|
|
1789
1789
|
expect(s.isValid).toBe(false)
|
|
1790
1790
|
unmount()
|
|
1791
1791
|
})
|
|
@@ -1793,10 +1793,10 @@ describe("useFormState", () => {
|
|
|
1793
1793
|
|
|
1794
1794
|
// ─── FormProvider / useFormContext ────────────────────────────────────────────
|
|
1795
1795
|
|
|
1796
|
-
describe(
|
|
1797
|
-
it(
|
|
1796
|
+
describe('FormProvider / useFormContext', () => {
|
|
1797
|
+
it('provides form through context', () => {
|
|
1798
1798
|
let contextForm: FormState<{ email: string }> | undefined
|
|
1799
|
-
const el = document.createElement(
|
|
1799
|
+
const el = document.createElement('div')
|
|
1800
1800
|
document.body.appendChild(el)
|
|
1801
1801
|
|
|
1802
1802
|
function ContextConsumer() {
|
|
@@ -1806,7 +1806,7 @@ describe("FormProvider / useFormContext", () => {
|
|
|
1806
1806
|
|
|
1807
1807
|
function ContextTest() {
|
|
1808
1808
|
const form = useForm({
|
|
1809
|
-
initialValues: { email:
|
|
1809
|
+
initialValues: { email: 'context@test.com' },
|
|
1810
1810
|
onSubmit: () => {
|
|
1811
1811
|
/* noop */
|
|
1812
1812
|
},
|
|
@@ -1817,13 +1817,13 @@ describe("FormProvider / useFormContext", () => {
|
|
|
1817
1817
|
const unmount = mount(<ContextTest />, el)
|
|
1818
1818
|
|
|
1819
1819
|
expect(contextForm).toBeDefined()
|
|
1820
|
-
expect(contextForm!.fields.email.value()).toBe(
|
|
1820
|
+
expect(contextForm!.fields.email.value()).toBe('context@test.com')
|
|
1821
1821
|
unmount()
|
|
1822
1822
|
el.remove()
|
|
1823
1823
|
})
|
|
1824
1824
|
|
|
1825
|
-
it(
|
|
1826
|
-
const el = document.createElement(
|
|
1825
|
+
it('throws when useFormContext is called outside FormProvider', () => {
|
|
1826
|
+
const el = document.createElement('div')
|
|
1827
1827
|
document.body.appendChild(el)
|
|
1828
1828
|
|
|
1829
1829
|
let error: Error | undefined
|
|
@@ -1841,8 +1841,8 @@ describe("FormProvider / useFormContext", () => {
|
|
|
1841
1841
|
)
|
|
1842
1842
|
|
|
1843
1843
|
expect(error).toBeDefined()
|
|
1844
|
-
expect(error!.message).toContain(
|
|
1845
|
-
expect(error!.message).toContain(
|
|
1844
|
+
expect(error!.message).toContain('useFormContext')
|
|
1845
|
+
expect(error!.message).toContain('FormProvider')
|
|
1846
1846
|
unmount()
|
|
1847
1847
|
el.remove()
|
|
1848
1848
|
})
|