@tanstack/solid-form 0.10.1 → 0.10.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/solid-form",
3
- "version": "0.10.1",
3
+ "version": "0.10.3",
4
4
  "description": "Powerful, type-safe forms for Solid.",
5
5
  "author": "tannerlinsley",
6
6
  "license": "MIT",
@@ -55,7 +55,7 @@
55
55
  },
56
56
  "dependencies": {
57
57
  "@tanstack/solid-store": "^0.2.1",
58
- "@tanstack/form-core": "0.10.1"
58
+ "@tanstack/form-core": "0.10.3"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "solid-js": "^1.6.0"
@@ -1,8 +1,10 @@
1
1
  import { render, screen, waitFor } from '@solidjs/testing-library'
2
2
  import '@testing-library/jest-dom'
3
3
  import userEvent from '@testing-library/user-event'
4
- import { createFormFactory, createForm } from '..'
5
- import { Show, createSignal } from 'solid-js'
4
+ import { Show, createSignal, onCleanup } from 'solid-js'
5
+ import { createForm, createFormFactory } from '..'
6
+ import { sleep } from './utils'
7
+ import type { ValidationErrorMap } from '@tanstack/form-core'
6
8
 
7
9
  const user = userEvent.setup()
8
10
 
@@ -147,4 +149,319 @@ describe('createForm', () => {
147
149
  await user.click(getByText('Mount form'))
148
150
  expect(formMounted()).toBe(true)
149
151
  })
152
+
153
+ it('should not validate on change if isTouched is false', async () => {
154
+ type Person = {
155
+ firstName: string
156
+ lastName: string
157
+ }
158
+ const error = 'Please enter a different value'
159
+
160
+ const formFactory = createFormFactory<Person, unknown>()
161
+
162
+ function Comp() {
163
+ const form = formFactory.createForm(() => ({
164
+ onChange: (value) =>
165
+ value.firstName.includes('other') ? error : undefined,
166
+ }))
167
+
168
+ return (
169
+ <form.Provider>
170
+ <form.Field
171
+ name="firstName"
172
+ children={(field) => (
173
+ <div>
174
+ <input
175
+ data-testid="fieldinput"
176
+ name={field().name}
177
+ value={field().state.value}
178
+ onBlur={field().handleBlur}
179
+ onInput={(e) => field().setValue(e.currentTarget.value)}
180
+ />
181
+ <p>{form.useStore((s) => s.errors)}</p>
182
+ </div>
183
+ )}
184
+ />
185
+ </form.Provider>
186
+ )
187
+ }
188
+
189
+ const { getByTestId, queryByText } = render(() => <Comp />)
190
+ const input = getByTestId('fieldinput')
191
+ await user.type(input, 'other')
192
+ expect(queryByText(error)).not.toBeInTheDocument()
193
+ })
194
+
195
+ it('should validate on change if isTouched is true', async () => {
196
+ type Person = {
197
+ firstName: string
198
+ lastName: string
199
+ }
200
+ const error = 'Please enter a different value'
201
+
202
+ const formFactory = createFormFactory<Person, unknown>()
203
+
204
+ function Comp() {
205
+ const form = formFactory.createForm(() => ({
206
+ onChange: (value) =>
207
+ value.firstName.includes('other') ? error : undefined,
208
+ }))
209
+
210
+ const [errors, setErrors] = createSignal<ValidationErrorMap>()
211
+ onCleanup(form.store.subscribe(() => setErrors(form.state.errorMap)))
212
+
213
+ return (
214
+ <form.Provider>
215
+ <form.Field
216
+ name="firstName"
217
+ defaultMeta={{ isTouched: true }}
218
+ children={(field) => {
219
+ return (
220
+ <div>
221
+ <input
222
+ data-testid="fieldinput"
223
+ name={field().name}
224
+ value={field().state.value}
225
+ onBlur={field().handleBlur}
226
+ onInput={(e) => field().setValue(e.currentTarget.value)}
227
+ />
228
+ <p>{errors()?.onChange}</p>
229
+ </div>
230
+ )
231
+ }}
232
+ />
233
+ </form.Provider>
234
+ )
235
+ }
236
+
237
+ const { getByTestId, getByText, queryByText } = render(() => <Comp />)
238
+ const input = getByTestId('fieldinput')
239
+ expect(queryByText(error)).not.toBeInTheDocument()
240
+ await user.type(input, 'other')
241
+ expect(getByText(error)).toBeInTheDocument()
242
+ })
243
+
244
+ it('should validate on change and on blur', async () => {
245
+ type Person = {
246
+ firstName: string
247
+ lastName: string
248
+ }
249
+ const onChangeError = 'Please enter a different value (onChangeError)'
250
+ const onBlurError = 'Please enter a different value (onBlurError)'
251
+
252
+ const formFactory = createFormFactory<Person, unknown>()
253
+
254
+ function Comp() {
255
+ const form = formFactory.createForm(() => ({
256
+ onChange: (value) =>
257
+ value.firstName.includes('other') ? onChangeError : undefined,
258
+ onBlur: (value) =>
259
+ value.firstName.includes('other') ? onBlurError : undefined,
260
+ }))
261
+
262
+ const [errors, setErrors] = createSignal<ValidationErrorMap>()
263
+ onCleanup(form.store.subscribe(() => setErrors(form.state.errorMap)))
264
+
265
+ return (
266
+ <form.Provider>
267
+ <form.Field
268
+ name="firstName"
269
+ defaultMeta={{ isTouched: true }}
270
+ children={(field) => (
271
+ <div>
272
+ <input
273
+ data-testid="fieldinput"
274
+ name={field().name}
275
+ value={field().state.value}
276
+ onBlur={field().handleBlur}
277
+ onInput={(e) => field().handleChange(e.currentTarget.value)}
278
+ />
279
+ <p>{errors()?.onChange}</p>
280
+ <p>{errors()?.onBlur}</p>
281
+ </div>
282
+ )}
283
+ />
284
+ </form.Provider>
285
+ )
286
+ }
287
+
288
+ const { getByTestId, getByText, queryByText } = render(() => <Comp />)
289
+ const input = getByTestId('fieldinput')
290
+ expect(queryByText(onChangeError)).not.toBeInTheDocument()
291
+ expect(queryByText(onBlurError)).not.toBeInTheDocument()
292
+ await user.type(input, 'other')
293
+ expect(getByText(onChangeError)).toBeInTheDocument()
294
+ // @ts-expect-error unsure why the 'vitest/globals' in tsconfig doesnt work here
295
+ await user.click(document.body)
296
+ expect(queryByText(onBlurError)).toBeInTheDocument()
297
+ })
298
+
299
+ it('should validate async on change', async () => {
300
+ type Person = {
301
+ firstName: string
302
+ lastName: string
303
+ }
304
+ const error = 'Please enter a different value'
305
+
306
+ const formFactory = createFormFactory<Person, unknown>()
307
+
308
+ function Comp() {
309
+ const form = formFactory.createForm(() => ({
310
+ onChangeAsync: async () => {
311
+ await sleep(10)
312
+ return error
313
+ },
314
+ }))
315
+
316
+ const [errors, setErrors] = createSignal<ValidationErrorMap>()
317
+ onCleanup(form.store.subscribe(() => setErrors(form.state.errorMap)))
318
+
319
+ return (
320
+ <form.Provider>
321
+ <form.Field
322
+ name="firstName"
323
+ defaultMeta={{ isTouched: true }}
324
+ children={(field) => (
325
+ <div>
326
+ <input
327
+ data-testid="fieldinput"
328
+ name={field().name}
329
+ value={field().state.value}
330
+ onBlur={field().handleBlur}
331
+ onInput={(e) => field().handleChange(e.currentTarget.value)}
332
+ />
333
+ <p>{errors()?.onChange}</p>
334
+ </div>
335
+ )}
336
+ />
337
+ </form.Provider>
338
+ )
339
+ }
340
+
341
+ const { getByTestId, getByText, queryByText } = render(() => <Comp />)
342
+ const input = getByTestId('fieldinput')
343
+ expect(queryByText(error)).not.toBeInTheDocument()
344
+ await user.type(input, 'other')
345
+ await waitFor(() => getByText(error))
346
+ expect(getByText(error)).toBeInTheDocument()
347
+ })
348
+
349
+ it('should validate async on change and async on blur', async () => {
350
+ type Person = {
351
+ firstName: string
352
+ lastName: string
353
+ }
354
+ const onChangeError = 'Please enter a different value (onChangeError)'
355
+ const onBlurError = 'Please enter a different value (onBlurError)'
356
+
357
+ const formFactory = createFormFactory<Person, unknown>()
358
+
359
+ function Comp() {
360
+ const form = formFactory.createForm(() => ({
361
+ async onChangeAsync() {
362
+ await sleep(10)
363
+ return onChangeError
364
+ },
365
+ async onBlurAsync() {
366
+ await sleep(10)
367
+ return onBlurError
368
+ },
369
+ }))
370
+
371
+ const [errors, setErrors] = createSignal<ValidationErrorMap>()
372
+ onCleanup(form.store.subscribe(() => setErrors(form.state.errorMap)))
373
+
374
+ return (
375
+ <form.Provider>
376
+ <form.Field
377
+ name="firstName"
378
+ defaultMeta={{ isTouched: true }}
379
+ children={(field) => (
380
+ <div>
381
+ <input
382
+ data-testid="fieldinput"
383
+ name={field().name}
384
+ value={field().state.value}
385
+ onBlur={field().handleBlur}
386
+ onInput={(e) => field().handleChange(e.currentTarget.value)}
387
+ />
388
+ <p>{errors()?.onChange}</p>
389
+ <p>{errors()?.onBlur}</p>
390
+ </div>
391
+ )}
392
+ />
393
+ </form.Provider>
394
+ )
395
+ }
396
+
397
+ const { getByTestId, getByText, queryByText } = render(() => <Comp />)
398
+ const input = getByTestId('fieldinput')
399
+
400
+ expect(queryByText(onChangeError)).not.toBeInTheDocument()
401
+ expect(queryByText(onBlurError)).not.toBeInTheDocument()
402
+ await user.type(input, 'other')
403
+ await waitFor(() => getByText(onChangeError))
404
+ expect(getByText(onChangeError)).toBeInTheDocument()
405
+ // @ts-expect-error unsure why the 'vitest/globals' in tsconfig doesnt work here
406
+ await user.click(document.body)
407
+ await waitFor(() => getByText(onBlurError))
408
+ expect(getByText(onBlurError)).toBeInTheDocument()
409
+ })
410
+
411
+ it('should validate async on change with debounce', async () => {
412
+ type Person = {
413
+ firstName: string
414
+ lastName: string
415
+ }
416
+ const mockFn = vi.fn()
417
+ const error = 'Please enter a different value'
418
+ const formFactory = createFormFactory<Person, unknown>()
419
+
420
+ function Comp() {
421
+ const form = formFactory.createForm(() => ({
422
+ onChangeAsyncDebounceMs: 100,
423
+ onChangeAsync: async () => {
424
+ mockFn()
425
+ await sleep(10)
426
+ return error
427
+ },
428
+ }))
429
+
430
+ const [errors, setErrors] = createSignal<string>()
431
+ onCleanup(
432
+ form.store.subscribe(() =>
433
+ setErrors(form.state.errorMap.onChange || ''),
434
+ ),
435
+ )
436
+
437
+ return (
438
+ <form.Provider>
439
+ <form.Field
440
+ name="firstName"
441
+ defaultMeta={{ isTouched: true }}
442
+ children={(field) => (
443
+ <div>
444
+ <input
445
+ data-testid="fieldinput"
446
+ name={field().name}
447
+ value={field().state.value}
448
+ onBlur={field().handleBlur}
449
+ onInput={(e) => field().handleChange(e.currentTarget.value)}
450
+ />
451
+ <p>{errors()}</p>
452
+ </div>
453
+ )}
454
+ />
455
+ </form.Provider>
456
+ )
457
+ }
458
+
459
+ const { getByTestId, getByText } = render(() => <Comp />)
460
+ const input = getByTestId('fieldinput')
461
+ await user.type(input, 'other')
462
+ // mockFn will have been called 5 times without onChangeAsyncDebounceMs
463
+ expect(mockFn).toHaveBeenCalledTimes(0)
464
+ await waitFor(() => getByText(error))
465
+ expect(getByText(error)).toBeInTheDocument()
466
+ })
150
467
  })