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

feat: Map and Label Happy Path Tests #3690

Merged
merged 9 commits into from
Sep 17, 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
263 changes: 184 additions & 79 deletions editor.planx.uk/src/@planx/components/MapAndLabel/Public/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,18 @@ import { setup } from "testUtils";
import { vi } from "vitest";
import { axe } from "vitest-axe";

import { point1, point2 } from "../test/mocks/geojson";
import { point1, point2, point3 } from "../test/mocks/geojson";
import { props } from "../test/mocks/Trees";
import { addFeaturesToMap, addMultipleFeatures } from "../test/utils";
import {
addFeaturesToMap,
addMultipleFeatures,
checkErrorMessagesEmpty,
checkErrorMessagesPopulated,
clickContinue,
fillOutFirstHalfOfForm,
fillOutForm,
fillOutSecondHalfOfForm,
} from "../test/utils";

beforeAll(() => {
if (!window.customElements.get("my-map")) {
Expand Down Expand Up @@ -40,25 +49,23 @@ describe("Basic UI", () => {
it("removes the prompt once a feature is added", async () => {
const { queryByText, getByTestId } = setup(<MapAndLabel {...props} />);
const map = getByTestId("map-and-label-map");
expect(map).toBeInTheDocument();

addFeaturesToMap(map, [point1]);

await waitFor(() =>
expect(
queryByText("Plot a feature on the map to begin"),
).not.toBeInTheDocument(),
queryByText("Plot a feature on the map to begin")
).not.toBeInTheDocument()
);
});

it("renders the schema name as the tab title", async () => {
const { queryByText, getByRole, getByTestId } = setup(
<MapAndLabel {...props} />,
<MapAndLabel {...props} />
);
expect(queryByText(/Tree 1/)).not.toBeInTheDocument();

const map = getByTestId("map-and-label-map");
expect(map).toBeInTheDocument();

addFeaturesToMap(map, [point1]);

Expand All @@ -68,12 +75,11 @@ describe("Basic UI", () => {

it("should not have any accessibility violations", async () => {
const { queryByText, getByTestId, container } = setup(
<MapAndLabel {...props} />,
<MapAndLabel {...props} />
);
expect(queryByText(/Tree 1/)).not.toBeInTheDocument();

const map = getByTestId("map-and-label-map");
expect(map).toBeInTheDocument();

addFeaturesToMap(map, [point1]);

Expand All @@ -85,23 +91,26 @@ describe("Basic UI", () => {
// Schema and field validation is handled in both List and Schema folders - here we're only testing the MapAndLabel specific error handling
describe("validation and error handling", () => {
it("shows all fields are required", async () => {
const { getAllByTestId, getByTestId, getByRole, user } = setup(
<MapAndLabel {...props} />,
const { getByTestId, user, queryByRole, getAllByTestId } = setup(
<MapAndLabel {...props} />
);
const map = getByTestId("map-and-label-map");
expect(map).toBeInTheDocument();

addFeaturesToMap(map, [point1]);

expect(getByRole("tab", { name: /Tree 1/ })).toBeInTheDocument();
const tabOne = queryByRole("tab", { name: /Tree 1/ });
expect(tabOne).toBeInTheDocument();

const firstTabPanel = getByTestId("vertical-tabpanel-0");
const firstSpeciesInput = within(firstTabPanel).getByLabelText("Species");

const continueButton = getByRole("button", { name: /Continue/ });
expect(continueButton).toBeInTheDocument();
await user.click(continueButton);
// check input is empty
expect(firstSpeciesInput).toHaveDisplayValue("");

await clickContinue(user);

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

// Date field has been removed so only 4 inputs
expect(errorMessages).toHaveLength(4);

errorMessages.forEach((message) => {
Expand All @@ -111,119 +120,215 @@ describe("validation and error handling", () => {

// it shows all fields are required in a tab
it("should show all fields are required, for all feature tabs", async () => {
const { getByTestId, getByRole, user, debug } = setup(
<MapAndLabel {...props} />,
);
const map = getByTestId("map-and-label-map");
expect(map).toBeInTheDocument();
debug();
const { getByTestId, getByRole, user } = setup(<MapAndLabel {...props} />);

addMultipleFeatures([point1, point2]);

// vertical side tab query
const firstTab = getByRole("tab", { name: /Tree 1/ });
const secondTab = getByRole("tab", { name: /Tree 2/ });

// side tab validation
expect(firstTab).toBeInTheDocument();
expect(secondTab).toBeInTheDocument();

// form for each tab
const firstTabPanel = getByTestId("vertical-tabpanel-0");
const secondTabPanel = getByTestId("vertical-tabpanel-1");

// default is to start on seond tab panel since we add two points
// default is to start on second tab panel since we add two points
expect(firstTabPanel).not.toBeVisible();
expect(secondTabPanel).toBeVisible();

// Form is generate for secondTabPanel but not the first
expect(secondTabPanel.childElementCount).toBeGreaterThan(0);
expect(firstTabPanel.childElementCount).toBe(0);

const continueButton = getByRole("button", { name: /Continue/ });
await user.click(continueButton);
await clickContinue(user);

// error messages appear
const errorMessagesTabTwo =
within(secondTabPanel).getAllByTestId(/error-message-input/);
expect(errorMessagesTabTwo).toHaveLength(4);

// error messages are empty but visible before error state induced
// this ensures they contain the error message text
errorMessagesTabTwo.forEach((input) => {
expect(input).not.toBeEmptyDOMElement();
});
await checkErrorMessagesPopulated();

await user.click(firstTab);

expect(firstTabPanel).toBeVisible();

// error messages persist
const errorMessagesTabOne =
within(firstTabPanel).getAllByTestId(/error-message-input/);
expect(errorMessagesTabOne).toHaveLength(4);
await checkErrorMessagesPopulated();
});

// it shows all fields are required across different tabs
it("should show an error if the minimum number of items is not met", async () => {
const { getByTestId, getByRole, user } = setup(<MapAndLabel {...props} />);
const map = getByTestId("map-and-label-map");
expect(map).toBeInTheDocument();

const continueButton = getByRole("button", { name: /Continue/ });
const { getByTestId, user } = setup(<MapAndLabel {...props} />);

await user.click(continueButton);
await clickContinue(user);

const errorWrapper = getByTestId(/error-wrapper/);

const errorMessage = within(errorWrapper).getByText(/You must plot /);

expect(errorMessage).toBeInTheDocument();
expect(errorMessage).toBeVisible();
});
// ??
it("an error state is applied to a tabpanel button, when it's associated feature is invalid", async () => {
const { getByTestId, getByRole, user, getAllByTestId } = setup(
<MapAndLabel {...props} />,
const { getByTestId, user, queryByRole } = setup(
<MapAndLabel {...props} />
);
const map = getByTestId("map-and-label-map");
expect(map).toBeInTheDocument();

addFeaturesToMap(map, [point1]);

const tabOne = getByRole("tab", { name: /Tree 1/ });
const tabOne = queryByRole("tab", { name: /Tree 1/ });

expect(tabOne).toBeInTheDocument();

const continueButton = getByRole("button", { name: /Continue/ });
expect(continueButton).toBeInTheDocument();
await user.click(continueButton);
await clickContinue(user);

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

// check error messages are correct amount and contain info
expect(errorMessages).toHaveLength(4);

errorMessages.forEach((message) => {
expect(message).not.toBeEmptyDOMElement();
});
await checkErrorMessagesPopulated();

expect(tabOne).toHaveStyle("border-left: 5px solid #D4351C");
});
// shows the error state on a tab when it's invalid
});

it("does not trigger handleSubmit when errors exist", async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌

const handleSubmit = vi.fn();
const { getByTestId, user } = setup(
<MapAndLabel {...props} handleSubmit={handleSubmit} />
);
const map = getByTestId("map-and-label-map");

addFeaturesToMap(map, [point1]);

await clickContinue(user);

await checkErrorMessagesPopulated();

expect(handleSubmit).not.toBeCalled();
});
test.todo("an error displays if the maximum number of items is exceeded");

describe("basic interactions - happy path", () => {
test.todo("adding an item to the map adds a feature tab");
// add feature, see a tab (one feature only)
test.todo("a user can input details on a single feature and submit");
// only one feature, fill out form, submit
test.todo("adding multiple features to the map adds multiple feature tabs");
// add more than one feature, see multiple tabs
test.todo("a user can input details on multiple features and submit");
// add details to more than one tab, submit
test.todo("a user can input details on feature tabs in any order");
// ??
it("adding an item to the map adds a feature tab", async () => {
const { getByTestId } = setup(<MapAndLabel {...props} />);
const map = getByTestId("map-and-label-map");

addFeaturesToMap(map, [point1]);

const firstTabPanel = getByTestId("vertical-tabpanel-0");

expect(firstTabPanel).toBeVisible();
});

it("a user can input details on a single feature and submit", async () => {
const { getByTestId, user } = setup(<MapAndLabel {...props} />);

const map = getByTestId("map-and-label-map");

addFeaturesToMap(map, [point1]);

const firstTabPanel = getByTestId("vertical-tabpanel-0");

expect(firstTabPanel).toBeVisible();

await fillOutForm(user);

await clickContinue(user);

await checkErrorMessagesEmpty();
});

it("adding multiple features to the map adds multiple feature tabs", async () => {
const { queryByRole } = setup(<MapAndLabel {...props} />);

addMultipleFeatures([point1, point2, point3]);

// vertical side tab query
const firstTab = queryByRole("tab", { name: /Tree 1/ });
const secondTab = queryByRole("tab", { name: /Tree 2/ });
const thirdTab = queryByRole("tab", { name: /Tree 3/ });
const fourthTab = queryByRole("tab", { name: /Tree 4/ });

// check the right amount are in the document
expect(firstTab).toBeInTheDocument();
expect(secondTab).toBeInTheDocument();
expect(thirdTab).toBeInTheDocument();
expect(fourthTab).not.toBeInTheDocument();

expect(firstTab).toHaveAttribute("aria-selected", "false");
expect(secondTab).toHaveAttribute("aria-selected", "false");
expect(thirdTab).toHaveAttribute("aria-selected", "true");
});

it("a user can input details on multiple features and submit", async () => {
const { getByTestId, getByRole, user } = setup(<MapAndLabel {...props} />);
getByTestId("map-and-label-map");

addMultipleFeatures([point1, point2]);

// vertical side tab query
const firstTab = getByRole("tab", { name: /Tree 1/ });
const firstTabPanel = getByTestId("vertical-tabpanel-0");
const secondTabPanel = getByTestId("vertical-tabpanel-1");

await fillOutForm(user);
const secondSpeciesInput = within(secondTabPanel).getByLabelText("Species");

expect(secondSpeciesInput).toHaveDisplayValue("Larch");

await user.click(firstTab);

// check form on screen is reset
const firstSpeciesInput = within(firstTabPanel).getByLabelText("Species");
expect(secondSpeciesInput).not.toBeInTheDocument();
expect(firstSpeciesInput).not.toHaveDisplayValue("Larch");

await fillOutForm(user);

await clickContinue(user);

await checkErrorMessagesEmpty();
});
it("a user can input details on feature tabs in any order", async () => {
const { getByTestId, getByRole, user } = setup(<MapAndLabel {...props} />);

addMultipleFeatures([point1, point2]);

const firstTab = getByRole("tab", { name: /Tree 1/ });
const secondTab = getByRole("tab", { name: /Tree 2/ });

const firstTabPanel = getByTestId("vertical-tabpanel-0");
const secondTabPanel = getByTestId("vertical-tabpanel-1");

await user.click(firstTab);

const firstSpeciesInput = within(firstTabPanel).getByLabelText("Species");
expect(firstSpeciesInput).not.toHaveDisplayValue("Larch");

// partially fill out firstTabPanel
await fillOutFirstHalfOfForm(user);

await user.click(secondTab);
const secondSpeciesInput = within(secondTabPanel).getByLabelText("Species");
expect(secondSpeciesInput).not.toHaveDisplayValue("Larch");

// partially fill out secondTabPanel
await fillOutFirstHalfOfForm(user);

await user.click(firstTab);

// check that the data stays within the firstTabPanel
expect(firstSpeciesInput).toHaveDisplayValue("Larch");

// Complete the filling out of the firstTabPanel
await fillOutSecondHalfOfForm(user);

await user.click(secondTab);

// check that the data stays within the secondTabPanel
expect(secondSpeciesInput).toHaveDisplayValue("Larch");

// Complete the filling out of the secondTabPanel
await fillOutSecondHalfOfForm(user);

await clickContinue(user);

await checkErrorMessagesEmpty();
});
});

describe("copy feature select", () => {
Expand All @@ -232,7 +337,7 @@ describe("copy feature select", () => {
it.todo("is enabled once multiple features are present");
// copy select enabled once you add more features
it.todo(
"lists all other features as options (the current feature is not listed)",
"lists all other features as options (the current feature is not listed)"
);
// current tree is not an option in the copy select
it.todo("copies all data from one feature to another");
Expand All @@ -254,11 +359,11 @@ describe("payload generation", () => {
test.todo("a submitted payload contains a GeoJSON feature collection");
// check payload contains GeoJSON feature collection
test.todo(
"the feature collection contains all geospatial data inputted by the user",
"the feature collection contains all geospatial data inputted by the user"
);
// feature collection matches the mocked data
test.todo(
"each feature's properties correspond with the details entered for that feature",
"each feature's properties correspond with the details entered for that feature"
);
// feature properties contain the answers to inputs
});
Loading
Loading