Skip to content

Commit

Permalink
feat(a11y): add createButtonEventHandlers
Browse files Browse the repository at this point in the history
  • Loading branch information
Morris Allison III committed Sep 25, 2018
1 parent bba60c4 commit 05b93f4
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 1 deletion.
45 changes: 45 additions & 0 deletions packages/utils/src/createButtonEventHandlers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import memoize from "lodash.memoize";

/**
* @typedef {Object} ButtonEventHandlers
* @property {function(MouseEvent, ...any): void} handleClick
* @property {function(KeyboardEvent, ...any): void} handleKeyDown
*/

/**
* @typedef {Object} Options
* @property {boolean} [preventDefault]
*/

const KEYBOARD_INTERACTIONS = [" ", "Enter"];

/**
* Create event handlers for native button behavior for non-button elements
* @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/button_role
*
* @param {function(MouseEvent|KeyboardEvent, ...any): void} [handler] the event handler function
* @param {Options} [options]
* @returns {ButtonEventHandlers}
*/
export default function createButtonEventHandlers(handler, options = {}) {
if (!handler) return {};

const { preventDefault = true } = options;

return {
handleClick: handler,
handleKeyDown(event, ...args) {
const { key } = event;

if (!KEYBOARD_INTERACTIONS.includes(key)) return;
// Prevent space key default scrolling behavior
if (preventDefault) event.preventDefault();

handler(event, ...args);
}
};
}

export function memoizeCreateButtonEventHandlers() {
return memoize(createButtonEventHandlers);
}
106 changes: 106 additions & 0 deletions packages/utils/src/createButtonEventHandlers.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import createButtonEventHandlers, {
memoizeCreateButtonEventHandlers
} from "./createButtonEventHandlers";

describe("utils/createButtonEventHandlers", () => {
describe("createButtonEventHandlers", () => {
it("returns an object of handlers", () => {
const handlers = createButtonEventHandlers(() => {});

expect(handlers).toHaveProperty("handleClick", expect.any(Function));
expect(handlers).toHaveProperty("handleKeyDown", expect.any(Function));
});

describe("when no handler is provided", () => {
it("returns an empty object", () => {
const handlers = createButtonEventHandlers();

expect(handlers).not.toHaveProperty("handleClick");
expect(handlers).not.toHaveProperty("handleKeyDown");
});
});

describe("ButtonEventHandlers", () => {
const handler = jest.fn();
const preventDefault = jest.fn();

let args;
let event;

beforeEach(() => {
event = { preventDefault };
args = [event, 1, "2", true];

jest.clearAllMocks();
});

describe("handleClick", () => {
it("calls the handler with the given arguments", () => {
const result = createButtonEventHandlers(handler);

result.handleClick(...args);

expect(handler).toBeCalledWith(...args);
});
});

describe("handleKeyDown", () => {
it("doesn't call the handler for keys that aren't space or enter", () => {
const result = createButtonEventHandlers(handler);

"abcdefghijklmnopqrstuvwxyz".split("").forEach(key => {
event.key = key;
result.handleKeyDown(...args);
});

expect(handler).not.toBeCalled();
});

[" ", "Enter"].forEach(key => {
describe(`when the "${key}" key is pressed`, () => {
beforeEach(() => {
event.key = key;
});

it("calls the handler", () => {
const result = createButtonEventHandlers(handler);

result.handleKeyDown(...args);
expect(handler).toBeCalledWith(...args);
});

it("prevents the default behavior", () => {
const result = createButtonEventHandlers(handler);

result.handleKeyDown(...args);
expect(preventDefault).toBeCalled();
});

describe("when the `preventDefault` option is disabled", () => {
it("doesn't prevent default behavior", () => {
const result = createButtonEventHandlers(handler, {
preventDefault: false
});

result.handleKeyDown(...args);
expect(preventDefault).not.toBeCalled();
});
});
});
});
});
});
});

describe("memoizeCreateButtonEventHandlers", () => {
it("memoizes createButtonEventHandlers", () => {
const createHandlers = memoizeCreateButtonEventHandlers();
const handler1 = () => {};
const handler2 = () => {};

expect(createHandlers(handler1)).toEqual(createHandlers(handler1));
expect(createHandlers(handler2)).toEqual(createHandlers(handler2));
expect(createHandlers(handler1)).not.toEqual(createHandlers(handler2));
});
});
});
9 changes: 8 additions & 1 deletion packages/utils/src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
/* eslint-disable import/prefer-default-export */
export { default as combineEventHandlers } from "./combineEventHandlers";
export {
default as combineEventHandlers,
memoizeCombineEventHandlers
} from "./combineEventHandlers";
export {
default as createButtonEventHandlers,
memoizeCreateButtonEventHandlers
} from "./createButtonEventHandlers";
export { default as generateId } from "./generateId";

0 comments on commit 05b93f4

Please sign in to comment.