diff --git a/app/components/form/ValidatedFlowForm.tsx b/app/components/form/ValidatedFlowForm.tsx new file mode 100644 index 000000000..fb4fe43d4 --- /dev/null +++ b/app/components/form/ValidatedFlowForm.tsx @@ -0,0 +1,49 @@ +import { useParams, useLocation } from "@remix-run/react"; +import { ValidatedForm } from "remix-validated-form"; +import type { ButtonNavigationProps } from "~/components/form/ButtonNavigation"; +import { ButtonNavigation } from "~/components/form/ButtonNavigation"; +import type { Context } from "~/domains/contexts"; +import { StrapiFormComponents } from "~/services/cms/components/StrapiFormComponents"; +import type { StrapiFormComponent } from "~/services/cms/models/StrapiFormComponent"; +import { splatFromParams } from "~/services/params"; +import { CSRFKey } from "~/services/security/csrf/csrfKey"; +import { validatorForFieldnames } from "~/services/validation/buildStepValidator"; + +type ValidatedFlowFormProps = { + stepData: Context; + formElements: StrapiFormComponent[]; + buttonNavigationProps: ButtonNavigationProps; + csrf: string; +}; + +function ValidatedFlowForm({ + stepData, + formElements, + buttonNavigationProps, + csrf, +}: Readonly) { + const stepId = splatFromParams(useParams()); + const { pathname } = useLocation(); + const fieldNames = formElements.map((entry) => entry.name); + const validator = validatorForFieldnames(fieldNames, pathname); + return ( + + +
+
+ +
+ +
+
+ ); +} + +export default ValidatedFlowForm; diff --git a/app/components/form/__test__/ValidatedFlowForm.test.tsx b/app/components/form/__test__/ValidatedFlowForm.test.tsx new file mode 100644 index 000000000..8bcd8b5ec --- /dev/null +++ b/app/components/form/__test__/ValidatedFlowForm.test.tsx @@ -0,0 +1,424 @@ +import { withZod } from "@remix-validated-form/with-zod"; +import { fireEvent, render, waitFor } from "@testing-library/react"; +import { RouterProvider, createMemoryRouter } from "react-router-dom"; +import { z } from "zod"; +import { getStrapiCheckboxComponent } from "tests/factories/cmsModels/strapiCheckboxComponent"; +import { getStrapiDropdownComponent } from "tests/factories/cmsModels/strapiDropdownComponent"; +import { + getStrapiInputComponent, + StrapiInputType, +} from "tests/factories/cmsModels/strapiInputComponent"; +import { getStrapiSelectComponent } from "tests/factories/cmsModels/strapiSelectComponent"; +import { getStrapiTextareaComponent } from "tests/factories/cmsModels/strapiTextareaComponent"; +import { getStrapiTileGroupComponent } from "tests/factories/cmsModels/strapiTileGroupComponent"; +import ValidatedFlowForm from "~/components/form/ValidatedFlowForm"; +import type { StrapiFormComponent } from "~/services/cms/models/StrapiFormComponent"; +import * as buildStepValidator from "~/services/validation/buildStepValidator"; +import { checkedRequired } from "~/services/validation/checkedCheckbox"; +import { createDateSchema } from "~/services/validation/date"; +import { integerSchema } from "~/services/validation/integer"; +import { stringRequiredSchema } from "~/services/validation/stringRequired"; +import { timeSchema } from "~/services/validation/time"; +import { + customRequiredErrorMessage, + YesNoAnswer, +} from "~/services/validation/YesNoAnswer"; + +vi.mock("@remix-run/react", () => ({ + useParams: vi.fn(), + useLocation: vi.fn(() => ({ + pathname: "", + })), +})); + +vi.mock("~/services/params", () => ({ + splatFromParams: vi.fn(), +})); + +const fieldNameValidatorSpy = vi.spyOn( + buildStepValidator, + "validatorForFieldnames", +); + +describe("ValidatedFlowForm", () => { + it("should render", () => { + fieldNameValidatorSpy.mockImplementationOnce(vi.fn()); + const { getByText } = renderValidatedFlowForm([]); + expect(getByText("NEXT")).toBeInTheDocument(); + }); + + describe("Input Component", () => { + beforeAll(() => { + fieldNameValidatorSpy.mockImplementation(() => + withZod(z.object({ myInput: integerSchema })), + ); + }); + const { component, expectInputErrorToExist } = getStrapiInputComponent({ + code: "invalidInteger", + text: "Please enter a valid integer.", + }); + + it("should display an error if the user enters an invalid integer", async () => { + const { getByText, getByPlaceholderText } = renderValidatedFlowForm([ + component, + ]); + + const nextButton = getByText("NEXT"); + const inputComponent = getByPlaceholderText("input"); + expect(nextButton).toBeInTheDocument(); + + fireEvent.input(inputComponent, { + target: { value: "1,230.12" }, + }); + fireEvent.blur(inputComponent); + await expectInputErrorToExist(); + + fireEvent.click(nextButton); + await waitFor(async () => { + expect(inputComponent).toHaveFocus(); + await expectInputErrorToExist(); + }); + }); + + it("should not display an error for correct input", async () => { + const { getByText, getByPlaceholderText, queryByTestId } = + renderValidatedFlowForm([component]); + const inputComponent = getByPlaceholderText("input"); + + fireEvent.input(inputComponent, { + target: { value: "123" }, + }); + fireEvent.click(getByText("NEXT")); + await waitFor(() => { + expect(inputComponent).not.toHaveClass("has-error"); + expect(queryByTestId("inputError")).not.toBeInTheDocument(); + expect(queryByTestId("ErrorOutlineIcon")).not.toBeInTheDocument(); + }); + }); + }); + + describe("Date Input Component", () => { + beforeAll(() => { + fieldNameValidatorSpy.mockImplementation(() => + withZod(z.object({ myDateInput: createDateSchema() })), + ); + }); + const { component, expectInputErrorToExist } = getStrapiInputComponent( + { + code: "invalid", + text: "Please enter a valid date.", + }, + StrapiInputType.Date, + ); + + it("should display an error if the user enters an invalid date", async () => { + const { getByText, getByPlaceholderText } = renderValidatedFlowForm([ + component, + ]); + + const nextButton = getByText("NEXT"); + const dateInputComponent = getByPlaceholderText("date input"); + expect(nextButton).toBeInTheDocument(); + + fireEvent.input(dateInputComponent, { + target: { value: "13,20,2024" }, + }); + fireEvent.blur(dateInputComponent); + await expectInputErrorToExist(); + + fireEvent.click(nextButton); + await waitFor(async () => { + expect(dateInputComponent).toHaveFocus(); + await expectInputErrorToExist(); + }); + }); + + it("should not display an error for correct input", async () => { + const { getByText, getByPlaceholderText, queryByTestId } = + renderValidatedFlowForm([component]); + const dateInputComponent = getByPlaceholderText("date input"); + + fireEvent.input(dateInputComponent, { + target: { value: "10.12.2024" }, + }); + fireEvent.click(getByText("NEXT")); + await waitFor(() => { + expect(dateInputComponent).not.toHaveClass("has-error"); + expect(queryByTestId("inputError")).not.toBeInTheDocument(); + expect(queryByTestId("ErrorOutlineIcon")).not.toBeInTheDocument(); + }); + }); + }); + + describe("Time Input Component", () => { + beforeAll(() => { + fieldNameValidatorSpy.mockImplementation(() => + withZod(z.object({ myTimeInput: timeSchema })), + ); + }); + const { component, expectInputErrorToExist } = getStrapiInputComponent( + { + code: "invalid", + text: "Please enter a valid time.", + }, + StrapiInputType.Time, + ); + + it("should display an error if the user enters an invalid time", async () => { + const { getByText, getByPlaceholderText } = renderValidatedFlowForm([ + component, + ]); + + const nextButton = getByText("NEXT"); + const timeInputComponent = getByPlaceholderText("time input"); + expect(nextButton).toBeInTheDocument(); + + fireEvent.input(timeInputComponent, { + target: { value: "27:13" }, + }); + fireEvent.blur(timeInputComponent); + await expectInputErrorToExist(); + + fireEvent.click(nextButton); + await waitFor(async () => { + expect(timeInputComponent).toHaveFocus(); + await expectInputErrorToExist(); + }); + }); + + it("should not display an error for correct input", async () => { + const { getByText, getByPlaceholderText, queryByTestId } = + renderValidatedFlowForm([component]); + const timeInputComponent = getByPlaceholderText("time input"); + + fireEvent.input(timeInputComponent, { + target: { value: "23:59" }, + }); + fireEvent.click(getByText("NEXT")); + await waitFor(() => { + expect(timeInputComponent).not.toHaveClass("has-error"); + expect(queryByTestId("inputError")).not.toBeInTheDocument(); + expect(queryByTestId("ErrorOutlineIcon")).not.toBeInTheDocument(); + }); + }); + }); + + describe("Textarea Component", () => { + beforeAll(() => { + fieldNameValidatorSpy.mockImplementation(() => + withZod(z.object({ myTextarea: stringRequiredSchema })), + ); + }); + const { component, expectTextareaErrorToExist } = + getStrapiTextareaComponent({ + code: "required", + text: "Please enter a value.", + }); + + it("should display an error if the user leaves the textarea empty", async () => { + const { getByText, getByPlaceholderText } = renderValidatedFlowForm([ + component, + ]); + + const nextButton = getByText("NEXT"); + const textareaComponent = getByPlaceholderText("textarea"); + expect(nextButton).toBeInTheDocument(); + + fireEvent.blur(textareaComponent); + await expectTextareaErrorToExist(); + + fireEvent.click(nextButton); + await waitFor(async () => { + expect(textareaComponent).toHaveFocus(); + await expectTextareaErrorToExist(); + }); + }); + + it("should not display an error for correct input", async () => { + const { getByText, getByPlaceholderText, queryByTestId } = + renderValidatedFlowForm([component]); + const textareaComponent = getByPlaceholderText("textarea"); + + fireEvent.input(textareaComponent, { + target: { value: "Hello World" }, + }); + fireEvent.click(getByText("NEXT")); + await waitFor(() => { + expect(textareaComponent).not.toHaveClass("has-error"); + expect(queryByTestId("inputError")).not.toBeInTheDocument(); + expect(queryByTestId("ErrorOutlineIcon")).not.toBeInTheDocument(); + }); + }); + }); + + describe("Select Component", () => { + beforeAll(() => { + fieldNameValidatorSpy.mockImplementation(() => + withZod(z.object({ mySelect: YesNoAnswer })), + ); + }); + const { component, expectSelectErrorToExist } = getStrapiSelectComponent({ + code: "required", + text: "Please select a value.", + }); + + it("should display an error if the user doesn't select an option", async () => { + const { getByText } = renderValidatedFlowForm([component]); + + const nextButton = getByText("NEXT"); + expect(nextButton).toBeInTheDocument(); + fireEvent.click(nextButton); + await waitFor(async () => { + await expectSelectErrorToExist(); + }); + }); + + it("should not display an error if the user has selected an option", async () => { + const { getByText, queryByTestId, getByLabelText } = + renderValidatedFlowForm([component]); + + fireEvent.click(getByLabelText("Ja")); + fireEvent.click(getByText("NEXT")); + await waitFor(() => { + expect(queryByTestId("inputError")).not.toBeInTheDocument(); + expect(queryByTestId("ErrorOutlineIcon")).not.toBeInTheDocument(); + }); + }); + }); + + describe("Dropdown Component", () => { + beforeAll(() => { + fieldNameValidatorSpy.mockImplementation(() => + withZod( + z.object({ + myDropdown: z.enum( + ["option1", "option2", "option3"], + customRequiredErrorMessage, + ), + }), + ), + ); + }); + const { component, expectDropdownErrorToExist } = + getStrapiDropdownComponent({ + code: "required", + text: "Please select a value.", + }); + + it("should display an error if the user doesn't select an option", async () => { + const { getByText, getByRole } = renderValidatedFlowForm([component]); + + const nextButton = getByText("NEXT"); + expect(nextButton).toBeInTheDocument(); + fireEvent.blur(getByRole("option", { name: "Select a value." })); + await expectDropdownErrorToExist(); + }); + + it("should not display an error if the user has selected an option", async () => { + const { getByText, queryByTestId } = renderValidatedFlowForm([component]); + + fireEvent.click(getByText("Option 1")); + fireEvent.click(getByText("NEXT")); + await waitFor(() => { + expect(queryByTestId("inputError")).not.toBeInTheDocument(); + expect(queryByTestId("ErrorOutlineIcon")).not.toBeInTheDocument(); + }); + }); + }); + + describe("Checkbox Component", () => { + beforeAll(() => { + fieldNameValidatorSpy.mockImplementation(() => + withZod( + z.object({ + myCheckbox: checkedRequired, + }), + ), + ); + }); + const { component, expectCheckboxErrorToExist } = + getStrapiCheckboxComponent({ + code: "required", + text: "Selection required.", + }); + + it("should display an error if the user doesn't select the checkbox", async () => { + const { getByText, getByLabelText } = renderValidatedFlowForm([ + component, + ]); + + const nextButton = getByText("NEXT"); + expect(nextButton).toBeInTheDocument(); + fireEvent.blur(getByLabelText("Checkbox")); + await expectCheckboxErrorToExist(); + }); + + it("should not display an error if the user has selected the checkbox", async () => { + const { getByText, queryByTestId, getByLabelText } = + renderValidatedFlowForm([component]); + fireEvent.click(getByLabelText("Checkbox")); + fireEvent.click(getByText("NEXT")); + await waitFor(() => { + expect(queryByTestId("inputError")).not.toBeInTheDocument(); + expect(queryByTestId("ErrorOutlineIcon")).not.toBeInTheDocument(); + }); + }); + }); + + describe("TileGroup Component", () => { + beforeAll(() => { + fieldNameValidatorSpy.mockImplementation(() => + withZod( + z.object({ + myTileGroup: z.enum(["tile1"], customRequiredErrorMessage), + }), + ), + ); + }); + const { component, expectTileGroupErrorToExist } = + getStrapiTileGroupComponent({ + code: "required", + text: "Selection required.", + }); + + it("should display an error if the user doesn't select a tile", async () => { + const { getByText } = renderValidatedFlowForm([component]); + + const nextButton = getByText("NEXT"); + expect(nextButton).toBeInTheDocument(); + fireEvent.click(nextButton); + await expectTileGroupErrorToExist(); + }); + + it("should not display an error if the user has selected a tile", async () => { + const { getByText, queryByTestId } = renderValidatedFlowForm([component]); + fireEvent.click(getByText("Tile 1")); + fireEvent.click(getByText("NEXT")); + await waitFor(() => { + expect(queryByTestId("inputError")).not.toBeInTheDocument(); + expect(queryByTestId("ErrorOutlineIcon")).not.toBeInTheDocument(); + }); + }); + }); +}); + +function renderValidatedFlowForm(formElements: Partial[]) { + const router = createMemoryRouter([ + { + path: "/", + element: ( + + ), + action() { + return true; + }, + }, + ]); + return render(); +} diff --git a/app/routes/shared/components/FormFlowPage.tsx b/app/routes/shared/components/FormFlowPage.tsx index 490e98486..d0eea67d0 100644 --- a/app/routes/shared/components/FormFlowPage.tsx +++ b/app/routes/shared/components/FormFlowPage.tsx @@ -1,17 +1,12 @@ -import { useLoaderData, useLocation, useParams } from "@remix-run/react"; -import { ValidatedForm } from "remix-validated-form"; +import { useLoaderData, useLocation } from "@remix-run/react"; import ArraySummary from "~/components/arraySummary/ArraySummary"; import Background from "~/components/Background"; -import { ButtonNavigation } from "~/components/form/ButtonNavigation"; +import ValidatedFlowForm from "~/components/form/ValidatedFlowForm"; import Heading from "~/components/Heading"; import MigrationDataOverview from "~/components/MigrationDataOverview"; import FlowNavigation from "~/components/navigation/FlowNavigation"; import PageContent from "~/components/PageContent"; import SummaryDataOverview from "~/domains/fluggastrechte/components/SummaryDataOverview"; -import { StrapiFormComponents } from "~/services/cms/components/StrapiFormComponents"; -import { splatFromParams } from "~/services/params"; -import { CSRFKey } from "~/services/security/csrf/csrfKey"; -import { validatorForFieldnames } from "~/services/validation/buildStepValidator"; import type { loader } from "../formular.server"; export function FormFlowPage() { @@ -31,10 +26,7 @@ export function FormFlowPage() { translations, navigationA11yLabels, } = useLoaderData(); - const stepId = splatFromParams(useParams()); const { pathname } = useLocation(); - const fieldNames = formElements.map((entry) => entry.name); - const validator = validatorForFieldnames(fieldNames, pathname); return ( @@ -83,22 +75,12 @@ export function FormFlowPage() { csrf={csrf} /> ))} - - -
-
- -
- -
-
+ diff --git a/app/routes/shared/components/VorabcheckPage.tsx b/app/routes/shared/components/VorabcheckPage.tsx index f08886c32..97056db96 100644 --- a/app/routes/shared/components/VorabcheckPage.tsx +++ b/app/routes/shared/components/VorabcheckPage.tsx @@ -1,14 +1,9 @@ -import { useLoaderData, useLocation, useParams } from "@remix-run/react"; -import { ValidatedForm } from "remix-validated-form"; +import { useLoaderData } from "@remix-run/react"; import Background from "~/components/Background"; import Container from "~/components/Container"; -import { ButtonNavigation } from "~/components/form/ButtonNavigation"; import { ProgressBar } from "~/components/form/ProgressBar"; +import ValidatedFlowForm from "~/components/form/ValidatedFlowForm"; import PageContent from "~/components/PageContent"; -import { StrapiFormComponents } from "~/services/cms/components/StrapiFormComponents"; -import { splatFromParams } from "~/services/params"; -import { CSRFKey } from "~/services/security/csrf/csrfKey"; -import { validatorForFieldnames } from "~/services/validation/buildStepValidator"; import type { loader } from "../vorabcheck.server"; export function VorabcheckPage() { @@ -20,11 +15,6 @@ export function VorabcheckPage() { progressProps, buttonNavigationProps, } = useLoaderData(); - const stepId = splatFromParams(useParams()); - const { pathname } = useLocation(); - const fieldNames = formElements.map((entry) => entry.name); - const validator = validatorForFieldnames(fieldNames, pathname); - return (
@@ -37,22 +27,12 @@ export function VorabcheckPage() { className="ds-stack-16" fullScreen={false} /> - - -
-
- -
- -
-
+
diff --git a/tests/factories/cmsModels/strapiCheckboxComponent.ts b/tests/factories/cmsModels/strapiCheckboxComponent.ts new file mode 100644 index 000000000..d95655a57 --- /dev/null +++ b/tests/factories/cmsModels/strapiCheckboxComponent.ts @@ -0,0 +1,31 @@ +import { waitFor, screen } from "@testing-library/react"; +import type { z } from "zod"; +import type { StrapiCheckboxComponentSchema } from "~/services/cms/components/StrapiCheckbox"; +import type { StrapiFieldErrorSchema } from "~/services/cms/models/StrapiFieldError"; + +export function getStrapiCheckboxComponent( + errorCode: z.infer, +): { + component: Partial>; + expectCheckboxErrorToExist: () => Promise; +} { + return { + component: { + __component: "form-elements.checkbox", + name: "myCheckbox", + label: "Checkbox", + isRequiredError: { + name: "", + id: 0, + errorCodes: [errorCode], + }, + }, + expectCheckboxErrorToExist: async function () { + await waitFor(() => { + expect(screen.getByText(errorCode.text)).toBeInTheDocument(); + expect(screen.getByTestId("inputError")).toBeInTheDocument(); + expect(screen.getByTestId("ErrorOutlineIcon")).toBeInTheDocument(); + }); + }, + }; +} diff --git a/tests/factories/cmsModels/strapiDropdownComponent.ts b/tests/factories/cmsModels/strapiDropdownComponent.ts new file mode 100644 index 000000000..542847db7 --- /dev/null +++ b/tests/factories/cmsModels/strapiDropdownComponent.ts @@ -0,0 +1,47 @@ +import { waitFor, screen } from "@testing-library/react"; +import type { z } from "zod"; +import type { StrapiDropdownComponentSchema } from "~/services/cms/components/StrapiDropdown"; +import type { StrapiFieldErrorSchema } from "~/services/cms/models/StrapiFieldError"; + +export function getStrapiDropdownComponent( + errorCode: z.infer, +): { + component: Partial>; + expectDropdownErrorToExist: () => Promise; +} { + return { + component: { + __component: "form-elements.dropdown", + name: `myDropdown`, + placeholder: "Select a value.", + options: [ + { + text: "Option 1", + value: "option1", + }, + { + text: "Option 2", + value: "option2", + }, + { + text: "Option 3", + value: "option3", + }, + ], + errors: [ + { + name: "", + id: 0, + errorCodes: [errorCode], + }, + ], + }, + expectDropdownErrorToExist: async function () { + await waitFor(() => { + expect(screen.getByText(errorCode.text)).toBeInTheDocument(); + expect(screen.getByTestId("inputError")).toBeInTheDocument(); + expect(screen.getByTestId("ErrorOutlineIcon")).toBeInTheDocument(); + }); + }, + }; +} diff --git a/tests/factories/cmsModels/strapiInputComponent.ts b/tests/factories/cmsModels/strapiInputComponent.ts new file mode 100644 index 000000000..31af168b7 --- /dev/null +++ b/tests/factories/cmsModels/strapiInputComponent.ts @@ -0,0 +1,48 @@ +import { waitFor, screen } from "@testing-library/react"; +import type { z } from "zod"; +import type { StrapiFieldErrorSchema } from "~/services/cms/models/StrapiFieldError"; +import type { StrapiFormComponent } from "~/services/cms/models/StrapiFormComponent"; +import { uppercaseFirstLetter } from "~/util/strings"; + +export enum StrapiInputType { + Date = "date", + Time = "time", +} + +export function getStrapiInputComponent( + errorCode: z.infer, + type?: StrapiInputType, +) { + const componentTypes: Record< + StrapiInputType, + StrapiFormComponent["__component"] + > = { + [StrapiInputType.Date]: "form-elements.date-input", + [StrapiInputType.Time]: "form-elements.time-input", + }; + + return { + component: { + __component: type ? componentTypes[type] : "form-elements.input", + name: `my${type ? uppercaseFirstLetter(type) : ""}Input`, + placeholder: `${type ? type + " " : ""}input`, + errors: [ + { + name: "", + id: 0, + errorCodes: [errorCode], + }, + ], + } as StrapiFormComponent, + expectInputErrorToExist: async function () { + await waitFor(() => { + expect(screen.getByText(errorCode.text)).toBeInTheDocument(); + expect( + screen.getByPlaceholderText(`${type ? type + " " : ""}input`), + ).toHaveClass("has-error"); + expect(screen.getByTestId("inputError")).toBeInTheDocument(); + expect(screen.getByTestId("ErrorOutlineIcon")).toBeInTheDocument(); + }); + }, + }; +} diff --git a/tests/factories/cmsModels/strapiSelectComponent.ts b/tests/factories/cmsModels/strapiSelectComponent.ts new file mode 100644 index 000000000..c277d908c --- /dev/null +++ b/tests/factories/cmsModels/strapiSelectComponent.ts @@ -0,0 +1,42 @@ +import { waitFor, screen } from "@testing-library/react"; +import type { z } from "zod"; +import type { StrapiSelectComponentSchema } from "~/services/cms/components/StrapiSelect"; +import type { StrapiFieldErrorSchema } from "~/services/cms/models/StrapiFieldError"; + +export function getStrapiSelectComponent( + errorCode: z.infer, +): { + component: Partial>; + expectSelectErrorToExist: () => Promise; +} { + return { + component: { + __component: "form-elements.select", + name: `mySelect`, + options: [ + { + text: "Ja", + value: "yes", + }, + { + text: "Nein", + value: "no", + }, + ], + errors: [ + { + name: "", + id: 0, + errorCodes: [errorCode], + }, + ], + }, + expectSelectErrorToExist: async function () { + await waitFor(() => { + expect(screen.getByText(errorCode.text)).toBeInTheDocument(); + expect(screen.getByTestId("inputError")).toBeInTheDocument(); + expect(screen.getByTestId("ErrorOutlineIcon")).toBeInTheDocument(); + }); + }, + }; +} diff --git a/tests/factories/cmsModels/strapiTextareaComponent.ts b/tests/factories/cmsModels/strapiTextareaComponent.ts new file mode 100644 index 000000000..de2266304 --- /dev/null +++ b/tests/factories/cmsModels/strapiTextareaComponent.ts @@ -0,0 +1,36 @@ +import { waitFor, screen } from "@testing-library/react"; +import type { z } from "zod"; +import type { StrapiTextareaComponentSchema } from "~/services/cms/components/StrapiTextarea"; +import type { StrapiFieldErrorSchema } from "~/services/cms/models/StrapiFieldError"; + +export function getStrapiTextareaComponent( + errorCode: z.infer, +): { + component: Partial>; + expectTextareaErrorToExist: () => Promise; +} { + return { + component: { + __component: "form-elements.textarea", + name: `myTextarea`, + placeholder: `textarea`, + errors: [ + { + name: "", + id: 0, + errorCodes: [errorCode], + }, + ], + }, + expectTextareaErrorToExist: async function () { + await waitFor(() => { + expect(screen.getByText(errorCode.text)).toBeInTheDocument(); + expect(screen.getByPlaceholderText(`textarea`)).toHaveClass( + "has-error", + ); + expect(screen.getByTestId("inputError")).toBeInTheDocument(); + expect(screen.getByTestId("ErrorOutlineIcon")).toBeInTheDocument(); + }); + }, + }; +} diff --git a/tests/factories/cmsModels/strapiTileGroupComponent.ts b/tests/factories/cmsModels/strapiTileGroupComponent.ts new file mode 100644 index 000000000..d90b79c61 --- /dev/null +++ b/tests/factories/cmsModels/strapiTileGroupComponent.ts @@ -0,0 +1,40 @@ +import { waitFor, screen } from "@testing-library/react"; +import type { z } from "zod"; +import type { StrapiTileGroupComponentSchema } from "~/services/cms/components/StrapiTileGroup"; +import type { StrapiFieldErrorSchema } from "~/services/cms/models/StrapiFieldError"; + +export function getStrapiTileGroupComponent( + errorCode: z.infer, +): { + component: Partial>; + expectTileGroupErrorToExist: () => Promise; +} { + return { + component: { + __component: "form-elements.tile-group", + name: "myTileGroup", + options: [ + { + title: "Tile 1", + value: "tile1", + description: null, + tagDescription: null, + }, + ], + errors: [ + { + name: "", + id: 0, + errorCodes: [errorCode], + }, + ], + }, + expectTileGroupErrorToExist: async function () { + await waitFor(() => { + expect(screen.getByText(errorCode.text)).toBeInTheDocument(); + expect(screen.getByTestId("inputError")).toBeInTheDocument(); + expect(screen.getByTestId("ErrorOutlineIcon")).toBeInTheDocument(); + }); + }, + }; +}