From fa7481559d3b69ffda4e2167f6945f6492b98658 Mon Sep 17 00:00:00 2001 From: Avram Walden Date: Thu, 23 May 2024 12:57:15 -0700 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20Builds=20data=20obje?= =?UTF-8?q?ct=20from=20useInertiaInput?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `data` prop is no longer strictly necessary to pass in to the Form component. Calling `useInertaInput` will build the data object if one isn't provided. --- src/Form/index.tsx | 7 +++-- src/useInertiaForm.ts | 7 ++--- src/useInertiaInput/index.ts | 32 +++++++++++++++++------ src/utils.ts | 2 +- tests/.eslintrc | 7 +++++ tests/useInertiaInput.test.tsx | 47 ++++++++++++++++++++++++++-------- 6 files changed, 78 insertions(+), 24 deletions(-) create mode 100644 tests/.eslintrc diff --git a/src/Form/index.tsx b/src/Form/index.tsx index c2b418d..0c60180 100644 --- a/src/Form/index.tsx +++ b/src/Form/index.tsx @@ -9,7 +9,7 @@ import { unset } from 'lodash' type PartialHTMLForm = Omit, 'onChange'|'onSubmit'|'onError'> export interface FormProps extends PartialHTMLForm { - data: TForm + data?: TForm model?: string method?: HTTPVerb to: string @@ -40,6 +40,9 @@ const Form = ({ onError, ...props }: Omit, 'railsAttributes'>) => { + /** + * Omit values by key from the data object + */ const filteredData = useCallback((data: TForm) => { if(!filter) return data @@ -57,7 +60,7 @@ const Form = ({ const contextValueObject = useCallback((): UseFormProps => ( { ...form, model, method, to, submit } - ), [data, form.data, form.errors]) + ), [data, form.data, form.errors, model, method, to]) /** * Submits the form. If async prop is true, submits using axios, diff --git a/src/useInertiaForm.ts b/src/useInertiaForm.ts index 9109530..bb6109d 100644 --- a/src/useInertiaForm.ts +++ b/src/useInertiaForm.ts @@ -108,7 +108,7 @@ export default function useInertiaForm( return keys[0] } return undefined - }, []) + }, [data]) // Errors const [errors, setErrors] = rememberKey @@ -293,9 +293,9 @@ export default function useInertiaForm( wasSuccessful, recentlySuccessful, - transform: useCallback((callback) => { + transform: (callback) => { transformRef.current = callback - }, []), + }, onChange: (callback) => { onChangeRef.current = callback @@ -310,6 +310,7 @@ export default function useInertiaForm( } set(clone as NestedObject, keyOrData, maybeValue) + return clone }) } diff --git a/src/useInertiaInput/index.ts b/src/useInertiaInput/index.ts index 5a277f0..1a6033f 100644 --- a/src/useInertiaInput/index.ts +++ b/src/useInertiaInput/index.ts @@ -1,12 +1,13 @@ +import { useEffect, useRef } from 'react' import { useForm } from '../Form' import { useNestedAttribute } from '../NestedFields' import inputStrategy, { type InputStrategy } from './inputStrategy' import { type NestedObject } from '../useInertiaForm' -import { useEffect } from 'react' -export interface UseInertiaInputProps { +export interface UseInertiaInputProps { name: string model?: string + defaultValue?: T errorKey?: string strategy?: InputStrategy clearErrorsOnChange?: boolean @@ -15,32 +16,47 @@ export interface UseInertiaInputProps { /** * Returns form data and input specific methods to use with an input. */ -const useInertiaInput = ({ +const useInertiaInput = ({ name, model, + defaultValue, errorKey, strategy = inputStrategy, clearErrorsOnChange = true, -}: UseInertiaInputProps) => { +}: UseInertiaInputProps) => { const form = useForm() let usedModel = model ?? form.model - try { const nested = useNestedAttribute() usedModel += `.${nested}` } catch(e) {} - const { inputName, inputId } = strategy(name, usedModel) + // Add a valid default value to the data object + const initializingRef = useRef(true) + if(usedModel === 'values') { + } + useEffect(() => { + if(!initializingRef.current) return + + const inputValue = form.getData(inputName) + if(inputValue === null || inputValue === undefined) { + form.setData(inputName, defaultValue || '') + } + + initializingRef.current = false + }, []) + const value = form.getData(inputName) as T const usedErrorKey = errorKey ?? inputName const error = form.getError(usedErrorKey) // Clear errors when input value changes useEffect(() => { - if(!clearErrorsOnChange || !error) return + if(initializingRef.current || !clearErrorsOnChange || !error) return + form.clearErrors(usedErrorKey) }, [value]) @@ -48,7 +64,7 @@ const useInertiaInput = ({ form, inputName: inputName, inputId, - value, + value: value || '', setValue: (value: T) => { return form.setData(inputName, value) }, diff --git a/src/utils.ts b/src/utils.ts index dec0716..a52f9a7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -34,7 +34,7 @@ export const unsetCompact = (data: NestedObject, path: string) => { } export const fillEmptyValues = (data: TForm) => { - const clone = structuredClone(data) + const clone = structuredClone(data ?? {} as TForm) for(const key in clone) { if(isPlainObject(clone[key])) { diff --git a/tests/.eslintrc b/tests/.eslintrc new file mode 100644 index 0000000..f3fa314 --- /dev/null +++ b/tests/.eslintrc @@ -0,0 +1,7 @@ +{ + "rules": { + "no-unused-vars": "off", + "@typescript-eslint/member-delimiter-style": "off", + "@typescript-eslint/indent": "off" + } +} \ No newline at end of file diff --git a/tests/useInertiaInput.test.tsx b/tests/useInertiaInput.test.tsx index cd81d93..d4b0339 100644 --- a/tests/useInertiaInput.test.tsx +++ b/tests/useInertiaInput.test.tsx @@ -4,12 +4,13 @@ import '@testing-library/jest-dom' import { Form, useForm } from '../src/Form' import TestInput from './TestInput' -const FormContextTest = () => { + +const ErrorContextTest = () => { const { errors, setError } = useForm() const handleClick = e => { e.preventDefault() - setError('name', 'Error') + setError('errors.name', 'Error') } return ( @@ -21,12 +22,38 @@ const FormContextTest = () => { } describe ('useInertiaInput', () => { + describe('With defaultValue', () => { + + it('builds the data object from inputs', async () => { + + const ValuesContextTest = () => { + const { data } = useForm() + + return
{ JSON.stringify(data) }
+ } + + await render( +
+ + + , + ) + + const input = screen.getByRole('input') + + expect(screen.getByTestId('data')).toHaveTextContent('{"values":{"name":""}}') + + fireEvent.change(input, { target: { value: 'value' } }) + expect(screen.getByTestId('data')).toHaveTextContent('{"values":{"name":"value"}}') + }) + }) + describe('With clearErrorsOnChange = true', () => { it('clears errors on an input when the value changes ', () => { render( -
- + + , ) @@ -34,7 +61,7 @@ describe ('useInertiaInput', () => { const input = screen.getByRole('input') fireEvent.click(errorButton) - expect(screen.getByTestId('errors')).toHaveTextContent('{"name":"Error"}') + expect(screen.getByTestId('errors')).toHaveTextContent('{"errors.name":"Error"}') fireEvent.change(input, { target: { value: 'something' } }) expect(screen.getByTestId('errors')).toHaveTextContent('{}') @@ -42,12 +69,12 @@ describe ('useInertiaInput', () => { }) - describe('With clearErrorsOnChange = true', () => { + describe('With clearErrorsOnChange = false', () => { it('doesn\'t clear errors on an input when the value changes', () => { render( -
- + + , ) @@ -55,10 +82,10 @@ describe ('useInertiaInput', () => { const input = screen.getByRole('input') fireEvent.click(errorButton) - expect(screen.getByTestId('errors')).toHaveTextContent('{"name":"Error"}') + expect(screen.getByTestId('errors')).toHaveTextContent('{"errors.name":"Error"}') fireEvent.change(input, { target: { value: 'something' } }) - expect(screen.getByTestId('errors')).toHaveTextContent('{"name":"Error"}') + expect(screen.getByTestId('errors')).toHaveTextContent('{"errors.name":"Error"}') }) }) From cf1c77747ba0ae2594b3991d6238bfa67e568f8d Mon Sep 17 00:00:00 2001 From: Avram Walden Date: Thu, 23 May 2024 17:50:30 -0700 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Updates=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/useInertiaInput/index.ts | 6 +++--- src/utils.ts | 30 +++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/useInertiaInput/index.ts b/src/useInertiaInput/index.ts index 1a6033f..33e6d13 100644 --- a/src/useInertiaInput/index.ts +++ b/src/useInertiaInput/index.ts @@ -4,7 +4,7 @@ import { useNestedAttribute } from '../NestedFields' import inputStrategy, { type InputStrategy } from './inputStrategy' import { type NestedObject } from '../useInertiaForm' -export interface UseInertiaInputProps { +export interface UseInertiaInputProps { name: string model?: string defaultValue?: T @@ -16,7 +16,7 @@ export interface UseInertiaInputProps { /** * Returns form data and input specific methods to use with an input. */ -const useInertiaInput = ({ +const useInertiaInput = ({ name, model, defaultValue, @@ -64,7 +64,7 @@ const useInertiaInput = ({ form, inputName: inputName, inputId, - value: value || '', + value: value ?? '' as T, setValue: (value: T) => { return form.setData(inputName, value) }, diff --git a/src/utils.ts b/src/utils.ts index a52f9a7..5443abd 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -110,9 +110,36 @@ export const isUnset = (v: any) => { return isEmpty(v) } +// Added recursion limit to path types to prevent the error: +// "Type instantiation is excessively deep and possibly infinite" +type Increment = [0, ...A]; + +type PathImpl = + A['length'] extends 5 ? never : + K extends string + ? T[K] extends Record + ? T[K] extends ArrayLike + ? K | `${K}.${PathImpl, Increment>}` + : K | `${K}.${PathImpl>}` + : K + : never; + +export type Path = PathImpl | Extract; + +export type PathValue>> = + P extends `${infer K}.${infer Rest}` + ? K extends keyof Required + ? Rest extends Path[K]> + ? PathValue[K], Rest> + : never + : never + : P extends keyof Required + ? Required[P] + : never; + // Copied from https://gist.github.com/balthild/1f23725059aef8b9231d6c346494b918 // which was copied from https://twitter.com/diegohaz/status/1309489079378219009 -type PathImpl = +/*type PathImpl = K extends string ? T[K] extends Record ? T[K] extends ArrayLike @@ -133,3 +160,4 @@ export type PathValue>> = : P extends keyof Required ? Required[P] : never +*/ From 165a639e63d141e70323d81e73c38b1816dfe539 Mon Sep 17 00:00:00 2001 From: Avram Walden Date: Fri, 24 May 2024 09:21:42 -0700 Subject: [PATCH 3/4] =?UTF-8?q?test:=20=F0=9F=92=8D=20Adding=20tests=20for?= =?UTF-8?q?=20input=20data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Inputs/Input.tsx | 45 ++++--- src/Inputs/index.ts | 19 +++ src/useInertiaInput/index.ts | 3 +- tests/.eslintrc | 4 +- tests/TestInput.tsx | 29 ----- tests/components/ContextTest.tsx | 28 +++++ tests/components/data.ts | 40 ++++++ tests/formComponent.test.tsx | 208 ++++++++++++++----------------- tests/useDynamicInputs.test.tsx | 182 +++++++++++++++++++++++++++ tests/useInertiaInput.test.tsx | 59 +++------ 10 files changed, 411 insertions(+), 206 deletions(-) delete mode 100644 tests/TestInput.tsx create mode 100644 tests/components/ContextTest.tsx create mode 100644 tests/components/data.ts create mode 100644 tests/useDynamicInputs.test.tsx diff --git a/src/Inputs/Input.tsx b/src/Inputs/Input.tsx index be6e563..df62818 100644 --- a/src/Inputs/Input.tsx +++ b/src/Inputs/Input.tsx @@ -1,39 +1,54 @@ import React from 'react' import useInertiaInput from '../useInertiaInput' +import { NestedObject } from '../useInertiaForm' +import { BaseFormInputProps, InputConflicts } from '.' -interface InputProps extends React.InputHTMLAttributes { - name: string - model?: string +interface InputProps + extends + Omit, InputConflicts>, + BaseFormInputProps +{ component?: React.ElementType } -const Input = React.forwardRef(( - { name, component = 'input', model, onChange, ...props }, - ref, +const Input = ( + { name, + component = 'input', + type = 'text', + model, + onChange, + errorKey, + defaultValue, + clearErrorsOnChange, + ...props + }: InputProps, ) => { - const { inputName, inputId, value, setValue } = useInertiaInput({ name, model }) + const { form, inputName, inputId, value, setValue } = useInertiaInput({ + name, + model, + errorKey, + defaultValue, + clearErrorsOnChange, + }) const handleChange = (e: React.ChangeEvent) => { - if(onChange) { - onChange(e) - return - } - - setValue(e.target.value) + const value = (e.target?.checked || e.target.value) as T + setValue(value) + onChange?.(value, form) } const Element = component return ( ) -}) +} export default Input diff --git a/src/Inputs/index.ts b/src/Inputs/index.ts index acf5cbd..b93530e 100644 --- a/src/Inputs/index.ts +++ b/src/Inputs/index.ts @@ -1,2 +1,21 @@ +import { UseFormProps } from '../Form' +import { NestedObject } from '../useInertiaForm' +import { UseInertiaInputProps } from '../useInertiaInput' + export { default as Input } from './Input' export { default as Submit } from './Submit' + +export type InputConflicts = 'name'|'onChange'|'onBlur'|'onFocus'|'value'|'defaultValue' +export interface BaseFormInputProps + extends UseInertiaInputProps +{ + model?: string + errorKey?: string + field?: boolean + required?: boolean + hidden?: boolean + onChange?: (value: T, form: UseFormProps) => void + onBlur?: (value: T, form: UseFormProps) => void + onFocus?: (value: T, form: UseFormProps) => void + wrapperProps?: Record +} diff --git a/src/useInertiaInput/index.ts b/src/useInertiaInput/index.ts index 33e6d13..db31d8e 100644 --- a/src/useInertiaInput/index.ts +++ b/src/useInertiaInput/index.ts @@ -36,8 +36,7 @@ const useInertiaInput = ({ // Add a valid default value to the data object const initializingRef = useRef(true) - if(usedModel === 'values') { - } + useEffect(() => { if(!initializingRef.current) return diff --git a/tests/.eslintrc b/tests/.eslintrc index f3fa314..fcd5c61 100644 --- a/tests/.eslintrc +++ b/tests/.eslintrc @@ -1,7 +1,5 @@ { "rules": { - "no-unused-vars": "off", - "@typescript-eslint/member-delimiter-style": "off", - "@typescript-eslint/indent": "off" + "no-unused-vars": "off" } } \ No newline at end of file diff --git a/tests/TestInput.tsx b/tests/TestInput.tsx deleted file mode 100644 index 083be22..0000000 --- a/tests/TestInput.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react' -import { useInertiaInput } from '../src' - -interface TestInputProps { - name: string - model?: string - clearErrorsOnChange?: boolean -} - -const TestInput = ({ name, model, clearErrorsOnChange = true }: TestInputProps) => { - const { inputName, inputId, value, setValue } = useInertiaInput({ - name, - model, - clearErrorsOnChange, - }) - - return ( - setValue(e.target.value) } - /> - ) -} - -export default TestInput diff --git a/tests/components/ContextTest.tsx b/tests/components/ContextTest.tsx new file mode 100644 index 0000000..c5ffb4a --- /dev/null +++ b/tests/components/ContextTest.tsx @@ -0,0 +1,28 @@ +import React, { useEffect } from 'react' +import { NestedObject, useForm, UseFormProps } from '../../src' + +interface ContextTestProps { + cb?: (form: UseFormProps) => void +} + +const ContextTest = ({ cb }: ContextTestProps) => { + const form = useForm() + + useEffect(() => { + cb?.(form) + }, [cb]) + + return ( + <> +
+ { JSON.stringify(form.data) } +
+
+ { JSON.stringify(form.errors) } +
+ + ) + +} + +export default ContextTest diff --git a/tests/components/data.ts b/tests/components/data.ts new file mode 100644 index 0000000..90999f6 --- /dev/null +++ b/tests/components/data.ts @@ -0,0 +1,40 @@ +export const multiRootData = { + user: { + username: 'some name', + }, + person: { + first_name: 'first', + last_name: 'last', + middle_name: undefined, + nested: { + key: 'value', + }, + }, + contact: { + phones: [ + { number: '1234567890' }, + { number: '2234567890' }, + { number: '3234567890' }, + ], + }, +} + +export const singleRootData = { + person: { + first_name: 'first', + last_name: 'last', + middle_name: undefined, + nested: { + key: 'value', + }, + }, +} + +export const flatData = { + first_name: 'first', + last_name: 'last', + middle_name: undefined, + nested: { + key: 'value', + }, +} diff --git a/tests/formComponent.test.tsx b/tests/formComponent.test.tsx index f795dec..fe6e0c3 100644 --- a/tests/formComponent.test.tsx +++ b/tests/formComponent.test.tsx @@ -1,54 +1,98 @@ import React from 'react' import { fireEvent, render, screen } from '@testing-library/react' import '@testing-library/jest-dom' -import { Form, useForm } from '../src/Form' +import { Form } from '../src/Form' import Input from '../src/Inputs/Input' -import { DynamicInputs, Submit, useDynamicInputs } from '../src' +import { Submit } from '../src' import { router } from '@inertiajs/react' import { get } from 'lodash' -import { act } from '@testing-library/react-hooks' - -const initialData = { - user: { - username: 'some name', - }, - person: { - first_name: 'first', - last_name: 'last', - middle_name: undefined, - nested: { - key: 'value', - }, - }, - contact: { - phones: [ - { number: '1234567890' }, - { number: '2234567890' }, - { number: '3234567890' }, - ], - }, -} +import ContextTest from './components/ContextTest' +import { multiRootData, singleRootData } from './components/data' + describe('Form Component', () => { + describe('When not passed a data object', () => { + it('builds the data object from inputs', () => { + render( +
+ + + + + + , + ) + + expect(screen.getByTestId('data')).toHaveTextContent( + '{"user":{"username":"","firstName":"","lastName":""}}', + ) + }) + }) + + describe('When passed a data object', () => { + it('it uses the data values ignoring defaultValue', () => { + render( +
+ + + + + + , + ) + + expect(screen.getByTestId('data')).toHaveTextContent( + '{"user":{"username":"username","firstName":"Firsty","lastName":"Lasty"}}', + ) + }) + + it('adds missing keys to the data object from inputs', () => { + render( +
+ + + + + + , + ) + + expect(screen.getByTestId('data')).toHaveTextContent( + '{"user":{"username":"username","firstName":"Firsty","lastName":"Lasty"}}', + ) + }) + }) + /** * Rails Attributes `false` tests */ describe('With railsAttributes false', () => { it('renders a form with values in inputs', () => { render( -
+
, ) const input = screen.getByRole('textbox') - expect(input).toHaveValue(initialData.user.username) + expect(input).toHaveValue(multiRootData.user.username) }) it('updates form data with user input', () => { render( -
+
, ) @@ -67,7 +111,7 @@ describe('Form Component', () => { }) render( -
+ Submit @@ -87,18 +131,30 @@ describe('Form Component', () => { describe('With railsAttributes true', () => { it('renders a form with values in inputs', () => { render( - +
, ) const input = screen.getByRole('textbox') - expect(input).toHaveValue(initialData.user.username) + expect(input).toHaveValue(multiRootData.user.username) }) it('updates values as normal', () => { render( -
+
, ) @@ -113,8 +169,8 @@ describe('Form Component', () => { const mockRequest = jest.spyOn(router, 'visit').mockImplementation((route, request) => { const data = request?.data - expect(get(data, 'user.username')).toBe(initialData.user.username) - expect(get(data, 'person.nested_attributes.key')).toBe(initialData.person.nested.key) + expect(get(data, 'user.username')).toBe(multiRootData.user.username) + expect(get(data, 'person.nested_attributes.key')).toBe(multiRootData.person.nested.key) expect(get(data, 'extra.value')).toBe('exists') return Promise.resolve({ data: request?.data }) @@ -128,7 +184,7 @@ describe('Form Component', () => {
{ }) - describe('DynamicInputs', () => { - it('renders dynamic input fields', () => { - render( - - - - -
, - ) - - const buttons = screen.getAllByRole('button') - - expect(buttons.length).toBe(4) - }) - - it('adds inputs', () => { - let form, inputs - - render( -
- - , - ) - - function TestComponent() { - form = useForm() - inputs = useDynamicInputs({ - model: 'phones', - emptyData: { number: '' }, - }) - return null - } - - act(() => { - inputs.addInput() - inputs.addInput({ number: '1' }) - inputs.addInput(records => { - return ({ - number: `${parseInt(records[1].number) + 1}`, - }) - }) - }) - - const phones = form.getData('contact.phones') - - expect(phones).toContainEqual({ number: '' }) - expect(phones).toContainEqual({ number: '1' }) - expect(phones).toContainEqual({ number: '2234567891' }) - }) - - it('removes inputs', () => { - let form, inputs - - render( -
- - , - ) - - function TestComponent() { - form = useForm() - inputs = useDynamicInputs({ - model: 'phones', - emptyData: { number: '' }, - }) - return null - } - - let phones = form.getData('contact.phones') - expect(phones.length).toEqual(3) - - act(() => { - inputs.removeInput(1) - }) - - phones = form.getData('contact.phones') - - expect(phones.length).toEqual(2) - expect(phones).not.toContainEqual({ number: '2234567890' }) - }) - }) - describe('Filter', () => { it('unsets data at the given paths', () => { const handleChange = (form) => { @@ -241,7 +215,7 @@ describe('Form Component', () => {
diff --git a/tests/useDynamicInputs.test.tsx b/tests/useDynamicInputs.test.tsx new file mode 100644 index 0000000..291c61d --- /dev/null +++ b/tests/useDynamicInputs.test.tsx @@ -0,0 +1,182 @@ +import React from 'react' +import { render, screen } from '@testing-library/react' +import '@testing-library/jest-dom' +import { Form, useForm } from '../src/Form' +import Input from '../src/Inputs/Input' +import { DynamicInputs, useDynamicInputs } from '../src' +import { act } from '@testing-library/react-hooks' +import { multiRootData } from './components/data' + +describe('DynamicInputs', () => { + describe('With data object passed in', () => { + + it('renders dynamic input fields', () => { + render( + + + + +
, + ) + + const buttons = screen.getAllByRole('button') + + expect(buttons.length).toBe(4) + }) + + it('adds inputs', () => { + let form, inputs + + render( +
+ + , + ) + + function TestComponent() { + form = useForm() + inputs = useDynamicInputs({ + model: 'phones', + emptyData: { number: '' }, + }) + return null + } + + act(() => { + inputs.addInput() + inputs.addInput({ number: '1' }) + inputs.addInput(records => { + return ({ + number: `${parseInt(records[1].number) + 1}`, + }) + }) + }) + + const phones = form.getData('contact.phones') + + expect(phones).toContainEqual({ number: '' }) + expect(phones).toContainEqual({ number: '1' }) + expect(phones).toContainEqual({ number: '2234567891' }) + }) + + it('removes inputs', () => { + let form, inputs + + render( +
+ + , + ) + + function TestComponent() { + form = useForm() + inputs = useDynamicInputs({ + model: 'phones', + emptyData: { number: '' }, + }) + return null + } + + let phones = form.getData('contact.phones') + expect(phones.length).toEqual(3) + + act(() => { + inputs.removeInput(1) + }) + + phones = form.getData('contact.phones') + + expect(phones.length).toEqual(2) + expect(phones).not.toContainEqual({ number: '2234567890' }) + }) + }) + + + + + + + + describe('With no data object passed in', () => { + + it('renders dynamic input fields', () => { + render( +
+ + + +
, + ) + + const buttons = screen.getAllByRole('button') + + expect(buttons.length).toBe(1) + }) + + it('adds inputs', () => { + let form, inputs + + function TestComponent() { + form = useForm() + inputs = useDynamicInputs({ + model: 'phones', + emptyData: { number: '' }, + }) + return null + } + + render( +
+ + , + ) + + act(() => { + inputs.addInput() + inputs.addInput({ number: '1' }) + inputs.addInput(records => { + return ({ + number: `${parseInt(records[1].number) + 1}`, + }) + }) + }) + + const phones = form.getData('contact.phones') + + expect(phones).toContainEqual({ number: '' }) + expect(phones).toContainEqual({ number: '1' }) + expect(phones).toContainEqual({ number: '2' }) + }) + + it('removes inputs', () => { + let form, inputs + + render( +
+ + , + ) + + function TestComponent() { + form = useForm() + inputs = useDynamicInputs({ + model: 'phones', + emptyData: { number: '' }, + }) + return null + } + + let phones = form.getData('contact.phones') + expect(phones.length).toEqual(3) + + act(() => { + inputs.removeInput(1) + }) + + phones = form.getData('contact.phones') + + expect(phones.length).toEqual(2) + expect(phones).not.toContainEqual({ number: '2234567890' }) + }) + }) +}) diff --git a/tests/useInertiaInput.test.tsx b/tests/useInertiaInput.test.tsx index d4b0339..b537365 100644 --- a/tests/useInertiaInput.test.tsx +++ b/tests/useInertiaInput.test.tsx @@ -1,47 +1,24 @@ import React from 'react' import { fireEvent, render, screen } from '@testing-library/react' import '@testing-library/jest-dom' -import { Form, useForm } from '../src/Form' -import TestInput from './TestInput' - - -const ErrorContextTest = () => { - const { errors, setError } = useForm() - - const handleClick = e => { - e.preventDefault() - setError('errors.name', 'Error') - } - - return ( - <> -