Fix: AccessibleButton does not set disabled attribute (PSF-1055) (#8682)

* remove old styles for pin drop buttons

Signed-off-by: Kerry Archibald <kerrya@element.io>

* fully disable share location button until location is shared

Signed-off-by: Kerry Archibald <kerrya@element.io>

* set disabled on button

Signed-off-by: Kerry Archibald <kerrya@element.io>

* test AccessibleButton disabled

Signed-off-by: Kerry Archibald <kerrya@element.io>

* remove disbaled check in LocationPicker

Signed-off-by: Kerry Archibald <kerrya@element.io>
This commit is contained in:
Kerry 2022-05-27 09:13:50 +02:00 committed by GitHub
parent 0d1bb3bd9a
commit 14cf6275d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 291 additions and 9 deletions

View file

@ -63,14 +63,6 @@ limitations under the License.
&:hover, &:focus {
border-color: $accent;
}
// this style is only during active development
// when lab is enabled but feature not fully implemented
// pin drop option will be disabled
&.mx_AccessibleButton_disabled {
pointer-events: none;
opacity: 0.4;
}
}
.mx_ShareType_option-icon {

View file

@ -85,6 +85,7 @@ export default function AccessibleButton({
const newProps: IAccessibleButtonProps = restProps;
if (disabled) {
newProps["aria-disabled"] = true;
newProps["disabled"] = true;
} else {
if (triggerOnMouseDown) {
newProps.onMouseDown = onClick;

View file

@ -0,0 +1,204 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import AccessibleButton from '../../../../src/components/views/elements/AccessibleButton';
import { Key } from '../../../../src/Keyboard';
import { mockPlatformPeg, unmockPlatformPeg } from '../../../test-utils';
describe('<AccessibleButton />', () => {
const defaultProps = {
onClick: jest.fn(),
children: 'i am a button',
};
const getComponent = (props = {}) =>
mount(<AccessibleButton {...defaultProps} {...props} />);
beforeEach(() => {
mockPlatformPeg();
});
afterAll(() => {
unmockPlatformPeg();
});
const makeKeyboardEvent = (key: string) => ({
key,
stopPropagation: jest.fn(),
preventDefault: jest.fn(),
}) as unknown as KeyboardEvent;
it('renders div with role button by default', () => {
const component = getComponent();
expect(component).toMatchSnapshot();
});
it('renders a button element', () => {
const component = getComponent({ element: 'button' });
expect(component).toMatchSnapshot();
});
it('renders with correct classes when button has kind', () => {
const component = getComponent({
kind: 'primary',
});
expect(component).toMatchSnapshot();
});
it('disables button correctly', () => {
const onClick = jest.fn();
const component = getComponent({
onClick,
disabled: true,
});
expect(component.find('.mx_AccessibleButton').props().disabled).toBeTruthy();
expect(component.find('.mx_AccessibleButton').props()['aria-disabled']).toBeTruthy();
act(() => {
component.simulate('click');
});
expect(onClick).not.toHaveBeenCalled();
act(() => {
const keydownEvent = makeKeyboardEvent(Key.ENTER);
component.simulate('keydown', keydownEvent);
});
expect(onClick).not.toHaveBeenCalled();
});
it('calls onClick handler on button click', () => {
const onClick = jest.fn();
const component = getComponent({
onClick,
});
act(() => {
component.simulate('click');
});
expect(onClick).toHaveBeenCalled();
});
it('calls onClick handler on button mousedown when triggerOnMousedown is passed', () => {
const onClick = jest.fn();
const component = getComponent({
onClick,
triggerOnMouseDown: true,
});
act(() => {
component.simulate('mousedown');
});
expect(onClick).toHaveBeenCalled();
});
describe('handling keyboard events', () => {
it('calls onClick handler on enter keydown', () => {
const onClick = jest.fn();
const component = getComponent({
onClick,
});
const keyboardEvent = makeKeyboardEvent(Key.ENTER);
act(() => {
component.simulate('keydown', keyboardEvent);
});
expect(onClick).toHaveBeenCalled();
act(() => {
component.simulate('keyup', keyboardEvent);
});
// handler only called once on keydown
expect(onClick).toHaveBeenCalledTimes(1);
// called for both keyup and keydown
expect(keyboardEvent.stopPropagation).toHaveBeenCalledTimes(2);
expect(keyboardEvent.preventDefault).toHaveBeenCalledTimes(2);
});
it('calls onClick handler on space keyup', () => {
const onClick = jest.fn();
const component = getComponent({
onClick,
});
const keyboardEvent = makeKeyboardEvent(Key.SPACE);
act(() => {
component.simulate('keydown', keyboardEvent);
});
expect(onClick).not.toHaveBeenCalled();
act(() => {
component.simulate('keyup', keyboardEvent);
});
// handler only called once on keyup
expect(onClick).toHaveBeenCalledTimes(1);
// called for both keyup and keydown
expect(keyboardEvent.stopPropagation).toHaveBeenCalledTimes(2);
expect(keyboardEvent.preventDefault).toHaveBeenCalledTimes(2);
});
it('calls onKeydown/onKeyUp handlers for keys other than space and enter', () => {
const onClick = jest.fn();
const onKeyDown = jest.fn();
const onKeyUp = jest.fn();
const component = getComponent({
onClick,
onKeyDown,
onKeyUp,
});
const keyboardEvent = makeKeyboardEvent(Key.K);
act(() => {
component.simulate('keydown', keyboardEvent);
component.simulate('keyup', keyboardEvent);
});
expect(onClick).not.toHaveBeenCalled();
expect(onKeyDown).toHaveBeenCalled();
expect(onKeyUp).toHaveBeenCalled();
expect(keyboardEvent.stopPropagation).not.toHaveBeenCalled();
expect(keyboardEvent.preventDefault).not.toHaveBeenCalled();
});
it('does nothing on non space/enter key presses when no onKeydown/onKeyUp handlers provided', () => {
const onClick = jest.fn();
const component = getComponent({
onClick,
});
const keyboardEvent = makeKeyboardEvent(Key.K);
act(() => {
component.simulate('keydown', keyboardEvent);
component.simulate('keyup', keyboardEvent);
});
// no onClick call, no problems
expect(onClick).not.toHaveBeenCalled();
expect(keyboardEvent.stopPropagation).not.toHaveBeenCalled();
expect(keyboardEvent.preventDefault).not.toHaveBeenCalled();
});
});
});

View file

@ -0,0 +1,62 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<AccessibleButton /> renders a button element 1`] = `
<AccessibleButton
element="button"
onClick={[MockFunction]}
role="button"
tabIndex={0}
>
<button
className="mx_AccessibleButton"
onClick={[MockFunction]}
onKeyDown={[Function]}
onKeyUp={[Function]}
role="button"
tabIndex={0}
>
i am a button
</button>
</AccessibleButton>
`;
exports[`<AccessibleButton /> renders div with role button by default 1`] = `
<AccessibleButton
element="div"
onClick={[MockFunction]}
role="button"
tabIndex={0}
>
<div
className="mx_AccessibleButton"
onClick={[MockFunction]}
onKeyDown={[Function]}
onKeyUp={[Function]}
role="button"
tabIndex={0}
>
i am a button
</div>
</AccessibleButton>
`;
exports[`<AccessibleButton /> renders with correct classes when button has kind 1`] = `
<AccessibleButton
element="div"
kind="primary"
onClick={[MockFunction]}
role="button"
tabIndex={0}
>
<div
className="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
onClick={[MockFunction]}
onKeyDown={[Function]}
onKeyUp={[Function]}
role="button"
tabIndex={0}
>
i am a button
</div>
</AccessibleButton>
`;

View file

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PollCreateDialog renders a blank poll 1`] = `"<div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-lock-disabled=\\"false\\" role=\\"dialog\\" aria-labelledby=\\"mx_CompoundDialog_title\\" aria-describedby=\\"mx_CompoundDialog_content\\" class=\\"mx_CompoundDialog mx_ScrollableBaseDialog\\"><div class=\\"mx_CompoundDialog_header\\"><h1>Create poll</h1><div aria-label=\\"Close dialog\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_CompoundDialog_cancelButton\\"></div></div><form><div class=\\"mx_CompoundDialog_content\\"><div class=\\"mx_PollCreateDialog\\"><h2>Poll type</h2><div class=\\"mx_Field mx_Field_select\\"><select type=\\"text\\" id=\\"mx_Field_1\\"><option value=\\"org.matrix.msc3381.poll.disclosed\\">Open poll</option><option value=\\"org.matrix.msc3381.poll.undisclosed\\">Closed poll</option></select><label for=\\"mx_Field_1\\"></label></div><p>Voters see results as soon as they have voted</p><h2>What is your poll question or topic?</h2><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"poll-topic-input\\" maxlength=\\"340\\" label=\\"Question or topic\\" placeholder=\\"Write something...\\" type=\\"text\\" value=\\"\\"><label for=\\"poll-topic-input\\">Question or topic</label></div><h2>Create options</h2><div class=\\"mx_PollCreateDialog_option\\"><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"pollcreate_option_0\\" maxlength=\\"340\\" label=\\"Option 1\\" placeholder=\\"Write an option\\" type=\\"text\\" value=\\"\\"><label for=\\"pollcreate_option_0\\">Option 1</label></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_removeOption\\"></div></div><div class=\\"mx_PollCreateDialog_option\\"><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"pollcreate_option_1\\" maxlength=\\"340\\" label=\\"Option 2\\" placeholder=\\"Write an option\\" type=\\"text\\" value=\\"\\"><label for=\\"pollcreate_option_1\\">Option 2</label></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_removeOption\\"></div></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_addOption mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary\\">Add option</div></div></div><div class=\\"mx_CompoundDialog_footer\\"><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline\\">Cancel</div><button type=\\"submit\\" role=\\"button\\" tabindex=\\"0\\" aria-disabled=\\"true\\" class=\\"mx_AccessibleButton mx_Dialog_nonDialogButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary mx_AccessibleButton_disabled\\">Create Poll</button></div></form></div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div>"`;
exports[`PollCreateDialog renders a blank poll 1`] = `"<div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-lock-disabled=\\"false\\" role=\\"dialog\\" aria-labelledby=\\"mx_CompoundDialog_title\\" aria-describedby=\\"mx_CompoundDialog_content\\" class=\\"mx_CompoundDialog mx_ScrollableBaseDialog\\"><div class=\\"mx_CompoundDialog_header\\"><h1>Create poll</h1><div aria-label=\\"Close dialog\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_CompoundDialog_cancelButton\\"></div></div><form><div class=\\"mx_CompoundDialog_content\\"><div class=\\"mx_PollCreateDialog\\"><h2>Poll type</h2><div class=\\"mx_Field mx_Field_select\\"><select type=\\"text\\" id=\\"mx_Field_1\\"><option value=\\"org.matrix.msc3381.poll.disclosed\\">Open poll</option><option value=\\"org.matrix.msc3381.poll.undisclosed\\">Closed poll</option></select><label for=\\"mx_Field_1\\"></label></div><p>Voters see results as soon as they have voted</p><h2>What is your poll question or topic?</h2><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"poll-topic-input\\" maxlength=\\"340\\" label=\\"Question or topic\\" placeholder=\\"Write something...\\" type=\\"text\\" value=\\"\\"><label for=\\"poll-topic-input\\">Question or topic</label></div><h2>Create options</h2><div class=\\"mx_PollCreateDialog_option\\"><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"pollcreate_option_0\\" maxlength=\\"340\\" label=\\"Option 1\\" placeholder=\\"Write an option\\" type=\\"text\\" value=\\"\\"><label for=\\"pollcreate_option_0\\">Option 1</label></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_removeOption\\"></div></div><div class=\\"mx_PollCreateDialog_option\\"><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"pollcreate_option_1\\" maxlength=\\"340\\" label=\\"Option 2\\" placeholder=\\"Write an option\\" type=\\"text\\" value=\\"\\"><label for=\\"pollcreate_option_1\\">Option 2</label></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_removeOption\\"></div></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_addOption mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary\\">Add option</div></div></div><div class=\\"mx_CompoundDialog_footer\\"><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline\\">Cancel</div><button type=\\"submit\\" role=\\"button\\" tabindex=\\"0\\" aria-disabled=\\"true\\" disabled=\\"\\" class=\\"mx_AccessibleButton mx_Dialog_nonDialogButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary mx_AccessibleButton_disabled\\">Create Poll</button></div></form></div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div>"`;
exports[`PollCreateDialog renders a question and some options 1`] = `"<div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-lock-disabled=\\"false\\" role=\\"dialog\\" aria-labelledby=\\"mx_CompoundDialog_title\\" aria-describedby=\\"mx_CompoundDialog_content\\" class=\\"mx_CompoundDialog mx_ScrollableBaseDialog\\"><div class=\\"mx_CompoundDialog_header\\"><h1>Create poll</h1><div aria-label=\\"Close dialog\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_CompoundDialog_cancelButton\\"></div></div><form><div class=\\"mx_CompoundDialog_content\\"><div class=\\"mx_PollCreateDialog\\"><h2>Poll type</h2><div class=\\"mx_Field mx_Field_select\\"><select type=\\"text\\" id=\\"mx_Field_4\\"><option value=\\"org.matrix.msc3381.poll.disclosed\\">Open poll</option><option value=\\"org.matrix.msc3381.poll.undisclosed\\">Closed poll</option></select><label for=\\"mx_Field_4\\"></label></div><p>Voters see results as soon as they have voted</p><h2>What is your poll question or topic?</h2><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"poll-topic-input\\" maxlength=\\"340\\" label=\\"Question or topic\\" placeholder=\\"Write something...\\" type=\\"text\\" value=\\"How many turnips is the optimal number?\\"><label for=\\"poll-topic-input\\">Question or topic</label></div><h2>Create options</h2><div class=\\"mx_PollCreateDialog_option\\"><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"pollcreate_option_0\\" maxlength=\\"340\\" label=\\"Option 1\\" placeholder=\\"Write an option\\" type=\\"text\\" value=\\"As many as my neighbour\\"><label for=\\"pollcreate_option_0\\">Option 1</label></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_removeOption\\"></div></div><div class=\\"mx_PollCreateDialog_option\\"><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"pollcreate_option_1\\" maxlength=\\"340\\" label=\\"Option 2\\" placeholder=\\"Write an option\\" type=\\"text\\" value=\\"The question is meaningless\\"><label for=\\"pollcreate_option_1\\">Option 2</label></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_removeOption\\"></div></div><div class=\\"mx_PollCreateDialog_option\\"><div class=\\"mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft mx_Field_placeholderIsHint\\"><input id=\\"pollcreate_option_2\\" maxlength=\\"340\\" label=\\"Option 3\\" placeholder=\\"Write an option\\" type=\\"text\\" value=\\"Mu\\"><label for=\\"pollcreate_option_2\\">Option 3</label></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_removeOption\\"></div></div><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_PollCreateDialog_addOption mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary\\">Add option</div></div></div><div class=\\"mx_CompoundDialog_footer\\"><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline\\">Cancel</div><button type=\\"submit\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_Dialog_nonDialogButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary\\">Create Poll</button></div></form></div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div>"`;

View file

@ -186,6 +186,28 @@ describe("LocationPicker", () => {
expect(wrapper.find('MemberAvatar').length).toBeTruthy();
});
it('disables submit button until geolocation completes', () => {
const onChoose = jest.fn();
const wrapper = getComponent({ shareType, onChoose });
// submit button is enabled when position is truthy
expect(findByTestId(wrapper, 'location-picker-submit-button').at(0).props().disabled).toBeTruthy();
act(() => {
findByTestId(wrapper, 'location-picker-submit-button').at(0).simulate('click');
});
// nothing happens on button click
expect(onChoose).not.toHaveBeenCalled();
act(() => {
// @ts-ignore
mocked(mockGeolocate).emit('geolocate', mockGeolocationPosition);
wrapper.setProps({});
});
// submit button is enabled when position is truthy
expect(findByTestId(wrapper, 'location-picker-submit-button').at(0).props().disabled).toBeFalsy();
});
it('submits location', () => {
const onChoose = jest.fn();
const wrapper = getComponent({ onChoose, shareType });

View file

@ -89,6 +89,7 @@ exports[`<LocationShareMenu /> with live location disabled goes to labs flag scr
aria-disabled={true}
className="mx_AccessibleButton mx_EnableLiveShare_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary mx_AccessibleButton_disabled"
data-test-id="enable-live-share-submit"
disabled={true}
role="button"
tabIndex={0}
>