Skip to content

Commit

Permalink
fix: Vue useStore typings are now correct in JSX usage
Browse files Browse the repository at this point in the history
* initial attempt to add back form validation

* uncomment tests

* fixed form validation not running

* onChange + onBlur

* feat: mount method on FormApi

* fix solid-form test case

* fix checkLatest

* add onMount logic + test

* react form validation tests

* solid tests

* prettier

* starting vue tests

* vue test struggles

* chore: remove Vue 2 compat

* test: refactor of Vue tests

* fix: Vue's typings for useStore are now correct

* chore: fix last failing Vue test

---------

Co-authored-by: Corbin Crutchley <[email protected]>
  • Loading branch information
aadito123 and crutchcorn authored Dec 3, 2023
1 parent 988a3a7 commit 8e90e4f
Show file tree
Hide file tree
Showing 15 changed files with 1,024 additions and 117 deletions.
2 changes: 1 addition & 1 deletion examples/vue/simple/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ import createVuePlugin from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [createVuePlugin()],
optimizeDeps: {
exclude: ['@tanstack/vue-form', 'vue-demi'],
exclude: ['@tanstack/vue-form'],
},
})
2 changes: 1 addition & 1 deletion examples/vue/valibot/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ import createVuePlugin from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [createVuePlugin()],
optimizeDeps: {
exclude: ['@tanstack/vue-form', 'vue-demi'],
exclude: ['@tanstack/vue-form'],
},
})
2 changes: 1 addition & 1 deletion examples/vue/yup/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ import createVuePlugin from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [createVuePlugin()],
optimizeDeps: {
exclude: ['@tanstack/vue-form', 'vue-demi'],
exclude: ['@tanstack/vue-form'],
},
})
2 changes: 1 addition & 1 deletion examples/vue/zod/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ import createVuePlugin from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [createVuePlugin()],
optimizeDeps: {
exclude: ['@tanstack/vue-form', 'vue-demi'],
exclude: ['@tanstack/vue-form'],
},
})
298 changes: 296 additions & 2 deletions packages/react-form/src/tests/useForm.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/// <reference lib="dom" />
import '@testing-library/jest-dom'
import { render, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import '@testing-library/jest-dom'
import * as React from 'react'
import { createFormFactory, useForm } from '..'
import { sleep } from './utils'

const user = userEvent.setup()

Expand Down Expand Up @@ -173,7 +174,6 @@ describe('useForm', () => {
return error
},
})

return (
<form.Provider>
<form.Field
Expand Down Expand Up @@ -202,4 +202,298 @@ describe('useForm', () => {
await waitFor(() => getByText(error))
expect(getByText(error)).toBeInTheDocument()
})

it('should not validate on change if isTouched is false', async () => {
type Person = {
firstName: string
lastName: string
}
const error = 'Please enter a different value'

const formFactory = createFormFactory<Person, unknown>()

function Comp() {
const form = formFactory.useForm({
onChange: (value) => (value.firstName === 'other' ? error : undefined),
})

const errors = form.useStore((s) => s.errors)
return (
<form.Provider>
<form.Field
name="firstName"
children={(field) => (
<div>
<input
data-testid="fieldinput"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.setValue(e.target.value)}
/>
<p>{errors}</p>
</div>
)}
/>
</form.Provider>
)
}

const { getByTestId, queryByText } = render(<Comp />)
const input = getByTestId('fieldinput')
await user.type(input, 'other')
expect(queryByText(error)).not.toBeInTheDocument()
})

it('should validate on change if isTouched is true', async () => {
type Person = {
firstName: string
lastName: string
}
const error = 'Please enter a different value'

const formFactory = createFormFactory<Person, unknown>()

function Comp() {
const form = formFactory.useForm({
onChange: (value) => (value.firstName === 'other' ? error : undefined),
})
const errors = form.useStore((s) => s.errorMap)
return (
<form.Provider>
<form.Field
name="firstName"
defaultMeta={{ isTouched: true }}
children={(field) => (
<div>
<input
data-testid="fieldinput"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
<p>{errors.onChange}</p>
</div>
)}
/>
</form.Provider>
)
}

const { getByTestId, getByText, queryByText } = render(<Comp />)
const input = getByTestId('fieldinput')
expect(queryByText(error)).not.toBeInTheDocument()
await user.type(input, 'other')
expect(getByText(error)).toBeInTheDocument()
})

