The shop uses the same card frame for specials, news, and reviews, with different content inside. Build Card, a named export of App.jsx, that draws the frame and leaves the content to whoever uses it. The default export, App, is only for the Preview.
Card takes three props: title (a string), children (whatever JSX you put between <Card> and </Card>), and footer (more JSX, for the bottom of the card). Each is optional.
The card is an <article>. Inside it, in this order: the title in an <h2>, then a <div> holding the children, then a <footer> holding footer.
Leave out the parts that have nothing to show: no <h2> when there's no title, and no <footer> element when footer is missing, null, or false (so footer={isOpen && <a href="/menu">Menu</a>} works).
<Card title="Today's special" footer={<a href="/menu">See the full menu</a>}>
<p>Hojicha, a roasted green tea.</p>
<button>Add to order</button>
</Card>
// draws:
<article>
<h2>Today's special</h2>
<div>
<p>Hojicha, a roasted green tea.</p>
<button>Add to order</button>
</div>
<footer><a href="/menu">See the full menu</a></footer>
</article>
render(<Card title="Today's special">Hojicha</Card>);
expect(within(screen.getByRole("article")).getByRole("heading", { level: 2, name: "Today's special" })).toBeInTheDocument();
render(
<Card title="Today's special">
<p>Hojicha, a roasted green tea.</p>
<button>Add to order</button>
</Card>,
);
const card = screen.getByRole("article");
expect(within(card).getByText("Hojicha, a roasted green tea.")).toBeInTheDocument();
expect(within(card).getByRole("button", { name: "Add to order" })).toBeInTheDocument();
const onAdd = spy();
render(
<Card title="Today's special">
<button onClick={onAdd}>Add to order</button>
</Card>,
);
await user.click(screen.getByRole("button", { name: "Add to order" }));
expect(onAdd).toHaveBeenCalledTimes(1);
render(
<Card title="News" footer={<a href="/menu">See the full menu</a>}>
<p>New teas every Friday.</p>
</Card>,
);
const footer = screen.getByRole("article").querySelector("footer");
expect(footer).not.toBeNull();
expect(within(footer).getByRole("link", { name: "See the full menu" })).toHaveAttribute("href", "/menu");
render(
<Card title="News" footer={<a href="/menu">See the full menu</a>}>
<p>New teas every Friday.</p>
</Card>,
);
const parts = [...screen.getByRole("article").children];
expect(parts.map((part) => part.tagName)).toEqual(["H2", "DIV", "FOOTER"]);
expect(parts[1]).toHaveTextContent("New teas every Friday.");
render(
<Card title="News">
<p>New teas every Friday.</p>
</Card>,
);
expect(screen.getByRole("article").querySelector("footer")).toBeNull();
const { rerender } = render(<Card title="News" footer={null}>Hello</Card>);
expect(screen.getByRole("article").querySelector("footer")).toBeNull();
rerender(<Card title="News" footer={false}>Hello</Card>);
expect(screen.getByRole("article").querySelector("footer")).toBeNull();
render(
<Card footer={<small>Posted on Monday</small>}>
<p>New teas every Friday.</p>
</Card>,
);
expect(screen.queryByRole("heading")).not.toBeInTheDocument();
expect(screen.getByText("Posted on Monday")).toBeInTheDocument();
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 Card with slots. 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.