Why do my tests get flaky after I add a global keydown handler?

Hey folks, I’m working on a little pixel-art canvas editor and I’m trying to add keyboard shortcuts, but my Jest tests started randomly failing after I introduced a global keydown listener; sometimes it fires twice and my undo stack ends up off-by-one.

// shortcuts.js
export function attachShortcuts(store) {
  const onKeyDown = (e) => {
    if ((e.ctrlKey || e.metaKey) && e.key === "z") {
      store.undo();
    }
  };

  window.addEventListener("keydown", onKeyDown);
  return () => window.removeEventListener("keydown", onKeyDown);
}

// shortcuts.test.js
import { attachShortcuts } from "./shortcuts";

test("ctrl+z undoes once", () => {
  const store = { undo: jest.fn() };
  attachShortcuts(store);
  window.dispatchEvent(new KeyboardEvent("keydown", { key: "z", ctrlKey: true }));
  expect(store.undo).toHaveBeenCalledTimes(1);
});

What’s the cleanest pattern to make this reliable in tests without leaking handlers between tests or over-mocking the browser event system?

Sora :blush:

you’re basically hot-plugging a global listener and never pulling it back out. attachShortcuts returns the cleanup, but your test ignores it, so the next test run can have multiple keydown handlers stacked on window and one dispatchEvent turns into 2+ undo() calls.

I’d keep it boring: capture the detach and always run it in afterEach (or beforeEach + afterEach if you want to be extra safe). That way you’re not mocking events, you’re just not leaking state between tests. Global listeners are like leaving a mic channel unmuted between sets — it’s fine until it suddenly isn’t.