Use semantic list elements for menu lists and tab lists (#10902)

* Use semantic list elements for pop up menu lists

* Use semantic list elements for tab lists

* Fix tests

* Update snapshot
This commit is contained in:
Michael Telatynski 2023-05-15 14:33:49 +01:00 committed by GitHub
parent 8b7eb8bb1d
commit 296ed2273e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 92 additions and 82 deletions

View file

@ -18,6 +18,9 @@ limitations under the License.
.mx_IconizedContextMenu {
min-width: 146px;
width: max-content;
// override default ul styles
margin: 0;
padding: 0;
.mx_IconizedContextMenu_optionList {
& > * {

View file

@ -121,6 +121,7 @@ export default class TabbedView<T extends string> extends React.Component<IProps
role="tab"
aria-selected={isActive}
aria-controls={id}
element="li"
>
{tabIcon}
<span className="mx_TabbedView_tabLabel_text" id={`${id}_label`}>
@ -166,14 +167,14 @@ export default class TabbedView<T extends string> extends React.Component<IProps
handleUpDown={this.props.tabLocation == TabLocation.LEFT}
>
{({ onKeyDownHandler }) => (
<div
<ul
className="mx_TabbedView_tabLabels"
role="tablist"
aria-orientation={this.props.tabLocation == TabLocation.LEFT ? "vertical" : "horizontal"}
onKeyDown={onKeyDownHandler}
>
{labels}
</div>
</ul>
)}
</RovingTabIndexProvider>
{panel}

View file

@ -126,6 +126,7 @@ export const IconizedContextMenuOption: React.FC<IOptionProps> = ({
}) => {
return (
<MenuItem
element="li"
{...props}
className={classNames(className, {
mx_IconizedContextMenu_item: true,
@ -171,7 +172,9 @@ const IconizedContextMenu: React.FC<React.PropsWithChildren<IProps>> = ({ classN
return (
<ContextMenu chevronFace={ChevronFace.None} {...props}>
<div className={classes}>{children}</div>
<ul role="none" className={classes}>
{children}
</ul>
</ContextMenu>
);
};

View file

@ -5,12 +5,12 @@ exports[`<TabbedView /> renders tabs 1`] = `
<div
class="mx_TabbedView mx_TabbedView_tabsOnLeft"
>
<div
<ul
aria-orientation="vertical"
class="mx_TabbedView_tabLabels"
role="tablist"
>
<div
<li
aria-controls="mx_tabpanel_GENERAL"
aria-selected="true"
class="mx_AccessibleButton mx_TabbedView_tabLabel mx_TabbedView_tabLabel_active"
@ -27,8 +27,8 @@ exports[`<TabbedView /> renders tabs 1`] = `
>
General
</span>
</div>
<div
</li>
<li
aria-controls="mx_tabpanel_LABS"
aria-selected="false"
class="mx_AccessibleButton mx_TabbedView_tabLabel"
@ -45,8 +45,8 @@ exports[`<TabbedView /> renders tabs 1`] = `
>
Labs
</span>
</div>
<div
</li>
<li
aria-controls="mx_tabpanel_SECURITY"
aria-selected="false"
class="mx_AccessibleButton mx_TabbedView_tabLabel"
@ -63,8 +63,8 @@ exports[`<TabbedView /> renders tabs 1`] = `
>
Security
</span>
</div>
</div>
</li>
</ul>
<div
aria-labelledby="mx_tabpanel_GENERAL_label"
class="mx_TabbedView_tabPanel"

View file

@ -99,7 +99,7 @@ describe("MessageContextMenu", () => {
createMenu(event, {}, {}, undefined, room);
expect(document.querySelector('div[aria-label="Pin"]')).toBeFalsy();
expect(document.querySelector('li[aria-label="Pin"]')).toBeFalsy();
});
it("does not show pin option for beacon_info event", () => {
@ -111,7 +111,7 @@ describe("MessageContextMenu", () => {
createMenu(deadBeaconEvent, {}, {}, undefined, room);
expect(document.querySelector('div[aria-label="Pin"]')).toBeFalsy();
expect(document.querySelector('li[aria-label="Pin"]')).toBeFalsy();
});
it("does not show pin option when pinning feature is disabled", () => {
@ -130,7 +130,7 @@ describe("MessageContextMenu", () => {
createMenu(pinnableEvent, {}, {}, undefined, room);
expect(document.querySelector('div[aria-label="Pin"]')).toBeFalsy();
expect(document.querySelector('li[aria-label="Pin"]')).toBeFalsy();
});
it("shows pin option when pinning feature is enabled", () => {
@ -147,7 +147,7 @@ describe("MessageContextMenu", () => {
createMenu(pinnableEvent, {}, {}, undefined, room);
expect(document.querySelector('div[aria-label="Pin"]')).toBeTruthy();
expect(document.querySelector('li[aria-label="Pin"]')).toBeTruthy();
});
it("pins event on pin option click", () => {
@ -171,7 +171,7 @@ describe("MessageContextMenu", () => {
createMenu(pinnableEvent, { onFinished }, {}, undefined, room);
fireEvent.click(document.querySelector('div[aria-label="Pin"]')!);
fireEvent.click(document.querySelector('li[aria-label="Pin"]')!);
// added to account data
expect(client.setRoomAccountData).toHaveBeenCalledWith(roomId, ReadPinsEventId, {
@ -225,7 +225,7 @@ describe("MessageContextMenu", () => {
createMenu(pinnableEvent, {}, {}, undefined, room);
fireEvent.click(document.querySelector('div[aria-label="Unpin"]')!);
fireEvent.click(document.querySelector('li[aria-label="Unpin"]')!);
expect(client.setRoomAccountData).not.toHaveBeenCalled();
@ -244,13 +244,13 @@ describe("MessageContextMenu", () => {
it("allows forwarding a room message", () => {
const eventContent = createMessageEventContent("hello");
createMenuWithContent(eventContent);
expect(document.querySelector('div[aria-label="Forward"]')).toBeTruthy();
expect(document.querySelector('li[aria-label="Forward"]')).toBeTruthy();
});
it("does not allow forwarding a poll", () => {
const eventContent = PollStartEvent.from("why?", ["42"], M_POLL_KIND_DISCLOSED);
createMenuWithContent(eventContent);
expect(document.querySelector('div[aria-label="Forward"]')).toBeFalsy();
expect(document.querySelector('li[aria-label="Forward"]')).toBeFalsy();
});
it("should not allow forwarding a voice broadcast", () => {
@ -261,7 +261,7 @@ describe("MessageContextMenu", () => {
"ABC123",
);
createMenu(broadcastStartEvent);
expect(document.querySelector('div[aria-label="Forward"]')).toBeFalsy();
expect(document.querySelector('li[aria-label="Forward"]')).toBeFalsy();
});
describe("forwarding beacons", () => {
@ -273,7 +273,7 @@ describe("MessageContextMenu", () => {
const beacons = new Map<BeaconIdentifier, Beacon>();
beacons.set(getBeaconInfoIdentifier(deadBeaconEvent), beacon);
createMenu(deadBeaconEvent, {}, {}, beacons);
expect(document.querySelector('div[aria-label="Forward"]')).toBeFalsy();
expect(document.querySelector('li[aria-label="Forward"]')).toBeFalsy();
});
it("does not allow forwarding a beacon that is not live but has a latestLocation", () => {
@ -288,7 +288,7 @@ describe("MessageContextMenu", () => {
const beacons = new Map<BeaconIdentifier, Beacon>();
beacons.set(getBeaconInfoIdentifier(deadBeaconEvent), beacon);
createMenu(deadBeaconEvent, {}, {}, beacons);
expect(document.querySelector('div[aria-label="Forward"]')).toBeFalsy();
expect(document.querySelector('li[aria-label="Forward"]')).toBeFalsy();
});
it("does not allow forwarding a live beacon that does not have a latestLocation", () => {
@ -298,7 +298,7 @@ describe("MessageContextMenu", () => {
const beacons = new Map<BeaconIdentifier, Beacon>();
beacons.set(getBeaconInfoIdentifier(beaconEvent), beacon);
createMenu(beaconEvent, {}, {}, beacons);
expect(document.querySelector('div[aria-label="Forward"]')).toBeFalsy();
expect(document.querySelector('li[aria-label="Forward"]')).toBeFalsy();
});
it("allows forwarding a live beacon that has a location", () => {
@ -313,7 +313,7 @@ describe("MessageContextMenu", () => {
const beacons = new Map<BeaconIdentifier, Beacon>();
beacons.set(getBeaconInfoIdentifier(liveBeaconEvent), beacon);
createMenu(liveBeaconEvent, {}, {}, beacons);
expect(document.querySelector('div[aria-label="Forward"]')).toBeTruthy();
expect(document.querySelector('li[aria-label="Forward"]')).toBeTruthy();
});
it("opens forward dialog with correct event", () => {
@ -330,7 +330,7 @@ describe("MessageContextMenu", () => {
beacons.set(getBeaconInfoIdentifier(liveBeaconEvent), beacon);
createMenu(liveBeaconEvent, {}, {}, beacons);
fireEvent.click(document.querySelector('div[aria-label="Forward"]')!);
fireEvent.click(document.querySelector('li[aria-label="Forward"]')!);
// called with forwardableEvent, not beaconInfo event
expect(dispatchSpy).toHaveBeenCalledWith(
@ -395,7 +395,7 @@ describe("MessageContextMenu", () => {
mocked(getSelectedText).mockReturnValue(text);
createRightClickMenuWithContent(eventContent);
const copyButton = document.querySelector('div[aria-label="Copy"]')!;
const copyButton = document.querySelector('li[aria-label="Copy"]')!;
fireEvent.mouseDown(copyButton);
expect(copyPlaintext).toHaveBeenCalledWith(text);
});
@ -406,7 +406,7 @@ describe("MessageContextMenu", () => {
mocked(getSelectedText).mockReturnValue("");
createRightClickMenuWithContent(eventContent);
const copyButton = document.querySelector('div[aria-label="Copy"]');
const copyButton = document.querySelector('li[aria-label="Copy"]');
expect(copyButton).toBeFalsy();
});
@ -415,7 +415,7 @@ describe("MessageContextMenu", () => {
mocked(canEditContent).mockReturnValue(true);
createRightClickMenuWithContent(eventContent);
const editButton = document.querySelector('div[aria-label="Edit"]');
const editButton = document.querySelector('li[aria-label="Edit"]');
expect(editButton).toBeTruthy();
});
@ -424,7 +424,7 @@ describe("MessageContextMenu", () => {
mocked(canEditContent).mockReturnValue(false);
createRightClickMenuWithContent(eventContent);
const editButton = document.querySelector('div[aria-label="Edit"]');
const editButton = document.querySelector('li[aria-label="Edit"]');
expect(editButton).toBeFalsy();
});
@ -435,7 +435,7 @@ describe("MessageContextMenu", () => {
};
createRightClickMenuWithContent(eventContent, context);
const replyButton = document.querySelector('div[aria-label="Reply"]');
const replyButton = document.querySelector('li[aria-label="Reply"]');
expect(replyButton).toBeTruthy();
});
@ -449,7 +449,7 @@ describe("MessageContextMenu", () => {
unsentMessage.setStatus(EventStatus.QUEUED);
createMenu(unsentMessage, {}, context);
const replyButton = document.querySelector('div[aria-label="Reply"]');
const replyButton = document.querySelector('li[aria-label="Reply"]');
expect(replyButton).toBeFalsy();
});
@ -460,7 +460,7 @@ describe("MessageContextMenu", () => {
};
createRightClickMenuWithContent(eventContent, context);
const reactButton = document.querySelector('div[aria-label="React"]');
const reactButton = document.querySelector('li[aria-label="React"]');
expect(reactButton).toBeTruthy();
});
@ -471,7 +471,7 @@ describe("MessageContextMenu", () => {
};
createRightClickMenuWithContent(eventContent, context);
const reactButton = document.querySelector('div[aria-label="React"]');
const reactButton = document.querySelector('li[aria-label="React"]');
expect(reactButton).toBeFalsy();
});
@ -487,7 +487,7 @@ describe("MessageContextMenu", () => {
};
createMenu(mxEvent, props, context);
const reactButton = document.querySelector('div[aria-label="View in room"]');
const reactButton = document.querySelector('li[aria-label="View in room"]');
expect(reactButton).toBeTruthy();
});
@ -495,7 +495,7 @@ describe("MessageContextMenu", () => {
const eventContent = createMessageEventContent("hello");
createRightClickMenuWithContent(eventContent);
const reactButton = document.querySelector('div[aria-label="View in room"]');
const reactButton = document.querySelector('li[aria-label="View in room"]');
expect(reactButton).toBeFalsy();
});
@ -511,7 +511,7 @@ describe("MessageContextMenu", () => {
createRightClickMenu(mxEvent, context);
const replyInThreadButton = document.querySelector('div[aria-label="Reply in thread"]')!;
const replyInThreadButton = document.querySelector('li[aria-label="Reply in thread"]')!;
fireEvent.click(replyInThreadButton);
expect(dispatcher.dispatch).toHaveBeenCalledWith({

View file

@ -16,8 +16,9 @@ exports[`RoomGeneralContextMenu renders an empty context menu for archived rooms
<div
class="mx_ContextualMenu_chevron_left"
/>
<div
<ul
class="mx_IconizedContextMenu mx_RoomGeneralContextMenu mx_IconizedContextMenu_compact"
role="none"
>
<div
class="mx_IconizedContextMenu_optionList mx_IconizedContextMenu_optionList_notFirst"
@ -25,7 +26,7 @@ exports[`RoomGeneralContextMenu renders an empty context menu for archived rooms
<div
class="mx_IconizedContextMenu_optionList mx_IconizedContextMenu_optionList_notFirst mx_IconizedContextMenu_optionList_red"
>
<div
<li
aria-label="Forget Room"
class="mx_AccessibleButton mx_IconizedContextMenu_option_red mx_IconizedContextMenu_item"
role="menuitem"
@ -39,9 +40,9 @@ exports[`RoomGeneralContextMenu renders an empty context menu for archived rooms
>
Forget Room
</span>
</div>
</li>
</div>
</div>
</ul>
</div>
</div>
</div>
@ -63,8 +64,9 @@ exports[`RoomGeneralContextMenu renders the default context menu 1`] = `
<div
class="mx_ContextualMenu_chevron_left"
/>
<div
<ul
class="mx_IconizedContextMenu mx_RoomGeneralContextMenu mx_IconizedContextMenu_compact"
role="none"
>
<div
class="mx_IconizedContextMenu_optionList mx_IconizedContextMenu_optionList_notFirst"
@ -72,7 +74,7 @@ exports[`RoomGeneralContextMenu renders the default context menu 1`] = `
<div
class="mx_IconizedContextMenu_optionList mx_IconizedContextMenu_optionList_notFirst mx_IconizedContextMenu_optionList_red"
>
<div
<li
aria-label="Forget Room"
class="mx_AccessibleButton mx_IconizedContextMenu_option_red mx_IconizedContextMenu_item"
role="menuitem"
@ -86,9 +88,9 @@ exports[`RoomGeneralContextMenu renders the default context menu 1`] = `
>
Forget Room
</span>
</div>
</li>
</div>
</div>
</ul>
</div>
</div>
</div>

View file

@ -16,8 +16,9 @@ exports[`<SpaceContextMenu /> renders menu correctly 1`] = `
class="mx_ContextualMenu"
role="menu"
>
<div
<ul
class="mx_IconizedContextMenu mx_SpacePanel_contextMenu mx_IconizedContextMenu_compact"
role="none"
>
<div
class="mx_SpacePanel_contextMenu_header"
@ -27,7 +28,7 @@ exports[`<SpaceContextMenu /> renders menu correctly 1`] = `
<div
class="mx_IconizedContextMenu_optionList"
>
<div
<li
aria-label="Space home"
class="mx_AccessibleButton mx_IconizedContextMenu_item focus-visible"
data-focus-visible-added=""
@ -42,8 +43,8 @@ exports[`<SpaceContextMenu /> renders menu correctly 1`] = `
>
Space home
</span>
</div>
<div
</li>
<li
aria-label="Explore rooms"
class="mx_AccessibleButton mx_IconizedContextMenu_item"
role="menuitem"
@ -57,8 +58,8 @@ exports[`<SpaceContextMenu /> renders menu correctly 1`] = `
>
Explore rooms
</span>
</div>
<div
</li>
<li
aria-label="Preferences"
class="mx_AccessibleButton mx_IconizedContextMenu_item"
role="menuitem"
@ -72,8 +73,8 @@ exports[`<SpaceContextMenu /> renders menu correctly 1`] = `
>
Preferences
</span>
</div>
<div
</li>
<li
aria-label="Leave space"
class="mx_AccessibleButton mx_IconizedContextMenu_option_red mx_IconizedContextMenu_item"
data-testid="leave-option"
@ -88,9 +89,9 @@ exports[`<SpaceContextMenu /> renders menu correctly 1`] = `
>
Leave space
</span>
</div>
</li>
</div>
</div>
</ul>
</div>
</div>
</div>

View file

@ -2,7 +2,7 @@
exports[`<RoomSettingsDialog /> Settings tabs renders default tabs correctly 1`] = `
NodeList [
<div
<li
aria-controls="mx_tabpanel_ROOM_GENERAL_TAB"
aria-selected="true"
class="mx_AccessibleButton mx_TabbedView_tabLabel mx_TabbedView_tabLabel_active"
@ -19,8 +19,8 @@ NodeList [
>
General
</span>
</div>,
<div
</li>,
<li
aria-controls="mx_tabpanel_ROOM_SECURITY_TAB"
aria-selected="false"
class="mx_AccessibleButton mx_TabbedView_tabLabel"
@ -37,8 +37,8 @@ NodeList [
>
Security & Privacy
</span>
</div>,
<div
</li>,
<li
aria-controls="mx_tabpanel_ROOM_ROLES_TAB"
aria-selected="false"
class="mx_AccessibleButton mx_TabbedView_tabLabel"
@ -55,8 +55,8 @@ NodeList [
>
Roles & Permissions
</span>
</div>,
<div
</li>,
<li
aria-controls="mx_tabpanel_ROOM_NOTIFICATIONS_TAB"
aria-selected="false"
class="mx_AccessibleButton mx_TabbedView_tabLabel"
@ -73,8 +73,8 @@ NodeList [
>
Notifications
</span>
</div>,
<div
</li>,
<li
aria-controls="mx_tabpanel_ROOM_POLL_HISTORY_TAB"
aria-selected="false"
class="mx_AccessibleButton mx_TabbedView_tabLabel"
@ -91,7 +91,7 @@ NodeList [
>
Poll history
</span>
</div>,
</li>,
]
`;

View file

@ -2,7 +2,7 @@
exports[`<UserSettingsDialog /> renders tabs correctly 1`] = `
NodeList [
<div
<li
aria-controls="mx_tabpanel_USER_GENERAL_TAB"
aria-selected="true"
class="mx_AccessibleButton mx_TabbedView_tabLabel mx_TabbedView_tabLabel_active"
@ -19,8 +19,8 @@ NodeList [
>
General
</span>
</div>,
<div
</li>,
<li
aria-controls="mx_tabpanel_USER_APPEARANCE_TAB"
aria-selected="false"
class="mx_AccessibleButton mx_TabbedView_tabLabel"
@ -37,8 +37,8 @@ NodeList [
>
Appearance
</span>
</div>,
<div
</li>,
<li
aria-controls="mx_tabpanel_USER_NOTIFICATIONS_TAB"
aria-selected="false"
class="mx_AccessibleButton mx_TabbedView_tabLabel"
@ -55,8 +55,8 @@ NodeList [
>
Notifications
</span>
</div>,
<div
</li>,
<li
aria-controls="mx_tabpanel_USER_PREFERENCES_TAB"
aria-selected="false"
class="mx_AccessibleButton mx_TabbedView_tabLabel"
@ -73,8 +73,8 @@ NodeList [
>
Preferences
</span>
</div>,
<div
</li>,
<li
aria-controls="mx_tabpanel_USER_KEYBOARD_TAB"
aria-selected="false"
class="mx_AccessibleButton mx_TabbedView_tabLabel"
@ -91,8 +91,8 @@ NodeList [
>
Keyboard
</span>
</div>,
<div
</li>,
<li
aria-controls="mx_tabpanel_USER_SIDEBAR_TAB"
aria-selected="false"
class="mx_AccessibleButton mx_TabbedView_tabLabel"
@ -109,8 +109,8 @@ NodeList [
>
Sidebar
</span>
</div>,
<div
</li>,
<li
aria-controls="mx_tabpanel_USER_SECURITY_TAB"
aria-selected="false"
class="mx_AccessibleButton mx_TabbedView_tabLabel"
@ -127,8 +127,8 @@ NodeList [
>
Security & Privacy
</span>
</div>,
<div
</li>,
<li
aria-controls="mx_tabpanel_USER_LABS_TAB"
aria-selected="false"
class="mx_AccessibleButton mx_TabbedView_tabLabel"
@ -145,8 +145,8 @@ NodeList [
>
Labs
</span>
</div>,
<div
</li>,
<li
aria-controls="mx_tabpanel_USER_HELP_TAB"
aria-selected="false"
class="mx_AccessibleButton mx_TabbedView_tabLabel"
@ -163,6 +163,6 @@ NodeList [
>
Help & About
</span>
</div>,
</li>,
]
`;