it('should validate on change and on blur', async () => {
const onChangeError = 'Please enter a different value (onChangeError)'
const onBlurError = 'Please enter a different value (onBlurError)'

function Comp() {
const form = useForm({
defaultValues: {
firstName: '',
},
onChange: (vals) => {
if (vals.firstName === 'other') return onChangeError
return undefined
},
onBlur: (vals) => {
if (vals.firstName === 'other') return onBlurError
return undefined
},
})

const errors = form.useStore((s) => s.errorMap)
return (
<form.Provider>
<form.Field
name="firstName"
defaultMeta={{ isTouched: true }}
children={(field) => (
<div>
<input
data-testid="fieldinput"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
<p>{errors.onChange}</p>
<p>{errors.onBlur}</p>
</div>
)}
/>
</form.Provider>
)
}
const { getByTestId, getByText, queryByText } = render(<Comp />)
const input = getByTestId('fieldinput')
expect(queryByText(onChangeError)).not.toBeInTheDocument()
expect(queryByText(onBlurError)).not.toBeInTheDocument()
await user.type(input, 'other')
expect(getByText(onChangeError)).toBeInTheDocument()
await user.click(document.body)
expect(queryByText(onBlurError)).toBeInTheDocument()
})

it('should validate async on change', async () => {
type Person = {
firstName: string
lastName: string
}
const error = 'Please enter a different value'

const formFactory = createFormFactory<Person, unknown>()

function Comp() {
const form = formFactory.useForm({
onChangeAsync: async () => {
await sleep(10)
return error
},
})
const errors = form.useStore((s) => s.errorMap)
return (
<form.Provider>
<form.Field
name="firstName"
defaultMeta={{ isTouched: true }}
children={(field) => (
<div>
<input
data-testid="fieldinput"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
<p>{errors.onChange}</p>
</div>
)}
/>
</form.Provider>
)
}

const { getByTestId, getByText, queryByText } = render(<Comp />)
const input = getByTestId('fieldinput')
expect(queryByText(error)).not.toBeInTheDocument()
await user.type(input, 'other')
await waitFor(() => getByText(error))
expect(getByText(error)).toBeInTheDocument()
})

it('should validate async on change and async on blur', async () => {
type Person = {
firstName: string
lastName: string
}
const onChangeError = 'Please enter a different value (onChangeError)'
const onBlurError = 'Please enter a different value (onBlurError)'

const formFactory = createFormFactory<Person, unknown>()

function Comp() {
const form = formFactory.useForm({
onChangeAsync: async () => {
await sleep(10)
return onChangeError
},
onBlurAsync: async () => {
await sleep(10)
return onBlurError
},
})
const errors = form.useStore((s) => s.errorMap)

return (
<form.Provider>
<form.Field
name="firstName"
defaultMeta={{ isTouched: true }}
children={(field) => (
<div>
<input
data-testid="fieldinput"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
<p>{errors.onChange}</p>
<p>{errors.onBlur}</p>
</div>
)}
/>
</form.Provider>
)
}

const { getByTestId, getByText, queryByText } = render(<Comp />)
const input = getByTestId('fieldinput')

expect(queryByText(onChangeError)).not.toBeInTheDocument()
expect(queryByText(onBlurError)).not.toBeInTheDocument()
await user.type(input, 'other')
await waitFor(() => getByText(onChangeError))
expect(getByText(onChangeError)).toBeInTheDocument()
await user.click(document.body)
await waitFor(() => getByText(onBlurError))
expect(getByText(onBlurError)).toBeInTheDocument()
})

it('should validate async on change with debounce', async () => {
type Person = {
firstName: string
lastName: string
}
const mockFn = vi.fn()
const error = 'Please enter a different value'
const formFactory = createFormFactory<Person, unknown>()

function Comp() {
const form = formFactory.useForm({
onChangeAsyncDebounceMs: 100,
onChangeAsync: async () => {
mockFn()
await sleep(10)
return error
},
})
const errors = form.useStore((s) => s.errors)

return (
<form.Provider>
<form.Field
name="firstName"
defaultMeta={{ isTouched: true }}
children={(field) => (
<div>
<input
data-testid="fieldinput"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
<p>{errors}</p>
</div>
)}
/>
</form.Provider>
)
}

const { getByTestId, getByText } = render(<Comp />)
const input = getByTestId('fieldinput')
await user.type(input, 'other')
// mockFn will have been called 5 times without onChangeAsyncDebounceMs
expect(mockFn).toHaveBeenCalledTimes(0)
await waitFor(() => getByText(error))
expect(getByText(error)).toBeInTheDocument()
})
})
Loading

0 comments on commit 8e90e4f

Please sign in to comment.