jQuery .off('keydown') on document removes handlers you didn't write
TLDR
An Enter-key handler bound with$(document).on('keydown', '#input', fn) worked in tests and did nothing in the app. Another module ran $(document).off('keydown') to reset its own global shortcut, which detaches all keydown handlers on document, mine included. The fix: bind to the element itself, out of the blast radius.
Symptom
Pressing Return in a class-name field was supposed to add the class as a pill. In the jsdom test harness, triggering the event worked:ok class added to cell
ok pill rendered for added class
In the running app: nothing. No error, no console output, no handler invocation. The classic signature of a handler that was bound and then silently removed, as opposed to one that throws.
Why it happens
jQuery's .off(events) with a bare event name removes every handler for that event on the selected element. Not your handlers. Every handler, from every module, including delegated ones, regardless of namespace.The app's core module resets its global keyboard shortcuts like this:
$(document).off('keydown').on('keydown', function (e) {
// global shortcut dispatch
});
Idempotent rebinding is a sensible goal: run it twice and you get one handler, not two. But document is shared infrastructure. My module, loaded later, delegated from the same element:
$(document).on('keydown', '#classInput', addPillOnEnter);
The next time the core module's reset line ran, my delegation was gone. Namespacing my handler would not have helped; a bare .off('keydown') removes namespaced handlers too. The namespace only protects you when the remover also scopes itself, as in .off('keydown.myModule').
The fix
// wrong: delegated from document, inside the reset's blast radius
$(document).on('keydown', '#classInput', addPillOnEnter);
// correct: bound to the element; document-level .off can't reach it $('#classInput').off('keydown.pills').on('keydown.pills', addPillOnEnter);
Handlers bound to #classInput live on that element, so a .off() against document never touches them. The namespace on my own binding keeps the rebind idempotent without endangering anyone else.
Evidence
Reproducible in three lines against any page with jQuery:$(document).on('keydown', '#x', () => console.log('fires'));
$(document).off('keydown'); // someone else's "reset"
// pressing a key in #x now logs nothing
The jsdom tests passed because the harness never executed the other module's reset path; it loaded the panel module in isolation. The bug only existed in the composed app.
How to prevent it
Two rules, either one of which prevents the class of bug. Never call.off(event) un-namespaced on a shared element like document, window, or body; always scope the removal to your own namespace. And prefer binding to the most specific element that exists at bind time; delegation from document is for elements that do not exist yet, which a static input is not.
A grep for \.off\('(keydown|click|mouseup)'\) on shared elements is a cheap audit for an existing codebase.
The generalizable lesson
Event handlers ondocument are a shared namespace with no access control. Any module that bulk-removes there is deleting other people's code, and any module that binds there is trusting nobody will.