@tanstack/vue-form 0.23.2 → 0.23.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/vue-form",
3
- "version": "0.23.2",
3
+ "version": "0.23.3",
4
4
  "description": "Powerful, type-safe forms for Vue.",
5
5
  "author": "tannerlinsley",
6
6
  "license": "MIT",
@@ -38,7 +38,7 @@
38
38
  ],
39
39
  "dependencies": {
40
40
  "@tanstack/vue-store": "^0.5.0",
41
- "@tanstack/form-core": "0.23.2"
41
+ "@tanstack/form-core": "0.23.3"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@vitejs/plugin-vue": "^5.0.4",
@@ -1,447 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest'
2
- import { defineComponent, h } from 'vue'
3
- import { render, waitFor } from '@testing-library/vue'
4
- import '@testing-library/jest-dom/vitest'
5
- import userEvent from '@testing-library/user-event'
6
- import { useForm } from '../index'
7
- import { sleep } from './utils'
8
- import type { FieldApi } from '../index'
9
-
10
- const user = userEvent.setup()
11
-
12
- describe('useField', () => {
13
- it('should allow to set default value', async () => {
14
- type Person = {
15
- firstName: string
16
- lastName: string
17
- }
18
-
19
- const Comp = defineComponent(() => {
20
- const form = useForm<Person>()
21
-
22
- return () => (
23
- <form.Field name="firstName" defaultValue="FirstName">
24
- {({
25
- field,
26
- }: {
27
- field: FieldApi<Person, 'firstName', never, never>
28
- }) => (
29
- <input
30
- data-testid={'fieldinput'}
31
- value={field.state.value}
32
- onBlur={field.handleBlur}
33
- onInput={(e) =>
34
- field.handleChange((e.target as HTMLInputElement).value)
35
- }
36
- />
37
- )}
38
- </form.Field>
39
- )
40
- })
41
-
42
- const { getByTestId } = render(Comp)
43
- const input = getByTestId('fieldinput')
44
- await waitFor(() => expect(input).toHaveValue('FirstName'))
45
- })
46
-
47
- it('should not validate on change if isTouched is false', async () => {
48
- type Person = {
49
- firstName: string
50
- lastName: string
51
- }
52
- const error = 'Please enter a different value'
53
-
54
- const Comp = defineComponent(() => {
55
- const form = useForm<Person>()
56
-
57
- return () => (
58
- <form.Field
59
- name="firstName"
60
- validators={{
61
- onChange: ({ value }) => (value === 'other' ? error : undefined),
62
- }}
63
- >
64
- {({
65
- field,
66
- }: {
67
- field: FieldApi<Person, 'firstName', never, never>
68
- }) => (
69
- <div>
70
- <input
71
- data-testid="fieldinput"
72
- name={field.name}
73
- value={field.state.value}
74
- onBlur={field.handleBlur}
75
- onInput={(e) =>
76
- field.setValue((e.target as HTMLInputElement).value)
77
- }
78
- />
79
- <p>{field.getMeta().errors}</p>
80
- </div>
81
- )}
82
- </form.Field>
83
- )
84
- })
85
-
86
- const { getByTestId, queryByText } = render(Comp)
87
- const input = getByTestId('fieldinput')
88
- await user.type(input, 'other')
89
- expect(queryByText(error)).not.toBeInTheDocument()
90
- })
91
-
92
- it('should validate on change if isTouched is true', async () => {
93
- type Person = {
94
- firstName: string
95
- lastName: string
96
- }
97
- const error = 'Please enter a different value'
98
-
99
- const Comp = defineComponent(() => {
100
- const form = useForm<Person>()
101
-
102
- return () => (
103
- <form.Field
104
- name="firstName"
105
- validators={{
106
- onChange: ({ value }) => (value === 'other' ? error : undefined),
107
- }}
108
- >
109
- {({
110
- field,
111
- }: {
112
- field: FieldApi<Person, 'firstName', never, never>
113
- }) => (
114
- <div>
115
- <input
116
- data-testid="fieldinput"
117
- name={field.name}
118
- value={field.state.value}
119
- onBlur={field.handleBlur}
120
- onInput={(e) =>
121
- field.handleChange((e.target as HTMLInputElement).value)
122
- }
123
- />
124
- <p>{field.getMeta().errors}</p>
125
- </div>
126
- )}
127
- </form.Field>
128
- )
129
- })
130
-
131
- const { getByTestId, getByText, queryByText } = render(Comp)
132
- const input = getByTestId('fieldinput')
133
- expect(queryByText(error)).not.toBeInTheDocument()
134
- await user.type(input, 'other')
135
- expect(getByText(error)).toBeInTheDocument()
136
- })
137
-
138
- it('should validate async on change', async () => {
139
- type Person = {
140
- firstName: string
141
- lastName: string
142
- }
143
- const error = 'Please enter a different value'
144
-
145
- const Comp = defineComponent(() => {
146
- const form = useForm<Person>()
147
-
148
- return () => (
149
- <form.Field
150
- name="firstName"
151
- defaultMeta={{ isTouched: true }}
152
- validators={{
153
- onChangeAsync: async () => {
154
- await sleep(10)
155
- return error
156
- },
157
- }}
158
- >
159
- {({
160
- field,
161
- }: {
162
- field: FieldApi<Person, 'firstName', never, never>
163
- }) => (
164
- <div>
165
- <input
166
- data-testid="fieldinput"
167
- name={field.name}
168
- value={field.state.value}
169
- onBlur={field.handleBlur}
170
- onInput={(e) =>
171
- field.handleChange((e.target as HTMLInputElement).value)
172
- }
173
- />
174
- <p>{field.getMeta().errors}</p>
175
- </div>
176
- )}
177
- </form.Field>
178
- )
179
- })
180
-
181
- const { getByTestId, getByText, queryByText } = render(Comp)
182
- const input = getByTestId('fieldinput')
183
- expect(queryByText(error)).not.toBeInTheDocument()
184
- await user.type(input, 'other')
185
- await waitFor(() => getByText(error))
186
- expect(getByText(error)).toBeInTheDocument()
187
- })
188
-
189
- it('should validate async on change with debounce', async () => {
190
- type Person = {
191
- firstName: string
192
- lastName: string
193
- }
194
-
195
- const mockFn = vi.fn()
196
- const error = 'Please enter a different value'
197
-
198
- const Comp = defineComponent(() => {
199
- const form = useForm<Person>()
200
-
201
- return () => (
202
- <form.Field
203
- name="firstName"
204
- defaultMeta={{ isTouched: true }}
205
- validators={{
206
- onChangeAsyncDebounceMs: 100,
207
- onChangeAsync: async () => {
208
- mockFn()
209
- await sleep(10)
210
- return error
211
- },
212
- }}
213
- >
214
- {({
215
- field,
216
- }: {
217
- field: FieldApi<Person, 'firstName', never, never>
218
- }) => (
219
- <div>
220
- <input
221
- data-testid="fieldinput"
222
- name={field.name}
223
- value={field.state.value}
224
- onBlur={field.handleBlur}
225
- onInput={(e) =>
226
- field.handleChange((e.target as HTMLInputElement).value)
227
- }
228
- />
229
- <p>{field.getMeta().errors}</p>
230
- </div>
231
- )}
232
- </form.Field>
233
- )
234
- })
235
-
236
- const { getByTestId, getByText } = render(<Comp />)
237
- const input = getByTestId('fieldinput')
238
- await user.type(input, 'other')
239
- // mockFn will have been called 5 times without onChangeAsyncDebounceMs
240
- expect(mockFn).toHaveBeenCalledTimes(0)
241
- await waitFor(() => getByText(error))
242
- expect(getByText(error)).toBeInTheDocument()
243
- })
244
-
245
- it('should handle arrays with subvalues', async () => {
246
- const fn = vi.fn()
247
-
248
- type CompVal = { people: Array<string> }
249
-
250
- const Comp = defineComponent(() => {
251
- const form = useForm({
252
- defaultValues: {
253
- people: [],
254
- } as CompVal,
255
- onSubmit: ({ value }) => fn(value),
256
- })
257
-
258
- return () => (
259
- <div>
260
- <form
261
- onSubmit={(e) => {
262
- e.preventDefault()
263
- e.stopPropagation()
264
- form.handleSubmit()
265
- }}
266
- >
267
- <form.Field name="people">
268
- {({
269
- field,
270
- }: {
271
- field: FieldApi<CompVal, 'people', never, never>
272
- }) => (
273
- <div>
274
- {field.state.value.map((_, i) => {
275
- return (
276
- <form.Field key={i} name={`people[${i}]`}>
277
- {({
278
- field: subField,
279
- }: {
280
- field: FieldApi<
281
- CompVal,
282
- `people[${number}]`,
283
- never,
284
- never
285
- >
286
- }) => (
287
- <div>
288
- <label>
289
- <div>Name for person {i}</div>
290
- <input
291
- value={subField.state.value}
292
- onChange={(e) =>
293
- subField.handleChange(
294
- (e.target as HTMLInputElement).value,
295
- )
296
- }
297
- />
298
- </label>
299
- <button
300
- onClick={() => field.removeValue(i)}
301
- type="button"
302
- >
303
- Remove person {i}
304
- </button>
305
- </div>
306
- )}
307
- </form.Field>
308
- )
309
- })}
310
- <button onClick={() => field.pushValue('')} type="button">
311
- Add person
312
- </button>
313
- </div>
314
- )}
315
- </form.Field>
316
- <button type="submit">Submit</button>
317
- </form>
318
- </div>
319
- )
320
- })
321
-
322
- const { queryByText, getByText, findByLabelText, findByText } = render(Comp)
323
- expect(queryByText('Name for person 0')).not.toBeInTheDocument()
324
- expect(queryByText('Name for person 1')).not.toBeInTheDocument()
325
- await user.click(getByText('Add person'))
326
- const input = await findByLabelText('Name for person 0')
327
- expect(input).toBeInTheDocument()
328
- await user.type(input, 'John')
329
-
330
- await user.click(getByText('Add person'))
331
- const input2 = await findByLabelText('Name for person 1')
332
- expect(input).toBeInTheDocument()
333
- await user.type(input2, 'Jack')
334
-
335
- expect(queryByText('Name for person 0')).toBeInTheDocument()
336
- expect(queryByText('Name for person 1')).toBeInTheDocument()
337
- await user.click(getByText('Remove person 1'))
338
- expect(queryByText('Name for person 0')).toBeInTheDocument()
339
- expect(queryByText('Name for person 1')).not.toBeInTheDocument()
340
-
341
- await user.click(await findByText('Submit'))
342
- expect(fn).toHaveBeenCalledWith({ people: ['John'] })
343
- })
344
-
345
- it('should handle arrays with subvalues', async () => {
346
- const fn = vi.fn()
347
-
348
- type CompVal = { people: Array<{ age: number; name: string }> }
349
-
350
- const Comp = defineComponent(() => {
351
- const form = useForm({
352
- defaultValues: {
353
- people: [],
354
- } as CompVal,
355
- onSubmit: ({ value }) => fn(value),
356
- })
357
-
358
- return () => (
359
- <div>
360
- <form
361
- onSubmit={(e) => {
362
- e.preventDefault()
363
- e.stopPropagation()
364
- form.handleSubmit()
365
- }}
366
- >
367
- <form.Field name="people">
368
- {({
369
- field,
370
- }: {
371
- field: FieldApi<CompVal, 'people', never, never>
372
- }) => (
373
- <div>
374
- {field.state.value.map((_, i) => {
375
- return (
376
- <form.Field key={i} name={`people[${i}].name`}>
377
- {({
378
- field: subField,
379
- }: {
380
- field: FieldApi<
381
- CompVal,
382
- `people[${number}].name`,
383
- never,
384
- never
385
- >
386
- }) => (
387
- <div>
388
- <label>
389
- <div>Name for person {i}</div>
390
- <input
391
- value={subField.state.value}
392
- onChange={(e) =>
393
- subField.handleChange(
394
- (e.target as HTMLInputElement).value,
395
- )
396
- }
397
- />
398
- </label>
399
- <button
400
- onClick={() => field.removeValue(i)}
401
- type="button"
402
- >
403
- Remove person {i}
404
- </button>
405
- </div>
406
- )}
407
- </form.Field>
408
- )
409
- })}
410
- <button
411
- onClick={() => field.pushValue({ name: '', age: 0 })}
412
- type="button"
413
- >
414
- Add person
415
- </button>
416
- </div>
417
- )}
418
- </form.Field>
419
- <button type="submit">Submit</button>
420
- </form>
421
- </div>
422
- )
423
- })
424
-
425
- const { queryByText, getByText, findByLabelText, findByText } = render(Comp)
426
- expect(queryByText('Name for person 0')).not.toBeInTheDocument()
427
- expect(queryByText('Name for person 1')).not.toBeInTheDocument()
428
- await user.click(getByText('Add person'))
429
- const input = await findByLabelText('Name for person 0')
430
- expect(input).toBeInTheDocument()
431
- await user.type(input, 'John')
432
-
433
- await user.click(getByText('Add person'))
434
- const input2 = await findByLabelText('Name for person 1')
435
- expect(input).toBeInTheDocument()
436
- await user.type(input2, 'Jack')
437
-
438
- expect(queryByText('Name for person 0')).toBeInTheDocument()
439
- expect(queryByText('Name for person 1')).toBeInTheDocument()
440
- await user.click(getByText('Remove person 1'))
441
- expect(queryByText('Name for person 0')).toBeInTheDocument()
442
- expect(queryByText('Name for person 1')).not.toBeInTheDocument()
443
-
444
- await user.click(await findByText('Submit'))
445
- expect(fn).toHaveBeenCalledWith({ people: [{ name: 'John', age: 0 }] })
446
- })
447
- })
@@ -1,511 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest'
2
- import userEvent from '@testing-library/user-event'
3
- import { render, waitFor } from '@testing-library/vue'
4
- import { defineComponent, h, ref } from 'vue'
5
- import { useForm } from '../index'
6
- import { sleep } from './utils'
7
-
8
- import type { FieldApi, ValidationErrorMap } from '../index'
9
-
10
- const user = userEvent.setup()
11
-
12
- type Person = {
13
- firstName: string
14
- lastName: string
15
- }
16
-
17
- describe('useForm', () => {
18
- it('preserved field state', async () => {
19
- const Comp = defineComponent(() => {
20
- const form = useForm<Person>()
21
-
22
- return () => (
23
- <form.Field name="firstName" defaultValue="">
24
- {({
25
- field,
26
- }: {
27
- field: FieldApi<Person, 'firstName', never, never>
28
- }) => (
29
- <input
30
- data-testid={'fieldinput'}
31
- value={field.state.value}
32
- onBlur={field.handleBlur}
33
- onInput={(e) =>
34
- field.handleChange((e.target as HTMLInputElement).value)
35
- }
36
- />
37
- )}
38
- </form.Field>
39
- )
40
- })
41
-
42
- const { getByTestId, queryByText } = render(<Comp />)
43
- const input = getByTestId('fieldinput')
44
- expect(queryByText('FirstName')).not.toBeInTheDocument()
45
- await user.type(input, 'FirstName')
46
- expect(input).toHaveValue('FirstName')
47
- })
48
-
49
- it('should allow default values to be set', async () => {
50
- const Comp = defineComponent(() => {
51
- const form = useForm({
52
- defaultValues: {
53
- firstName: 'FirstName',
54
- lastName: 'LastName',
55
- } as Person,
56
- })
57
-
58
- return () => (
59
- <form.Field name="firstName">
60
- {({
61
- field,
62
- }: {
63
- field: FieldApi<Person, 'firstName', never, never>
64
- }) => <p>{field.state.value}</p>}
65
- </form.Field>
66
- )
67
- })
68
-
69
- const { findByText, queryByText } = render(<Comp />)
70
- expect(await findByText('FirstName')).toBeInTheDocument()
71
- expect(queryByText('LastName')).not.toBeInTheDocument()
72
- })
73
-
74
- it('should handle submitting properly', async () => {
75
- const Comp = defineComponent(() => {
76
- const submittedData = ref<{ firstName: string }>()
77
-
78
- const form = useForm({
79
- defaultValues: {
80
- firstName: 'FirstName',
81
- },
82
- onSubmit: ({ value }) => {
83
- submittedData.value = value
84
- },
85
- })
86
-
87
- return () => (
88
- <div>
89
- <form.Field name="firstName">
90
- {({
91
- field,
92
- }: {
93
- field: FieldApi<Person, 'firstName', never, never>
94
- }) => {
95
- return (
96
- <input
97
- value={field.state.value}
98
- onBlur={field.handleBlur}
99
- onInput={(e) =>
100
- field.handleChange((e.target as HTMLInputElement).value)
101
- }
102
- placeholder={'First name'}
103
- />
104
- )
105
- }}
106
- </form.Field>
107
- <button onClick={form.handleSubmit}>Submit</button>
108
- {submittedData.value && (
109
- <p>Submitted data: {submittedData.value.firstName}</p>
110
- )}
111
- </div>
112
- )
113
- })
114
-
115
- const { findByPlaceholderText, getByText } = render(<Comp />)
116
- const input = await findByPlaceholderText('First name')
117
- await user.clear(input)
118
- await user.type(input, 'OtherName')
119
- await user.click(getByText('Submit'))
120
- await waitFor(() =>
121
- expect(getByText('Submitted data: OtherName')).toBeInTheDocument(),
122
- )
123
- })
124
-
125
- it('should run on form mount', async () => {
126
- const Comp = defineComponent(() => {
127
- const formMounted = ref(false)
128
- const mountForm = ref(false)
129
-
130
- const form = useForm({
131
- defaultValues: {
132
- firstName: 'FirstName',
133
- },
134
- validators: {
135
- onMount: () => {
136
- formMounted.value = true
137
- return undefined
138
- },
139
- },
140
- })
141
-
142
- return () =>
143
- mountForm.value ? (
144
- <div>
145
- <h1>{formMounted.value ? 'Form mounted' : 'Not mounted'}</h1>
146
- </div>
147
- ) : (
148
- <button onClick={() => (mountForm.value = true)}>Mount form</button>
149
- )
150
- })
151
-
152
- const { getByText, findByText } = render(<Comp />)
153
- await user.click(getByText('Mount form'))
154
- expect(await findByText('Form mounted')).toBeInTheDocument()
155
- })
156
-
157
- it('should validate async on change for the form', async () => {
158
- const error = 'Please enter a different value'
159
-
160
- const Comp = defineComponent(() => {
161
- const form = useForm<Person>({
162
- validators: {
163
- onChange() {
164
- return error
165
- },
166
- },
167
- })
168
-
169
- return () => (
170
- <div>
171
- <form.Field name="firstName">
172
- {({
173
- field,
174
- }: {
175
- field: FieldApi<Person, 'firstName', never, never>
176
- }) => (
177
- <input
178
- data-testid="fieldinput"
179
- name={field.name}
180
- value={field.state.value}
181
- onBlur={field.handleBlur}
182
- onInput={(e) =>
183
- field.handleChange((e.target as HTMLInputElement).value)
184
- }
185
- />
186
- )}
187
- </form.Field>
188
- <form.Subscribe selector={(state) => state.errorMap}>
189
- {(errorMap: ValidationErrorMap) => <p>{errorMap.onChange}</p>}
190
- </form.Subscribe>
191
- </div>
192
- )
193
- })
194
-
195
- const { getByTestId, getByText, queryByText } = render(<Comp />)
196
- const input = getByTestId('fieldinput')
197
- expect(queryByText(error)).not.toBeInTheDocument()
198
- await user.type(input, 'other')
199
- await waitFor(() => getByText(error))
200
- expect(getByText(error)).toBeInTheDocument()
201
- })
202
- it('should not validate on change if isTouched is false', async () => {
203
- const error = 'Please enter a different value'
204
-
205
- const Comp = defineComponent(() => {
206
- const form = useForm<Person>({
207
- validators: {
208
- onChange: ({ value }) =>
209
- value.firstName === 'other' ? error : undefined,
210
- },
211
- })
212
-
213
- const errors = form.useStore((s) => s.errors)
214
-
215
- return () => (
216
- <div>
217
- <form.Field name="firstName">
218
- {({
219
- field,
220
- }: {
221
- field: FieldApi<Person, 'firstName', never, never>
222
- }) => (
223
- <div>
224
- <input
225
- data-testid="fieldinput"
226
- name={field.name}
227
- value={field.state.value}
228
- onBlur={field.handleBlur}
229
- onInput={(e) =>
230
- field.setValue((e.target as HTMLInputElement).value)
231
- }
232
- />
233
- </div>
234
- )}
235
- </form.Field>
236
- <p>{errors}</p>
237
- </div>
238
- )
239
- })
240
-
241
- const { getByTestId, queryByText } = render(<Comp />)
242
- const input = getByTestId('fieldinput')
243
- await user.type(input, 'other')
244
- expect(queryByText(error)).not.toBeInTheDocument()
245
- })
246
-
247
- it('should validate on change if isTouched is true', async () => {
248
- const error = 'Please enter a different value'
249
-
250
- const Comp = defineComponent(() => {
251
- const form = useForm<Person>({
252
- validators: {
253
- onChange: ({ value }) =>
254
- value.firstName === 'other' ? error : undefined,
255
- },
256
- })
257
-
258
- const errors = form.useStore((s) => s.errorMap)
259
-
260
- return () => (
261
- <div>
262
- <form.Field name="firstName" defaultMeta={{ isTouched: true }}>
263
- {({
264
- field,
265
- }: {
266
- field: FieldApi<Person, 'firstName', never, never>
267
- }) => (
268
- <div>
269
- <input
270
- data-testid="fieldinput"
271
- name={field.name}
272
- value={field.state.value}
273
- onBlur={field.handleBlur}
274
- onInput={(e) =>
275
- field.handleChange((e.target as HTMLInputElement).value)
276
- }
277
- />
278
- <p>{errors.value.onChange}</p>
279
- </div>
280
- )}
281
- </form.Field>
282
- </div>
283
- )
284
- })
285
-
286
- const { getByTestId, getByText, queryByText } = render(<Comp />)
287
- const input = getByTestId('fieldinput')
288
- expect(queryByText(error)).not.toBeInTheDocument()
289
- await user.type(input, 'other')
290
- expect(getByText(error)).toBeInTheDocument()
291
- })
292
-
293
- it('should validate on change and on blur', async () => {
294
- const onChangeError = 'Please enter a different value (onChangeError)'
295
- const onBlurError = 'Please enter a different value (onBlurError)'
296
-
297
- const Comp = defineComponent(() => {
298
- const form = useForm({
299
- defaultValues: {
300
- firstName: '',
301
- },
302
- validators: {
303
- onChange: ({ value }) => {
304
- if (value.firstName === 'other') return onChangeError
305
- return undefined
306
- },
307
- onBlur: ({ value }) => {
308
- if (value.firstName === 'other') return onBlurError
309
- return undefined
310
- },
311
- },
312
- })
313
-
314
- const errors = form.useStore((s) => s.errorMap)
315
-
316
- return () => (
317
- <div>
318
- <form.Field name="firstName" defaultMeta={{ isTouched: true }}>
319
- {({
320
- field,
321
- }: {
322
- field: FieldApi<Person, 'firstName', never, never>
323
- }) => (
324
- <div>
325
- <input
326
- data-testid="fieldinput"
327
- name={field.name}
328
- value={field.state.value}
329
- onBlur={field.handleBlur}
330
- onInput={(e) =>
331
- field.handleChange((e.target as HTMLInputElement).value)
332
- }
333
- />
334
- <p>{errors.value.onChange}</p>
335
- <p>{errors.value.onBlur}</p>
336
- </div>
337
- )}
338
- </form.Field>
339
- </div>
340
- )
341
- })
342
- const { getByTestId, getByText, queryByText } = render(<Comp />)
343
- const input = getByTestId('fieldinput')
344
- expect(queryByText(onChangeError)).not.toBeInTheDocument()
345
- expect(queryByText(onBlurError)).not.toBeInTheDocument()
346
- await user.type(input, 'other')
347
- expect(getByText(onChangeError)).toBeInTheDocument()
348
- await user.click(document.body)
349
- expect(queryByText(onBlurError)).toBeInTheDocument()
350
- })
351
-
352
- it('should validate async on change', async () => {
353
- const error = 'Please enter a different value'
354
-
355
- const Comp = defineComponent(() => {
356
- const form = useForm<Person>({
357
- validators: {
358
- onChangeAsync: async () => {
359
- await sleep(10)
360
- return error
361
- },
362
- },
363
- })
364
-
365
- const errors = form.useStore((s) => s.errorMap)
366
-
367
- return () => (
368
- <div>
369
- <form.Field name="firstName" defaultMeta={{ isTouched: true }}>
370
- {({
371
- field,
372
- }: {
373
- field: FieldApi<Person, 'firstName', never, never>
374
- }) => (
375
- <div>
376
- <input
377
- data-testid="fieldinput"
378
- name={field.name}
379
- value={field.state.value}
380
- onBlur={field.handleBlur}
381
- onInput={(e) =>
382
- field.handleChange((e.target as HTMLInputElement).value)
383
- }
384
- />
385
- <p>{errors.value.onChange}</p>
386
- </div>
387
- )}
388
- </form.Field>
389
- </div>
390
- )
391
- })
392
-
393
- const { getByTestId, getByText, queryByText } = render(<Comp />)
394
- const input = getByTestId('fieldinput')
395
- expect(queryByText(error)).not.toBeInTheDocument()
396
- await user.type(input, 'other')
397
- await waitFor(() => getByText(error))
398
- expect(getByText(error)).toBeInTheDocument()
399
- })
400
-
401
- it('should validate async on change and async on blur', async () => {
402
- const onChangeError = 'Please enter a different value (onChangeError)'
403
- const onBlurError = 'Please enter a different value (onBlurError)'
404
-
405
- const Comp = defineComponent(() => {
406
- const form = useForm<Person>({
407
- validators: {
408
- onChangeAsync: async () => {
409
- await sleep(10)
410
- return onChangeError
411
- },
412
- onBlurAsync: async () => {
413
- await sleep(10)
414
- return onBlurError
415
- },
416
- },
417
- })
418
- const errors = form.useStore((s) => s.errorMap)
419
-
420
- return () => (
421
- <div>
422
- <form.Field name="firstName" defaultMeta={{ isTouched: true }}>
423
- {({
424
- field,
425
- }: {
426
- field: FieldApi<Person, 'firstName', never, never>
427
- }) => (
428
- <div>
429
- <input
430
- data-testid="fieldinput"
431
- name={field.name}
432
- value={field.state.value}
433
- onBlur={field.handleBlur}
434
- onInput={(e) =>
435
- field.handleChange((e.target as HTMLInputElement).value)
436
- }
437
- />
438
- <p>{errors.value.onChange}</p>
439
- <p>{errors.value.onBlur}</p>
440
- </div>
441
- )}
442
- </form.Field>
443
- </div>
444
- )
445
- })
446
-
447
- const { getByTestId, getByText, queryByText } = render(<Comp />)
448
- const input = getByTestId('fieldinput')
449
-
450
- expect(queryByText(onChangeError)).not.toBeInTheDocument()
451
- expect(queryByText(onBlurError)).not.toBeInTheDocument()
452
- await user.type(input, 'other')
453
- await waitFor(() => getByText(onChangeError))
454
- expect(getByText(onChangeError)).toBeInTheDocument()
455
- await user.click(document.body)
456
- await waitFor(() => getByText(onBlurError))
457
- expect(getByText(onBlurError)).toBeInTheDocument()
458
- })
459
-
460
- it('should validate async on change with debounce', async () => {
461
- const mockFn = vi.fn()
462
- const error = 'Please enter a different value'
463
-
464
- const Comp = defineComponent(() => {
465
- const form = useForm<Person>({
466
- validators: {
467
- onChangeAsyncDebounceMs: 100,
468
- onChangeAsync: async () => {
469
- mockFn()
470
- await sleep(10)
471
- return error
472
- },
473
- },
474
- })
475
- const errors = form.useStore((s) => s.errors)
476
-
477
- return () => (
478
- <div>
479
- <form.Field name="firstName" defaultMeta={{ isTouched: true }}>
480
- {({
481
- field,
482
- }: {
483
- field: FieldApi<Person, 'firstName', never, never>
484
- }) => (
485
- <div>
486
- <input
487
- data-testid="fieldinput"
488
- name={field.name}
489
- value={field.state.value}
490
- onBlur={field.handleBlur}
491
- onInput={(e) =>
492
- field.handleChange((e.target as HTMLInputElement).value)
493
- }
494
- />
495
- <p>{errors.value.join(',')}</p>
496
- </div>
497
- )}
498
- </form.Field>
499
- </div>
500
- )
501
- })
502
-
503
- const { getByTestId, getByText } = render(<Comp />)
504
- const input = getByTestId('fieldinput')
505
- await user.type(input, 'other')
506
- // mockFn will have been called 5 times without onChangeAsyncDebounceMs
507
- expect(mockFn).toHaveBeenCalledTimes(0)
508
- await waitFor(() => getByText(error))
509
- expect(getByText(error)).toBeInTheDocument()
510
- })
511
- })
@@ -1,5 +0,0 @@
1
- export function sleep(timeout: number): Promise<void> {
2
- return new Promise((resolve, _reject) => {
3
- setTimeout(resolve, timeout)
4
- })
5
- }