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