Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Run pnpm lint:fix in Editor #3372

Merged
merged 1 commit into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export const ListProvider: React.FC<ListProviderProps> = (props) => {
if (formik.values.userData.length < schema.min) {
return setMinError(true);
}

formik.handleSubmit();
};

Expand Down
32 changes: 7 additions & 25 deletions editor.planx.uk/src/@planx/components/List/Public/Fields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import MenuItem from "@mui/material/MenuItem";
import RadioGroup from "@mui/material/RadioGroup";
import { visuallyHidden } from "@mui/utils";
import { getIn } from "formik";
import { get } from "lodash";
import React from "react";
import SelectInput from "ui/editor/SelectInput";
import InputLabel from "ui/public/InputLabel";
Expand All @@ -23,21 +24,14 @@ import type {
TextField,
} from "../model";
import { useListContext } from "./Context";
import { get } from "lodash";

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

export const TextFieldInput: React.FC<Props<TextField>> = ({
id,
data,
}) => {
export const TextFieldInput: React.FC<Props<TextField>> = ({ id, data }) => {
const { formik, activeIndex } = useListContext();

return (
<InputLabel
label={data.title}
htmlFor={id}
>
<InputLabel label={data.title} htmlFor={id}>
<Input
type={((type) => {
if (type === "email") return "email";
Expand All @@ -48,10 +42,7 @@ export const TextFieldInput: React.FC<Props<TextField>> = ({
bordered
value={formik.values.userData[activeIndex][data.fn]}
onChange={formik.handleChange}
errorMessage={get(
formik.errors,
["userData", activeIndex, data.fn],
)}
errorMessage={get(formik.errors, ["userData", activeIndex, data.fn])}
id={id}
rows={
data.type && ["long", "extraLong"].includes(data.type) ? 5 : undefined
Expand Down Expand Up @@ -80,10 +71,7 @@ export const NumberFieldInput: React.FC<Props<NumberField>> = ({
const { formik, activeIndex } = useListContext();

return (
<InputLabel
label={data.title}
htmlFor={id}
>
<InputLabel label={data.title} htmlFor={id}>
<Box sx={{ display: "flex", alignItems: "baseline" }}>
<Input
required
Expand All @@ -92,10 +80,7 @@ export const NumberFieldInput: React.FC<Props<NumberField>> = ({
type="number"
value={formik.values.userData[activeIndex][data.fn]}
onChange={formik.handleChange}
errorMessage={get(
formik.errors,
["userData", activeIndex, data.fn],
)}
errorMessage={get(formik.errors, ["userData", activeIndex, data.fn])}
inputProps={{
"aria-describedby": [
data.description ? DESCRIPTION_TEXT : "",
Expand Down Expand Up @@ -161,10 +146,7 @@ export const SelectFieldInput: React.FC<Props<QuestionField>> = (props) => {
const { id, data } = props;

return (
<InputLabel
label={data.title}
id={`select-label-${id}`}
>
<InputLabel label={data.title} id={`select-label-${id}`}>
<ErrorWrapper
id={`${id}-error`}
error={get(formik.errors, ["userData", activeIndex, data.fn])}
Expand Down
180 changes: 112 additions & 68 deletions editor.planx.uk/src/@planx/components/List/Public/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -380,28 +380,30 @@ describe("Building a list", () => {

describe("Form validation and error handling", () => {
test("form validation is triggered when saving an item", async () => {
const { user, getByRole, getAllByTestId } = setup(<ListComponent {...mockZooProps} />);
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)
expect(errorMessages).toHaveLength(mockZooProps.schema.fields.length);

// All are empty initially
errorMessages.forEach(message => {
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)
expect(errorMessages).toHaveLength(mockZooProps.schema.fields.length);

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

/**
Expand All @@ -410,75 +412,112 @@ describe("Form validation and error handling", () => {
*/
describe("existing validation schemas are correctly referenced", () => {
test("text fields", async () => {
const { user, getByRole, getByTestId } = setup(<ListComponent {...mockZooProps} />);
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.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/);
const nameInputErrorMessage = getByTestId(
/error-message-input-text-name/,
);

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

test("number fields", async () => {
const { user, getByRole, getByTestId } = setup(<ListComponent {...mockZooProps} />);
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/);
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} />);
const { user, getByRole, getByTestId } = setup(
<ListComponent {...mockZooProps} />,
);

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

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

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

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

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

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

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

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

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

const foodInputErrorMessage = getByTestId(/error-message-input-checklist-food/);
const foodInputErrorMessage = getByTestId(
/error-message-input-checklist-food/,
);

expect(foodInputErrorMessage).toHaveTextContent(/Select at least one option/);
})
expect(foodInputErrorMessage).toHaveTextContent(
/Select at least one option/,
);
});
});

test("an error displays if the minimum number of items is not met", async () => {
const { user, getByRole, getByTestId, getByText } = setup(<ListComponent {...mockZooProps} />);
const { user, getByRole, getByTestId, getByText } = setup(
<ListComponent {...mockZooProps} />,
);

const minNumberOfItems = mockZooProps.schema.min;
expect(minNumberOfItems).toEqual(1);

await user.click(getByRole("button", { name: /Cancel/ }));
await user.click(getByTestId("continue-button"));

const minItemsErrorMessage = getByText(`You must provide at least ${minNumberOfItems} response(s)`)
const minItemsErrorMessage = getByText(
`You must provide at least ${minNumberOfItems} response(s)`,
);
expect(minItemsErrorMessage).toBeVisible();
});

test("an error displays if the maximum number of items is exceeded", async () => {
const { user, getAllByTestId, getByTestId, getByText } = setup(<ListComponent {...mockZooProps} />);
const { user, getAllByTestId, getByTestId, getByText } = setup(
<ListComponent {...mockZooProps} />,
);
const addItemButton = getByTestId(/list-add-button/);

const maxNumberOfItems = mockZooProps.schema.max;
Expand All @@ -497,45 +536,51 @@ describe("Form validation and error handling", () => {
// Try to add a fourth
await user.click(getByTestId(/list-add-button/));

const maxItemsErrorMessage = getByText(`You can provide at most ${maxNumberOfItems} response(s)`)
const maxItemsErrorMessage = getByText(
`You can provide at most ${maxNumberOfItems} response(s)`,
);
expect(maxItemsErrorMessage).toBeVisible();
});

test(
"an error displays if you add a new item, without saving the active item", async () => {
const { user, getByTestId, getByText, getByLabelText } = setup(<ListComponent {...mockZooProps} />);
// Start filling out item
const nameInput = getByLabelText(/name/);
await user.type(nameInput, "Richard Parker");

const emailInput = getByLabelText(/email/);
await user.type(emailInput, "[email protected]");

// Try to add a new item
await user.click(getByTestId(/list-add-button/));

const activeItemErrorMessage = getByText(/Please save all responses before adding another/)
expect(activeItemErrorMessage).toBeVisible();
}
);

test(
"an error displays if you continue, without saving the active item", async () => {
const { user, getByTestId, getByText, getByLabelText } = setup(<ListComponent {...mockZooProps} />);
// Start filling out item
const nameInput = getByLabelText(/name/);
await user.type(nameInput, "Richard Parker");

const emailInput = getByLabelText(/email/);
await user.type(emailInput, "[email protected]");

// Try to continue
await user.click(getByTestId(/continue-button/));

const unsavedItemErrorMessage = getByText(/Please save in order to continue/)
expect(unsavedItemErrorMessage).toBeVisible();
}
);
test("an error displays if you add a new item, without saving the active item", async () => {
const { user, getByTestId, getByText, getByLabelText } = setup(
<ListComponent {...mockZooProps} />,
);
// Start filling out item
const nameInput = getByLabelText(/name/);
await user.type(nameInput, "Richard Parker");

const emailInput = getByLabelText(/email/);
await user.type(emailInput, "[email protected]");

// Try to add a new item
await user.click(getByTestId(/list-add-button/));

const activeItemErrorMessage = getByText(
/Please save all responses before adding another/,
);
expect(activeItemErrorMessage).toBeVisible();
});

test("an error displays if you continue, without saving the active item", async () => {
const { user, getByTestId, getByText, getByLabelText } = setup(
<ListComponent {...mockZooProps} />,
);
// Start filling out item
const nameInput = getByLabelText(/name/);
await user.type(nameInput, "Richard Parker");

const emailInput = getByLabelText(/email/);
await user.type(emailInput, "[email protected]");

// Try to continue
await user.click(getByTestId(/continue-button/));

const unsavedItemErrorMessage = getByText(
/Please save in order to continue/,
);
expect(unsavedItemErrorMessage).toBeVisible();
});
});

describe("Payload generation", () => {
Expand All @@ -559,9 +604,8 @@ describe("Payload generation", () => {

it("generates a valid payload with summary stats on submission (Units)", async () => {
const handleSubmit = jest.fn();
const { getByTestId, user, getByRole, getAllByRole, getByLabelText } = setup(
<ListComponent {...mockUnitsProps} handleSubmit={handleSubmit} />,
);
const { getByTestId, user, getByRole, getAllByRole, getByLabelText } =
setup(<ListComponent {...mockUnitsProps} handleSubmit={handleSubmit} />);

const addItemButton = getByTestId("list-add-button");

Expand Down Expand Up @@ -601,7 +645,7 @@ describe("Payload generation", () => {
gardenYesRadio = getAllByRole("radio")[0];
gardenNoRadio = getAllByRole("radio")[1];
unitsNumberInput = getByLabelText(/identical units/);

await user.click(developmentSelect);
await user.click(getByRole("option", { name: /Change of use to a home/ }));
await user.click(gardenNoRadio);
Expand All @@ -611,7 +655,7 @@ describe("Payload generation", () => {
await user.click(getByTestId("continue-button"));

expect(handleSubmit).toHaveBeenCalled();
const output = handleSubmit.mock.calls[0][0]
const output = handleSubmit.mock.calls[0][0];
expect(output).toMatchObject(mockUnitsPayload);
});
});
Expand Down
6 changes: 3 additions & 3 deletions editor.planx.uk/src/@planx/components/List/Public/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,14 @@ const ActiveListCard: React.FC<{
}> = ({ index: i }) => {
const { schema, saveItem, cancelEditItem, errors, isPageComponent } =
useListContext();

const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (ref.current) {
ref.current.scrollIntoView({ behavior: "smooth" })
ref.current.scrollIntoView({ behavior: "smooth" });
}
}, []);

return (
<ErrorWrapper
error={errors.unsavedItem ? "Please save in order to continue" : ""}
Expand Down
Loading
Loading