@acusti/use-keyboard-events
Version:
React hook that takes keyboard event handlers and attaches them to the document
177 lines • 9.34 kB
JavaScript
// @vitest-environment happy-dom
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-static-element-interactions */
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';
import React from 'react';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { addHandler, handlersData, isEventTargetUsingKeyEvent } from './handlers.js';
const noop = () => { }; // eslint-disable-line @typescript-eslint/no-empty-function
describe('@acusti/use-keyboard-events', () => {
describe('handlers', () => {
describe('addHandler', () => {
it('adds the passed-in handler based on eventType and priority', () => {
const cleanupKeyDownHandler = addHandler({
eventType: 'keydown',
handler: noop,
});
// priority should default to 0, which gets normalized to 50
expect(handlersData.keydown[50].has(noop)).toBe(true);
expect(handlersData.keydown[0]).toBe(undefined);
const cleanupKeyPressHandler = addHandler({
eventType: 'keypress',
handler: noop,
priority: 10,
});
// priority of 10 normalizes to 60
expect(handlersData.keypress[60].has(noop)).toBe(true);
expect(handlersData.keypress[50]).toBe(undefined);
const cleanupKeyUpHandler = addHandler({
eventType: 'keyup',
handler: noop,
priority: -50,
});
// minimum priority (-50) normalizes to 0
expect(handlersData.keyup[0].has(noop)).toBe(true);
expect(handlersData.keyup[50]).toBe(undefined);
cleanupKeyDownHandler();
cleanupKeyPressHandler();
cleanupKeyUpHandler();
});
it('returns a cleanup function that removes the added handler from handlersData', () => {
addHandler({ eventType: 'keydown', handler: noop })();
addHandler({ eventType: 'keypress', handler: noop, priority: 10 })();
addHandler({ eventType: 'keyup', handler: noop, priority: -50 })();
expect(handlersData.keydown[50]).toBe(undefined);
expect(handlersData.keypress[60]).toBe(undefined);
expect(handlersData.keyup[0]).toBe(undefined);
});
it('resizes the handler arrays on removing a handler when possible to avoid unnecessary iteration', () => {
const cleanupKeyDownHandler = addHandler({
eventType: 'keydown',
handler: noop,
});
expect(handlersData.keydown.length).toBe(51);
const cleanupKeyUpHandler = addHandler({
eventType: 'keyup',
handler: noop,
priority: -50,
});
expect(handlersData.keyup.length).toBe(1);
cleanupKeyDownHandler();
expect(handlersData.keydown.length).toBe(0);
cleanupKeyUpHandler();
expect(handlersData.keyup.length).toBe(0);
});
it('bounds priority to a minimum of -50 and a maximum of 50', () => {
const cleanupKeyDownHandler = addHandler({
eventType: 'keydown',
handler: noop,
priority: -1000,
});
expect(handlersData.keydown.length).toBe(1);
const cleanupKeyPressHandler = addHandler({
eventType: 'keypress',
handler: noop,
priority: Infinity,
});
expect(handlersData.keypress.length).toBe(101);
const cleanupKeyUpHandler = addHandler({
eventType: 'keyup',
handler: noop,
priority: 299,
});
expect(handlersData.keyup.length).toBe(101);
cleanupKeyDownHandler();
expect(handlersData.keydown.length).toBe(0);
cleanupKeyPressHandler();
expect(handlersData.keypress.length).toBe(0);
cleanupKeyUpHandler();
expect(handlersData.keyup.length).toBe(0);
});
});
describe('isEventTargetUsingKeyEvent', () => {
let isUsingKeyEvent = null;
const handleKeyEvent = (event) => {
isUsingKeyEvent = isEventTargetUsingKeyEvent(event.nativeEvent);
};
beforeEach(() => {
isUsingKeyEvent = null;
});
afterEach(cleanup);
it('detects that textual <input>s are using key events triggered on them', async () => {
const user = userEvent.setup();
const { rerender } = render(React.createElement("input", { onKeyDown: handleKeyEvent, type: "text" }));
const input = screen.getByRole('textbox');
expect(isUsingKeyEvent).toBe(null);
await user.type(input, 'A');
expect(isUsingKeyEvent).toBe(true);
isUsingKeyEvent = null;
rerender(React.createElement("input", { onKeyUp: handleKeyEvent, type: "password" }));
expect(isUsingKeyEvent).toBe(null);
await user.type(input, ' ');
expect(isUsingKeyEvent).toBe(true);
});
it('detects that <textarea>s are using key events triggered on them', async () => {
const user = userEvent.setup();
const { rerender } = render(React.createElement("textarea", { onKeyDown: handleKeyEvent }));
const textarea = screen.getByRole('textbox');
expect(isUsingKeyEvent).toBe(null);
await user.type(textarea, 'z');
expect(isUsingKeyEvent).toBe(true);
isUsingKeyEvent = null;
rerender(React.createElement("textarea", { onKeyUp: handleKeyEvent }));
expect(isUsingKeyEvent).toBe(null);
await user.type(textarea, '{Enter}');
expect(isUsingKeyEvent).toBe(true);
});
it('detects that contenteditable elements are using key events triggered on them', async () => {
const user = userEvent.setup();
// const text = 'Lorem ipsum dolor sit amet.';
const { rerender } = render(React.createElement("div", { contentEditable: true, "data-testid": "contenteditable", onKeyDown: handleKeyEvent }));
const element = screen.getByTestId('contenteditable');
expect(isUsingKeyEvent).toBe(null);
await user.type(element, 'z');
expect(isUsingKeyEvent).toBe(true);
isUsingKeyEvent = null;
rerender(React.createElement("div", { contentEditable: true, onKeyUp: handleKeyEvent }));
expect(isUsingKeyEvent).toBe(null);
await user.type(element, '{Enter}');
expect(isUsingKeyEvent).toBe(true);
});
it('detects that non-interactive elements aren’t using key events triggered on them', async () => {
const text = 'Lorem ipsum dolor sit amet.';
render(React.createElement("p", { onKeyDown: handleKeyEvent }, text));
expect(isUsingKeyEvent).toBe(null);
fireEvent.keyDown(screen.getByText(text), { code: 'KeyA', key: 'A' });
expect(isUsingKeyEvent).toBe(false);
});
it('detects that range <input>s use arrow key events', async () => {
const user = userEvent.setup();
render(React.createElement("input", { defaultValue: "1", onKeyDown: handleKeyEvent, type: "range" }));
const input = screen.getByRole('slider');
expect(isUsingKeyEvent).toBe(null);
await user.type(input, 'A');
expect(isUsingKeyEvent).toBe(false);
isUsingKeyEvent = null;
await user.type(input, '{ArrowDown}');
expect(isUsingKeyEvent).toBe(true);
isUsingKeyEvent = null;
expect(isUsingKeyEvent).toBe(null);
await user.type(input, '{ArrowRight}');
expect(isUsingKeyEvent).toBe(true);
});
it('detects that checkbox <input>s use spacebar and enter events', async () => {
const user = userEvent.setup();
render(React.createElement("input", { onKeyDown: handleKeyEvent, type: "checkbox" }));
const input = screen.getByRole('checkbox');
expect(isUsingKeyEvent).toBe(null);
await user.type(input, 'A');
expect(isUsingKeyEvent).toBe(false);
isUsingKeyEvent = null;
await user.type(input, ' ');
expect(isUsingKeyEvent).toBe(true);
});
});
});
});
//# sourceMappingURL=handlers.test.js.map