@pyreon/form 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
- import { mount } from '@pyreon/runtime-dom'
2
- import type { FormState } from '../index'
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 '../index'
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('div')
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('useForm', () => {
49
- it('initializes with correct values', () => {
48
+ describe("useForm", () => {
49
+ it("initializes with correct values", () => {
50
50
  const { result: form, unmount } = mountWith(() =>
51
51
  useForm({
52
- initialValues: { email: 'test@test.com', password: '' },
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('test@test.com')
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('setValue updates field value and marks dirty', () => {
68
+ it("setValue updates field value and marks dirty", () => {
69
69
  const { result: form, unmount } = mountWith(() =>
70
70
  useForm<LoginForm>({
71
- initialValues: { email: '', password: '' },
71
+ initialValues: { email: "", password: "" },
72
72
  onSubmit: () => {
73
73
  /* noop */
74
74
  },
75
75
  }),
76
76
  )
77
77
 
78
- form.fields.email.setValue('hello@world.com')
79
- expect(form.fields.email.value()).toBe('hello@world.com')
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('setTouched marks field as touched', () => {
85
+ it("setTouched marks field as touched", () => {
86
86
  const { result: form, unmount } = mountWith(() =>
87
87
  useForm<LoginForm>({
88
- initialValues: { email: '', password: '' },
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('field-level validation on blur', async () => {
101
+ it("field-level validation on blur", async () => {
102
102
  const { result: form, unmount } = mountWith(() =>
103
103
  useForm<LoginForm>({
104
- initialValues: { email: '', password: '' },
104
+ initialValues: { email: "", password: "" },
105
105
  validators: {
106
- email: (v) => (!v ? 'Required' : undefined),
106
+ email: (v) => (!v ? "Required" : undefined),
107
107
  },
108
108
  onSubmit: () => {
109
109
  /* noop */
110
110
  },
111
- validateOn: 'blur',
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('Required')
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('test@test.com')
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('field-level validation on change', async () => {
135
+ it("field-level validation on change", async () => {
136
136
  const { result: form, unmount } = mountWith(() =>
137
137
  useForm<LoginForm>({
138
- initialValues: { email: '', password: '' },
138
+ initialValues: { email: "", password: "" },
139
139
  validators: {
140
- email: (v) => (!v ? 'Required' : undefined),
140
+ email: (v) => (!v ? "Required" : undefined),
141
141
  },
142
142
  onSubmit: () => {
143
143
  /* noop */
144
144
  },
145
- validateOn: 'change',
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('Required')
151
+ expect(form.fields.email.error()).toBe("Required")
152
152
 
153
- form.fields.email.setValue('hello')
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('handleSubmit validates and calls onSubmit when valid', async () => {
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: 'a@b.com', password: '12345678' },
163
+ initialValues: { email: "a@b.com", password: "12345678" },
164
164
  validators: {
165
- email: (v) => (!v ? 'Required' : undefined),
166
- password: (v) => (v.length < 8 ? 'Too short' : undefined),
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: 'a@b.com', password: '12345678' })
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('handleSubmit does not call onSubmit when invalid', async () => {
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: '', password: '' },
184
+ initialValues: { email: "", password: "" },
185
185
  validators: {
186
- email: (v) => (!v ? 'Required' : undefined),
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('Required')
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('handleSubmit sets isSubmitting during async onSubmit', async () => {
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: 'a@b.com', password: '12345678' },
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('schema validation runs after field validators', async () => {
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: '12345678', confirmPassword: '12345679' },
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 = 'Passwords must match'
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('Passwords must match')
242
+ expect(form.fields.confirmPassword.error()).toBe("Passwords must match")
245
243
 
246
244
  // Fix the value
247
- form.fields.confirmPassword.setValue('12345678')
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('values() returns all current values', () => {
251
+ it("values() returns all current values", () => {
254
252
  const { result: form, unmount } = mountWith(() =>
255
253
  useForm<LoginForm>({
256
- initialValues: { email: 'a@b.com', password: 'secret' },
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: 'a@b.com', password: 'secret' })
264
- form.fields.email.setValue('new@email.com')
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: 'new@email.com',
267
- password: 'secret',
264
+ email: "new@email.com",
265
+ password: "secret",
268
266
  })
269
267
  unmount()
270
268
  })
271
269
 
272
- it('errors() returns all current errors', async () => {
270
+ it("errors() returns all current errors", async () => {
273
271
  const { result: form, unmount } = mountWith(() =>
274
272
  useForm<LoginForm>({
275
- initialValues: { email: '', password: '' },
273
+ initialValues: { email: "", password: "" },
276
274
  validators: {
277
- email: (v) => (!v ? 'Required' : undefined),
278
- password: (v) => (!v ? 'Required' : undefined),
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: 'Required',
289
- password: 'Required',
286
+ email: "Required",
287
+ password: "Required",
290
288
  })
291
289
  unmount()
292
290
  })
293
291
 
294
- it('reset() restores initial values and clears state', async () => {
292
+ it("reset() restores initial values and clears state", async () => {
295
293
  const { result: form, unmount } = mountWith(() =>
296
294
  useForm<LoginForm>({
297
- initialValues: { email: '', password: '' },
295
+ initialValues: { email: "", password: "" },
298
296
  validators: {
299
- email: (v) => (!v ? 'Required' : undefined),
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('changed')
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('validate() returns true when all valid', async () => {
318
+ it("validate() returns true when all valid", async () => {
321
319
  const { result: form, unmount } = mountWith(() =>
322
320
  useForm<LoginForm>({
323
- initialValues: { email: 'test@test.com', password: '12345678' },
321
+ initialValues: { email: "test@test.com", password: "12345678" },
324
322
  validators: {
325
- email: (v) => (!v ? 'Required' : undefined),
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('async validators work', async () => {
336
+ it("async validators work", async () => {
339
337
  const { result: form, unmount } = mountWith(() =>
340
338
  useForm({
341
- initialValues: { username: 'taken' },
339
+ initialValues: { username: "taken" },
342
340
  validators: {
343
341
  username: async (v) => {
344
342
  await new Promise((r) => setTimeout(r, 5))
345
- return v === 'taken' ? 'Already taken' : undefined
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('Already taken')
354
+ expect(form.fields.username.error()).toBe("Already taken")
357
355
  unmount()
358
356
  })
359
357
 
360
- it('setting value back to initial clears dirty', () => {
358
+ it("setting value back to initial clears dirty", () => {
361
359
  const { result: form, unmount } = mountWith(() =>
362
360
  useForm<LoginForm>({
363
- initialValues: { email: 'original', password: '' },
361
+ initialValues: { email: "original", password: "" },
364
362
  onSubmit: () => {
365
363
  /* noop */
366
364
  },
367
365
  }),
368
366
  )
369
367
 
370
- form.fields.email.setValue('changed')
368
+ form.fields.email.setValue("changed")
371
369
  expect(form.fields.email.dirty()).toBe(true)
372
370
 
373
- form.fields.email.setValue('original')
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('cross-field validation — validators receive all form values', async () => {
377
+ it("cross-field validation — validators receive all form values", async () => {
380
378
  const { result: form, unmount } = mountWith(() =>
381
379
  useForm({
382
- initialValues: { password: 'abc123', confirmPassword: 'different' },
380
+ initialValues: { password: "abc123", confirmPassword: "different" },
383
381
  validators: {
384
382
  confirmPassword: (value, allValues) =>
385
- value !== allValues.password ? 'Passwords must match' : undefined,
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('Passwords must match')
393
+ expect(form.fields.confirmPassword.error()).toBe("Passwords must match")
396
394
 
397
- form.fields.confirmPassword.setValue('abc123')
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('register() returns value signal and event handlers', () => {
402
+ it("register() returns value signal and event handlers", () => {
405
403
  const { result: form, unmount } = mountWith(() =>
406
404
  useForm<LoginForm>({
407
- initialValues: { email: '', password: '' },
405
+ initialValues: { email: "", password: "" },
408
406
  onSubmit: () => {
409
407
  /* noop */
410
408
  },
411
409
  }),
412
410
  )
413
411
 
414
- const props = form.register('email')
412
+ const props = form.register("email")
415
413
  expect(props.value).toBe(form.fields.email.value)
416
- expect(typeof props.onInput).toBe('function')
417
- expect(typeof props.onBlur).toBe('function')
414
+ expect(typeof props.onInput).toBe("function")
415
+ expect(typeof props.onBlur).toBe("function")
418
416
  unmount()
419
417
  })
420
418
 
421
- it('register() onInput updates field value', () => {
419
+ it("register() onInput updates field value", () => {
422
420
  const { result: form, unmount } = mountWith(() =>
423
421
  useForm<LoginForm>({
424
- initialValues: { email: '', password: '' },
422
+ initialValues: { email: "", password: "" },
425
423
  onSubmit: () => {
426
424
  /* noop */
427
425
  },
428
426
  }),
429
427
  )
430
428
 
431
- const props = form.register('email')
432
- const fakeEvent = { target: { value: 'test@test.com' } } as unknown as Event
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('test@test.com')
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('register() onBlur marks field as touched and validates', async () => {
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: '', password: '' },
441
+ initialValues: { email: "", password: "" },
444
442
  validators: {
445
- email: (v) => (!v ? 'Required' : undefined),
443
+ email: (v) => (!v ? "Required" : undefined),
446
444
  },
447
445
  onSubmit: () => {
448
446
  /* noop */
449
447
  },
450
- validateOn: 'blur',
448
+ validateOn: "blur",
451
449
  }),
452
450
  )
453
451
 
454
- const props = form.register('email')
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('Required')
457
+ expect(form.fields.email.error()).toBe("Required")
460
458
  unmount()
461
459
  })
462
460
 
463
- it('setFieldError sets a single field error', () => {
461
+ it("setFieldError sets a single field error", () => {
464
462
  const { result: form, unmount } = mountWith(() =>
465
463
  useForm<LoginForm>({
466
- initialValues: { email: '', password: '' },
464
+ initialValues: { email: "", password: "" },
467
465
  onSubmit: () => {
468
466
  /* noop */
469
467
  },
470
468
  }),
471
469
  )
472
470
 
473
- form.setFieldError('email', 'Server error: email taken')
474
- expect(form.fields.email.error()).toBe('Server error: email taken')
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('email', undefined)
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('setErrors sets multiple field errors at once', () => {
481
+ it("setErrors sets multiple field errors at once", () => {
484
482
  const { result: form, unmount } = mountWith(() =>
485
483
  useForm<LoginForm>({
486
- initialValues: { email: '', password: '' },
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: 'Invalid email',
495
- password: 'Too weak',
492
+ email: "Invalid email",
493
+ password: "Too weak",
496
494
  })
497
- expect(form.fields.email.error()).toBe('Invalid email')
498
- expect(form.fields.password.error()).toBe('Too weak')
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('debounceMs delays validation', async () => {
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 ? 'Required' : undefined
509
+ return !v ? "Required" : undefined
512
510
  },
513
511
  },
514
512
  onSubmit: () => {
515
513
  /* noop */
516
514
  },
517
- validateOn: 'blur',
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('Required')
532
+ expect(form.fields.name.error()).toBe("Required")
535
533
  unmount()
536
534
  })
537
535
 
538
- it('validate() bypasses debounce for immediate validation', async () => {
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 ? 'Required' : undefined
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('setFieldValue sets a field value from the form level', () => {
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: '', password: '' },
564
+ initialValues: { email: "", password: "" },
567
565
  onSubmit: () => {
568
566
  /* noop */
569
567
  },
570
568
  }),
571
569
  )
572
570
 
573
- form.setFieldValue('email', 'new@email.com')
574
- expect(form.fields.email.value()).toBe('new@email.com')
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('clearErrors clears all field errors', async () => {
577
+ it("clearErrors clears all field errors", async () => {
580
578
  const { result: form, unmount } = mountWith(() =>
581
579
  useForm<LoginForm>({
582
- initialValues: { email: '', password: '' },
580
+ initialValues: { email: "", password: "" },
583
581
  validators: {
584
- email: (v) => (!v ? 'Required' : undefined),
585
- password: (v) => (!v ? 'Required' : undefined),
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('resetField resets a single field without affecting others', () => {
601
+ it("resetField resets a single field without affecting others", () => {
604
602
  const { result: form, unmount } = mountWith(() =>
605
603
  useForm<LoginForm>({
606
- initialValues: { email: '', password: '' },
604
+ initialValues: { email: "", password: "" },
607
605
  onSubmit: () => {
608
606
  /* noop */
609
607
  },
610
608
  }),
611
609
  )
612
610
 
613
- form.fields.email.setValue('changed')
614
- form.fields.password.setValue('changed')
611
+ form.fields.email.setValue("changed")
612
+ form.fields.password.setValue("changed")
615
613
  form.fields.email.setTouched()
616
614
 
617
- form.resetField('email')
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('changed')
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('isValidating tracks async validation state', async () => {
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 ? 'Required' : undefined
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('handleSubmit calls preventDefault on event', async () => {
649
+ it("handleSubmit calls preventDefault on event", async () => {
652
650
  const { result: form, unmount } = mountWith(() =>
653
651
  useForm<LoginForm>({
654
- initialValues: { email: 'a@b.com', password: '12345678' },
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('register() with checkbox type uses checked property', () => {
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('remember', { type: 'checkbox' })
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: 'on' },
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('register() with number type uses valueAsNumber when valid', () => {
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('age', { type: 'number' })
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: '25', valueAsNumber: 25 },
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: '', valueAsNumber: NaN },
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('register() returns same props for repeated calls (memoized)', () => {
724
+ it("register() returns same props for repeated calls (memoized)", () => {
727
725
  const { result: form, unmount } = mountWith(() =>
728
726
  useForm<LoginForm>({
729
- initialValues: { email: '', password: '' },
727
+ initialValues: { email: "", password: "" },
730
728
  onSubmit: () => {
731
729
  /* noop */
732
730
  },
733
731
  }),
734
732
  )
735
733
 
736
- const first = form.register('email')
737
- const second = form.register('email')
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('submitError captures onSubmit errors', async () => {
740
+ it("submitError captures onSubmit errors", async () => {
743
741
  const { result: form, unmount } = mountWith(() =>
744
742
  useForm<LoginForm>({
745
- initialValues: { email: 'a@b.com', password: '12345678' },
743
+ initialValues: { email: "a@b.com", password: "12345678" },
746
744
  onSubmit: async () => {
747
- throw new Error('Server 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('Server error')
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('dirty detection works for object field values', () => {
763
+ it("dirty detection works for object field values", () => {
766
764
  const { result: form, unmount } = mountWith(() =>
767
765
  useForm({
768
- initialValues: { address: { city: 'NYC', zip: '10001' } },
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: 'NYC', zip: '10001' })
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: 'LA', zip: '90001' })
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('dirty detection works for array field values', () => {
783
+ it("dirty detection works for array field values", () => {
786
784
  const { result: form, unmount } = mountWith(() =>
787
785
  useForm({
788
- initialValues: { tags: ['a', 'b'] },
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(['a', 'b'])
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(['a', 'b', 'c'])
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('useFieldArray', () => {
809
- it('initializes with provided values', () => {
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(['a', 'b', 'c'])
811
+ expect(arr.values()).toEqual(["a", "b", "c"])
816
812
  unmount()
817
813
  })
818
814
 
819
- it('initializes empty by default', () => {
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('append adds to end', () => {
828
- const { result: arr, unmount } = mountWith(() => useFieldArray(['a']))
823
+ it("append adds to end", () => {
824
+ const { result: arr, unmount } = mountWith(() => useFieldArray(["a"]))
829
825
 
830
- arr.append('b')
831
- expect(arr.values()).toEqual(['a', 'b'])
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('prepend adds to start', () => {
837
- const { result: arr, unmount } = mountWith(() => useFieldArray(['b']))
832
+ it("prepend adds to start", () => {
833
+ const { result: arr, unmount } = mountWith(() => useFieldArray(["b"]))
838
834
 
839
- arr.prepend('a')
840
- expect(arr.values()).toEqual(['a', 'b'])
835
+ arr.prepend("a")
836
+ expect(arr.values()).toEqual(["a", "b"])
841
837
  unmount()
842
838
  })
843
839
 
844
- it('insert at index', () => {
845
- const { result: arr, unmount } = mountWith(() => useFieldArray(['a', 'c']))
840
+ it("insert at index", () => {
841
+ const { result: arr, unmount } = mountWith(() => useFieldArray(["a", "c"]))
846
842
 
847
- arr.insert(1, 'b')
848
- expect(arr.values()).toEqual(['a', 'b', 'c'])
843
+ arr.insert(1, "b")
844
+ expect(arr.values()).toEqual(["a", "b", "c"])
849
845
  unmount()
850
846
  })
851
847
 
852
- it('remove by index', () => {
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(['a', 'c'])
852
+ expect(arr.values()).toEqual(["a", "c"])
859
853
  unmount()
860
854
  })
861
855
 
862
- it('move reorders items', () => {
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(['b', 'c', 'a'])
860
+ expect(arr.values()).toEqual(["b", "c", "a"])
869
861
  unmount()
870
862
  })
871
863
 
872
- it('swap exchanges items', () => {
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(['c', 'b', 'a'])
868
+ expect(arr.values()).toEqual(["c", "b", "a"])
879
869
  unmount()
880
870
  })
881
871
 
882
- it('replace replaces all items', () => {
883
- const { result: arr, unmount } = mountWith(() => useFieldArray(['a', 'b']))
872
+ it("replace replaces all items", () => {
873
+ const { result: arr, unmount } = mountWith(() => useFieldArray(["a", "b"]))
884
874
 
885
- arr.replace(['x', 'y', 'z'])
886
- expect(arr.values()).toEqual(['x', 'y', 'z'])
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('items have stable keys', () => {
892
- const { result: arr, unmount } = mountWith(() => useFieldArray(['a', 'b']))
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('c')
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('individual item values are reactive signals', () => {
908
- const { result: arr, unmount } = mountWith(() => useFieldArray(['a', 'b']))
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('a')
912
- item.value.set('updated')
913
- expect(item.value()).toBe('updated')
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('update modifies value at index', () => {
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, 'updated')
923
- expect(arr.values()).toEqual(['a', 'updated', 'c'])
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('updated')
915
+ expect(item.value()).toBe("updated")
928
916
  unmount()
929
917
  })
930
918
 
931
- it('update with invalid (out-of-bounds) index is a no-op', () => {
932
- const { result: arr, unmount } = mountWith(() => useFieldArray(['a', 'b']))
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, 'nope')
935
- expect(arr.values()).toEqual(['a', 'b'])
922
+ arr.update(99, "nope")
923
+ expect(arr.values()).toEqual(["a", "b"])
936
924
 
937
- arr.update(-1, 'nope')
938
- expect(arr.values()).toEqual(['a', 'b'])
925
+ arr.update(-1, "nope")
926
+ expect(arr.values()).toEqual(["a", "b"])
939
927
  unmount()
940
928
  })
941
929
 
942
- it('move with invalid from index does not insert undefined', () => {
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(['a', 'b', 'c'])
937
+ expect(arr.values()).toEqual(["a", "b", "c"])
952
938
  unmount()
953
939
  })
954
940
 
955
- it('swap with one invalid index is a no-op', () => {
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(['a', 'b', 'c'])
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(['a', 'b', 'c'])
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(['a', 'b', 'c'])
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('structuredEqual coverage via dirty tracking', () => {
978
- it('arrays with different lengths are detected as dirty', () => {
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: ['a', 'b'] as string[] },
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(['a', 'b', 'c'])
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(['a'])
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(['a', 'b'])
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('arrays with same length but different elements are detected as dirty', () => {
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: ['a', 'b', 'c'] as string[] },
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(['a', 'b', 'x'])
996
+ form.fields.items.setValue(["a", "b", "x"])
1013
997
  expect(form.fields.items.dirty()).toBe(true)
1014
998
 
1015
- form.fields.items.setValue(['x', 'b', 'c'])
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(['a', 'b', 'c'])
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('objects with different number of keys are detected as dirty', () => {
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('objects with same key count but different values are detected as dirty', () => {
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('null vs object is detected as dirty', () => {
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('validateOn: submit', () => {
1082
- it('does not validate on blur', async () => {
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 ? 'Required' : undefined
1074
+ return !v ? "Required" : undefined
1091
1075
  },
1092
1076
  },
1093
1077
  onSubmit: () => {
1094
1078
  /* noop */
1095
1079
  },
1096
- validateOn: 'submit',
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('hello')
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('validates only when handleSubmit is called', async () => {
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 ? 'Required' : undefined),
1104
+ name: (v) => (!v ? "Required" : undefined),
1121
1105
  },
1122
1106
  onSubmit: () => {
1123
1107
  submitted = true
1124
1108
  },
1125
- validateOn: 'submit',
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('Required')
1121
+ expect(form.fields.name.error()).toBe("Required")
1138
1122
 
1139
1123
  // Fix and resubmit
1140
- form.fields.name.setValue('hello')
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('debounceMs field validation', () => {
1150
- it('debounced validation on change mode', async () => {
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 ? 'Required' : undefined
1142
+ return !v ? "Required" : undefined
1159
1143
  },
1160
1144
  },
1161
1145
  onSubmit: () => {
1162
1146
  /* noop */
1163
1147
  },
1164
- validateOn: 'change',
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('a')
1171
- form.fields.name.setValue('ab')
1172
- form.fields.name.setValue('abc')
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('debounced validation resolves after timer fires', async () => {
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 ? 'Required' : undefined),
1174
+ name: (v) => (!v ? "Required" : undefined),
1191
1175
  },
1192
1176
  onSubmit: () => {
1193
1177
  /* noop */
1194
1178
  },
1195
- validateOn: 'blur',
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('Required')
1192
+ expect(form.fields.name.error()).toBe("Required")
1209
1193
  unmount()
1210
1194
  })
1211
1195
 
1212
- it('reset clears pending debounce timers', async () => {
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 ? 'Required' : undefined
1204
+ return !v ? "Required" : undefined
1221
1205
  },
1222
1206
  },
1223
1207
  onSubmit: () => {
1224
1208
  /* noop */
1225
1209
  },
1226
- validateOn: 'blur',
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('useForm nonexistent field operations', () => {
1246
- it('setFieldValue with nonexistent field throws', () => {
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: 'Alice' },
1233
+ initialValues: { name: "Alice" },
1250
1234
  onSubmit: () => {
1251
1235
  /* noop */
1252
1236
  },
1253
1237
  }),
1254
1238
  )
1255
1239
 
1256
- expect(() => form.setFieldValue('nonexistent' as any, 'value')).toThrow(
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('Alice')
1243
+ expect(form.fields.name.value()).toBe("Alice")
1260
1244
  unmount()
1261
1245
  })
1262
1246
 
1263
- it('setFieldError with nonexistent field throws', () => {
1247
+ it("setFieldError with nonexistent field throws", () => {
1264
1248
  const { result: form, unmount } = mountWith(() =>
1265
1249
  useForm({
1266
- initialValues: { name: 'Alice' },
1250
+ initialValues: { name: "Alice" },
1267
1251
  onSubmit: () => {
1268
1252
  /* noop */
1269
1253
  },
1270
1254
  }),
1271
1255
  )
1272
1256
 
1273
- expect(() => form.setFieldError('nonexistent' as any, 'error')).toThrow(
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('resetField with nonexistent field is a no-op', () => {
1264
+ it("resetField with nonexistent field is a no-op", () => {
1281
1265
  const { result: form, unmount } = mountWith(() =>
1282
1266
  useForm({
1283
- initialValues: { name: 'Alice' },
1267
+ initialValues: { name: "Alice" },
1284
1268
  onSubmit: () => {
1285
1269
  /* noop */
1286
1270
  },
1287
1271
  }),
1288
1272
  )
1289
1273
 
1290
- form.resetField('nonexistent' as any)
1291
- expect(form.fields.name.value()).toBe('Alice')
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('dirty detection with mixed types', () => {
1299
- it('number vs string is dirty', () => {
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('0' as any)
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('validate() branch coverage', () => {
1318
- it('getErrors returns empty when no errors exist', async () => {
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: 'valid', email: 'a@b.com' },
1305
+ initialValues: { name: "valid", email: "a@b.com" },
1322
1306
  validators: {
1323
- name: (v) => (!v ? 'Required' : undefined),
1324
- email: (v) => (!v ? 'Required' : undefined),
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('stale async field-level validation on blur is discarded', async () => {
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: 'blur',
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]!('Stale error from blur')
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('stale async validation results are discarded during validate()', async () => {
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]!('Stale error')
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('field-level validator throwing during validate() captures error', async () => {
1394
+ it("field-level validator throwing during validate() captures error", async () => {
1411
1395
  const { result: form, unmount } = mountWith(() =>
1412
1396
  useForm({
1413
- initialValues: { name: 'Alice' },
1397
+ initialValues: { name: "Alice" },
1414
1398
  validators: {
1415
1399
  name: () => {
1416
- throw new Error('Validator crashed')
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('Validator crashed')
1411
+ expect(form.fields.name.error()).toBe("Validator crashed")
1428
1412
  unmount()
1429
1413
  })
1430
1414
 
1431
- it('field-level validator throwing on blur captures error', async () => {
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('Blur validator crashed')
1421
+ throw new Error("Blur validator crashed")
1438
1422
  },
1439
1423
  },
1440
- validateOn: 'blur',
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('Blur validator crashed')
1433
+ expect(form.fields.name.error()).toBe("Blur validator crashed")
1450
1434
  unmount()
1451
1435
  })
1452
1436
 
1453
- it('schema validator with keys having undefined value does not block submit', async () => {
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: 'Alice', email: 'a@b.com' },
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('schema validator throwing sets submitError and returns false', async () => {
1458
+ it("schema validator throwing sets submitError and returns false", async () => {
1475
1459
  const { result: form, unmount } = mountWith(() =>
1476
1460
  useForm({
1477
- initialValues: { name: 'Alice', email: 'a@b.com' },
1461
+ initialValues: { name: "Alice", email: "a@b.com" },
1478
1462
  schema: () => {
1479
- throw new Error('Schema exploded')
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('Schema exploded')
1474
+ expect((form.submitError() as Error).message).toBe("Schema exploded")
1491
1475
  unmount()
1492
1476
  })
1493
1477
 
1494
- it('fields without validators return undefined in validate()', async () => {
1478
+ it("fields without validators return undefined in validate()", async () => {
1495
1479
  const { result: form, unmount } = mountWith(() =>
1496
1480
  useForm({
1497
- initialValues: { name: 'Alice', noValidator: 'test' },
1481
+ initialValues: { name: "Alice", noValidator: "test" },
1498
1482
  validators: {
1499
- name: (v) => (!v ? 'Required' : undefined),
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('debounceMs with validateOn change', () => {
1518
- it('debounces validation on change', async () => {
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 ? 'Too short' : undefined
1510
+ return v.length < 3 ? "Too short" : undefined
1527
1511
  },
1528
1512
  },
1529
- validateOn: 'change',
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('a')
1538
- form.fields.name.setValue('ab')
1539
- form.fields.name.setValue('abc')
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('useField', () => {
1555
- it('extracts a single field from a form', () => {
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: '', password: '' },
1542
+ initialValues: { email: "", password: "" },
1559
1543
  onSubmit: () => {
1560
1544
  /* noop */
1561
1545
  },
1562
1546
  })
1563
- const field = useField(form, 'email')
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('test@test.com')
1569
- expect(result.form.fields.email.value()).toBe('test@test.com')
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('hasError and showError computed correctly', async () => {
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 ? 'Required' : undefined) },
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, 'email')
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('register() delegates to form.register()', () => {
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, 'email')
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('email')
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('register() with checkbox type', () => {
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, 'remember')
1611
+ const field = useField(form, "remember")
1628
1612
  return { form, field }
1629
1613
  })
1630
1614
 
1631
- const props = result.field.register({ type: 'checkbox' })
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('reset delegates to field reset', () => {
1621
+ it("reset delegates to field reset", () => {
1638
1622
  const { result, unmount } = mountWith(() => {
1639
1623
  const form = useForm({
1640
- initialValues: { name: 'initial' },
1624
+ initialValues: { name: "initial" },
1641
1625
  onSubmit: () => {
1642
1626
  /* noop */
1643
1627
  },
1644
1628
  })
1645
- const field = useField(form, 'name')
1629
+ const field = useField(form, "name")
1646
1630
  return { form, field }
1647
1631
  })
1648
1632
 
1649
- result.field.setValue('changed')
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('initial')
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('useWatch', () => {
1665
- it('watches a single field value', () => {
1648
+ describe("useWatch", () => {
1649
+ it("watches a single field value", () => {
1666
1650
  const { result, unmount } = mountWith(() => {
1667
1651
  const form = useForm({
1668
- initialValues: { email: 'a@b.com', password: '' },
1652
+ initialValues: { email: "a@b.com", password: "" },
1669
1653
  onSubmit: () => {
1670
1654
  /* noop */
1671
1655
  },
1672
1656
  })
1673
- const email = useWatch(form, 'email')
1657
+ const email = useWatch(form, "email")
1674
1658
  return { form, email }
1675
1659
  })
1676
1660
 
1677
- expect(result.email()).toBe('a@b.com')
1678
- result.form.fields.email.setValue('new@email.com')
1679
- expect(result.email()).toBe('new@email.com')
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('watches multiple fields', () => {
1667
+ it("watches multiple fields", () => {
1684
1668
  const { result, unmount } = mountWith(() => {
1685
1669
  const form = useForm({
1686
- initialValues: { first: 'John', last: 'Doe' },
1670
+ initialValues: { first: "John", last: "Doe" },
1687
1671
  onSubmit: () => {
1688
1672
  /* noop */
1689
1673
  },
1690
1674
  })
1691
- const [first, last] = useWatch(form, ['first', 'last'])
1675
+ const [first, last] = useWatch(form, ["first", "last"])
1692
1676
  return { form, first, last }
1693
1677
  })
1694
1678
 
1695
- expect(result.first!()).toBe('John')
1696
- expect(result.last!()).toBe('Doe')
1697
- result.form.fields.first.setValue('Jane')
1698
- expect(result.first!()).toBe('Jane')
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('watches all fields when no name provided', () => {
1686
+ it("watches all fields when no name provided", () => {
1703
1687
  const { result, unmount } = mountWith(() => {
1704
1688
  const form = useForm({
1705
- initialValues: { email: 'a@b.com', name: 'Alice' },
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: 'a@b.com', name: 'Alice' })
1715
- result.form.fields.email.setValue('new@email.com')
1716
- expect(result.all()).toEqual({ email: 'new@email.com', name: 'Alice' })
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('useFormState', () => {
1724
- it('returns full form state summary', async () => {
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: '', password: '' },
1728
- validators: { email: (v) => (!v ? 'Required' : undefined) },
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('test')
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('works with selector for fine-grained reactivity', async () => {
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 ? 'Required' : undefined) },
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('test@test.com')
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('tracks errors in summary', async () => {
1770
+ it("tracks errors in summary", async () => {
1787
1771
  const { result, unmount } = mountWith(() => {
1788
1772
  const form = useForm({
1789
- initialValues: { email: '', name: '' },
1773
+ initialValues: { email: "", name: "" },
1790
1774
  validators: {
1791
- email: (v) => (!v ? 'Email required' : undefined),
1792
- name: (v) => (!v ? 'Name required' : undefined),
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: 'Email required', name: 'Name required' })
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('FormProvider / useFormContext', () => {
1813
- it('provides form through context', () => {
1796
+ describe("FormProvider / useFormContext", () => {
1797
+ it("provides form through context", () => {
1814
1798
  let contextForm: FormState<{ email: string }> | undefined
1815
- const el = document.createElement('div')
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: 'context@test.com' },
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('context@test.com')
1820
+ expect(contextForm!.fields.email.value()).toBe("context@test.com")
1841
1821
  unmount()
1842
1822
  el.remove()
1843
1823
  })
1844
1824
 
1845
- it('throws when useFormContext is called outside FormProvider', () => {
1846
- const el = document.createElement('div')
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('useFormContext')
1865
- expect(error!.message).toContain('FormProvider')
1844
+ expect(error!.message).toContain("useFormContext")
1845
+ expect(error!.message).toContain("FormProvider")
1866
1846
  unmount()
1867
1847
  el.remove()
1868
1848
  })