Skip to content

Commit

Permalink
fix(form-core): remove 500ms fallback for asyncDebounceMs
Browse files Browse the repository at this point in the history
* test(form-core): add async validation tests

* chore(form-core): remove old asyncDebounceMs api

* Revert "chore(form-core): remove old asyncDebounceMs api"

This reverts commit cd7dbc1.

* fix(form-core): make sure to fallback to asyncDebounceMs

* chore(form-core): remove 500ms fallback

---------

Co-authored-by: João Pedro Magalhães <[email protected]>
  • Loading branch information
joaom00 and João Pedro Magalhães authored Sep 4, 2023
1 parent de22b6c commit 3b7f6c1
Show file tree
Hide file tree
Showing 3 changed files with 252 additions and 15 deletions.
4 changes: 1 addition & 3 deletions packages/form-core/src/FieldApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,6 @@ export class FieldApi<TData, TFormData> {
update = (opts: FieldApiOptions<typeof this._tdata, TFormData>) => {
this.options = {
asyncDebounceMs: this.form.options.asyncDebounceMs ?? 0,
onChangeAsyncDebounceMs: this.form.options.onChangeAsyncDebounceMs ?? 0,
onBlurAsyncDebounceMs: this.form.options.onBlurAsyncDebounceMs ?? 0,
...opts,
} as never

Expand Down Expand Up @@ -312,7 +310,7 @@ export class FieldApi<TData, TFormData> {
? onChangeAsyncDebounceMs
: onBlurAsyncDebounceMs) ??
asyncDebounceMs ??
500
0

if (this.state.meta.isValidating !== true)
this.setMeta((prev) => ({ ...prev, isValidating: true }))
Expand Down
258 changes: 246 additions & 12 deletions packages/form-core/src/tests/FieldApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { expect } from 'vitest'

import { FormApi } from '../FormApi'
import { FieldApi } from '../FieldApi'
import { sleep } from './utils'

describe('field api', () => {
it('should have an initial value', () => {
Expand Down Expand Up @@ -151,7 +152,30 @@ describe('field api', () => {
expect(subfield.getValue()).toBe('one')
})

it('should run validation onChange', async () => {
it('should not throw errors when no meta info is stored on a field and a form re-renders', async () => {
const form = new FormApi({
defaultValues: {
name: 'test',
},
})

const field = new FieldApi({
form,
name: 'name',
})

field.mount()

expect(() =>
form.update({
defaultValues: {
name: 'other',
},
}),
).not.toThrow()
})

it('should run validation onChange', () => {
const form = new FormApi({
defaultValues: {
name: 'test',
Expand All @@ -162,10 +186,33 @@ describe('field api', () => {
form,
name: 'name',
onChange: (value) => {
if (value === 'other') {
return 'Please enter a different value'
}
if (value === 'other') return 'Please enter a different value'
return
},
})

field.mount()

expect(field.getMeta().error).toBeUndefined()
field.setValue('other', { touch: true })
expect(field.getMeta().error).toBe('Please enter a different value')
})

it('should run async validation onChange', async () => {
vi.useFakeTimers()

const form = new FormApi({
defaultValues: {
name: 'test',
},
})

const field = new FieldApi({
form,
name: 'name',
onChangeAsync: async (value) => {
await sleep(1000)
if (value === 'other') return 'Please enter a different value'
return
},
})
Expand All @@ -174,10 +221,14 @@ describe('field api', () => {

expect(field.getMeta().error).toBeUndefined()
field.setValue('other', { touch: true })
await vi.runAllTimersAsync()
expect(field.getMeta().error).toBe('Please enter a different value')
})

it('should not throw errors when no meta info is stored on a field and a form re-renders', async () => {
it('should run async validation onChange with debounce', async () => {
vi.useFakeTimers()
const sleepMock = vi.fn().mockImplementation(sleep)

const form = new FormApi({
defaultValues: {
name: 'test',
Expand All @@ -187,16 +238,199 @@ describe('field api', () => {
const field = new FieldApi({
form,
name: 'name',
onChangeAsyncDebounceMs: 1000,
onChangeAsync: async (value) => {
await sleepMock(1000)
if (value === 'other') return 'Please enter a different value'
return
},
})

field.mount()

expect(() =>
form.update({
defaultValues: {
name: 'other',
},
}),
).not.toThrow()
expect(field.getMeta().error).toBeUndefined()
field.setValue('other', { touch: true })
field.setValue('other')
await vi.runAllTimersAsync()
// sleepMock will have been called 2 times without onChangeAsyncDebounceMs
expect(sleepMock).toHaveBeenCalledTimes(1)
expect(field.getMeta().error).toBe('Please enter a different value')
})

it('should run async validation onChange with asyncDebounceMs', async () => {
vi.useFakeTimers()
const sleepMock = vi.fn().mockImplementation(sleep)

const form = new FormApi({
defaultValues: {
name: 'test',
},
})

const field = new FieldApi({
form,
name: 'name',
asyncDebounceMs: 1000,
onChangeAsync: async (value) => {
await sleepMock(1000)
if (value === 'other') return 'Please enter a different value'
return
},
})

field.mount()

expect(field.getMeta().error).toBeUndefined()
field.setValue('other', { touch: true })
field.setValue('other')
await vi.runAllTimersAsync()
// sleepMock will have been called 2 times without asyncDebounceMs
expect(sleepMock).toHaveBeenCalledTimes(1)
expect(field.getMeta().error).toBe('Please enter a different value')
})

it('should run validation onBlur', () => {
const form = new FormApi({
defaultValues: {
name: 'other',
},
})

const field = new FieldApi({
form,
name: 'name',
onBlur: (value) => {
if (value === 'other') return 'Please enter a different value'
return
},
})

field.mount()

field.setValue('other', { touch: true })
field.validate('blur')
expect(field.getMeta().error).toBe('Please enter a different value')
})

it('should run async validation onBlur', async () => {
vi.useFakeTimers()

const form = new FormApi({
defaultValues: {
name: 'test',
},
})

const field = new FieldApi({
form,
name: 'name',
onBlurAsync: async (value) => {
await sleep(1000)
if (value === 'other') return 'Please enter a different value'
return
},
})

field.mount()

expect(field.getMeta().error).toBeUndefined()
field.setValue('other', { touch: true })
field.validate('blur')
await vi.runAllTimersAsync()
expect(field.getMeta().error).toBe('Please enter a different value')
})

it('should run async validation onBlur with debounce', async () => {
vi.useFakeTimers()
const sleepMock = vi.fn().mockImplementation(sleep)

const form = new FormApi({
defaultValues: {
name: 'test',
},
})

const field = new FieldApi({
form,
name: 'name',
onBlurAsyncDebounceMs: 1000,
onBlurAsync: async (value) => {
await sleepMock(10)
if (value === 'other') return 'Please enter a different value'
return
},
})

field.mount()

expect(field.getMeta().error).toBeUndefined()
field.setValue('other', { touch: true })
field.validate('blur')
field.validate('blur')
await vi.runAllTimersAsync()
// sleepMock will have been called 2 times without onBlurAsyncDebounceMs
expect(sleepMock).toHaveBeenCalledTimes(1)
expect(field.getMeta().error).toBe('Please enter a different value')
})

it('should run async validation onBlur with asyncDebounceMs', async () => {
vi.useFakeTimers()
const sleepMock = vi.fn().mockImplementation(sleep)

const form = new FormApi({
defaultValues: {
name: 'test',
},
})

const field = new FieldApi({
form,
name: 'name',
asyncDebounceMs: 1000,
onBlurAsync: async (value) => {
await sleepMock(10)
if (value === 'other') return 'Please enter a different value'
return
},
})

field.mount()

expect(field.getMeta().error).toBeUndefined()
field.setValue('other', { touch: true })
field.validate('blur')
field.validate('blur')
await vi.runAllTimersAsync()
// sleepMock will have been called 2 times without asyncDebounceMs
expect(sleepMock).toHaveBeenCalledTimes(1)
expect(field.getMeta().error).toBe('Please enter a different value')
})

it('should run async validation onSubmit', async () => {
vi.useFakeTimers()

const form = new FormApi({
defaultValues: {
name: 'test',
},
})

const field = new FieldApi({
form,
name: 'name',
onSubmitAsync: async (value) => {
await sleep(1000)
if (value === 'other') return 'Please enter a different value'
return
},
})

field.mount()

expect(field.getMeta().error).toBeUndefined()
field.setValue('other', { touch: true })
field.validate('submit')
await vi.runAllTimersAsync()
expect(field.getMeta().error).toBe('Please enter a different value')
})
})
5 changes: 5 additions & 0 deletions packages/form-core/src/tests/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function sleep(timeout: number): Promise<void> {
return new Promise((resolve, _reject) => {
setTimeout(resolve, timeout)
})
}

0 comments on commit 3b7f6c1

Please sign in to comment.