A colleague wrote TeaFinder for the tea shop's quick order page, and it doesn't work: the page crashes as soon as it opens, with Maximum update depth exceeded, and the Added notice doesn't go away on time. The starter is their code. Find the two bugs and fix them, keeping the same markup.
TeaFinder takes teas, a list of { id, name }, and noticeMs, how long the notice stays (2000 by default). A text box labelled Search teas filters the list: it shows the teas whose name contains the search text, ignoring case and spaces at either end. An empty search shows every tea. Deleting letters brings teas back, and a new teas prop shows the new teas.
Each tea is an <li> with its name in a <span> and a button named Add plus the tea's name, like Add Sencha. Clicking it shows Added Sencha in a <p role="status">, which goes back to empty noticeMs after the click, even if you keep typing in the search box in the meantime.
Export TeaFinder by name. The default export, App, renders it with a few teas for the Preview, and the tests don't use it.
<TeaFinder teas={[{ id: "sencha", name: "Sencha" }, { id: "rooibos", name: "Rooibos" }]} noticeMs={1500} />
// renders, after searching "sen" and adding Sencha:
<label for="...">Search teas</label>
<input id="..." value="sen" />
<ul>
<li><span>Sencha</span> <button aria-label="Add Sencha">Add</button></li>
</ul>
<p role="status">Added Sencha</p>
const teas = [
{ id: "sencha", name: "Sencha" },
{ id: "earl-grey", name: "Earl Grey" },
{ id: "genmaicha", name: "Genmaicha" },
{ id: "rooibos", name: "Rooibos" },
];
render(<TeaFinder teas={teas} />);
expect(screen.getAllByRole("listitem")).toHaveLength(4);
for (const tea of teas) expect(screen.getByRole("button", { name: "Add " + tea.name })).toBeInTheDocument();
const teas = [
{ id: "sencha", name: "Sencha" },
{ id: "earl-grey", name: "Earl Grey" },
{ id: "genmaicha", name: "Genmaicha" },
{ id: "rooibos", name: "Rooibos" },
];
render(<TeaFinder teas={teas} />);
await user.type(screen.getByLabelText("Search teas"), "cha");
expect(screen.getAllByRole("listitem")).toHaveLength(2);
expect(screen.getByText("Sencha")).toBeInTheDocument();
expect(screen.getByText("Genmaicha")).toBeInTheDocument();
expect(screen.queryByText("Earl Grey")).not.toBeInTheDocument();
const teas = [
{ id: "sencha", name: "Sencha" },
{ id: "earl-grey", name: "Earl Grey" },
{ id: "rooibos", name: "Rooibos" },
];
render(<TeaFinder teas={teas} />);
await user.type(screen.getByLabelText("Search teas"), " EARL ");
expect(screen.getAllByRole("listitem")).toHaveLength(1);
expect(screen.getByText("Earl Grey")).toBeInTheDocument();
const teas = [
{ id: "sencha", name: "Sencha" },
{ id: "earl-grey", name: "Earl Grey" },
{ id: "genmaicha", name: "Genmaicha" },
{ id: "rooibos", name: "Rooibos" },
];
render(<TeaFinder teas={teas} />);
const search = screen.getByLabelText("Search teas");
await user.type(search, "sen");
expect(screen.getAllByRole("listitem")).toHaveLength(1);
await user.type(search, "{Backspace}{Backspace}");
expect(screen.getAllByRole("listitem")).toHaveLength(2);
await user.clear(search);
expect(screen.getAllByRole("listitem")).toHaveLength(4);
const { rerender } = render(<TeaFinder teas={[{ id: "sencha", name: "Sencha" }]} />);
rerender(<TeaFinder teas={[{ id: "oolong", name: "Oolong" }, { id: "assam", name: "Assam" }]} />);
expect(screen.getAllByRole("listitem")).toHaveLength(2);
expect(screen.getByText("Oolong")).toBeInTheDocument();
expect(screen.queryByText("Sencha")).not.toBeInTheDocument();
render(<TeaFinder teas={[{ id: "sencha", name: "Sencha" }, { id: "rooibos", name: "Rooibos" }]} />);
expect(screen.getByRole("status").textContent).toBe("");
await user.click(screen.getByRole("button", { name: "Add Rooibos" }));
expect(screen.getByRole("status").textContent).toBe("Added Rooibos");
const clock = useFakeTimers();
render(<TeaFinder teas={[{ id: "sencha", name: "Sencha" }]} noticeMs={1000} />);
await user.click(screen.getByRole("button", { name: "Add Sencha" }));
await clock.advance(999);
expect(screen.getByRole("status").textContent).toBe("Added Sencha");
await clock.advance(1);
expect(screen.getByRole("status").textContent).toBe("");
const clock = useFakeTimers();
render(<TeaFinder teas={[{ id: "sencha", name: "Sencha" }, { id: "rooibos", name: "Rooibos" }]} />);
await user.click(screen.getByRole("button", { name: "Add Sencha" }));
await clock.advance(1000);
await user.type(screen.getByLabelText("Search teas"), "s");
await clock.advance(500);
await user.type(screen.getByLabelText("Search teas"), "e");
await clock.advance(500);
expect(screen.getByRole("status").textContent).toBe("");
Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.
Ctrl + Enter runs the tests (⌘ + Enter on a Mac).
I'm here to help with Fix the effect loop. I give hints, never the solution, so you get to solve it yourself. Ask for a hint, select code to ask about it, or press Ask Tuto why on a failing test.
Your first 3 answers need no account. for free to keep asking.