Skip to the code
cf.

Stopwatch with laps

EasyTiming

Write createStopwatch(). It returns a stopwatch, with the methods below, that starts out stopped, at 0 ms.

start() starts counting, or carries on from where it stopped. stop() stops counting and keeps the time counted so far. Calling start() while it's running, or stop() while it's stopped, does nothing.

elapsed() returns the total time counted, in milliseconds, exact to the millisecond. Time while it's stopped doesn't count. It must stay exact even when timers run late or rarely, as they do in a background tab.

lap() ends the current lap and starts the next one. It returns the lap's length: the time counted since the previous lap, or, for the first lap, since 0 ms.

laps() returns the lengths of all the laps so far, oldest first, in a new array each time.

reset() stops the stopwatch, sets it back to 0 ms, and forgets the laps.

const watch = createStopwatch();
watch.start();
// 1.5 s later:
watch.lap(); // 1500
// 2 s later:
watch.stop();
watch.elapsed(); // 3500
// 10 s later, still stopped:
watch.start();
// 0.25 s later:
watch.lap(); // 2250: 2000 before stop() and 250 after start()
watch.laps(); // [1500, 2250]
watch.reset();
watch.elapsed(); // 0
watch.laps(); // []

What the tests check

  1. starts out stopped, at 0
    const clock = useFakeTimers();
    const watch = createStopwatch();
    clock.tick(500);
    expect(watch.elapsed()).toBe(0);
    expect(watch.laps()).toEqual([]);
    
  2. elapsed() counts the exact milliseconds while running
    const clock = useFakeTimers();
    const watch = createStopwatch();
    watch.start();
    clock.tick(1234);
    expect(watch.elapsed()).toBe(1234);
    clock.tick(1);
    expect(watch.elapsed()).toBe(1235);
    
  3. stays exact when timers run only once a second
    const clock = useFakeTimers();
    const fakeSetTimeout = globalThis.setTimeout;
    const fakeSetInterval = globalThis.setInterval;
    // As in a background tab: no timer runs more often than once a second.
    globalThis.setTimeout = (callback, ms, ...args) => fakeSetTimeout(callback, Math.max(1000, ms || 0), ...args);
    globalThis.setInterval = (callback, ms, ...args) => fakeSetInterval(callback, Math.max(1000, ms || 0), ...args);
    let elapsed;
    try {
      const watch = createStopwatch();
      watch.start();
      clock.tick(2500);
      elapsed = watch.elapsed();
    } finally {
      globalThis.setTimeout = fakeSetTimeout;
      globalThis.setInterval = fakeSetInterval;
    }
    expect(elapsed).toBe(2500);
    
  4. stop() keeps the time, and start() carries on from it
    const clock = useFakeTimers();
    const watch = createStopwatch();
    watch.start();
    clock.tick(1000);
    watch.stop();
    clock.tick(5000);
    expect(watch.elapsed()).toBe(1000);
    watch.start();
    clock.tick(500);
    expect(watch.elapsed()).toBe(1500);
    
  5. extra start() and stop() calls change nothing
    const clock = useFakeTimers();
    const watch = createStopwatch();
    watch.stop();
    watch.start();
    clock.tick(300);
    watch.start();
    clock.tick(200);
    expect(watch.elapsed()).toBe(500);
    watch.stop();
    clock.tick(100);
    watch.stop();
    expect(watch.elapsed()).toBe(500);
    
  6. lap() returns the time since the previous lap
    const clock = useFakeTimers();
    const watch = createStopwatch();
    watch.start();
    clock.tick(1500);
    expect(watch.lap()).toBe(1500);
    clock.tick(2000);
    expect(watch.lap()).toBe(2000);
    clock.tick(250);
    expect(watch.lap()).toBe(250);
    expect(watch.elapsed()).toBe(3750);
    
  7. a lap doesn't count the time while stopped
    const clock = useFakeTimers();
    const watch = createStopwatch();
    watch.start();
    clock.tick(1500);
    watch.lap();
    clock.tick(2000);
    watch.stop();
    clock.tick(10000);
    watch.start();
    clock.tick(250);
    expect(watch.lap()).toBe(2250);
    
  8. laps() lists the laps, oldest first, in a new array
    const clock = useFakeTimers();
    const watch = createStopwatch();
    watch.start();
    clock.tick(100);
    watch.lap();
    clock.tick(200);
    watch.lap();
    const laps = watch.laps();
    expect(laps).toEqual([100, 200]);
    laps.push(999);
    expect(watch.laps()).toEqual([100, 200]);
    
  9. reset() stops it at 0 and forgets the laps
    const clock = useFakeTimers();
    const watch = createStopwatch();
    watch.start();
    clock.tick(1000);
    watch.lap();
    clock.tick(500);
    watch.reset();
    expect(watch.elapsed()).toBe(0);
    expect(watch.laps()).toEqual([]);
    clock.tick(1000);
    expect(watch.elapsed()).toBe(0);
    watch.start();
    clock.tick(300);
    expect(watch.lap()).toBe(300);
    

Learn more

solution.jsSaved in this browser

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).