Iterate landmarks around the app in order to improve a11y (#12064)

* Iterate landmarks around the app in order to improve a11y

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add missing aria-label

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update snapshots

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* i18n

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update screenshots which have changed a fraction due to default heading margins being different in different landmarks

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2023-12-20 15:32:24 +00:00 committed by GitHub
parent af31965866
commit 0a881e2123
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 68 additions and 40 deletions

View file

@ -25,7 +25,7 @@ test.describe("UserView", () => {
test("should render the user view as expected", async ({ page, homeserver, user, bot }) => {
await page.goto(`/#/user/${bot.credentials.userId}`);
const rightPanel = page.getByRole("complementary");
const rightPanel = page.locator("#mx_RightPanel");
await expect(rightPanel.getByRole("heading", { name: bot.credentials.displayName, exact: true })).toBeVisible();
await expect(rightPanel.getByText("1 session")).toBeVisible();
await expect(rightPanel).toMatchScreenshot("user-info.png", {

View file

@ -90,11 +90,11 @@ export class Settings {
}
/**
* Open room settings (via room menu), returns a locator to the dialog
* Open room settings (via room header menu), returns a locator to the dialog
* @param tab the name of the tab to switch to after opening, optional.
*/
public async openRoomSettings(tab?: string): Promise<Locator> {
await this.page.getByRole("main").getByRole("button", { name: "Room options", exact: true }).click();
await this.page.getByRole("banner").getByRole("button", { name: "Room options", exact: true }).click();
await this.page.locator(".mx_RoomTile_contextMenu").getByRole("menuitem", { name: "Settings" }).click();
if (tab) await this.switchTab(tab);
return this.page.locator(".mx_Dialog").filter({ has: this.page.locator(".mx_RoomSettingsDialog") });

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 563 KiB

After

Width:  |  Height:  |  Size: 563 KiB

View file

@ -315,6 +315,8 @@ export default class LeftPanel extends React.Component<IProps, IState> {
if (this.state.showBreadcrumbs === BreadcrumbsMode.Legacy && !this.props.isMinimized) {
return (
<IndicatorScrollbar
role="navigation"
aria-label={_t("a11y|recent_rooms")}
className="mx_LeftPanel_breadcrumbsContainer mx_AutoHideScrollbar"
verticalScrollsHorizontally={true}
>
@ -356,6 +358,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
onFocus={this.onFocus}
onBlur={this.onBlur}
onKeyDown={this.onKeyDown}
role="search"
>
<RoomSearch isMinimized={this.props.isMinimized} />
@ -397,7 +400,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
selected={this.props.pageType === PageType.HomePage}
minimized={this.props.isMinimized}
/>
<div className="mx_LeftPanel_roomListWrapper">
<nav className="mx_LeftPanel_roomListWrapper" aria-label={_t("common|rooms")}>
<div
className={roomListClasses}
ref={this.listContainerRef}
@ -407,7 +410,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
>
{roomList}
</div>
</div>
</nav>
</div>
</div>
);

View file

@ -683,7 +683,7 @@ class LoggedInView extends React.Component<IProps, IState> {
<div className={bodyClasses}>
<div className="mx_LeftPanel_outerWrapper">
<LeftPanelLiveShareWarning isMinimized={this.props.collapseLhs || false} />
<nav className="mx_LeftPanel_wrapper">
<div className="mx_LeftPanel_wrapper">
<BackdropPanel blurMultiplier={0.5} backgroundImage={this.state.backgroundImage} />
<SpacePanel />
<BackdropPanel backgroundImage={this.state.backgroundImage} />
@ -698,7 +698,7 @@ class LoggedInView extends React.Component<IProps, IState> {
resizeNotifier={this.props.resizeNotifier}
/>
</div>
</nav>
</div>
</div>
<ResizeHandle passRef={this.resizeHandler} id="lp-resizer" />
<div className="mx_RoomView_wrapper">{pageElement}</div>

View file

@ -406,7 +406,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private unmounted = false;
private permalinkCreators: Record<string, RoomPermalinkCreator> = {};
private roomView = createRef<HTMLElement>();
private roomView = createRef<HTMLDivElement>();
private searchResultsPanel = createRef<ScrollPanel>();
private messagePanel: TimelinePanel | null = null;
private roomViewBody = createRef<HTMLDivElement>();
@ -2302,7 +2302,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// if statusBar does not exist then statusBarArea is blank and takes up unnecessary space on the screen
// show statusBarArea only if statusBar is present
const statusBarArea = statusBar && (
<div className={statusBarAreaClass}>
<div role="region" className={statusBarAreaClass} aria-label={_t("a11y|room_status_bar")}>
<div className="mx_RoomView_statusAreaBox">
<div className="mx_RoomView_statusAreaBox_line" />
{statusBar}
@ -2528,13 +2528,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
<Measured sensor={this.roomViewBody.current} onMeasurement={this.onMeasurement} />
)}
{auxPanel}
<div className={timelineClasses}>
<main className={timelineClasses}>
<FileDropTarget parent={this.roomView.current} onFileDrop={this.onFileDrop} />
{topUnreadMessagesBar}
{jumpToBottom}
{messagePanel}
{searchResultsPanel}
</div>
</main>
{statusBarArea}
{previewBar}
{messageComposer}
@ -2550,6 +2550,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
userId={this.context.client.getSafeUserId()}
resizeNotifier={this.props.resizeNotifier}
showApps={true}
role="main"
/>
{previewBar}
</>
@ -2563,6 +2564,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
room={this.state.room}
resizing={this.state.resizing}
waitForCall={isVideoRoom(this.state.room)}
role="main"
/>
{previewBar}
</>
@ -2603,7 +2605,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
return (
<RoomContext.Provider value={this.state}>
<main
<div
className={mainClasses}
ref={this.roomView}
onKeyDown={this.onReactKeyDown}
@ -2655,7 +2657,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
</div>
</MainSplit>
</ErrorBoundary>
</main>
</div>
</RoomContext.Provider>
);
}

View file

@ -180,11 +180,11 @@ const EmptyThread: React.FC<EmptyThreadIProps> = ({ hasThreads, filterOption, sh
}
return (
<aside className="mx_ThreadPanel_empty">
<div className="mx_ThreadPanel_empty">
<div className="mx_ThreadPanel_largeIcon" />
<h2>{_t("threads|empty_heading")}</h2>
{body}
</aside>
</div>
);
};

View file

@ -362,7 +362,12 @@ class EmojiPicker extends React.Component<IProps, IState> {
{({ onKeyDownHandler }) => {
let heightBefore = 0;
return (
<div className="mx_EmojiPicker" data-testid="mx_EmojiPicker" onKeyDown={onKeyDownHandler}>
<section
className="mx_EmojiPicker"
data-testid="mx_EmojiPicker"
onKeyDown={onKeyDownHandler}
aria-label={_t("a11y|emoji_picker")}
>
<Header categories={this.categories} onAnchorClick={this.scrollToCategory} />
<Search
query={this.state.filter}
@ -407,7 +412,7 @@ class EmojiPicker extends React.Component<IProps, IState> {
selectedEmojis={this.props.selectedEmojis}
/>
)}
</div>
</section>
);
}}
</RovingTabIndexProvider>

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import React, { AriaRole } from "react";
import classNames from "classnames";
import { Resizable, Size } from "re-resizable";
import { Room } from "matrix-js-sdk/src/matrix";
@ -42,6 +42,7 @@ interface IProps {
resizeNotifier: ResizeNotifier;
showApps?: boolean; // Should apps be rendered
maxHeight: number;
role?: AriaRole;
}
interface IState {
@ -294,7 +295,7 @@ export default class AppsDrawer extends React.Component<IProps, IState> {
}
return (
<div className={classes}>
<div role={this.props.role} className={classes}>
{drawer}
{spinner}
</div>

View file

@ -65,7 +65,7 @@ export default class AuxPanel extends React.Component<IProps> {
}
return (
<AutoHideScrollbar className="mx_AuxPanel">
<AutoHideScrollbar role="region" className="mx_AuxPanel">
{this.props.children}
{appsDrawer}
{callView}

View file

@ -602,6 +602,8 @@ export class MessageComposer extends React.Component<IProps, IState> {
className={classes}
ref={this.ref}
aria-describedby={this.state.recordingTimeLeftSeconds ? this.tooltipId : undefined}
role="region"
aria-label={_t("a11y|message_composer")}
>
{recordingTooltip}
<div className="mx_MessageComposer_wrapper">

View file

@ -182,7 +182,7 @@ export default function RoomHeader({
)}
</Box>
</button>
<Flex as="nav" align="center" gap="var(--cpd-space-2x)">
<Flex align="center" gap="var(--cpd-space-2x)">
{additionalButtons?.map((props) => {
const label = props.label();

View file

@ -409,7 +409,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
}
return (
<div className="mx_RoomListHeader">
<aside className="mx_RoomListHeader" aria-label={_t("room|context_menu|title")}>
{contextMenuButton}
{pendingActionSummary ? (
<TooltipTarget label={pendingActionSummary}>
@ -427,7 +427,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
)}
{contextMenu}
</div>
</aside>
);
};

View file

@ -720,7 +720,7 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
);
return (
<div className={classes}>
<div role="complementary" className={classes}>
<div className="mx_RoomPreviewBar_message">
{titleElement}
{subTitleElements}

View file

@ -364,10 +364,11 @@ const SpacePanel: React.FC = () => {
onDragEndHandler();
}}
>
<div
<nav
className={classNames("mx_SpacePanel", { collapsed: isPanelCollapsed })}
onKeyDown={onKeyDownHandler}
ref={ref}
aria-label={_t("common|spaces")}
>
<UserMenu isPanelCollapsed={isPanelCollapsed}>
<AccessibleTooltipButton
@ -406,7 +407,7 @@ const SpacePanel: React.FC = () => {
</Droppable>
<QuickSettingsButton isPanelCollapsed={isPanelCollapsed} />
</div>
</nav>
</DragDropContext>
)}
</RovingTabIndexProvider>

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { FC, ReactNode, useState, useContext, useEffect, useMemo, useRef, useCallback } from "react";
import React, { FC, ReactNode, useState, useContext, useEffect, useMemo, useRef, useCallback, AriaRole } from "react";
import classNames from "classnames";
import { logger } from "matrix-js-sdk/src/logger";
import { defer, IDeferred } from "matrix-js-sdk/src/utils";
@ -297,9 +297,10 @@ interface StartCallViewProps {
resizing: boolean;
call: Call | null;
setStartingCall: (value: boolean) => void;
role?: AriaRole;
}
const StartCallView: FC<StartCallViewProps> = ({ room, resizing, call, setStartingCall }) => {
const StartCallView: FC<StartCallViewProps> = ({ room, resizing, call, setStartingCall, role }) => {
const cli = useContext(MatrixClientContext);
// Since connection has to be split across two different callbacks, we
@ -348,7 +349,7 @@ const StartCallView: FC<StartCallViewProps> = ({ room, resizing, call, setStarti
}, [call, connectDeferred]);
return (
<div className="mx_CallView">
<div className="mx_CallView" role={role}>
{connected ? null : <Lobby room={room} connect={connect} />}
{call !== null && (
<AppTile
@ -369,9 +370,10 @@ interface JoinCallViewProps {
room: Room;
resizing: boolean;
call: Call;
role?: AriaRole;
}
const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call }) => {
const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, role }) => {
const cli = useContext(MatrixClientContext);
const connected = isConnected(useConnectionState(call));
const members = useParticipatingMembers(call);
@ -415,7 +417,7 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call }) => {
}
return (
<div className="mx_CallView">
<div className="mx_CallView" role={role}>
{lobby}
{/* We render the widget even if we're disconnected, so it stays loaded */}
<AppTile
@ -439,16 +441,19 @@ interface CallViewProps {
* button will create a call if there isn't already one.
*/
waitForCall: boolean;
role?: AriaRole;
}
export const CallView: FC<CallViewProps> = ({ room, resizing, waitForCall }) => {
export const CallView: FC<CallViewProps> = ({ room, resizing, waitForCall, role }) => {
const call = useCall(room.roomId);
const [startingCall, setStartingCall] = useState(false);
if (call === null || startingCall) {
if (waitForCall) return null;
return <StartCallView room={room} resizing={resizing} call={call} setStartingCall={setStartingCall} />;
return (
<StartCallView room={room} resizing={resizing} call={call} setStartingCall={setStartingCall} role={role} />
);
} else {
return <JoinCallView room={room} resizing={resizing} call={call} />;
return <JoinCallView room={room} resizing={resizing} call={call} role={role} />;
}
};

View file

@ -1,6 +1,8 @@
{
"a11y": {
"emoji_picker": "Emoji picker",
"jump_first_invite": "Jump to first invite.",
"message_composer": "Message composer",
"n_unread_messages": {
"one": "1 unread message.",
"other": "%(count)s unread messages."
@ -9,7 +11,9 @@
"one": "1 unread mention.",
"other": "%(count)s unread messages including mentions."
},
"recent_rooms": "Recent rooms",
"room_name": "Room %(name)s",
"room_status_bar": "Room status bar",
"unread_messages": "Unread messages.",
"user_menu": "User menu"
},

View file

@ -164,7 +164,7 @@ export default class HTMLExporter extends Exporter {
<title>${_t("export_chat|html_title")}</title>
</head>
<body style="height: 100vh;">
<section
<div
id="matrixchat"
style="height: 100%; overflow: auto"
class="notranslate"
@ -237,7 +237,7 @@ export default class HTMLExporter extends Exporter {
</main>
</div>
</div>
</section>
</div>
<div id="snackbar"/>
</body>
</html>`;

View file

@ -373,7 +373,9 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
</div>
</div>
<div
aria-label="Message composer"
class="mx_MessageComposer"
role="region"
>
<div
class="mx_MessageComposer_wrapper"
@ -614,7 +616,9 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
</div>
</div>
<div
aria-label="Message composer"
class="mx_MessageComposer"
role="region"
>
<div
class="mx_MessageComposer_wrapper"
@ -740,6 +744,7 @@ exports[`RoomView should show error view if failed to look up room alias 1`] = `
>
<div
class="mx_RoomPreviewBar mx_RoomPreviewBar_RoomNotFound mx_RoomPreviewBar_dialog"
role="complementary"
>
<div
class="mx_RoomPreviewBar_message"

View file

@ -39,7 +39,7 @@ exports[`RoomHeader does not show the face pile for DMs 1`] = `
</div>
</div>
</button>
<nav
<div
class="mx_Flex"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x);"
>
@ -75,7 +75,7 @@ exports[`RoomHeader does not show the face pile for DMs 1`] = `
>
<div />
</button>
</nav>
</div>
</header>
</DocumentFragment>
`;

View file

@ -13,7 +13,7 @@ exports[`HTMLExport should export 1`] = `
<title>Exported Data</title>
</head>
<body style="height: 100vh;">
<section
<div
id="matrixchat"
style="height: 100%; overflow: auto"
class="notranslate"
@ -82,7 +82,7 @@ exports[`HTMLExport should export 1`] = `
</main>
</div>
</div>
</section>
</div>
<div id="snackbar"/>
</body>
</html>"