test('a key handler runs for the key it named and no other', () => {
const [count, setCount] = signal(0);
const [, dispose] = owned(() => onKey('Escape', () => setCount(count() + 1)));
document.fire('keydown', { key: 'Escape' });
assert.equal(count(), 1, 'the named key ran it');
document.fire('keydown', { key: 'a' });
document.fire('keydown', { key: 'ArrowLeft' });
document.fire('keydown', { key: 'escape' });
assert.equal(count(), 1, 'no other key reached it, and the match is exact');
dispose();
});
test('two handlers on two keys stay apart', () => {
const [left, setLeft] = signal(0);
const [right, setRight] = signal(0);
const [, dispose] = owned(() => {
onKey('ArrowLeft', () => setLeft(left() + 1));
onKey('ArrowRight', () => setRight(right() + 1));
});
document.fire('keydown', { key: 'ArrowRight' });
document.fire('keydown', { key: 'ArrowRight' });
document.fire('keydown', { key: 'ArrowLeft' });
assert.equal(left(), 1, 'left ran once');
assert.equal(right(), 2, 'right ran twice');
dispose();
});
test('a keystroke aimed at a field does not reach a document handler', () => {
const [seen, setSeen] = signal(0);
const [, dispose] = owned(() => onKey('r', () => setSeen(seen() + 1)));
document.fire('keydown', { key: 'r', target: createElement('input') });
assert.equal(seen(), 0, 'an input is somebody typing');
document.fire('keydown', { key: 'r', target: createElement('textarea') });
assert.equal(seen(), 0, 'so is a textarea');
document.fire('keydown', { key: 'r', target: createElement('select') });
assert.equal(seen(), 0, 'so is a select');
const editable = createElement('div');
editable.isContentEditable = true;
document.fire('keydown', { key: 'r', target: editable });
assert.equal(seen(), 0, 'so is anything contenteditable');
document.fire('keydown', { key: 'r', target: createElement('div') });
assert.equal(seen(), 1, 'a keystroke aimed at nothing editable reaches it');
dispose();
});
test('a named key is suppressed inside a field too', () => {
const [closed, setClosed] = signal(0);
const [, dispose] = owned(() => onKey('Escape', () => setClosed(closed() + 1)));
document.fire('keydown', { key: 'Escape', target: createElement('input') });
assert.equal(closed(), 0, 'the field has it');
document.fire('keydown', { key: 'Escape', target: createElement('button') });
assert.equal(closed(), 1, 'a button is not somewhere text goes');
dispose();
});
test('a discarded handler is removed from the document, not merely inert', () => {
const before = document.listenerCount('keydown');
const [count, setCount] = signal(0);
const [, dispose] = owned(() => onKey('Escape', () => setCount(count() + 1)));
assert.equal(document.listenerCount('keydown'), before + 1, 'registered');
document.fire('keydown', { key: 'Escape' });
assert.equal(count(), 1, 'it ran while it was live');
dispose();
assert.equal(document.listenerCount('keydown'), before, 'and it was removed');
document.fire('keydown', { key: 'Escape' });
assert.equal(count(), 1, 'a discarded handler does not fire');
});
test('mounting and discarding many times leaves nothing behind', () => {
const before = document.listenerCount('keydown');
const [count, setCount] = signal(0);
for (let i = 0; i < 200; i += 1) {
const [, dispose] = owned(() => onKey('Escape', () => setCount(count() + 1)));
dispose();
}
assert.equal(document.listenerCount('keydown'), before, 'none accumulated');
document.fire('keydown', { key: 'Escape' });
assert.equal(count(), 0, 'and none of the two hundred is still firing');
});
test('an inner scope is disposed with its outer one', () => {
const before = document.listenerCount('keydown');
const [count, setCount] = signal(0);
const [, disposeOuter] = owned(() => {
onKey('Escape', () => setCount(count() + 1));
owned(() => onKey('Enter', () => setCount(count() + 10)));
});
assert.equal(document.listenerCount('keydown'), before + 2, 'both registered');
disposeOuter();
assert.equal(document.listenerCount('keydown'), before, 'both removed');
document.fire('keydown', { key: 'Escape' });
document.fire('keydown', { key: 'Enter' });
assert.equal(count(), 0, 'neither fires');
});