The tea shop wants a little game for its loyalty page: pairs of tea cards, face down, and you find the pairs. Build MemoryGame, the default export.
It takes cards, a list of names, and shuffle, a function that takes an array and returns it in a new order. Both are optional: by default cards is six teas of your choice and shuffle is a real random shuffle. When the game starts, call shuffle once with every name twice ([...cards, ...cards]) and lay the cards out in the order it returns. Don't shuffle again while the game goes on.
Each card is a button with the accessible name Card 1, Card 2, and so on, in layout order (use aria-label). A face-down card has no text. A face-up card shows its name as its text and is disabled.
Clicking a face-down card turns it face up. Turning up a second card is one move. If the two cards have the same name, they're a pair: they stay face up for the rest of the game. If not, they stay face up for 1 second (1000 ms) and then both turn face down again. While a mismatched pair is showing, every other card is disabled too.
Show the number of moves as Moves: 0. When every pair is found, a <p aria-live="polite"> says You won in 6 moves! (with the real count). Before that it's empty.
If the game leaves the page while a mismatched pair is showing, clear the timer.
<MemoryGame /> // six teas, shuffled
<MemoryGame cards={["Sencha", "Matcha", "Chai"]} shuffle={(deck) => deck} />
// renders, with Card 1 turned over:
<p>Moves: 0</p>
<button aria-label="Card 1" disabled>Sencha</button>
<button aria-label="Card 2"></button>
...
<p aria-live="polite"></p>
render(<App cards={["Sencha", "Matcha", "Chai"]} shuffle={(deck) => deck} />);
for (let n = 1; n <= 6; n++) {
const card = screen.getByRole("button", { name: "Card " + n });
expect(card.textContent).toBe("");
expect(card).toBeEnabled();
}
expect(screen.queryByRole("button", { name: "Card 7" })).not.toBeInTheDocument();
expect(screen.getByText("Moves: 0")).toBeInTheDocument();
const card = (n) => screen.getByRole("button", { name: "Card " + n });
const shuffle = spy((deck) => [...deck].reverse());
render(<App cards={["Sencha", "Matcha", "Chai"]} shuffle={shuffle} />);
expect(shuffle.callCount).toBeGreaterThan(0);
expect(shuffle.calls[0].args[0]).toEqual(["Sencha", "Matcha", "Chai", "Sencha", "Matcha", "Chai"]);
const calls = shuffle.callCount;
await user.click(card(1));
expect(card(1)).toHaveTextContent("Chai");
await user.click(card(4));
expect(card(4)).toHaveTextContent("Chai");
expect(shuffle.callCount).toBe(calls);
const card = (n) => screen.getByRole("button", { name: "Card " + n });
render(<App cards={["Sencha", "Matcha", "Chai"]} shuffle={(deck) => deck} />);
await user.click(card(2));
expect(card(2)).toHaveTextContent("Matcha");
expect(card(2)).toBeDisabled();
expect(card(1).textContent).toBe("");
expect(screen.getByText("Moves: 0")).toBeInTheDocument();
const card = (n) => screen.getByRole("button", { name: "Card " + n });
const clock = useFakeTimers();
render(<App cards={["Sencha", "Matcha", "Chai"]} shuffle={(deck) => deck} />);
await user.click(card(1));
await user.click(card(4));
expect(screen.getByText("Moves: 1")).toBeInTheDocument();
await clock.advance(3000);
expect(card(1)).toHaveTextContent("Sencha");
expect(card(4)).toHaveTextContent("Sencha");
expect(card(1)).toBeDisabled();
expect(card(4)).toBeDisabled();
expect(card(2)).toBeEnabled();
const card = (n) => screen.getByRole("button", { name: "Card " + n });
const clock = useFakeTimers();
render(<App cards={["Sencha", "Matcha", "Chai"]} shuffle={(deck) => deck} />);
await user.click(card(1));
await user.click(card(2));
expect(card(1)).toHaveTextContent("Sencha");
expect(card(2)).toHaveTextContent("Matcha");
expect(screen.getByText("Moves: 1")).toBeInTheDocument();
await clock.advance(999);
expect(card(2)).toHaveTextContent("Matcha");
await clock.advance(1);
expect(card(1).textContent).toBe("");
expect(card(2).textContent).toBe("");
expect(card(1)).toBeEnabled();
await user.click(card(1));
expect(card(1)).toHaveTextContent("Sencha");
const card = (n) => screen.getByRole("button", { name: "Card " + n });
const clock = useFakeTimers();
render(<App cards={["Sencha", "Matcha", "Chai"]} shuffle={(deck) => deck} />);
await user.click(card(1));
await user.click(card(2));
expect(card(3)).toBeDisabled();
await user.click(card(3));
expect(card(3).textContent).toBe("");
expect(screen.getByText("Moves: 1")).toBeInTheDocument();
await clock.advance(1000);
expect(card(3)).toBeEnabled();
const card = (n) => screen.getByRole("button", { name: "Card " + n });
useFakeTimers();
render(<App cards={["Sencha", "Matcha", "Chai"]} shuffle={(deck) => deck} />);
await user.click(card(1));
await user.click(card(1));
expect(screen.getByText("Moves: 0")).toBeInTheDocument();
await user.click(card(4));
expect(screen.getByText("Moves: 1")).toBeInTheDocument();
expect(card(4)).toHaveTextContent("Sencha");
expect(card(4)).toBeDisabled();
const card = (n) => screen.getByRole("button", { name: "Card " + n });
const clock = useFakeTimers();
render(<App cards={["Sencha", "Matcha", "Chai"]} shuffle={(deck) => deck} />);
await user.click(card(1));
await user.click(card(2));
await clock.advance(1000);
await user.click(card(3));
await user.click(card(6));
await user.click(card(2));
await user.click(card(4));
await clock.advance(1000);
expect(screen.getByText("Moves: 3")).toBeInTheDocument();
const card = (n) => screen.getByRole("button", { name: "Card " + n });
const clock = useFakeTimers();
render(<App cards={["Sencha", "Matcha", "Chai"]} shuffle={(deck) => deck} />);
await user.click(card(1));
await user.click(card(5));
await clock.advance(1000);
await user.click(card(1));
await user.click(card(4));
await user.click(card(2));
await user.click(card(5));
expect(screen.queryByText(/You won/)).not.toBeInTheDocument();
await user.click(card(6));
await user.click(card(3));
expect(screen.getByText("You won in 4 moves!")).toBeInTheDocument();
const card = (n) => screen.getByRole("button", { name: "Card " + n });
const clock = useFakeTimers();
const { unmount } = render(<App cards={["Sencha", "Matcha", "Chai"]} shuffle={(deck) => deck} />);
await user.click(card(1));
await user.click(card(2));
expect(clock.pending()).toBeGreaterThan(0);
unmount();
expect(clock.pending()).toBe(0);
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 Memory game. 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.