import { expect, test, vi } from "vitest"; import { useRef, useState } from "react"; import { ErrorsMap, Field, FieldArray, Form } from "houseform"; import { cleanup, render, waitFor, waitForElementToBeRemoved, } from "@testing-library/react"; import { z } from "zod"; import { FormInstance } from "houseform"; import * as React from "react"; test("Form should render children", () => { const { getByText } = render(
{}}>{() =>
Test
}
); expect(getByText("Test")).toBeInTheDocument(); }); test("Form should submit with basic values in tact", async () => { const SubmitValues = () => { const [values, setValues] = useState(null); if (values) return

{values}

; return (
setValues(JSON.stringify(values))}> {({ submit }) => ( <> name={"email"} initialValue="test@example.com"> {() => <>} )} ); }; const { getByText, container } = render(); await user.click(getByText("Submit")); await waitFor(() => expect(container).toMatchInlineSnapshot(`

{"email":"test@example.com"}

`) ); }); test("Form should submit with simple array values in tact", async () => { const SubmitValues = () => { const [values, setValues] = useState(null); if (values) return

{values}

; return (
setValues(JSON.stringify(values))}> {({ submit }) => ( <> name={"email"} initialValue={["test@example.com"]} > {() => <>} )} ); }; const { getByText, container } = render(); await user.click(getByText("Submit")); await waitFor(() => expect(container).toMatchInlineSnapshot(`

{"email":["test@example.com"]}

`) ); }); test("Form should not submit if there are errors with onChangeValidate", async () => { const SubmitValues = () => { const [values, setValues] = useState(null); if (values) return

{values}

; return (
setValues(JSON.stringify(values))}> {({ submit }) => ( <> name={"email"} initialValue="" onChangeValidate={z.string().min(1)} > {() => <>} )} ); }; const { getByText } = render(); await user.click(getByText("Submit")); expect(getByText("Submit")).toBeInTheDocument(); }); test("Form should not submit if there are errors with onSubmitValidate", async () => { const SubmitValues = () => { const [values, setValues] = useState(null); if (values) return

{values}

; return (
setValues(JSON.stringify(values))}> {({ submit }) => ( <> name={"email"} initialValue="" onSubmitValidate={z.string().min(1)} > {() => <>} )} ); }; const { getByText } = render(); await user.click(getByText("Submit")); expect(getByText("Submit")).toBeInTheDocument(); }); test("Form should show isValid proper", async () => { const { findByText, getByPlaceholderText } = render(
{}}> {({ isValid }) => ( <> name={"email"} onChangeValidate={() => Promise.reject("Not valid")} initialValue="test@example.com" > {({ value, setValue, errors }) => (
setValue(e.target.value)} /> {errors.map((error) => (

{error}

))}
)}

{isValid ? "Is valid" : "Is not valid"}

)} ); expect(await findByText("Is valid")).toBeInTheDocument(); await user.type(getByPlaceholderText("Email"), "test"); expect(await findByText("Is not valid")).toBeInTheDocument(); }); test("Form should show isSubmitted proper", async () => { const { getByText, findByText } = render(
{}}> {({ isSubmitted, submit }) => ( <>

{isSubmitted ? "Submitted" : "Not submitted"}

)}
); expect(getByText("Not submitted")).toBeInTheDocument(); await user.click(getByText("Submit")); expect(await findByText("Submitted")).toBeInTheDocument(); }); test("Form should show isTouched proper", async () => { const { getByText, findByText } = render(
{}}> {({ isTouched, submit }) => ( <> {({ onBlur }) => ( )}

{isTouched ? "Form is touched" : "Form is not touched"}

)}
); expect(getByText("Form is not touched")).toBeInTheDocument(); await user.click(getByText("Touch a field")); expect(await findByText("Form is touched")).toBeInTheDocument(); }); test("Form should handle setIsTouched helper", async () => { const { getByText, findByText } = render(
{({ setIsTouched }) => ( <> {({ isTouched }) => (

{isTouched ? "Is touched" : "Is not touched"}

)}
)}
); expect(getByText("Is not touched")).toBeInTheDocument(); await user.click(getByText("Touch")); expect(await findByText("Is touched")).toBeInTheDocument(); }); test("Form should handle setIsDirty helper", async () => { const { getByText, findByText } = render(
{({ setIsDirty }) => ( <> {({ isDirty }) =>

{isDirty ? "Is dirty" : "Is not dirty"}

}
)}
); expect(getByText("Is not dirty")).toBeInTheDocument(); await user.click(getByText("Dirty")); expect(await findByText("Is dirty")).toBeInTheDocument(); }); test("Form should reset isTouched when all touched fields are not touched anymore", async () => { const { getByText, findByText } = render(
{}}> {({ isTouched, submit }) => ( <> {({ onBlur, setIsTouched }) => (
)}

{isTouched ? "Form is touched" : "Form is not touched"}

)}
); await user.click(getByText("Touch a field")); expect(await findByText("Form is touched")).toBeInTheDocument(); await user.click(getByText("Untouch a field")); expect(getByText("Form is not touched")).toBeInTheDocument(); }); test("Form should show isDirty proper", async () => { const { getByText, findByText } = render(
{}}> {({ isDirty, submit }) => ( <> {({ setValue }) => ( )}

{isDirty ? "Form is dirty" : "Form is not dirty"}

)}
); expect(getByText("Form is not dirty")).toBeInTheDocument(); await user.click(getByText("Dirty a field")); expect(await findByText("Form is dirty")).toBeInTheDocument(); }); test("Form should reset isDirty when all touched fields are not touched anymore", async () => { const { getByText, findByText } = render(
{}}> {({ isDirty, submit }) => ( <> {({ setValue, setIsDirty }) => (
)}

{isDirty ? "Form is dirty" : "Form is not dirty"}

)}
); await user.click(getByText("Dirty a field")); expect(await findByText("Form is dirty")).toBeInTheDocument(); await user.click(getByText("Undirty a field")); expect(getByText("Form is not dirty")).toBeInTheDocument(); }); test("Form should have context passed to ref", async () => { const Comp = () => { const formRef = useRef(undefined!); const [val, setVal] = useState(""); if (val) return

{val}

; return (
{}} ref={formRef}> {() => ( {() =>
}
)}
); }; const { getByText, queryByText, findByText } = render(); expect(queryByText("Test")).not.toBeInTheDocument(); await user.click(getByText("Submit")); expect(await findByText("Test")).toBeInTheDocument(); }); test("Form submit should return `true` if valid", async () => { const Comp = () => { const [val, setVal] = useState(null); if (val !== null) return

{val ? "True" : "False"}

; return (
{}}> {({ submit }) => ( { e.preventDefault(); const isValid = await submit(); setVal(isValid); }} > {() =>
}
)}
); }; const { getByText, queryByText, findByText } = render(); expect(queryByText("True")).not.toBeInTheDocument(); await user.click(getByText("Submit")); expect(await findByText("True")).toBeInTheDocument(); }); test("Form submit should return `false` if not valid", async () => { const Comp = () => { const [val, setVal] = useState(null); if (val !== null) return

{val ? "True" : "False"}

; return (
{}}> {({ submit }) => ( { e.preventDefault(); const isValid = await submit(); setVal(isValid); }} > {() =>
}
)}
); }; const { getByText, queryByText, findByText } = render(); expect(queryByText("False")).not.toBeInTheDocument(); await user.click(getByText("Submit")); expect(await findByText("False")).toBeInTheDocument(); }); test("Field with dot notation should submit with deep object value", async () => { const SubmitValues = () => { const [values, setValues] = useState(null); if (values) return

{values}

; return (
setValues(JSON.stringify(values))}> {({ submit }) => ( <> name={"test.other.email"} initialValue="test@example.com" > {() => <>} )} ); }; const { getByText, container } = render(); await user.click(getByText("Submit")); await waitFor(() => expect(container).toMatchInlineSnapshot(`

{"test":{"other":{"email":"test@example.com"}}}

`) ); }); test("Field with bracket notation should submit with deep object value", async () => { const SubmitValues = () => { const [values, setValues] = useState(null); if (values) return

{values}

; return (
setValues(JSON.stringify(values))}> {({ submit }) => ( <> name={"test['other']['email']"} initialValue="test@example.com" > {() => <>} )} ); }; const { getByText, container } = render(); await user.click(getByText("Submit")); await waitFor(() => expect(container).toMatchInlineSnapshot(`

{"test":{"other":{"email":"test@example.com"}}}

`) ); }); // should be gotten with `getFieldValue('test.other')` test("Form's `getFieldValue` should show dot notation for incorrect syntax", async () => { const Comp = () => { const formRef = useRef(undefined!); const [val, setVal] = useState(""); if (val) return

