The tea shop's brewing station needs a timer for each pot. Build Countdown: it takes seconds and counts down from it, one second at a time, starting as soon as it appears.
Show the time left as minutes and seconds, both with two digits, alone in a <p role="timer">. For example, 90 seconds shows 01:30 and 5 seconds shows 00:05.
A button reads Pause while the timer counts and Resume while it's paused. Resume carries on from the time shown.
At 00:00 the timer stops, the button is disabled, and it calls the onDone prop once. onDone is optional. The parent may pass a different onDone function on any render: that must not restart or delay the count, and the one passed last is the one called.
When seconds changes, start again from the new value, counting, even if the timer was paused or had finished. When the timer leaves the page, no timer is left running.
Export Countdown by name. The default export, App, is a small demo for the Preview, and the tests don't use it.
<Countdown seconds={90} onDone={() => alert("Your tea is ready")} />
// renders, after 5 seconds:
<p role="timer">01:25</p>
<button>Pause</button>
useFakeTimers();
render(<Countdown seconds={90} />);
expect(screen.getByRole("timer").textContent).toBe("01:30");
expect(screen.getByRole("button", { name: "Pause" })).toBeEnabled();
const clock = useFakeTimers();
render(<Countdown seconds={65} />);
await clock.advance(1000);
expect(screen.getByRole("timer").textContent).toBe("01:04");
await clock.advance(5000);
expect(screen.getByRole("timer").textContent).toBe("00:59");
const clock = useFakeTimers();
render(<Countdown seconds={10} />);
await clock.advance(2000);
await user.click(screen.getByRole("button", { name: "Pause" }));
expect(screen.getByRole("button", { name: "Resume" })).toBeInTheDocument();
await clock.advance(5000);
expect(screen.getByRole("timer").textContent).toBe("00:08");
const clock = useFakeTimers();
render(<Countdown seconds={10} />);
await clock.advance(2000);
await user.click(screen.getByRole("button", { name: "Pause" }));
await clock.advance(5000);
await user.click(screen.getByRole("button", { name: "Resume" }));
expect(screen.getByRole("button", { name: "Pause" })).toBeInTheDocument();
await clock.advance(3000);
expect(screen.getByRole("timer").textContent).toBe("00:05");
const clock = useFakeTimers();
const onDone = spy();
render(<Countdown seconds={3} onDone={onDone} />);
await clock.advance(2999);
expect(onDone.callCount).toBe(0);
await clock.advance(1);
expect(screen.getByRole("timer").textContent).toBe("00:00");
expect(onDone.callCount).toBe(1);
await clock.advance(5000);
expect(onDone.callCount).toBe(1);
const clock = useFakeTimers();
render(<Countdown seconds={2} />);
await clock.advance(2000);
expect(screen.getByRole("button", { name: "Pause" })).toBeDisabled();
expect(clock.pending()).toBe(0);
await clock.advance(3000);
expect(screen.getByRole("timer").textContent).toBe("00:00");
const clock = useFakeTimers();
const { rerender } = render(<Countdown seconds={10} />);
await clock.advance(3000);
await user.click(screen.getByRole("button", { name: "Pause" }));
rerender(<Countdown seconds={20} />);
expect(screen.getByRole("timer").textContent).toBe("00:20");
expect(screen.getByRole("button", { name: "Pause" })).toBeInTheDocument();
await clock.advance(1000);
expect(screen.getByRole("timer").textContent).toBe("00:19");
const clock = useFakeTimers();
const onDone = spy();
const { rerender } = render(<Countdown seconds={2} onDone={onDone} />);
await clock.advance(2000);
expect(onDone.callCount).toBe(1);
rerender(<Countdown seconds={3} onDone={onDone} />);
expect(screen.getByRole("timer").textContent).toBe("00:03");
expect(screen.getByRole("button", { name: "Pause" })).toBeEnabled();
await clock.advance(3000);
expect(onDone.callCount).toBe(2);
const clock = useFakeTimers();
const first = spy();
const second = spy();
const { rerender } = render(<Countdown seconds={3} onDone={first} />);
await clock.advance(500);
rerender(<Countdown seconds={3} onDone={second} />);
await clock.advance(2500);
expect(screen.getByRole("timer").textContent).toBe("00:00");
expect(first.callCount).toBe(0);
expect(second.callCount).toBe(1);
const clock = useFakeTimers();
const { unmount } = render(<Countdown seconds={30} />);
await clock.advance(1000);
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 Countdown timer. 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.