{}} role="submit" />
-
- );
-
- expect($button)
- .toMatchSnapshot();
- });
- });
-
- describe(`calls onClick`, () => {
- test(`on ENTER keyup`, () => {
- const onClickHandler = sinon.stub();
-
- const $button = shallow(
-
-
-
- );
-
- $button.find('[data-div]').simulate('keyup', {
- keyCode: keyCodes.ENTER
- });
-
- sinon.assert.calledOnce(onClickHandler);
- });
-
- test(`on SPACE keyup`, () => {
- const onClickHandler = sinon.stub();
-
- const $button = shallow(
-
-
-
- );
-
- $button.find('[data-div]').simulate('keyup', {
- keyCode: keyCodes.SPACE
- });
-
- sinon.assert.calledOnce(onClickHandler);
- });
- });
-
- describe(`child's props`, () => {
- test(`onKeyUp handler is called`, () => {
- const onKeyUpHandler = sinon.stub();
-
- const $button = shallow(
-
-
-
- );
-
- $button.find('[data-div]').simulate('keyup', {
- keyCode: 0,
- });
-
- sinon.assert.calledOnce(onKeyUpHandler);
- });
-
- test(`onKeyDown handler is called`, () => {
- const onKeyDownHandler = sinon.stub();
-
- const $button = shallow(
-
-
-
- );
-
- $button.find('[data-div]').simulate('keydown', {
- keyCode: 0,
- });
-
- sinon.assert.calledOnce(onKeyDownHandler);
- });
- });
-});
diff --git a/src/components/accessibility/keyboard_accessible.test.tsx b/src/components/accessibility/keyboard_accessible.test.tsx
new file mode 100644
index 00000000000..4484cf15108
--- /dev/null
+++ b/src/components/accessibility/keyboard_accessible.test.tsx
@@ -0,0 +1,233 @@
+import React from 'react';
+import {
+ render,
+ shallow,
+} from 'enzyme';
+import { noop } from 'lodash';
+
+import { EuiKeyboardAccessible } from './keyboard_accessible';
+
+import { keyCodes } from '../../services';
+
+describe('EuiKeyboardAccessible', () => {
+ describe('throws an error', () => {
+ // tslint:disable-next-line:no-console
+ const oldConsoleError = console.error;
+ let consoleStub: jest.Mock
;
+
+ beforeEach(() => {
+ // We don't use jest.spyOn() here, because EUI's tests apply a global
+ // console.error() override that throws an exception. For these
+ // tests, we just want to know if console.error() was called.
+
+ // tslint:disable-next-line:no-console
+ console.error = consoleStub = jest.fn();
+ });
+
+ afterEach(() => {
+ // tslint:disable-next-line:no-console
+ console.error = oldConsoleError;
+ });
+
+ test("when there's no child", () => {
+ // @ts-ignore unused var
+ const component = ;
+
+ expect(consoleStub).toBeCalled();
+ expect(consoleStub.mock.calls[0][0]).toMatch(
+ 'needs to wrap an element with which the user interacts.'
+ );
+ });
+
+ test('when the child is a button', () => {
+ // @ts-ignore unused var
+ const component = (
+
+
+
+ );
+
+ expect(consoleStub).toBeCalled();
+ expect(consoleStub.mock.calls[0][0]).toMatch(
+ "doesn't need to be used on a button."
+ );
+ });
+
+ test('when the child is a link with an href', () => {
+ // @ts-ignore unused var
+ const component = (
+
+
+
+ );
+
+ expect(consoleStub).toBeCalled();
+ expect(consoleStub.mock.calls[0][0]).toMatch(
+ "doesn't need to be used on a link if it has a href attribute."
+ );
+ });
+
+ test("when the child doesn't have an onClick prop", () => {
+ // @ts-ignore unused var
+ const component = (
+
+
+
+ );
+
+ expect(consoleStub).toBeCalled();
+ expect(consoleStub.mock.calls[0][0]).toMatch(
+ 'needs to wrap an element which has an onClick prop assigned.'
+ );
+ });
+
+ test("when the child's onClick prop isn't a function", () => {
+ // @ts-ignore unused var
+ const component = (
+
+
+
+ );
+
+ expect(consoleStub).toBeCalled();
+ expect(consoleStub.mock.calls[0][0]).toMatch(
+ "child's onClick prop needs to be a function."
+ );
+ });
+ });
+
+ describe("doesn't throw an error", () => {
+ let oldConsoleError: typeof console.error;
+ let consoleStub: jest.Mock;
+
+ beforeEach(() => {
+ // tslint:disable-next-line:no-console
+ oldConsoleError = console.error;
+ // tslint:disable-next-line:no-console
+ console.error = consoleStub = jest.fn();
+ });
+
+ afterEach(() => {
+ // tslint:disable-next-line:no-console
+ console.error = oldConsoleError;
+ });
+
+ test('when the element is a link without an href', () => {
+ // @ts-ignore unused var
+ const component = (
+
+
+
+ );
+
+ expect(consoleStub).not.toBeCalled();
+ });
+ });
+
+ describe('adds accessibility attributes', () => {
+ test('tabindex and role', () => {
+ const $button = render(
+
+
+
+ );
+
+ expect($button)
+ .toMatchSnapshot();
+ });
+ });
+
+ describe("doesn't override pre-existing accessibility attributes", () => {
+ test('tabindex', () => {
+ const $button = render(
+
+
+
+ );
+
+ expect($button)
+ .toMatchSnapshot();
+ });
+
+ test('role', () => {
+ const $button = render(
+
+
+
+ );
+
+ expect($button)
+ .toMatchSnapshot();
+ });
+ });
+
+ describe('calls onClick', () => {
+ test('on ENTER keyup', () => {
+ const onClickHandler = jest.fn();
+
+ const $button = shallow(
+
+
+
+ );
+
+ $button.find('[data-div]').simulate('keyup', {
+ keyCode: keyCodes.ENTER,
+ });
+
+ expect(onClickHandler).toBeCalled();
+ });
+
+ test('on SPACE keyup', () => {
+ const onClickHandler = jest.fn();
+
+ const $button = shallow(
+
+
+
+ );
+
+ $button.find('[data-div]').simulate('keyup', {
+ keyCode: keyCodes.SPACE,
+ });
+
+ expect(onClickHandler).toBeCalled();
+ });
+ });
+
+ describe("child's props", () => {
+ test('onKeyUp handler is called', () => {
+ const onKeyUpHandler = jest.fn();
+
+ const $button = shallow(
+
+
+
+ );
+
+ $button.find('[data-div]').simulate('keyup', {
+ keyCode: 0,
+ });
+
+ expect(onKeyUpHandler).toBeCalled();
+ });
+
+ test('onKeyDown handler is called', () => {
+ const onKeyDownHandler = jest.fn();
+
+ const $button = shallow(
+
+
+
+ );
+
+ $button.find('[data-div]').simulate('keydown', {
+ keyCode: 0,
+ });
+
+ expect(onKeyDownHandler).toBeCalled();
+ });
+ });
+});
diff --git a/src/components/accessibility/keyboard_accessible.js b/src/components/accessibility/keyboard_accessible.ts
similarity index 87%
rename from src/components/accessibility/keyboard_accessible.js
rename to src/components/accessibility/keyboard_accessible.ts
index 8a001e7fe07..80fdd65f09e 100644
--- a/src/components/accessibility/keyboard_accessible.js
+++ b/src/components/accessibility/keyboard_accessible.ts
@@ -23,12 +23,18 @@
import {
Component,
cloneElement,
+ KeyboardEvent,
+ ReactElement,
} from 'react';
import { keyCodes } from '../../services';
-export class EuiKeyboardAccessible extends Component {
- onKeyDown = e => {
+interface Props {
+ children: ReactElement;
+}
+
+export class EuiKeyboardAccessible extends Component {
+ onKeyDown = (e: KeyboardEvent) => {
// Prevent a scroll from occurring if the user has hit space.
if (e.keyCode === keyCodes.SPACE) {
e.preventDefault();
@@ -39,7 +45,7 @@ export class EuiKeyboardAccessible extends Component {
}
}
- onKeyUp = e => {
+ onKeyUp = (e: KeyboardEvent) => {
// Support keyboard accessibility by emulating mouse click on ENTER or SPACE keypress.
if (e.keyCode === keyCodes.ENTER || e.keyCode === keyCodes.SPACE) {
// Delegate to the click handler on the element.
@@ -51,7 +57,7 @@ export class EuiKeyboardAccessible extends Component {
}
}
- applyKeyboardAccessibility(child) {
+ applyKeyboardAccessibility = (child: ReactElement) => {
// Add attributes required for accessibility unless they are already specified.
const props = {
tabIndex: '0',
@@ -69,7 +75,13 @@ export class EuiKeyboardAccessible extends Component {
}
}
-const keyboardInaccessibleElement = (props, propName, componentName) => {
+// @ts-ignore defining this as a static on EuiKeyboardAccessible breaks the
+// tests
+EuiKeyboardAccessible.propTypes = {
+ children: keyboardInaccessibleElement,
+};
+
+function keyboardInaccessibleElement(props: Props, propName: string, componentName: string) {
const child = props.children;
if (!child) {
@@ -94,8 +106,4 @@ const keyboardInaccessibleElement = (props, propName, componentName) => {
if (typeof child.props.onClick !== 'function') {
throw new Error(`${componentName}'s child's onClick prop needs to be a function.`);
}
-};
-
-EuiKeyboardAccessible.propTypes = {
- children: keyboardInaccessibleElement,
-};
+}
diff --git a/src/components/accessibility/screen_reader.tsx b/src/components/accessibility/screen_reader.tsx
index d9f2fe5e4f5..871ae065657 100644
--- a/src/components/accessibility/screen_reader.tsx
+++ b/src/components/accessibility/screen_reader.tsx
@@ -9,8 +9,8 @@ export const EuiScreenReaderOnly: SFC = ({ children })
const classes = classNames('euiScreenReaderOnly', children.props.className);
const props = ({ ...children.props, ...{
- className: classes,
- } });
+ className: classes,
+ } });
return cloneElement(children, props);
};