The tea shop's search page filters a long catalogue with filterTeas, which is slow. The page also has an order note box. Typing a note has nothing to do with the search, but every keystroke renders the page, and the page runs the filter again. Make the page run the filter only when the teas or the search text change.
filterTeas(teas, query) is exported. It returns the teas whose name contains query, ignoring case and spaces at either end; an empty query returns every tea. The starter's version is right: keep it exported and working.
TeaSearch takes teas, a list of { id, name }, and filter, the function it filters with, which is filterTeas by default. The tests pass a spy wrapped around filterTeas to count the calls, so always call filter, not filterTeas directly. The page has a text box labelled Search teas, a list with one <li> per matching tea showing its name, and a text box labelled Order note. When no tea matches, it shows No teas found instead of the list.
What to fix: typing a note doesn't call filter. Each change to the search text calls it once, and so does a new teas list. Rendering TeaSearch again with the same teas and filter doesn't call it.
Export TeaSearch by name. The default export, App, renders it with a filter that logs each call for the Preview.
filterTeas([{ id: "sencha", name: "Sencha" }, { id: "assam", name: "Assam" }], " SEN "); // [{ id: "sencha", name: "Sencha" }]
<TeaSearch teas={teas} />
// renders:
<label>Search teas <input value="" /></label>
<ul>
<li>Sencha</li>
<li>Assam</li>
</ul>
<label>Order note <input value="" /></label>
const teas = [
{ id: "sencha", name: "Sencha" },
{ id: "assam", name: "Assam" },
{ id: "genmaicha", name: "Genmaicha" },
];
expect(filterTeas(teas, " CHA ")).toEqual([{ id: "sencha", name: "Sencha" }, { id: "genmaicha", name: "Genmaicha" }]);
expect(filterTeas(teas, "")).toEqual(teas);
expect(filterTeas(teas, "mint")).toEqual([]);
const teas = [
{ id: "sencha", name: "Sencha" },
{ id: "assam", name: "Assam" },
];
const filter = spy(filterTeas);
render(<TeaSearch teas={teas} filter={filter} />);
expect(screen.getAllByRole("listitem")).toHaveLength(2);
expect(filter.callCount).toBe(1);
expect(filter.calls[0].args).toEqual([teas, ""]);
const teas = [
{ id: "sencha", name: "Sencha" },
{ id: "assam", name: "Assam" },
{ id: "genmaicha", name: "Genmaicha" },
];
render(<TeaSearch 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("Assam")).not.toBeInTheDocument();
render(<TeaSearch teas={[{ id: "sencha", name: "Sencha" }]} />);
await user.type(screen.getByLabelText("Search teas"), "mint");
expect(screen.getByText("No teas found")).toBeInTheDocument();
expect(screen.queryByRole("listitem")).not.toBeInTheDocument();
const filter = spy(filterTeas);
render(<TeaSearch teas={[{ id: "sencha", name: "Sencha" }]} filter={filter} />);
await user.type(screen.getByLabelText("Order note"), "no sugar");
expect(screen.getByLabelText("Order note")).toHaveValue("no sugar");
expect(filter.callCount).toBe(1);
const teas = [
{ id: "sencha", name: "Sencha" },
{ id: "assam", name: "Assam" },
];
const filter = spy(filterTeas);
render(<TeaSearch teas={teas} filter={filter} />);
await user.type(screen.getByLabelText("Search teas"), "as");
expect(filter.callCount).toBe(3);
expect(filter.calls[2].args).toEqual([teas, "as"]);
expect(screen.getAllByRole("listitem")).toHaveLength(1);
const filter = spy(filterTeas);
const { rerender } = render(<TeaSearch teas={[{ id: "sencha", name: "Sencha" }]} filter={filter} />);
await user.type(screen.getByLabelText("Search teas"), "s");
rerender(<TeaSearch teas={[{ id: "assam", name: "Assam" }, { id: "oolong", name: "Oolong" }]} filter={filter} />);
expect(screen.getByText("Assam")).toBeInTheDocument();
expect(screen.queryByText("Sencha")).not.toBeInTheDocument();
expect(screen.queryByText("Oolong")).not.toBeInTheDocument();
expect(filter.callCount).toBe(3);
const teas = [{ id: "sencha", name: "Sencha" }];
const filter = spy(filterTeas);
const { rerender } = render(<TeaSearch teas={teas} filter={filter} />);
rerender(<TeaSearch teas={teas} filter={filter} />);
rerender(<TeaSearch teas={teas} filter={filter} />);
expect(filter.callCount).toBe(1);
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 Memoize an expensive filter. 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.