ARIA Accessibility improvements (#10675)
* Fix confusing tab indexes in EventTilePreview * Stop using headings inside buttons * Prefer labelledby and describedby over duplicated aria-labels * Improve semantics of tables used in settings * Fix types * Update tests * Fix timestamps
This commit is contained in:
parent
259b5fe253
commit
792a39a39b
21 changed files with 197 additions and 137 deletions
|
@ -23,15 +23,14 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid $input-border-color;
|
border: 1px solid $input-border-color;
|
||||||
font-size: $font-15px;
|
font-size: $font-17px;
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
|
|
||||||
> h3 {
|
> div {
|
||||||
font-weight: $font-semi-bold;
|
margin-top: 4px;
|
||||||
margin: 0 0 4px;
|
font-weight: normal;
|
||||||
}
|
font-size: $font-15px;
|
||||||
|
|
||||||
> span {
|
|
||||||
color: $secondary-content;
|
color: $secondary-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,12 @@ limitations under the License.
|
||||||
.mx_CrossSigningPanel_statusList {
|
.mx_CrossSigningPanel_statusList {
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
|
|
||||||
td {
|
th {
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
td,
|
||||||
|
th {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
&:first-of-type {
|
&:first-of-type {
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
.mx_CryptographyPanel_sessionInfo {
|
.mx_CryptographyPanel_sessionInfo {
|
||||||
padding: 0em;
|
padding: 0em;
|
||||||
border-spacing: 0px;
|
border-spacing: 0px;
|
||||||
|
@ -5,13 +21,15 @@
|
||||||
.mx_CryptographyPanel_sessionInfo > tr {
|
.mx_CryptographyPanel_sessionInfo > tr {
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
padding: 0em;
|
padding: 0em;
|
||||||
}
|
|
||||||
|
|
||||||
.mx_CryptographyPanel_sessionInfo > tr > td {
|
th {
|
||||||
padding-bottom: 0em;
|
text-align: start;
|
||||||
padding-left: 0em;
|
}
|
||||||
padding-right: 1em;
|
|
||||||
padding-top: 0em;
|
td,
|
||||||
|
th {
|
||||||
|
padding: 0 1em 0 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_CryptographyPanel_importExportButtons .mx_AccessibleButton {
|
.mx_CryptographyPanel_importExportButtons .mx_AccessibleButton {
|
||||||
|
|
|
@ -50,7 +50,12 @@ limitations under the License.
|
||||||
.mx_SecureBackupPanel_statusList {
|
.mx_SecureBackupPanel_statusList {
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
|
|
||||||
td {
|
th {
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
td,
|
||||||
|
th {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
&:first-of-type {
|
&:first-of-type {
|
||||||
|
|
|
@ -476,7 +476,7 @@ const SpaceSetupPrivateScope: React.FC<{
|
||||||
onFinished(false);
|
onFinished(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h3>{_t("Just me")}</h3>
|
{_t("Just me")}
|
||||||
<div>{_t("A private space to organise your rooms")}</div>
|
<div>{_t("A private space to organise your rooms")}</div>
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
|
@ -485,7 +485,7 @@ const SpaceSetupPrivateScope: React.FC<{
|
||||||
onFinished(true);
|
onFinished(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h3>{_t("Me and my teammates")}</h3>
|
{_t("Me and my teammates")}
|
||||||
<div>{_t("A private space for you and your teammates")}</div>
|
<div>{_t("A private space for you and your teammates")}</div>
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -128,8 +128,8 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
|
||||||
const event = this.fakeEvent(this.state);
|
const event = this.fakeEvent(this.state);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className} role="presentation">
|
||||||
<EventTile mxEvent={event} layout={this.props.layout} as="div" />
|
<EventTile mxEvent={event} layout={this.props.layout} as="div" hideTimestamp inhibitInteraction />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||||
|
|
||||||
import ToggleSwitch from "./ToggleSwitch";
|
import ToggleSwitch from "./ToggleSwitch";
|
||||||
import { Caption } from "../typography/Caption";
|
import { Caption } from "../typography/Caption";
|
||||||
|
@ -43,18 +44,15 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class LabelledToggleSwitch extends React.PureComponent<IProps> {
|
export default class LabelledToggleSwitch extends React.PureComponent<IProps> {
|
||||||
|
private readonly id = `mx_LabelledToggleSwitch_${randomString(12)}`;
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
// This is a minimal version of a SettingsFlag
|
// This is a minimal version of a SettingsFlag
|
||||||
const { label, caption } = this.props;
|
const { label, caption } = this.props;
|
||||||
let firstPart = (
|
let firstPart = (
|
||||||
<span className="mx_SettingsFlag_label">
|
<span className="mx_SettingsFlag_label">
|
||||||
{label}
|
<div id={this.id}>{label}</div>
|
||||||
{caption && (
|
{caption && <Caption id={`${this.id}_caption`}>{caption}</Caption>}
|
||||||
<>
|
|
||||||
<br />
|
|
||||||
<Caption>{caption}</Caption>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
let secondPart = (
|
let secondPart = (
|
||||||
|
@ -62,15 +60,14 @@ export default class LabelledToggleSwitch extends React.PureComponent<IProps> {
|
||||||
checked={this.props.value}
|
checked={this.props.value}
|
||||||
disabled={this.props.disabled}
|
disabled={this.props.disabled}
|
||||||
onChange={this.props.onChange}
|
onChange={this.props.onChange}
|
||||||
title={this.props.label}
|
|
||||||
tooltip={this.props.tooltip}
|
tooltip={this.props.tooltip}
|
||||||
|
aria-labelledby={this.id}
|
||||||
|
aria-describedby={caption ? `${this.id}_caption` : undefined}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.props.toggleInFront) {
|
if (this.props.toggleInFront) {
|
||||||
const temp = firstPart;
|
[firstPart, secondPart] = [secondPart, firstPart];
|
||||||
firstPart = secondPart;
|
|
||||||
secondPart = temp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const classes = classNames("mx_SettingsFlag", this.props.className, {
|
const classes = classNames("mx_SettingsFlag", this.props.className, {
|
||||||
|
|
|
@ -41,7 +41,7 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Controlled Toggle Switch element, written with Accessibility in mind
|
// Controlled Toggle Switch element, written with Accessibility in mind
|
||||||
export default ({ checked, disabled = false, title, tooltip, onChange, ...props }: IProps): JSX.Element => {
|
export default ({ checked, disabled = false, onChange, ...props }: IProps): JSX.Element => {
|
||||||
const _onClick = (): void => {
|
const _onClick = (): void => {
|
||||||
if (disabled) return;
|
if (disabled) return;
|
||||||
onChange(!checked);
|
onChange(!checked);
|
||||||
|
@ -61,8 +61,6 @@ export default ({ checked, disabled = false, title, tooltip, onChange, ...props
|
||||||
role="switch"
|
role="switch"
|
||||||
aria-checked={checked}
|
aria-checked={checked}
|
||||||
aria-disabled={disabled}
|
aria-disabled={disabled}
|
||||||
title={title}
|
|
||||||
tooltip={tooltip}
|
|
||||||
>
|
>
|
||||||
<div className="mx_ToggleSwitch_ball" />
|
<div className="mx_ToggleSwitch_ball" />
|
||||||
</AccessibleTooltipButton>
|
</AccessibleTooltipButton>
|
||||||
|
|
|
@ -218,6 +218,10 @@ export interface EventTileProps {
|
||||||
// displayed to the current user either because they're
|
// displayed to the current user either because they're
|
||||||
// the author or they are a moderator
|
// the author or they are a moderator
|
||||||
isSeeingThroughMessageHiddenForModeration?: boolean;
|
isSeeingThroughMessageHiddenForModeration?: boolean;
|
||||||
|
|
||||||
|
// The following properties are used by EventTilePreview to disable tab indexes within the event tile
|
||||||
|
hideTimestamp?: boolean;
|
||||||
|
inhibitInteraction?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -1006,7 +1010,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.mxEvent.sender && avatarSize) {
|
if (this.props.mxEvent.sender && avatarSize) {
|
||||||
let member;
|
let member: RoomMember | null = null;
|
||||||
// set member to receiver (target) if it is a 3PID invite
|
// set member to receiver (target) if it is a 3PID invite
|
||||||
// so that the correct avatar is shown as the text is
|
// so that the correct avatar is shown as the text is
|
||||||
// `$target accepted the invitation for $email`
|
// `$target accepted the invitation for $email`
|
||||||
|
@ -1016,7 +1020,9 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||||
member = this.props.mxEvent.sender;
|
member = this.props.mxEvent.sender;
|
||||||
}
|
}
|
||||||
// In the ThreadsList view we use the entire EventTile as a click target to open the thread instead
|
// In the ThreadsList view we use the entire EventTile as a click target to open the thread instead
|
||||||
const viewUserOnClick = ![TimelineRenderingType.ThreadsList, TimelineRenderingType.Notification].includes(
|
const viewUserOnClick =
|
||||||
|
!this.props.inhibitInteraction &&
|
||||||
|
![TimelineRenderingType.ThreadsList, TimelineRenderingType.Notification].includes(
|
||||||
this.context.timelineRenderingType,
|
this.context.timelineRenderingType,
|
||||||
);
|
);
|
||||||
avatar = (
|
avatar = (
|
||||||
|
@ -1064,6 +1070,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||||
|
|
||||||
const showTimestamp =
|
const showTimestamp =
|
||||||
this.props.mxEvent.getTs() &&
|
this.props.mxEvent.getTs() &&
|
||||||
|
!this.props.hideTimestamp &&
|
||||||
(this.props.alwaysShowTimestamps ||
|
(this.props.alwaysShowTimestamps ||
|
||||||
this.props.last ||
|
this.props.last ||
|
||||||
this.state.hover ||
|
this.state.hover ||
|
||||||
|
@ -1101,7 +1108,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const linkedTimestamp = (
|
const linkedTimestamp = !this.props.hideTimestamp ? (
|
||||||
<a
|
<a
|
||||||
href={permalink}
|
href={permalink}
|
||||||
onClick={this.onPermalinkClicked}
|
onClick={this.onPermalinkClicked}
|
||||||
|
@ -1110,7 +1117,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||||
>
|
>
|
||||||
{timestamp}
|
{timestamp}
|
||||||
</a>
|
</a>
|
||||||
);
|
) : null;
|
||||||
|
|
||||||
const useIRCLayout = this.props.layout === Layout.IRC;
|
const useIRCLayout = this.props.layout === Layout.IRC;
|
||||||
const groupTimestamp = !useIRCLayout ? linkedTimestamp : null;
|
const groupTimestamp = !useIRCLayout ? linkedTimestamp : null;
|
||||||
|
|
|
@ -243,13 +243,12 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> {
|
||||||
<details>
|
<details>
|
||||||
<summary>{_t("Advanced")}</summary>
|
<summary>{_t("Advanced")}</summary>
|
||||||
<table className="mx_CrossSigningPanel_statusList">
|
<table className="mx_CrossSigningPanel_statusList">
|
||||||
<tbody>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>{_t("Cross-signing public keys:")}</td>
|
<th scope="row">{_t("Cross-signing public keys:")}</th>
|
||||||
<td>{crossSigningPublicKeysOnDevice ? _t("in memory") : _t("not found")}</td>
|
<td>{crossSigningPublicKeysOnDevice ? _t("in memory") : _t("not found")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{_t("Cross-signing private keys:")}</td>
|
<th scope="row">{_t("Cross-signing private keys:")}</th>
|
||||||
<td>
|
<td>
|
||||||
{crossSigningPrivateKeysInStorage
|
{crossSigningPrivateKeysInStorage
|
||||||
? _t("in secret storage")
|
? _t("in secret storage")
|
||||||
|
@ -257,22 +256,21 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> {
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{_t("Master private key:")}</td>
|
<th scope="row">{_t("Master private key:")}</th>
|
||||||
<td>{masterPrivateKeyCached ? _t("cached locally") : _t("not found locally")}</td>
|
<td>{masterPrivateKeyCached ? _t("cached locally") : _t("not found locally")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{_t("Self signing private key:")}</td>
|
<th scope="row">{_t("Self signing private key:")}</th>
|
||||||
<td>{selfSigningPrivateKeyCached ? _t("cached locally") : _t("not found locally")}</td>
|
<td>{selfSigningPrivateKeyCached ? _t("cached locally") : _t("not found locally")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{_t("User signing private key:")}</td>
|
<th scope="row">{_t("User signing private key:")}</th>
|
||||||
<td>{userSigningPrivateKeyCached ? _t("cached locally") : _t("not found locally")}</td>
|
<td>{userSigningPrivateKeyCached ? _t("cached locally") : _t("not found locally")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{_t("Homeserver feature support:")}</td>
|
<th scope="row">{_t("Homeserver feature support:")}</th>
|
||||||
<td>{homeserverSupportsCrossSigning ? _t("exists") : _t("not found")}</td>
|
<td>{homeserverSupportsCrossSigning ? _t("exists") : _t("not found")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
|
||||||
</table>
|
</table>
|
||||||
</details>
|
</details>
|
||||||
{errorSection}
|
{errorSection}
|
||||||
|
|
|
@ -75,22 +75,20 @@ export default class CryptographyPanel extends React.Component<IProps, IState> {
|
||||||
<div className="mx_SettingsTab_section mx_CryptographyPanel">
|
<div className="mx_SettingsTab_section mx_CryptographyPanel">
|
||||||
<span className="mx_SettingsTab_subheading">{_t("Cryptography")}</span>
|
<span className="mx_SettingsTab_subheading">{_t("Cryptography")}</span>
|
||||||
<table className="mx_SettingsTab_subsectionText mx_CryptographyPanel_sessionInfo">
|
<table className="mx_SettingsTab_subsectionText mx_CryptographyPanel_sessionInfo">
|
||||||
<tbody>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>{_t("Session ID:")}</td>
|
<th scope="row">{_t("Session ID:")}</th>
|
||||||
<td>
|
<td>
|
||||||
<code>{deviceId}</code>
|
<code>{deviceId}</code>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{_t("Session key:")}</td>
|
<th scope="row">{_t("Session key:")}</th>
|
||||||
<td>
|
<td>
|
||||||
<code>
|
<code>
|
||||||
<b>{identityKey}</b>
|
<b>{identityKey}</b>
|
||||||
</code>
|
</code>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
|
||||||
</table>
|
</table>
|
||||||
{importExportButtons}
|
{importExportButtons}
|
||||||
{noSendUnverifiedSetting}
|
{noSendUnverifiedSetting}
|
||||||
|
|
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React, { ReactNode } from "react";
|
||||||
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
|
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
|
||||||
import { TrustInfo } from "matrix-js-sdk/src/crypto/backup";
|
import { TrustInfo } from "matrix-js-sdk/src/crypto/backup";
|
||||||
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
|
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
|
||||||
|
@ -231,9 +231,9 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||||
sessionsRemaining,
|
sessionsRemaining,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
let statusDescription;
|
let statusDescription: JSX.Element;
|
||||||
let extraDetailsTableRows;
|
let extraDetailsTableRows: JSX.Element | undefined;
|
||||||
let extraDetails;
|
let extraDetails: JSX.Element | undefined;
|
||||||
const actions: JSX.Element[] = [];
|
const actions: JSX.Element[] = [];
|
||||||
if (error) {
|
if (error) {
|
||||||
statusDescription = <div className="error">{_t("Unable to load key backup status")}</div>;
|
statusDescription = <div className="error">{_t("Unable to load key backup status")}</div>;
|
||||||
|
@ -267,7 +267,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||||
restoreButtonCaption = _t("Connect this session to Key Backup");
|
restoreButtonCaption = _t("Connect this session to Key Backup");
|
||||||
}
|
}
|
||||||
|
|
||||||
let uploadStatus;
|
let uploadStatus: ReactNode;
|
||||||
if (!MatrixClientPeg.get().getKeyBackupEnabled()) {
|
if (!MatrixClientPeg.get().getKeyBackupEnabled()) {
|
||||||
// No upload status to show when backup disabled.
|
// No upload status to show when backup disabled.
|
||||||
uploadStatus = "";
|
uploadStatus = "";
|
||||||
|
@ -391,11 +391,11 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||||
extraDetailsTableRows = (
|
extraDetailsTableRows = (
|
||||||
<>
|
<>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{_t("Backup version:")}</td>
|
<th scope="row">{_t("Backup version:")}</th>
|
||||||
<td>{backupInfo.version}</td>
|
<td>{backupInfo.version}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{_t("Algorithm:")}</td>
|
<th scope="row">{_t("Algorithm:")}</th>
|
||||||
<td>{backupInfo.algorithm}</td>
|
<td>{backupInfo.algorithm}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</>
|
</>
|
||||||
|
@ -460,7 +460,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let actionRow;
|
let actionRow: JSX.Element | undefined;
|
||||||
if (actions.length) {
|
if (actions.length) {
|
||||||
actionRow = <div className="mx_SecureBackupPanel_buttonRow">{actions}</div>;
|
actionRow = <div className="mx_SecureBackupPanel_buttonRow">{actions}</div>;
|
||||||
}
|
}
|
||||||
|
@ -478,28 +478,26 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||||
<details>
|
<details>
|
||||||
<summary>{_t("Advanced")}</summary>
|
<summary>{_t("Advanced")}</summary>
|
||||||
<table className="mx_SecureBackupPanel_statusList">
|
<table className="mx_SecureBackupPanel_statusList">
|
||||||
<tbody>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>{_t("Backup key stored:")}</td>
|
<th scope="row">{_t("Backup key stored:")}</th>
|
||||||
<td>{backupKeyStored === true ? _t("in secret storage") : _t("not stored")}</td>
|
<td>{backupKeyStored === true ? _t("in secret storage") : _t("not stored")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{_t("Backup key cached:")}</td>
|
<th scope="row">{_t("Backup key cached:")}</th>
|
||||||
<td>
|
<td>
|
||||||
{backupKeyCached ? _t("cached locally") : _t("not found locally")}
|
{backupKeyCached ? _t("cached locally") : _t("not found locally")}
|
||||||
{backupKeyWellFormedText}
|
{backupKeyWellFormedText}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{_t("Secret storage public key:")}</td>
|
<th scope="row">{_t("Secret storage public key:")}</th>
|
||||||
<td>{secretStorageKeyInAccount ? _t("in account data") : _t("not found")}</td>
|
<td>{secretStorageKeyInAccount ? _t("in account data") : _t("not found")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{_t("Secret storage:")}</td>
|
<th scope="row">{_t("Secret storage:")}</th>
|
||||||
<td>{secretStorageReady ? _t("ready") : _t("not ready")}</td>
|
<td>{secretStorageReady ? _t("ready") : _t("not ready")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{extraDetailsTableRows}
|
{extraDetailsTableRows}
|
||||||
</tbody>
|
|
||||||
</table>
|
</table>
|
||||||
{extraDetails}
|
{extraDetails}
|
||||||
</details>
|
</details>
|
||||||
|
|
|
@ -89,8 +89,8 @@ const SpaceCreateMenuType: React.FC<{
|
||||||
}> = ({ title, description, className, onClick }) => {
|
}> = ({ title, description, className, onClick }) => {
|
||||||
return (
|
return (
|
||||||
<AccessibleButton className={classNames("mx_SpaceCreateMenuType", className)} onClick={onClick}>
|
<AccessibleButton className={classNames("mx_SpaceCreateMenuType", className)} onClick={onClick}>
|
||||||
<h3>{title}</h3>
|
{title}
|
||||||
<span>{description}</span>
|
<div>{description}</div>
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -52,7 +52,7 @@ const SpacePublicShare: React.FC<IProps> = ({ space, onFinished }) => {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h3>{_t("Share invite link")}</h3>
|
{_t("Share invite link")}
|
||||||
<span>{copiedText}</span>
|
<span>{copiedText}</span>
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
{space.canInvite(MatrixClientPeg.get()?.getUserId()) && shouldShowComponent(UIComponent.InviteUsers) ? (
|
{space.canInvite(MatrixClientPeg.get()?.getUserId()) && shouldShowComponent(UIComponent.InviteUsers) ? (
|
||||||
|
@ -63,8 +63,8 @@ const SpacePublicShare: React.FC<IProps> = ({ space, onFinished }) => {
|
||||||
showRoomInviteDialog(space.roomId);
|
showRoomInviteDialog(space.roomId);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h3>{_t("Invite people")}</h3>
|
{_t("Invite people")}
|
||||||
<span>{_t("Invite with email or username")}</span>
|
<div>{_t("Invite with email or username")}</div>
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -32,11 +32,9 @@ import DMRoomMap from "../../../src/utils/DMRoomMap";
|
||||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||||
|
|
||||||
// Fake random strings to give a predictable snapshot for checkbox IDs
|
// Fake random strings to give a predictable snapshot for checkbox IDs
|
||||||
jest.mock("matrix-js-sdk/src/randomstring", () => {
|
jest.mock("matrix-js-sdk/src/randomstring", () => ({
|
||||||
return {
|
|
||||||
randomString: () => "abdefghi",
|
randomString: () => "abdefghi",
|
||||||
};
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
describe("SpaceHierarchy", () => {
|
describe("SpaceHierarchy", () => {
|
||||||
describe("showRoom", () => {
|
describe("showRoom", () => {
|
||||||
|
|
|
@ -72,6 +72,11 @@ jest.mock("../../../../src/Modal", () => ({
|
||||||
ModalManagerEvent: { Opened: "opened" },
|
ModalManagerEvent: { Opened: "opened" },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Fake random strings to give a predictable snapshot for IDs
|
||||||
|
jest.mock("matrix-js-sdk/src/randomstring", () => ({
|
||||||
|
randomString: () => "abdefghi",
|
||||||
|
}));
|
||||||
|
|
||||||
describe("<LocationShareMenu />", () => {
|
describe("<LocationShareMenu />", () => {
|
||||||
const userId = "@ernie:server.org";
|
const userId = "@ernie:server.org";
|
||||||
const mockClient = getMockClientWithEventEmitter({
|
const mockClient = getMockClientWithEventEmitter({
|
||||||
|
|
|
@ -24,13 +24,17 @@ exports[`<LocationShareMenu /> with live location disabled goes to labs flag scr
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="mx_SettingsFlag_label"
|
class="mx_SettingsFlag_label"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
id="mx_LabelledToggleSwitch_abdefghi"
|
||||||
>
|
>
|
||||||
Enable live location sharing
|
Enable live location sharing
|
||||||
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
aria-checked="false"
|
aria-checked="false"
|
||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
aria-label="Enable live location sharing"
|
aria-labelledby="mx_LabelledToggleSwitch_abdefghi"
|
||||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||||
role="switch"
|
role="switch"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
|
|
@ -27,8 +27,10 @@ import {
|
||||||
ConditionKind,
|
ConditionKind,
|
||||||
IPushRuleCondition,
|
IPushRuleCondition,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
|
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||||
import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids";
|
import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids";
|
||||||
import { act, fireEvent, getByTestId, render, screen, waitFor, within } from "@testing-library/react";
|
import { act, fireEvent, getByTestId, render, screen, waitFor, within } from "@testing-library/react";
|
||||||
|
import { mocked } from "jest-mock";
|
||||||
|
|
||||||
import Notifications from "../../../../src/components/views/settings/Notifications";
|
import Notifications from "../../../../src/components/views/settings/Notifications";
|
||||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||||
|
@ -41,6 +43,11 @@ jest.mock("matrix-js-sdk/src/logger");
|
||||||
// Avoid indirectly importing any eagerly created stores that would require extra setup
|
// Avoid indirectly importing any eagerly created stores that would require extra setup
|
||||||
jest.mock("../../../../src/Notifier");
|
jest.mock("../../../../src/Notifier");
|
||||||
|
|
||||||
|
// Fake random strings to give a predictable snapshot for IDs
|
||||||
|
jest.mock("matrix-js-sdk/src/randomstring", () => ({
|
||||||
|
randomString: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
const masterRule: IPushRule = {
|
const masterRule: IPushRule = {
|
||||||
actions: [PushRuleActionName.DontNotify],
|
actions: [PushRuleActionName.DontNotify],
|
||||||
conditions: [],
|
conditions: [],
|
||||||
|
@ -271,6 +278,11 @@ describe("<Notifications />", () => {
|
||||||
mockClient.getPushRules.mockResolvedValue(pushRules);
|
mockClient.getPushRules.mockResolvedValue(pushRules);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
let i = 0;
|
||||||
|
mocked(randomString).mockImplementation(() => {
|
||||||
|
return "testid_" + i++;
|
||||||
|
});
|
||||||
|
|
||||||
mockClient.getPushRules.mockClear().mockResolvedValue(pushRules);
|
mockClient.getPushRules.mockClear().mockResolvedValue(pushRules);
|
||||||
mockClient.getPushers.mockClear().mockResolvedValue({ pushers: [] });
|
mockClient.getPushers.mockClear().mockResolvedValue({ pushers: [] });
|
||||||
mockClient.getThreePids.mockClear().mockResolvedValue({ threepids: [] });
|
mockClient.getThreePids.mockClear().mockResolvedValue({ threepids: [] });
|
||||||
|
|
|
@ -11,19 +11,24 @@ exports[`<Notifications /> main notification switches renders only enable notifi
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="mx_SettingsFlag_label"
|
class="mx_SettingsFlag_label"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
id="mx_LabelledToggleSwitch_testid_0"
|
||||||
>
|
>
|
||||||
Enable notifications for this account
|
Enable notifications for this account
|
||||||
<br />
|
</div>
|
||||||
<span
|
<span
|
||||||
class="mx_Caption"
|
class="mx_Caption"
|
||||||
|
id="mx_LabelledToggleSwitch_testid_0_caption"
|
||||||
>
|
>
|
||||||
Turn off to disable notifications on all your devices and sessions
|
Turn off to disable notifications on all your devices and sessions
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
aria-checked="false"
|
aria-checked="false"
|
||||||
|
aria-describedby="mx_LabelledToggleSwitch_testid_0_caption"
|
||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
aria-label="Enable notifications for this account"
|
aria-labelledby="mx_LabelledToggleSwitch_testid_0"
|
||||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||||
role="switch"
|
role="switch"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { mocked } from "jest-mock";
|
import { mocked } from "jest-mock";
|
||||||
|
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||||
import { act, fireEvent, render, RenderResult } from "@testing-library/react";
|
import { act, fireEvent, render, RenderResult } from "@testing-library/react";
|
||||||
import { EventType, MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
import { EventType, MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
||||||
import { GuestAccess, HistoryVisibility, JoinRule } from "matrix-js-sdk/src/@types/partials";
|
import { GuestAccess, HistoryVisibility, JoinRule } from "matrix-js-sdk/src/@types/partials";
|
||||||
|
@ -27,6 +28,11 @@ import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||||
|
|
||||||
const SpaceSettingsVisibilityTab = wrapInMatrixClientContext(_SpaceSettingsVisibilityTab);
|
const SpaceSettingsVisibilityTab = wrapInMatrixClientContext(_SpaceSettingsVisibilityTab);
|
||||||
|
|
||||||
|
// Fake random strings to give a predictable snapshot for IDs
|
||||||
|
jest.mock("matrix-js-sdk/src/randomstring", () => ({
|
||||||
|
randomString: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
|
|
||||||
describe("<SpaceSettingsVisibilityTab />", () => {
|
describe("<SpaceSettingsVisibilityTab />", () => {
|
||||||
|
@ -89,13 +95,16 @@ describe("<SpaceSettingsVisibilityTab />", () => {
|
||||||
const toggleButton = getByTestId("toggle-guest-access-btn")!;
|
const toggleButton = getByTestId("toggle-guest-access-btn")!;
|
||||||
fireEvent.click(toggleButton);
|
fireEvent.click(toggleButton);
|
||||||
};
|
};
|
||||||
const getGuestAccessToggle = ({ container }: RenderResult) =>
|
const getGuestAccessToggle = ({ getByLabelText }: RenderResult) => getByLabelText("Enable guest access");
|
||||||
container.querySelector('[aria-label="Enable guest access"]');
|
const getHistoryVisibilityToggle = ({ getByLabelText }: RenderResult) => getByLabelText("Preview Space");
|
||||||
const getHistoryVisibilityToggle = ({ container }: RenderResult) =>
|
|
||||||
container.querySelector('[aria-label="Preview Space"]');
|
|
||||||
const getErrorMessage = ({ getByTestId }: RenderResult) => getByTestId("space-settings-error")?.textContent;
|
const getErrorMessage = ({ getByTestId }: RenderResult) => getByTestId("space-settings-error")?.textContent;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
let i = 0;
|
||||||
|
mocked(randomString).mockImplementation(() => {
|
||||||
|
return "testid_" + i++;
|
||||||
|
});
|
||||||
|
|
||||||
(mockMatrixClient.sendStateEvent as jest.Mock).mockClear().mockResolvedValue({});
|
(mockMatrixClient.sendStateEvent as jest.Mock).mockClear().mockResolvedValue({});
|
||||||
MatrixClientPeg.get = jest.fn().mockReturnValue(mockMatrixClient);
|
MatrixClientPeg.get = jest.fn().mockReturnValue(mockMatrixClient);
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,7 @@ exports[`<SpaceSettingsVisibilityTab /> for a public space Access renders guest
|
||||||
<div
|
<div
|
||||||
aria-checked="true"
|
aria-checked="true"
|
||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
aria-label="Enable guest access"
|
aria-labelledby="mx_LabelledToggleSwitch_testid_1"
|
||||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
|
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
|
||||||
role="switch"
|
role="switch"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
@ -104,13 +104,17 @@ exports[`<SpaceSettingsVisibilityTab /> renders container 1`] = `
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="mx_SettingsFlag_label"
|
class="mx_SettingsFlag_label"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
id="mx_LabelledToggleSwitch_testid_0"
|
||||||
>
|
>
|
||||||
Preview Space
|
Preview Space
|
||||||
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
aria-checked="true"
|
aria-checked="true"
|
||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
aria-label="Preview Space"
|
aria-labelledby="mx_LabelledToggleSwitch_testid_0"
|
||||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
|
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
|
||||||
role="switch"
|
role="switch"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
|
Loading…
Reference in a new issue