Write createCountdown(seconds, { onTick, onDone }). It starts counting down from seconds right away and returns an object with pause(), resume(), and remaining().
Each time another whole second has been counted, it calls onTick(secondsLeft). When it reaches zero, it calls onDone() instead, once, and stops, leaving no timers running. A 3 second countdown calls onTick(2) after 1 s, onTick(1) after 2 s, and onDone() after 3 s.
remaining() returns the milliseconds left, exact to the millisecond: 1750 when 1.25 s of a 3 s countdown have passed, and 0 once it's done.
pause() freezes the countdown, keeping the time left, including the part of the current second. resume() continues from exactly there: paused 1.5 s into a 3 s countdown, the next onTick(1) comes 0.5 s after resume(). Pausing while paused, or resuming while running, does nothing.
const countdown = createCountdown(3, {
onTick: (secondsLeft) => console.log(secondsLeft),
onDone: () => console.log("go!"),
});
// after 1 s: logs 2. At 1.5 s:
countdown.pause();
countdown.remaining(); // 1500
countdown.resume();
// 0.5 s later: logs 1. 1 s after that: logs "go!"
const clock = useFakeTimers();
const onTick = spy();
createCountdown(3, { onTick, onDone: spy() });
clock.tick(999);
expect(onTick).toHaveBeenCalledTimes(0);
clock.tick(1);
expect(onTick).toHaveBeenLastCalledWith(2);
clock.tick(1000);
expect(onTick.calls.map((call) => call.args[0])).toEqual([2, 1]);
const clock = useFakeTimers();
const onTick = spy();
const onDone = spy();
createCountdown(3, { onTick, onDone });
clock.tick(2999);
expect(onDone).toHaveBeenCalledTimes(0);
clock.tick(1);
expect(onDone).toHaveBeenCalledTimes(1);
clock.tick(5000);
expect(onDone).toHaveBeenCalledTimes(1);
expect(onTick).toHaveBeenCalledTimes(2);
expect(clock.pending()).toBe(0);
const clock = useFakeTimers();
const countdown = createCountdown(3, { onTick: spy(), onDone: spy() });
expect(countdown.remaining()).toBe(3000);
clock.tick(1250);
expect(countdown.remaining()).toBe(1750);
clock.tick(5000);
expect(countdown.remaining()).toBe(0);
const clock = useFakeTimers();
const onTick = spy();
const onDone = spy();
const countdown = createCountdown(3, { onTick, onDone });
clock.tick(1500);
countdown.pause();
clock.tick(10000);
expect(countdown.remaining()).toBe(1500);
expect(onTick).toHaveBeenCalledTimes(1);
expect(onDone).toHaveBeenCalledTimes(0);
const clock = useFakeTimers();
const onTick = spy();
const onDone = spy();
const countdown = createCountdown(3, { onTick, onDone });
clock.tick(1500);
countdown.pause();
clock.tick(10000);
countdown.resume();
clock.tick(499);
expect(onTick).toHaveBeenCalledTimes(1);
clock.tick(1);
expect(onTick).toHaveBeenCalledTimes(2);
expect(onTick).toHaveBeenLastCalledWith(1);
clock.tick(999);
expect(onDone).toHaveBeenCalledTimes(0);
clock.tick(1);
expect(onDone).toHaveBeenCalledTimes(1);
const clock = useFakeTimers();
const onTick = spy();
const onDone = spy();
const countdown = createCountdown(3, { onTick, onDone });
clock.tick(1500);
countdown.pause();
countdown.pause();
clock.tick(1000);
expect(countdown.remaining()).toBe(1500);
countdown.resume();
countdown.resume();
clock.tick(500);
expect(onTick.calls.map((call) => call.args[0])).toEqual([2, 1]);
clock.tick(1000);
expect(onDone).toHaveBeenCalledTimes(1);
expect(onTick).toHaveBeenCalledTimes(2);
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 with pause and resume. 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.