test: add suite for utils/keyboard handlers (+4)
Covers onTabPress (Tab-only), preventScrollWithArrowKey (arrows-only), onEnterOrSpace (Enter/Space gate the callback), and stopPropagation's editable-element check (does not swallow keys when an input/textarea/ contenteditable is focused) via mock events + a document.activeElement stub. Full suite now 133 tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { onTabPress, preventScrollWithArrowKey, onEnterOrSpace, stopPropagation } from './keyboard';
|
||||
|
||||
type Evt = {
|
||||
key: string;
|
||||
which: number;
|
||||
altKey: boolean;
|
||||
ctrlKey: boolean;
|
||||
metaKey: boolean;
|
||||
shiftKey: boolean;
|
||||
prevented: boolean;
|
||||
preventDefault(): void;
|
||||
};
|
||||
const evt = (key: string, which: number): Evt => ({
|
||||
key,
|
||||
which,
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
shiftKey: false,
|
||||
prevented: false,
|
||||
preventDefault() {
|
||||
this.prevented = true;
|
||||
},
|
||||
});
|
||||
|
||||
test('onTabPress fires only on Tab', () => {
|
||||
let called = 0;
|
||||
const tab = evt('Tab', 9);
|
||||
onTabPress(tab, () => {
|
||||
called += 1;
|
||||
});
|
||||
assert.equal(called, 1);
|
||||
assert.equal(tab.prevented, true);
|
||||
|
||||
const a = evt('a', 65);
|
||||
onTabPress(a, () => {
|
||||
called += 1;
|
||||
});
|
||||
assert.equal(called, 1); // unchanged
|
||||
assert.equal(a.prevented, false);
|
||||
});
|
||||
|
||||
test('preventScrollWithArrowKey prevents default only on arrows', () => {
|
||||
const up = evt('ArrowUp', 38);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
preventScrollWithArrowKey(up as any);
|
||||
assert.equal(up.prevented, true);
|
||||
|
||||
const a = evt('a', 65);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
preventScrollWithArrowKey(a as any);
|
||||
assert.equal(a.prevented, false);
|
||||
});
|
||||
|
||||
test('onEnterOrSpace fires on Enter or Space, not others', () => {
|
||||
let count = 0;
|
||||
const handler = onEnterOrSpace(() => {
|
||||
count += 1;
|
||||
});
|
||||
|
||||
const enter = evt('Enter', 13);
|
||||
handler(enter);
|
||||
assert.equal(count, 1);
|
||||
assert.equal(enter.prevented, true);
|
||||
|
||||
const space = evt(' ', 32);
|
||||
handler(space);
|
||||
assert.equal(count, 2);
|
||||
assert.equal(space.prevented, true);
|
||||
|
||||
const other = evt('a', 65);
|
||||
handler(other);
|
||||
assert.equal(count, 2); // unchanged
|
||||
assert.equal(other.prevented, false);
|
||||
});
|
||||
|
||||
test('stopPropagation: stops unless an editable element is focused', () => {
|
||||
const makeKeyEvt = () => {
|
||||
let stopped = false;
|
||||
return {
|
||||
ev: {
|
||||
stopPropagation() {
|
||||
stopped = true;
|
||||
},
|
||||
},
|
||||
wasStopped: () => stopped,
|
||||
};
|
||||
};
|
||||
const withActive = (activeElement: unknown) => {
|
||||
(globalThis as { document?: unknown }).document = { activeElement };
|
||||
};
|
||||
|
||||
// nothing focused → stops, returns true
|
||||
withActive(null);
|
||||
let k = makeKeyEvt();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
assert.equal(stopPropagation(k.ev as any), true);
|
||||
assert.equal(k.wasStopped(), true);
|
||||
|
||||
// input focused → does not stop, returns false
|
||||
withActive({ nodeName: 'INPUT', getAttribute: () => null });
|
||||
k = makeKeyEvt();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
assert.equal(stopPropagation(k.ev as any), false);
|
||||
assert.equal(k.wasStopped(), false);
|
||||
|
||||
// contenteditable focused → does not stop
|
||||
withActive({
|
||||
nodeName: 'DIV',
|
||||
getAttribute: (a: string) => (a === 'contenteditable' ? 'true' : null),
|
||||
});
|
||||
k = makeKeyEvt();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
assert.equal(stopPropagation(k.ev as any), false);
|
||||
});
|
||||
Reference in New Issue
Block a user