Enable jsx-a11y/click-events-have-key-events eslint rule (#10362)

* enable "jsx-a11y/alt-text" lint rule

* enable "jsx-a11y/label-has-associated-control"

* make Spoilers keyboard accessible

* make invite reason keyboard accessible

* make invite suggestions keyboard accessible

* make avatar upload in space basic settings keyboard accessible

* ignore jsx-a11y/click-events-have-key-events issues in tests

* Update test expectation

---------

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Kerry 2023-08-09 18:27:31 +12:00 committed by GitHub
parent fe907ed66a
commit e0d498e338
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 28 additions and 10 deletions

View file

@ -173,7 +173,7 @@ describe("Composer", () => {
cy.findByRole("textbox").type("this is the spoiler text "); cy.findByRole("textbox").type("this is the spoiler text ");
cy.findByRole("button", { name: "Send message" }).click(); cy.findByRole("button", { name: "Send message" }).click();
// Check that a spoiler item has appeared in the timeline and contains the spoiler command text // Check that a spoiler item has appeared in the timeline and contains the spoiler command text
cy.get("span.mx_EventTile_spoiler").should("exist"); cy.get("button.mx_EventTile_spoiler").should("exist");
cy.findByText("this is the spoiler text").should("exist"); cy.findByText("this is the spoiler text").should("exist");
}); });
}); });

View file

@ -809,6 +809,16 @@ $left-gutter: 64px;
.mx_EventTile_spoiler { .mx_EventTile_spoiler {
cursor: pointer; cursor: pointer;
// clear button styles
appearance: none;
background: none;
border: none;
padding: 0;
margin: 0;
font-size: inherit;
font-family: inherit;
line-height: inherit;
.mx_EventTile_spoiler_reason { .mx_EventTile_spoiler_reason {
color: $event-timestamp-color; color: $event-timestamp-color;
font-size: $font-11px; font-size: $font-11px;
@ -821,6 +831,7 @@ $left-gutter: 64px;
&.visible > .mx_EventTile_spoiler_content { &.visible > .mx_EventTile_spoiler_content {
filter: none; filter: none;
user-select: auto;
} }
} }

View file

@ -274,7 +274,7 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
: this.highlightName(userIdentifier || this.props.member.userId); : this.highlightName(userIdentifier || this.props.member.userId);
return ( return (
<div className="mx_InviteDialog_tile mx_InviteDialog_tile--room" onClick={this.onClick}> <AccessibleButton className="mx_InviteDialog_tile mx_InviteDialog_tile--room" onClick={this.onClick}>
{stackedAvatar} {stackedAvatar}
<span className="mx_InviteDialog_tile_nameStack"> <span className="mx_InviteDialog_tile_nameStack">
<div className="mx_InviteDialog_tile_nameStack_name"> <div className="mx_InviteDialog_tile_nameStack_name">
@ -283,7 +283,7 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
<div className="mx_InviteDialog_tile_nameStack_userId">{caption}</div> <div className="mx_InviteDialog_tile_nameStack_userId">{caption}</div>
</span> </span>
{timestamp} {timestamp}
</div> </AccessibleButton>
); );
} }
} }

View file

@ -19,6 +19,7 @@ import React from "react";
import { sanitizedHtmlNode } from "../../../HtmlUtils"; import { sanitizedHtmlNode } from "../../../HtmlUtils";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import AccessibleButton from "./AccessibleButton";
interface IProps { interface IProps {
reason: string; reason: string;
@ -56,9 +57,9 @@ export default class InviteReason extends React.PureComponent<IProps, IState> {
<div className="mx_InviteReason_reason"> <div className="mx_InviteReason_reason">
{this.props.htmlReason ? sanitizedHtmlNode(this.props.htmlReason) : this.props.reason} {this.props.htmlReason ? sanitizedHtmlNode(this.props.htmlReason) : this.props.reason}
</div> </div>
<div className="mx_InviteReason_view" onClick={this.onViewClick}> <AccessibleButton kind="link_inline" className="mx_InviteReason_view" onClick={this.onViewClick}>
{_t("View message")} {_t("View message")}
</div> </AccessibleButton>
</div> </div>
); );
} }

View file

@ -50,7 +50,7 @@ export default class Spoiler extends React.Component<IProps, IState> {
// as such, we pass the this.props.contentHtml instead and then set the raw // as such, we pass the this.props.contentHtml instead and then set the raw
// HTML content. This is secure as the contents have already been parsed previously // HTML content. This is secure as the contents have already been parsed previously
return ( return (
<span <button
className={"mx_EventTile_spoiler" + (this.state.visible ? " visible" : "")} className={"mx_EventTile_spoiler" + (this.state.visible ? " visible" : "")}
onClick={this.toggleVisible} onClick={this.toggleVisible}
> >
@ -60,7 +60,7 @@ export default class Spoiler extends React.Component<IProps, IState> {
className="mx_EventTile_spoiler_content" className="mx_EventTile_spoiler_content"
dangerouslySetInnerHTML={{ __html: this.props.contentHtml }} dangerouslySetInnerHTML={{ __html: this.props.contentHtml }}
/> />
</span> </button>
); );
} }
} }

View file

@ -76,7 +76,11 @@ export const SpaceAvatar: React.FC<Pick<IProps, "avatarUrl" | "avatarDisabled" |
} else { } else {
avatarSection = ( avatarSection = (
<React.Fragment> <React.Fragment>
<div className="mx_SpaceBasicSettings_avatar" onClick={() => avatarUploadRef.current?.click()} /> <AccessibleButton
className="mx_SpaceBasicSettings_avatar"
onClick={() => avatarUploadRef.current?.click()}
alt=""
/>
<AccessibleButton <AccessibleButton
onClick={() => avatarUploadRef.current?.click()} onClick={() => avatarUploadRef.current?.click()}
kind="link" kind="link"

View file

@ -92,6 +92,7 @@ describe("PictureInPictureDragger", () => {
<PictureInPictureDragger draggable={true}> <PictureInPictureDragger draggable={true}>
{[ {[
({ onStartMoving }) => ( ({ onStartMoving }) => (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
<div onMouseDown={onStartMoving} onClick={clickSpy}> <div onMouseDown={onStartMoving} onClick={clickSpy}>
Hello Hello
</div> </div>

View file

@ -60,6 +60,7 @@ describe("<Pill>", () => {
} as PillProps; } as PillProps;
// wrap Pill with a div to allow testing of event bubbling // wrap Pill with a div to allow testing of event bubbling
renderResult = render( renderResult = render(
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
<div onClick={pillParentClickHandler}> <div onClick={pillParentClickHandler}>
<Pill {...withDefault} /> <Pill {...withDefault} />
</div>, </div>,

View file

@ -293,10 +293,10 @@ describe("<TextualBody />", () => {
expect(content).toContainHTML( expect(content).toContainHTML(
'<span class="mx_EventTile_body markdown-body" dir="auto">' + '<span class="mx_EventTile_body markdown-body" dir="auto">' +
"Hey <span>" + "Hey <span>" +
'<span class="mx_EventTile_spoiler">' + '<button class="mx_EventTile_spoiler">' +
'<span class="mx_EventTile_spoiler_reason">(movie)</span>&nbsp;' + '<span class="mx_EventTile_spoiler_reason">(movie)</span>&nbsp;' +
'<span class="mx_EventTile_spoiler_content"><span>the movie was awesome</span></span>' + '<span class="mx_EventTile_spoiler_content"><span>the movie was awesome</span></span>' +
"</span></span></span>", "</span></button></span>",
); );
}); });