diff --git a/packages/react-native-web/src/exports/createElement/__tests__/index-test.js b/packages/react-native-web/src/exports/createElement/__tests__/index-test.js index ea24dee0..a4a917d1 100644 --- a/packages/react-native-web/src/exports/createElement/__tests__/index-test.js +++ b/packages/react-native-web/src/exports/createElement/__tests__/index-test.js @@ -23,37 +23,5 @@ describe('exports/createElement', () => { const { container } = render(createElement(Custom, { accessibilityRole: 'link' })); expect(container.firstChild.nodeName).toBe('DIV'); }); - - const testRole = ({ accessibilityRole, disabled }) => { - [{ key: 'Enter' }, { key: ' ' }].forEach(({ key }) => { - test(`"onClick" is ${disabled ? 'not ' : ''}called when "${key}" key is pressed`, () => { - const onClick = jest.fn(); - const { container } = render( - createElement('span', { accessibilityRole, disabled, onClick }) - ); - const event = document.createEvent('CustomEvent'); - event.initCustomEvent('keydown', true, true); - event.key = key; - container.firstChild.dispatchEvent(event); - expect(onClick).toHaveBeenCalledTimes(disabled ? 0 : 1); - }); - }); - }; - - describe('value is "button" and disabled is "true"', () => { - testRole({ accessibilityRole: 'button', disabled: true }); - }); - - describe('value is "button" and disabled is "false"', () => { - testRole({ accessibilityRole: 'button', disabled: false }); - }); - - describe('value is "menuitem" and disabled is "true"', () => { - testRole({ accessibilityRole: 'menuitem', disabled: true }); - }); - - describe('value is "menuitem" and disabled is "false"', () => { - testRole({ accessibilityRole: 'menuitem', disabled: false }); - }); }); }); diff --git a/packages/react-native-web/src/modules/createDOMProps/__tests__/index-test.js b/packages/react-native-web/src/modules/createDOMProps/__tests__/index-test.js index 729e7633..a3626a84 100644 --- a/packages/react-native-web/src/modules/createDOMProps/__tests__/index-test.js +++ b/packages/react-native-web/src/modules/createDOMProps/__tests__/index-test.js @@ -140,6 +140,106 @@ describe('modules/createDOMProps', () => { }); }); + describe('prop "onClick"', () => { + const callsOnClick = (component, accessibilityRole, disabled = false) => { + const onClick = jest.fn(); + const event = { stopPropagation: jest.fn() }; + const finalProps = createDOMProps(component, { accessibilityRole, disabled, onClick }); + finalProps.onClick(event); + return onClick.mock.calls.length === 1; + }; + + test('is called for various roles', () => { + expect(callsOnClick('div', 'link')).toBe(true); + expect(callsOnClick('div', 'button')).toBe(true); + expect(callsOnClick('div', 'textbox')).toBe(true); + expect(callsOnClick('div', 'menuitem')).toBe(true); + expect(callsOnClick('div', 'bogus')).toBe(true); + expect(callsOnClick('a')).toBe(true); + expect(callsOnClick('button')).toBe(true); + expect(callsOnClick('input')).toBe(true); + expect(callsOnClick('select')).toBe(true); + expect(callsOnClick('textarea')).toBe(true); + expect(callsOnClick('h1')).toBe(true); + }); + + test('is not called when disabled is true', () => { + expect(callsOnClick('div', 'link', true)).toBe(false); + expect(callsOnClick('div', 'button', true)).toBe(false); + expect(callsOnClick('div', 'menuitem', true)).toBe(false); + expect(callsOnClick('a', undefined, true)).toBe(false); + expect(callsOnClick('button', undefined, true)).toBe(false); + expect(callsOnClick('input', undefined, true)).toBe(false); + expect(callsOnClick('select', undefined, true)).toBe(false); + expect(callsOnClick('textarea', undefined, true)).toBe(false); + + expect(callsOnClick('div', 'textbox', true)).toBe(true); + expect(callsOnClick('div', 'bogus', true)).toBe(true); + expect(callsOnClick('h1', undefined, true)).toBe(true); + }); + }); + + describe('prop "onKeyDown"', () => { + const callsOnClick = key => (component, accessibilityRole, disabled = false) => { + const onClick = jest.fn(); + const onKeyDown = jest.fn(); + const event = { key, preventDefault: jest.fn() }; + const finalProps = createDOMProps(component, { + accessibilityRole, + disabled, + onClick, + onKeyDown + }); + finalProps.onKeyDown(event); + // The original onKeyDown should always be called + expect(onKeyDown).toHaveBeenCalled(); + return onClick.mock.calls.length === 1; + }; + + const respondsToEnter = callsOnClick('Enter'); + const respondsToSpace = callsOnClick(' '); + + test('does not emulate "onClick" when disabled', () => { + expect(respondsToEnter('div', 'link', true)).toBe(false); + expect(respondsToEnter('div', 'button', true)).toBe(false); + expect(respondsToEnter('div', 'textbox', true)).toBe(false); + expect(respondsToEnter('div', 'menuitem', true)).toBe(false); + expect(respondsToEnter('div', 'bogus', true)).toBe(false); + }); + + test('does not emulate "onClick" for native elements', () => { + expect(respondsToEnter('a')).toBe(false); + expect(respondsToEnter('button')).toBe(false); + expect(respondsToEnter('input')).toBe(false); + expect(respondsToEnter('select')).toBe(false); + expect(respondsToEnter('textarea')).toBe(false); + expect(respondsToEnter('h1')).toBe(false); + expect(respondsToEnter('div', 'link')).toBe(false); + + expect(respondsToSpace('a')).toBe(false); + expect(respondsToSpace('button')).toBe(false); + expect(respondsToSpace('input')).toBe(false); + expect(respondsToSpace('select')).toBe(false); + expect(respondsToSpace('textarea')).toBe(false); + expect(respondsToSpace('h1')).toBe(false); + expect(respondsToSpace('div', 'link')).toBe(false); + }); + + test('emulates "onClick" for "Enter" for certain roles', () => { + expect(respondsToEnter('div', 'button')).toBe(true); + expect(respondsToEnter('div', 'menuitem')).toBe(true); + expect(respondsToEnter('div', 'textbox')).toBe(false); + expect(respondsToEnter('div', 'bogus')).toBe(false); + }); + + test('emulates "onClick" for "Space" for certain roles', () => { + expect(respondsToSpace('div', 'button')).toBe(true); + expect(respondsToSpace('div', 'menuitem')).toBe(true); + expect(respondsToSpace('div', 'textbox')).toBe(false); + expect(respondsToSpace('div', 'bogus')).toBe(false); + }); + }); + test('prop "accessibilityLabel" becomes "aria-label"', () => { const accessibilityLabel = 'accessibilityLabel'; const props = createProps({ accessibilityLabel }); diff --git a/packages/react-native-web/src/modules/createDOMProps/index.js b/packages/react-native-web/src/modules/createDOMProps/index.js index fbd1ecdf..2b35a354 100644 --- a/packages/react-native-web/src/modules/createDOMProps/index.js +++ b/packages/react-native-web/src/modules/createDOMProps/index.js @@ -225,7 +225,7 @@ const createDOMProps = (component, props) => { // Keyboard accessibility // Button-like roles should trigger 'onClick' if SPACE key is pressed. // Button-like roles should not trigger 'onClick' if they are disabled. - if (domProps['data-focusable']) { + if (isNativeInteractiveElement || role === 'button' || role === 'menuitem') { const onClick = domProps.onClick; if (onClick != null) { if (disabled) {