diff --git a/frontend/src/tests/ExplorePage.test.tsx b/frontend/src/tests/ExplorePage.test.tsx new file mode 100644 index 00000000..9e49f801 --- /dev/null +++ b/frontend/src/tests/ExplorePage.test.tsx @@ -0,0 +1,104 @@ +import { render, screen, within } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import ExplorePage from "../pages/explorePage/ExplorePage"; + +// mock i18next to return the key as the translation +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})); + +// set up the page and user interactions for each test +function setup() { + const user = userEvent.setup({ delay: null }); + render(); + return user; +} + +// get all plant cards displayed on the page +const cards = () => screen.getAllByRole("listitem"); + +describe("ExplorePage", () => { + // clear stored filter and search values before each test, + // and use fake timers to control the search debounce + beforeEach(() => { + sessionStorage.clear(); + // shouldAdvanceTime keeps userEvent from hanging while fake timers are on + vi.useFakeTimers({ shouldAdvanceTime: true }); + }); + + // restore real timers after each test + afterEach(() => { + vi.useRealTimers(); + }); + + // test that all plants are displayed when no filters are selected + it("displays all plants when no filters are selected", () => { + setup(); + expect(cards()).toHaveLength(15); + expect( + screen.getByRole("button", { name: "Monstera" }), + ).toBeInTheDocument(); + }); + + // test that clicking a plant card displays its information + it("opens and closes plant card information", async () => { + const user = setup(); + + const monstera = screen.getByRole("button", { name: "Monstera" }); + + // --- Closed state --- + expect(monstera).toHaveAttribute("aria-pressed", "false"); + expect( + within(monstera).queryByText("explorePage.average"), + ).not.toBeInTheDocument(); + + // --- Open the card --- + await user.click(monstera); + + // the card now reports that it is pressed (open) + expect(monstera).toHaveAttribute("aria-pressed", "true"); + + expect( + within(monstera).getByText("explorePage.average"), + ).toBeInTheDocument(); + expect( + within(monstera).getByText("explorePage.part_shade"), + ).toBeInTheDocument(); + expect( + within(monstera).getByText("explorePage.poison"), + ).toBeInTheDocument(); + expect( + within(monstera).getByText("explorePage.indoor"), + ).toBeInTheDocument(); + + // --- Close the card by clicking it again --- + await user.click(monstera); + + expect(monstera).toHaveAttribute("aria-pressed", "false"); + expect( + within(monstera).queryByText("explorePage.average"), + ).not.toBeInTheDocument(); + }); + + // --- Moves to backend (the resolver/database decides which plants match) --- + it.todo("filters plants by search"); // -> backend (incl. case-insensitive) + it.todo("filters plants by watering needs"); // -> backend + it.todo("filters plants by sunlight requirements"); // -> backend + it.todo("filters plants by location"); // -> backend + it.todo("filters plants by edibility"); // -> backend + it.todo("combines multiple filters"); // -> backend + it.todo("returns an empty list when nothing matches"); // -> backend + + // --- Stays in frontend, written after Apollo (MockedProvider) --- + it.todo("sends the search text in the query after the debounce delay"); + it.todo("sends the selected filter values in the query"); + it.todo("resets the search and filters"); + it.todo("shows a loading state"); + it.todo("shows an error message when the query fails"); + it.todo("shows a message when the result is empty"); + + // Snapshot tests ? +});