{val}

; return (
{}} ref={formRef}> {() => ( {() =>
}
)}
); }; const { getByText, queryByText, findByText } = render(); expect(queryByText("Test")).not.toBeInTheDocument(); await user.click(getByText("Submit")); expect(await findByText("Test")).toBeInTheDocument(); }); test("Form should show all field errors if requested", async () => { const SubmitValues = () => { return (
{({ errors }) => ( <> name={"email"} initialValue="" onMountValidate={z .string() .min(1, "Should have a min length of 1")} > {() => <>} name={"password"} initialValue="" onMountValidate={z .string() .min(3, "Should have a min length of 3")} > {() => <>} {errors.map((error) => (

{error}

))} )} ); }; const { findByText } = render(); expect(await findByText("Should have a min length of 1")).toBeInTheDocument(); expect(await findByText("Should have a min length of 3")).toBeInTheDocument(); }); test("Assigning a form to a ref should not break the application", async () => { const Comp = () => { const [formRef, setFormRef] = React.useState(); const setFormRefCB = React.useCallback((r: any) => { setFormRef(r); }, []); return (
{({ isValid, submit }) =>

Testing

}
); }; const { getByText } = render(); expect(getByText("Testing")).toBeInTheDocument(); }); test("Form should not submit when errors are present", async () => { const Comp = () => { const [isSubmitted, setIsSubmitted] = useState(false); if (isSubmitted) return

Submitted

; return (
setIsSubmitted(true)}> {({ submit }) => ( <> name={"email"} initialValue="" onMountValidate={z.string().min(12, "Must have 12 characters")} > {({ errors }) => ( <>{errors && errors.length &&

There are errors

} )} )} ); }; const { getByText, queryByText } = render(); await waitFor(() => expect(getByText("There are errors")).toBeInTheDocument() ); await user.click(getByText("Submit")); expect(queryByText("Submitted")).not.toBeInTheDocument(); }); test("Form should submit when errors are present and submitWhenInvalid is true", async () => { const Comp = () => { const [isSubmitted, setIsSubmitted] = useState(false); if (isSubmitted) return

Submitted

; return (
setIsSubmitted(true)} > {({ submit }) => ( <> name={"email"} initialValue="" onMountValidate={z.string().min(12, "Must have 12 characters")} > {({ errors }) => ( <>{errors && errors.length &&

There are errors

} )} )} ); }; const { getByText } = render(); await waitFor(() => expect(getByText("There are errors")).toBeInTheDocument() ); await user.click(getByText("Submit")); expect(getByText("Submitted")).toBeInTheDocument(); }); test("Form submission should receive initially empty errors array", async () => { const Comp = () => { const [formErrors, setFormErrors] = useState(null); if (formErrors !== null) { return

Form errors: {JSON.stringify(formErrors)}

; } return (
{ setFormErrors(form.errors); }} > {({ submit }) => }
); }; const { getByText, container } = render(); user.click(getByText("Submit")); await waitFor(() => expect(getByText(/Form errors/)).toBeInTheDocument()); expect(container).toMatchInlineSnapshot(`

Form errors: []

`); }); test("Form submission should receive correct errors array when errors are in use in form itself", async () => { const Comp = () => { const [formErrors, setFormErrors] = useState(null); if (formErrors !== null) { return

Form errors: {JSON.stringify(formErrors)}

; } return (
{ setFormErrors(form.errors); }} > {({ submit, errors }) => (
{({ errors }) => ( <>{errors && errors.length &&

There are errors

} )}

{JSON.stringify(errors)}

)}
); }; const { getByText, container } = render(); await waitFor(() => expect(getByText(/There are errors/)).toBeInTheDocument() ); user.click(getByText("Submit")); await waitFor(() => expect(getByText(/Form errors/)).toBeInTheDocument()); expect(container).toMatchInlineSnapshot(`

Form errors: ["You must have 12 characters"]

`); }); test("Form submission should receive correct errors array when errors are not in use in form itself", async () => { const Comp = () => { const [formErrors, setFormErrors] = useState(null); if (formErrors !== null) { return

Form errors: {JSON.stringify(formErrors)}

; } return (
{ setFormErrors(form.errors); }} > {({ submit, errors }) => (
{({ errors }) => ( <>{errors && errors.length &&

There are errors

} )}
)}
); }; const { getByText, container } = render(); await waitFor(() => expect(getByText(/There are errors/)).toBeInTheDocument() ); user.click(getByText("Submit")); await waitFor(() => expect(getByText(/Form errors/)).toBeInTheDocument()); expect(container).toMatchInlineSnapshot(`

Form errors: ["You must have 12 characters"]

`); }); test("Form submission should receive correct isValid", async () => { const Comp = () => { const [formIsValid, setFormIsValid] = useState(null); if (formIsValid !== null) { return

Form is valid: {formIsValid.toString()}

; } return (
{ setFormIsValid(form.isValid); }} > {({ submit, errors }) => (
{({ errors }) => ( <>{errors && errors.length &&

There are errors

} )}
)}
); }; const { getByText, container } = render(); await waitFor(() => expect(getByText(/There are errors/)).toBeInTheDocument() ); user.click(getByText("Submit")); await waitFor(() => expect(getByText(/Form is valid/)).toBeInTheDocument()); expect(container).toMatchInlineSnapshot(`

Form is valid: false

`); }); test("Form submission should receive correct isTouched", async () => { const Comp = () => { const [formIsTouched, setFormIsTouched] = useState(null); if (formIsTouched !== null) { return

Form is touched: {formIsTouched.toString()}

; } return (
{ setFormIsTouched(form.isTouched); }} > {({ submit }) => (
{({ onBlur }) => }
)}
); }; const { getByText, container } = render(); user.click(getByText("Blur me")); user.click(getByText("Submit")); await waitFor(() => expect(getByText(/Form is touched/)).toBeInTheDocument()); expect(container).toMatchInlineSnapshot(`

Form is touched: true

`); }); test("Form submission should receive correct isDirty", async () => { const Comp = () => { const [formIsDirty, setFormIsDirty] = useState(null); if (formIsDirty !== null) { return

Form is dirty: {formIsDirty.toString()}

; } return (
{ setFormIsDirty(form.isDirty); }} > {({ submit }) => (
{({ setValue }) => ( )}
)}
); }; const { getByText, container } = render(); user.click(getByText("Set value")); user.click(getByText("Submit")); await waitFor(() => expect(getByText(/Form is dirty/)).toBeInTheDocument()); expect(container).toMatchInlineSnapshot(`

Form is dirty: true

`); }); test("Form's memoChild should prevent re-renders", async () => { const formNonMemoHasRendered = vi.fn(); const NonMemoComp = () => { const [counter, setCounter] = useState(0); return (
{() => { formNonMemoHasRendered(); return
; }}

Counter: {counter}

); }; const { getByText: getByTextForNonMemo } = render(); expect(getByTextForNonMemo("Counter: 0")).toBeInTheDocument(); expect(formNonMemoHasRendered).toHaveBeenCalledTimes(1); user.click(getByTextForNonMemo("Add to counter")); await waitFor(() => expect(getByTextForNonMemo("Counter: 1")).toBeInTheDocument() ); expect(formNonMemoHasRendered).toHaveBeenCalledTimes(2); cleanup(); const formMemoHasRendered = vi.fn(); const MemoComp = () => { const [counter, setCounter] = useState(0); return (
{() => { formMemoHasRendered(); return
; }}

Counter: {counter}

); }; const { getByText: getByTextForMemo } = render(); expect(getByTextForMemo("Counter: 0")).toBeInTheDocument(); expect(formMemoHasRendered).toHaveBeenCalledTimes(1); user.click(getByTextForMemo("Add to counter")); await waitFor(() => expect(getByTextForMemo("Counter: 1")).toBeInTheDocument() ); expect(formMemoHasRendered).toHaveBeenCalledTimes(1); }); test("Form errorsMap should show specific field errors only.", async () => { const SubmitValues = () => { return (
{({ errorsMap }) => ( <> name={"email"} initialValue="" onMountValidate={z .string() .min(1, "Should have a min length of 1")} > {() => <>} name={"email2"} initialValue="" onMountValidate={z .string() .min(3, "Should have a min length of 3")} > {() => <>} {errorsMap["email"]?.map((error) => (

{error}

))} )} ); }; const { findByText, queryByText } = render(); expect(await findByText("Should have a min length of 1")).toBeInTheDocument(); expect(queryByText("Should have a min length of 3")).not.toBeInTheDocument(); }); test("Form submission should receive initially empty errorsMap object", async () => { const Comp = () => { const [formErrorsMap, setFormErrorsMap] = useState(null); if (formErrorsMap !== null) { return

Form errorsMap: {JSON.stringify(formErrorsMap)}

; } return (
{ setFormErrorsMap(form.errorsMap); }} > {({ submit }) => }
); }; const { getByText, container } = render(); user.click(getByText("Submit")); await waitFor(() => expect(getByText(/Form errors/)).toBeInTheDocument()); expect(container).toMatchInlineSnapshot(`

Form errorsMap: {}

`); }); test("Form should set isValidating proper", async () => { const { getByText, queryByText } = render(
{({ isValidating, submit }) => ( <> new Promise((resolve) => setTimeout(() => resolve(true), 50)) } > {({ value, setValue }) => ( setValue(e.target.value)} /> )} {isValidating &&

Validating

} )}
); expect(queryByText("Validating")).not.toBeInTheDocument(); await user.click(getByText("Submit")); expect(getByText("Validating")).toBeInTheDocument(); await waitForElementToBeRemoved(() => queryByText("Validating")); }); test("Form should reset with no backup values correctly", async () => { const ResetValues = () => { return (
{ reset(); }} > {({ submit, isDirty, isTouched }) => ( <> name={"test['other']['email']"}> {({ value, setValue }) => ( setValue(e.target.value)} /> )} name={"test['other']['password']"}> {({ value, setValue }) => ( setValue(e.target.value)} /> )}

{isDirty ? "isDirty" : "isNotDirty"}

{isTouched ? "isTouched" : "isNotTouched"}

)} ); }; const { getByText, container, getByPlaceholderText } = render( ); await user.type(getByPlaceholderText("Email"), "test"); await user.type(getByPlaceholderText("Password"), "test"); await user.click(getByText("Submit")); await waitFor(() => expect(container).toMatchInlineSnapshot(`

isNotDirty

isNotTouched

`) ); }); test("Form should reset with initial values correctly", async () => { const ResetValues = () => { return (
{ reset(); }} > {({ submit, isDirty, isTouched }) => ( <> name={"test['other']['email']"} initialValue="initial@email.com" > {({ value, setValue }) => ( setValue(e.target.value)} /> )} name={"test['other']['password']"} initialValue="password" > {({ value, setValue }) => ( setValue(e.target.value)} /> )}

{isDirty ? "isDirty" : "isNotDirty"}

{isTouched ? "isTouched" : "isNotTouched"}

)} ); }; const { getByText, container, getByPlaceholderText } = render( ); await user.type(getByPlaceholderText("Email"), "test"); await user.type(getByPlaceholderText("Password"), "test"); await user.click(getByText("Submit")); await waitFor(() => expect(container).toMatchInlineSnapshot(`

isNotDirty

isNotTouched

`) ); }); test("Form should reset with resetWithValues correctly", async () => { const ResetValues = () => { return (
{ reset(); }} > {({ submit, isDirty, isTouched }) => ( <> name={"test['other']['email']"} resetWithValue="initial@email.com" > {({ value, setValue }) => ( setValue(e.target.value)} /> )} name={"test['other']['password']"} resetWithValue="password" > {({ value, setValue }) => ( setValue(e.target.value)} /> )}

{isDirty ? "isDirty" : "isNotDirty"}

{isTouched ? "isTouched" : "isNotTouched"}

)} ); }; const { getByText, container, getByPlaceholderText } = render( ); await user.type(getByPlaceholderText("Email"), "test"); await user.type(getByPlaceholderText("Password"), "test"); await user.click(getByText("Submit")); await waitFor(() => expect(container).toMatchInlineSnapshot(`

isNotDirty

isNotTouched

`) ); }); test("Form should reset with empty string resetWithValue correctly", async () => { const ResetValues = () => { return (
{ reset(); }} > {({ submit, isDirty, isTouched }) => ( <> name={"test['other']['email']"} initialValue="initial@email.com" resetWithValue="" > {({ value, setValue }) => ( setValue(e.target.value)} /> )} name={"test['other']['password']"} resetWithValue="password" > {({ value, setValue }) => ( setValue(e.target.value)} /> )}

{isDirty ? "isDirty" : "isNotDirty"}

{isTouched ? "isTouched" : "isNotTouched"}

)} ); }; const { getByText, container, getByPlaceholderText } = render( ); await user.type(getByPlaceholderText("Email"), "test"); await user.type(getByPlaceholderText("Password"), "test"); await user.click(getByText("Submit")); await waitFor(() => expect(container).toMatchInlineSnapshot(`

isNotDirty

isNotTouched

`) ); }); test("Form should reset with resetWithValues and initial values correctly", async () => { const ResetValues = () => { return (
{ reset(); }} > {({ submit, isDirty, isTouched }) => ( <> name={"test['other']['email']"} initialValue="wrong@email.com" resetWithValue="initial@email.com" > {({ value, setValue }) => ( setValue(e.target.value)} /> )} name={"test['other']['password']"} initialValue="wrong password" resetWithValue="password" > {({ value, setValue }) => ( setValue(e.target.value)} /> )}

{isDirty ? "isDirty" : "isNotDirty"}

{isTouched ? "isTouched" : "isNotTouched"}

)} ); }; const { getByText, container, getByPlaceholderText } = render( ); await user.type(getByPlaceholderText("Email"), "test"); await user.type(getByPlaceholderText("Password"), "test"); await user.click(getByText("Submit")); await waitFor(() => expect(container).toMatchInlineSnapshot(`

isNotDirty

isNotTouched

`) ); }); test("Form submission should receive FormInstance value", async () => { const Comp = () => { const [formValue, setFormValue] = useState | null>( null ); if (formValue !== null) { return

Form values: {JSON.stringify(formValue)}

; } return (
{ setFormValue(form.value); }} > {({ submit }) => (
{() => <>}
)}
); }; const { getByText, container } = render(); user.click(getByText("Submit")); await waitFor(() => expect(getByText(/Form values/)).toBeInTheDocument()); expect(container).toMatchInlineSnapshot(`

Form values: {"test":"hello-world"}

`); }); test("Form should use value to conditionally hide field based on another's value", async () => { const Comp = () => { return (
{}}> {({ submit, value }) => (
{({ setValue }) => ( )} {value.always === "bye" ? null : ( {() =>

I am here

}
)}
)}
); }; const { getByText, queryByText } = render(); await waitFor(() => expect(getByText("I am here")).toBeInTheDocument()); user.click(getByText("Set")); await waitFor(() => expect(queryByText("I am here")).toBeInTheDocument()); }); test("Form `deleteField` should remove field", async () => { const Comp = () => { const [show, setShow] = useState(true); return (
{({ getFieldValue, deleteField }) => (
{show && ( name={"email"} initialValue="emailHere" preserveValue > {({ value }) => } )}

{getFieldValue("email")?.value}

)}
); }; const { rerender, getByText, queryByText } = render(); rerender(); expect(getByText("emailHere")).toBeInTheDocument(); await user.click(getByText("Unmount")); rerender(); expect(getByText("emailHere")).toBeInTheDocument(); await user.click(getByText("Delete field")); rerender(); expect(queryByText("emailHere")).not.toBeInTheDocument(); }); test("Form submit should reset errors", async () => { const submitMock = vi.fn(); const { getByText, queryByText, getByPlaceholderText } = render(
{({ submit, errors }) => (
name="email" onSubmitValidate={z.string().min(1)}> {({ value, setValue }) => ( setValue(e.target.value)} /> )} {errors.map((error) => { return

{error}

; })}
)}
); await user.click(getByText("Submit")); expect( getByText("String must contain at least 1 character(s)") ).toBeInTheDocument(); await user.type(getByPlaceholderText("Email"), "emailhere"); await user.click(getByText("Submit")); expect( queryByText("String must contain at least 1 character(s)") ).not.toBeInTheDocument(); expect(submitMock).toHaveBeenCalledTimes(1); }); test("Form should not trigger validation when reset", async () => { const Comp = () => { return (
{({ reset }) => (
name="email" onChangeValidate={z.string().min(1, "email error")} > {({ value, setValue, errors }) => ( <> setValue(e.target.value)} /> {errors.map((error) => (

{error}

))} )}
)}
); }; const { getByText, getByPlaceholderText, queryByText } = render(); await user.type(getByPlaceholderText("Email"), "emailHere"); await user.click(getByText("Reset")); expect(queryByText("email error")).not.toBeInTheDocument(); }); test("onSubmitTransform should work with transform function", async () => { const submitMock = vi.fn(); const { getByText, getByPlaceholderText } = render(
{({ submit }) => ( <> name={"price"} onSubmitTransform={(value) => parseInt(value)} > {({ value, setValue }) => ( setValue(e.target.value)} /> )} )} ); await user.type(getByPlaceholderText("Price"), "69"); await user.click(getByText("Submit")); expect(submitMock.mock.calls[0][0]).toEqual({ price: 69 }); }); test("onSubmitTransform should work with async transform function", async () => { const submitMock = vi.fn(); const { getByText, getByPlaceholderText } = render(
{({ submit }) => ( <> name={"price"} onSubmitTransform={async (value) => { await new Promise((resolve) => setTimeout(() => resolve(true), 50) ); return Number(value); }} > {({ value, setValue }) => ( setValue(e.target.value)} /> )} )} ); await user.type(getByPlaceholderText("Price"), "69"); await user.click(getByText("Submit")); await waitFor(() => expect(submitMock).toBeCalledTimes(1)); expect(submitMock.mock.calls[0][0]).toEqual({ price: 69 }); }); test("onSubmitTransform should work with zod transform", async () => { const submitMock = vi.fn(); const { getByText, getByPlaceholderText } = render(
{({ submit }) => ( <> name={"price"} onSubmitTransform={z.string().transform(Number)} > {({ value, setValue }) => ( setValue(e.target.value)} /> )} )} ); await user.type(getByPlaceholderText("Price"), "69"); await user.click(getByText("Submit")); expect(submitMock.mock.calls[0][0]).toEqual({ price: 69 }); }); test("onSubmitTransform should work with onSubmitValidate", async () => { const submitMock = vi.fn(); const { getByText, getByPlaceholderText, queryByText } = render(
{({ submit, isValidating }) => ( <> name={"price"} onSubmitValidate={() => new Promise((resolve) => setTimeout(() => resolve(true), 50)) } onSubmitTransform={Number} > {({ value, setValue }) => ( setValue(e.target.value)} /> )} {isValidating &&

Validating

} )} ); expect(queryByText("Validating")).not.toBeInTheDocument(); await user.type(getByPlaceholderText("Price"), "69"); await user.click(getByText("Submit")); expect(getByText("Validating")).toBeInTheDocument(); await waitForElementToBeRemoved(() => queryByText("Validating")); await waitFor(() => expect(submitMock).toBeCalledTimes(1)); expect(submitMock.mock.calls[0][0]).toEqual({ price: 69 }); }); test("onSubmitTransform should set errors and ignore value if it throws", async () => { const submitMock = vi.fn(); const { getByText, getByPlaceholderText, findByText, queryByText } = render(
{({ submit, errors }) => ( <> name={"price"} onSubmitTransform={z.string().min(1, "Not valid").transform(Number)} > {({ value, setValue }) => (
setValue(e.target.value)} /> {errors.map((error) => (

{error}

))}
)} )} ); await user.click(getByText("Submit")); expect(await findByText("Not valid")).toBeInTheDocument(); await waitFor(() => expect(submitMock).toBeCalledTimes(1)); expect(submitMock.mock.calls[0][0]).toEqual({}); await user.type(getByPlaceholderText("Price"), "69"); await user.click(getByText("Submit")); await waitFor(() => expect(submitMock).toBeCalledTimes(2)); expect(queryByText("Not valid")).not.toBeInTheDocument(); expect(submitMock.mock.calls[1][0]).toEqual({ price: 69 }); });