This commit is contained in:
2023-03-03 23:08:54 +01:00
parent a45bfdfe08
commit ae53882a82
7 changed files with 342 additions and 118 deletions

View File

@@ -1,34 +1,15 @@
import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'
import React, { useEffect } from 'react'
import { act, cleanup, fireEvent, render } from '@testing-library/react'
import React from 'react'
import { beforeEach, describe, expect, test } from 'vitest'
import { useForm } from '../lib'
import { Insight, Util } from './shared'
beforeEach(cleanup)
const Insight = {
Portal({ data }: { data: any }) {
return <div data-testid="result">{JSON.stringify(data)}</div>
},
async verify(obj: any) {
const result = await screen.findByTestId('result')
const data = JSON.parse(result.innerText)
expect(data).toMatchObject(obj)
},
}
const Util = {
find<E extends HTMLElement = HTMLInputElement>(id: string) {
return screen.findByTestId<E>(id)
},
writeToField(node: HTMLInputElement, value: string) {
fireEvent.change(node, { target: { value } })
},
}
describe('Field', () => {
test('Basic Form', async () => {
const BasicForm = () => {
function Component() {
const form = useForm({ username: '', password: '' })
const { field } = form
return (
@@ -47,7 +28,7 @@ describe('Field', () => {
)
}
render(<BasicForm />)
render(<Component />)
async function inputIntoForm(id: string, value: string) {
const node = await Util.find(id)
await act(() => {
@@ -60,27 +41,65 @@ describe('Field', () => {
await inputIntoForm('password', 'bar')
})
test('setField', async () => {
const value = 'foo'
const Component = () => {
const { field, setField, form } = useForm({ username: '', password: '' })
useEffect(() => setField('username', value), [])
test.skip('Checkbox', async () => {
function Component() {
const { field, form } = useForm({ cool: false })
return (
<div>
<input data-testid="field" {...field('username')}></input>
<form>
<input
data-testid="field"
type="checkbox"
{...field('cool', {
setter: 'checked',
getter: 'onChange',
extractor: (e) => e.target.checked,
})}
/>
<Insight.Portal data={form} />
</div>
</form>
)
}
render(<Component />)
const node = await screen.findByTestId<HTMLInputElement>('field')
expect(node.value).toBe(value)
Insight.verify({ username: value, password: '' })
const field = await Util.find('field')
expect(field.checked).toBe(false)
await Insight.verify({ cool: false })
await act(() => {
// Bugged for now
fireEvent.click(field)
})
expect(field.checked).toBe(true)
await Insight.verify({ cool: true })
})
test('Select', async () => {
function Component() {
const { form, field } = useForm({ letter: '' })
return (
<>
<select data-testid="field" {...field('letter')}>
<option value="a">A</option>
<option value="b">B</option>
<option value="c">C</option>
</select>
<Insight.Portal data={form} />
</>
)
}
render(<Component />)
const field = await Util.find('field')
const value = 'b'
await act(() => {
fireEvent.change(field, { target: { value } })
})
expect(field.value).toBe(value)
await Insight.verify({ letter: value })
})
test('Field sync', async () => {
const value = 'foo'
const Component = () => {
function Component() {
const { field, form } = useForm({ name: '' })
return (
<form>
@@ -101,54 +120,3 @@ describe('Field', () => {
expect(a.value).toBe(b.value)
})
})
describe('Validation', () => {
test('Basic', async () => {
const Component = () => {
const { errors, field } = useForm({ password: '' }, { rules: { password: [(p) => p.length > 8] } })
return (
<div>
<input {...field('password')} data-testid="field" />
<Insight.Portal data={errors} />
</div>
)
}
render(<Component />)
const node = await Util.find('field')
await act(() => {
Util.writeToField(node, '123')
})
Insight.verify({ password: true })
})
test('Array of rules', async () => {
const Component = () => {
const { errors, field } = useForm({ password: '' }, { rules: { password: [(p) => p.length > 8, /#/] } })
return (
<div>
<input {...field('password')} data-testid="field" />
<Insight.Portal data={errors} />
</div>
)
}
render(<Component />)
const node = await Util.find('field')
await act(() => {
Util.writeToField(node, '12345678')
})
Insight.verify({ password: true })
await act(() => {
Util.writeToField(node, '1234#5678')
})
Insight.verify({})
})
})
// Is valid
// Reset / setForm
// Set error
// Checkbox
// Extractor
// Custom extractor

11
test/blocks.tsx Normal file
View File

@@ -0,0 +1,11 @@
import React from 'react'
// Custom field with non standard setter and getter. Emulate custom component from a library
export function NumberField(props: { number: number; update: (value: number) => void }) {
return <input data-testid="field" value={props.number} onChange={(e) => props.update(parseInt(e.target.value))} />
}
// Component that needs a different extractor, as it's returning the actual value and not the event.
export function DirectReturnInput(props: { value: string; onChange: (v: string) => void }) {
return <input data-testid="field" value={props.value} onChange={(e) => props.onChange(e.target.value)} />
}

57
test/options.test.tsx Normal file
View File

@@ -0,0 +1,57 @@
import { act, cleanup, render } from '@testing-library/react'
import React from 'react'
import { beforeEach, describe, test } from 'vitest'
import { useForm } from '../lib'
import { DirectReturnInput, NumberField } from './blocks'
import { Insight, Util } from './shared'
beforeEach(cleanup)
describe('Options', () => {
test('Custom component props', async () => {
function Component() {
const { form, field } = useForm({ foo: 5 })
return (
<div>
<NumberField
{...field('foo', {
setter: 'number',
getter: 'update',
extractor: null,
})}
/>
<Insight.Portal data={form} />
</div>
)
}
render(<Component />)
const node = await Util.find('field')
await act(() => {
Util.writeToField(node, '123')
})
Insight.verify({ foo: 123 })
})
test('Disable default extractor', async () => {
function Component() {
const { form, field } = useForm({ username: '' })
return (
<div>
<DirectReturnInput {...field('username', { extractor: null })} />
<Insight.Portal data={form} />
</div>
)
}
render(<Component />)
const node = await Util.find('field')
await act(() => {
Util.writeToField(node, '123')
})
Insight.verify({ username: '123' })
})
})

23
test/shared.tsx Normal file
View File

@@ -0,0 +1,23 @@
import { fireEvent, screen } from '@testing-library/react'
import React from 'react'
import { expect } from 'vitest'
export const Insight = {
Portal({ data }: { data: any }) {
return <div data-testid="result">{JSON.stringify(data)}</div>
},
async verify(obj: any) {
const result = await screen.findByTestId('result')
const data = JSON.parse(result.innerText)
expect(data).toMatchObject(obj)
},
}
export const Util = {
find<E extends HTMLElement = HTMLInputElement>(id: string) {
return screen.findByTestId<E>(id)
},
writeToField(node: HTMLInputElement, value: string) {
fireEvent.change(node, { target: { value } })
},
}

53
test/utility.test.tsx Normal file
View File

@@ -0,0 +1,53 @@
import { cleanup, render } from '@testing-library/react'
import React, { useEffect } from 'react'
import { beforeEach, describe, expect, test } from 'vitest'
import { useForm } from '../lib'
import { Insight, Util } from './shared'
beforeEach(cleanup)
describe('Utility', () => {
test('Manually set a single field', async () => {
const value = 'foo'
function Component() {
const { field, setField, form } = useForm({ username: '', password: '' })
useEffect(() => setField('username', value), [])
return (
<div>
<input data-testid="field" {...field('username')}></input>
<Insight.Portal data={form} />
</div>
)
}
render(<Component />)
const node = await Util.find('field')
expect(node.value).toBe(value)
Insight.verify({ username: value, password: '' })
})
test('Manually set the form state later on', async () => {
const value = 'foo'
function Component() {
const { form, field, setForm } = useForm({ username: '' })
useEffect(() => {
setForm({
username: value,
})
}, [])
return (
<form>
<input data-testid="username" {...field('username')} />
<Insight.Portal data={form} />
</form>
)
}
render(<Component />)
const node = await Util.find('username')
expect(node.value).toBe(value)
await Insight.verify({ username: value })
})
})

109
test/validation.test.tsx Normal file
View File

@@ -0,0 +1,109 @@
import { act, cleanup, fireEvent, render } from '@testing-library/react'
import React from 'react'
import { beforeEach, describe, test } from 'vitest'
import { useForm } from '../lib'
import { Insight, Util } from './shared'
beforeEach(cleanup)
describe('Validation', () => {
test('Basic', async () => {
function Component() {
const { errors, field } = useForm({ password: '' }, { rules: { password: [(p) => p.length > 8] } })
return (
<div>
<input {...field('password')} data-testid="field" />
<Insight.Portal data={errors} />
</div>
)
}
render(<Component />)
const node = await Util.find('field')
await act(() => {
Util.writeToField(node, '123')
})
Insight.verify({ password: true })
})
test('Array of rules', async () => {
function Component() {
const { errors, field } = useForm({ password: '' }, { rules: { password: [(p) => p.length > 8, /#/] } })
return (
<div>
<input {...field('password')} data-testid="field" />
<Insight.Portal data={errors} />
</div>
)
}
render(<Component />)
const node = await Util.find('field')
await act(() => {
Util.writeToField(node, '12345678')
})
Insight.verify({ password: true })
await act(() => {
Util.writeToField(node, '1234#5678')
})
Insight.verify({})
})
// https://github.com/testing-library/react-testing-library/issues/828
test.skip('Invalid rule', async () => {
function Component() {
const { field } = useForm(
{ username: '' },
{
rules: {
username: [
// @ts-ignore Give an invalid rules and expect to fail
5,
],
},
}
)
return (
<form>
<input data-testid="field" {...field('username')} />
</form>
)
}
render(<Component />)
const field = await Util.find('field')
await act(() => {
Util.writeToField(field, 'abc')
})
})
test('Invalid dependency on other component', async () => {
function Component() {
const { errors, field } = useForm(
{ min: 10, max: 20 },
{
rules: {
max: (value, form) => value > form.min,
},
}
)
return (
<form>
<input type="number" {...field('min')} />
<input type="number" {...field('max')} data-testid="max" />
<Insight.Portal data={errors} />
</form>
)
}
render(<Component />)
const field = await Util.find('max')
const value = 5
await act(() => {
fireEvent.change(field, { target: { value } })
})
await Insight.verify({ max: true })
})
})