@pyreon/form 0.11.4 → 0.11.6

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