Skip to content

Commit

Permalink
test: Initial validation tests per-field
Browse files Browse the repository at this point in the history
  • Loading branch information
DafyddLlyr committed Jun 26, 2024
1 parent be6a176 commit bfc8f5a
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 14 deletions.
18 changes: 9 additions & 9 deletions editor.planx.uk/src/@planx/components/List/Public/Fields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import FormLabel from "@mui/material/FormLabel";
import MenuItem from "@mui/material/MenuItem";
import RadioGroup from "@mui/material/RadioGroup";
import { Option } from "@planx/components/shared";
import { getIn } from "formik";
import React from "react";
import SelectInput from "ui/editor/SelectInput";
import InputLabel from "ui/public/InputLabel";
Expand All @@ -16,6 +15,7 @@ import { DESCRIPTION_TEXT, ERROR_MESSAGE } from "../../shared/constants";
import BasicRadio from "../../shared/Radio/BasicRadio";
import type { NumberField, QuestionField, TextField } from "../model";
import { useListContext } from "./Context";
import { get } from "lodash";

type Props<T> = T & { id: string };

Expand All @@ -41,9 +41,9 @@ export const TextFieldInput: React.FC<Props<TextField>> = ({
bordered
value={formik.values.userData[activeIndex][data.fn]}
onChange={formik.handleChange}
errorMessage={getIn(
errorMessage={get(
formik.errors,
`userData[${activeIndex}][${data.fn}]`,
["userData", activeIndex, data.fn],
)}
id={id}
rows={
Expand All @@ -54,7 +54,7 @@ export const TextFieldInput: React.FC<Props<TextField>> = ({
inputProps={{
"aria-describedby": [
data.description ? DESCRIPTION_TEXT : "",
getIn(formik.errors, `userData[${activeIndex}][${data.fn}]`)
get(formik.errors, ["userData", activeIndex, data.fn])
? `${ERROR_MESSAGE}-${id}`
: "",
]
Expand Down Expand Up @@ -86,14 +86,14 @@ export const NumberFieldInput: React.FC<Props<NumberField>> = ({
type="number"
value={formik.values.userData[activeIndex][data.fn]}
onChange={formik.handleChange}
errorMessage={getIn(
errorMessage={get(
formik.errors,
`userData[${activeIndex}][${data.fn}]`,
["userData", activeIndex, data.fn],
)}
inputProps={{
"aria-describedby": [
data.description ? DESCRIPTION_TEXT : "",
getIn(formik.errors, `userData[${activeIndex}][${data.fn}]`)
get(formik.errors, ["userData", activeIndex, data.fn])
? `${ERROR_MESSAGE}-${id}`
: "",
]
Expand Down Expand Up @@ -128,7 +128,7 @@ export const RadioFieldInput: React.FC<Props<QuestionField>> = (props) => {
</FormLabel>
<ErrorWrapper
id={`${id}-error`}
error={getIn(formik.errors, `userData[${activeIndex}][${data.fn}]`)}
error={get(formik.errors, ["userData", activeIndex, data.fn])}
>
<RadioGroup
aria-labelledby={`radio-buttons-group-label-${id}`}
Expand Down Expand Up @@ -173,7 +173,7 @@ export const SelectFieldInput: React.FC<Props<QuestionField>> = (props) => {
>
<ErrorWrapper
id={`${id}-error`}
error={getIn(formik.errors, `userData[${activeIndex}][${data.fn}]`)}
error={get(formik.errors, ["userData", activeIndex, data.fn])}
>
<SelectInput
bordered
Expand Down
85 changes: 80 additions & 5 deletions editor.planx.uk/src/@planx/components/List/Public/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -378,11 +378,83 @@ describe("Building a list", () => {
});

describe("Form validation and error handling", () => {
test.todo("form validation is triggered when saving an item");
test.todo("text fields use existing validation schemas");
test.todo("number fields use existing validation schemas");
test.todo("question fields use validation schema");
test.todo("unique constraints are enforced on question where this is set");
test("form validation is triggered when saving an item", async () => {
const { user, getByRole, getAllByTestId } = setup(<ListComponent {...mockZooProps} />);

let errorMessages = getAllByTestId(/error-message-input/);

// Each field has an ErrorWrapper
expect(errorMessages).toHaveLength(mockZooProps.schema.fields.length)

// All are empty initially
errorMessages.forEach(message => {
expect(message).toBeEmptyDOMElement();
});

await user.click(getByRole("button", { name: /Save/ }));

// Error wrappers persist
errorMessages = getAllByTestId(/error-message-input/);
expect(errorMessages).toHaveLength(mockZooProps.schema.fields.length)

// Each field is in an error state
errorMessages.forEach(message => {
expect(message).not.toBeEmptyDOMElement();
});
});

/**
* These tests are not exhaustive tests of validation schemas, these can be tested in their respective model.test.ts files
* We are testing that the validation schemas are correctly "wired up" to out List component fields
*/
describe("existing validation schemas are correctly referenced", () => {
test("text fields", async () => {
const { user, getByRole, getByTestId } = setup(<ListComponent {...mockZooProps} />);

const nameInput = screen.getByLabelText(/name/);
await user.type(nameInput, "This is a long string of text over one hundred and twenty characters, which should trigger the 'short' text validation warning");
await user.click(getByRole("button", { name: /Save/ }));

const nameInputErrorMessage = getByTestId(/error-message-input-text-name/);

expect(nameInputErrorMessage).toHaveTextContent(/Your answer must be 120 characters or fewer/);
});

test("number fields", async () => {
const { user, getByRole, getByTestId } = setup(<ListComponent {...mockZooProps} />);

const ageInput = screen.getByLabelText(/old/);
await user.type(ageInput, "-35");
await user.click(getByRole("button", { name: /Save/ }));

const ageInputErrorMessage = getByTestId(/error-message-input-number-age/);

expect(ageInputErrorMessage).toHaveTextContent(/Enter a positive number/);
});

test("question fields", async () => {
const { user, getByRole, getByTestId } = setup(<ListComponent {...mockZooProps} />);

await user.click(getByRole("button", { name: /Save/ }));

const sizeInputErrorMessage = getByTestId(/error-message-input-question-size/);

expect(sizeInputErrorMessage).toHaveTextContent(/Select your answer before continuing/);
});

test("radio fields", async () => {
const { user, getByRole, getByTestId } = setup(<ListComponent {...mockZooProps} />);

await user.click(getByRole("button", { name: /Save/ }));

const cuteInputErrorMessage = getByTestId(/error-message-input-question-cute/);

expect(cuteInputErrorMessage).toHaveTextContent(/Select your answer before continuing/);
});

test.todo("checklist fields")
});

test.todo("optional fields can be empty when saving an item");
test.todo("an error displays if the minimum number of items is not met");
test.todo("an error displays if the maximum number of items is exceeded");
Expand All @@ -392,6 +464,9 @@ describe("Form validation and error handling", () => {
test.todo(
"an error displays if you continue, without saving the active item",
);

// TODO: Should we remove this feature?
test.todo("unique constraints are enforced on questions where this is set");
});

describe("Payload generation", () => {
Expand Down

0 comments on commit bfc8f5a

Please sign in to comment.