Bail out of RoomSettingsDialog
when room is not found (#10662)
* hack to fix console noise from unfaked timers and clearAllModals * remove old debug logging in AsyncWrapper * pass room to room settings tabs * add errorboundary for roomsettingsdialog * apply strictnullchecks to tabs/room * dedupe code to set toom in roomsettingdialog * add unit tests * test SecurityRoomSettingsTab * remove snapshot * strict fixes * more tests * 2% more test coverage * remove roomName from RoomSettingsDialogs state
This commit is contained in:
parent
f6e8ffe750
commit
223892bf0e
19 changed files with 1077 additions and 121 deletions
|
@ -1,6 +1,8 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019 New Vector Ltd
|
Copyright 2019 New Vector Ltd
|
||||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||||
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -16,7 +18,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { RoomEvent } from "matrix-js-sdk/src/models/room";
|
import { RoomEvent, Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
import TabbedView, { Tab } from "../../structures/TabbedView";
|
import TabbedView, { Tab } from "../../structures/TabbedView";
|
||||||
import { _t, _td } from "../../../languageHandler";
|
import { _t, _td } from "../../../languageHandler";
|
||||||
|
@ -36,6 +38,7 @@ import { VoipRoomSettingsTab } from "../settings/tabs/room/VoipRoomSettingsTab";
|
||||||
import { ActionPayload } from "../../../dispatcher/payloads";
|
import { ActionPayload } from "../../../dispatcher/payloads";
|
||||||
import { NonEmptyArray } from "../../../@types/common";
|
import { NonEmptyArray } from "../../../@types/common";
|
||||||
import { PollHistoryTab } from "../settings/tabs/room/PollHistoryTab";
|
import { PollHistoryTab } from "../settings/tabs/room/PollHistoryTab";
|
||||||
|
import ErrorBoundary from "../elements/ErrorBoundary";
|
||||||
|
|
||||||
export const ROOM_GENERAL_TAB = "ROOM_GENERAL_TAB";
|
export const ROOM_GENERAL_TAB = "ROOM_GENERAL_TAB";
|
||||||
export const ROOM_VOIP_TAB = "ROOM_VOIP_TAB";
|
export const ROOM_VOIP_TAB = "ROOM_VOIP_TAB";
|
||||||
|
@ -53,15 +56,17 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
roomName: string;
|
room: Room;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class RoomSettingsDialog extends React.Component<IProps, IState> {
|
class RoomSettingsDialog extends React.Component<IProps, IState> {
|
||||||
private dispatcherRef: string;
|
private dispatcherRef: string;
|
||||||
|
|
||||||
public constructor(props: IProps) {
|
public constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = { roomName: "" };
|
|
||||||
|
const room = this.getRoom();
|
||||||
|
this.state = { room };
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount(): void {
|
public componentDidMount(): void {
|
||||||
|
@ -70,6 +75,13 @@ export default class RoomSettingsDialog extends React.Component<IProps, IState>
|
||||||
this.onRoomName();
|
this.onRoomName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public componentDidUpdate(): void {
|
||||||
|
if (this.props.roomId !== this.state.room.roomId) {
|
||||||
|
const room = this.getRoom();
|
||||||
|
this.setState({ room });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public componentWillUnmount(): void {
|
public componentWillUnmount(): void {
|
||||||
if (this.dispatcherRef) {
|
if (this.dispatcherRef) {
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
|
@ -78,6 +90,21 @@ export default class RoomSettingsDialog extends React.Component<IProps, IState>
|
||||||
MatrixClientPeg.get().removeListener(RoomEvent.Name, this.onRoomName);
|
MatrixClientPeg.get().removeListener(RoomEvent.Name, this.onRoomName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get room from client
|
||||||
|
* @returns Room
|
||||||
|
* @throws when room is not found
|
||||||
|
*/
|
||||||
|
private getRoom(): Room {
|
||||||
|
const room = MatrixClientPeg.get().getRoom(this.props.roomId)!;
|
||||||
|
|
||||||
|
// something is really wrong if we encounter this
|
||||||
|
if (!room) {
|
||||||
|
throw new Error(`Cannot find room ${this.props.roomId}`);
|
||||||
|
}
|
||||||
|
return room;
|
||||||
|
}
|
||||||
|
|
||||||
private onAction = (payload: ActionPayload): void => {
|
private onAction = (payload: ActionPayload): void => {
|
||||||
// When view changes below us, close the room settings
|
// When view changes below us, close the room settings
|
||||||
// whilst the modal is open this can only be triggered when someone hits Leave Room
|
// whilst the modal is open this can only be triggered when someone hits Leave Room
|
||||||
|
@ -87,9 +114,8 @@ export default class RoomSettingsDialog extends React.Component<IProps, IState>
|
||||||
};
|
};
|
||||||
|
|
||||||
private onRoomName = (): void => {
|
private onRoomName = (): void => {
|
||||||
this.setState({
|
// rerender when the room name changes
|
||||||
roomName: MatrixClientPeg.get().getRoom(this.props.roomId)?.name ?? "",
|
this.forceUpdate();
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private getTabs(): NonEmptyArray<Tab> {
|
private getTabs(): NonEmptyArray<Tab> {
|
||||||
|
@ -100,7 +126,7 @@ export default class RoomSettingsDialog extends React.Component<IProps, IState>
|
||||||
ROOM_GENERAL_TAB,
|
ROOM_GENERAL_TAB,
|
||||||
_td("General"),
|
_td("General"),
|
||||||
"mx_RoomSettingsDialog_settingsIcon",
|
"mx_RoomSettingsDialog_settingsIcon",
|
||||||
<GeneralRoomSettingsTab roomId={this.props.roomId} />,
|
<GeneralRoomSettingsTab room={this.state.room} />,
|
||||||
"RoomSettingsGeneral",
|
"RoomSettingsGeneral",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -110,7 +136,7 @@ export default class RoomSettingsDialog extends React.Component<IProps, IState>
|
||||||
ROOM_VOIP_TAB,
|
ROOM_VOIP_TAB,
|
||||||
_td("Voice & Video"),
|
_td("Voice & Video"),
|
||||||
"mx_RoomSettingsDialog_voiceIcon",
|
"mx_RoomSettingsDialog_voiceIcon",
|
||||||
<VoipRoomSettingsTab roomId={this.props.roomId} />,
|
<VoipRoomSettingsTab room={this.state.room} />,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -119,12 +145,7 @@ export default class RoomSettingsDialog extends React.Component<IProps, IState>
|
||||||
ROOM_SECURITY_TAB,
|
ROOM_SECURITY_TAB,
|
||||||
_td("Security & Privacy"),
|
_td("Security & Privacy"),
|
||||||
"mx_RoomSettingsDialog_securityIcon",
|
"mx_RoomSettingsDialog_securityIcon",
|
||||||
(
|
<SecurityRoomSettingsTab room={this.state.room} closeSettingsFn={() => this.props.onFinished(true)} />,
|
||||||
<SecurityRoomSettingsTab
|
|
||||||
roomId={this.props.roomId}
|
|
||||||
closeSettingsFn={() => this.props.onFinished(true)}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
"RoomSettingsSecurityPrivacy",
|
"RoomSettingsSecurityPrivacy",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -133,7 +154,7 @@ export default class RoomSettingsDialog extends React.Component<IProps, IState>
|
||||||
ROOM_ROLES_TAB,
|
ROOM_ROLES_TAB,
|
||||||
_td("Roles & Permissions"),
|
_td("Roles & Permissions"),
|
||||||
"mx_RoomSettingsDialog_rolesIcon",
|
"mx_RoomSettingsDialog_rolesIcon",
|
||||||
<RolesRoomSettingsTab roomId={this.props.roomId} />,
|
<RolesRoomSettingsTab room={this.state.room} />,
|
||||||
"RoomSettingsRolesPermissions",
|
"RoomSettingsRolesPermissions",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -144,7 +165,7 @@ export default class RoomSettingsDialog extends React.Component<IProps, IState>
|
||||||
"mx_RoomSettingsDialog_notificationsIcon",
|
"mx_RoomSettingsDialog_notificationsIcon",
|
||||||
(
|
(
|
||||||
<NotificationSettingsTab
|
<NotificationSettingsTab
|
||||||
roomId={this.props.roomId}
|
roomId={this.state.room.roomId}
|
||||||
closeSettingsFn={() => this.props.onFinished(true)}
|
closeSettingsFn={() => this.props.onFinished(true)}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
@ -158,7 +179,7 @@ export default class RoomSettingsDialog extends React.Component<IProps, IState>
|
||||||
ROOM_BRIDGES_TAB,
|
ROOM_BRIDGES_TAB,
|
||||||
_td("Bridges"),
|
_td("Bridges"),
|
||||||
"mx_RoomSettingsDialog_bridgesIcon",
|
"mx_RoomSettingsDialog_bridgesIcon",
|
||||||
<BridgeSettingsTab roomId={this.props.roomId} />,
|
<BridgeSettingsTab room={this.state.room} />,
|
||||||
"RoomSettingsBridges",
|
"RoomSettingsBridges",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -169,7 +190,7 @@ export default class RoomSettingsDialog extends React.Component<IProps, IState>
|
||||||
ROOM_POLL_HISTORY_TAB,
|
ROOM_POLL_HISTORY_TAB,
|
||||||
_td("Poll history"),
|
_td("Poll history"),
|
||||||
"mx_RoomSettingsDialog_pollsIcon",
|
"mx_RoomSettingsDialog_pollsIcon",
|
||||||
<PollHistoryTab roomId={this.props.roomId} onFinished={() => this.props.onFinished(true)} />,
|
<PollHistoryTab room={this.state.room} onFinished={() => this.props.onFinished(true)} />,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -181,7 +202,7 @@ export default class RoomSettingsDialog extends React.Component<IProps, IState>
|
||||||
"mx_RoomSettingsDialog_warningIcon",
|
"mx_RoomSettingsDialog_warningIcon",
|
||||||
(
|
(
|
||||||
<AdvancedRoomSettingsTab
|
<AdvancedRoomSettingsTab
|
||||||
roomId={this.props.roomId}
|
room={this.state.room}
|
||||||
closeSettingsFn={() => this.props.onFinished(true)}
|
closeSettingsFn={() => this.props.onFinished(true)}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
@ -194,7 +215,7 @@ export default class RoomSettingsDialog extends React.Component<IProps, IState>
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
const roomName = this.state.roomName;
|
const roomName = this.state.room.name;
|
||||||
return (
|
return (
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
className="mx_RoomSettingsDialog"
|
className="mx_RoomSettingsDialog"
|
||||||
|
@ -213,3 +234,11 @@ export default class RoomSettingsDialog extends React.Component<IProps, IState>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const WrappedRoomSettingsDialog: React.FC<IProps> = (props) => (
|
||||||
|
<ErrorBoundary>
|
||||||
|
<RoomSettingsDialog {...props} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default WrappedRoomSettingsDialog;
|
||||||
|
|
|
@ -70,14 +70,14 @@ const SpaceSettingsDialog: React.FC<IProps> = ({ matrixClient: cli, space, onFin
|
||||||
SpaceSettingsTab.Roles,
|
SpaceSettingsTab.Roles,
|
||||||
_td("Roles & Permissions"),
|
_td("Roles & Permissions"),
|
||||||
"mx_RoomSettingsDialog_rolesIcon",
|
"mx_RoomSettingsDialog_rolesIcon",
|
||||||
<RolesRoomSettingsTab roomId={space.roomId} />,
|
<RolesRoomSettingsTab room={space} />,
|
||||||
),
|
),
|
||||||
SettingsStore.getValue(UIFeature.AdvancedSettings)
|
SettingsStore.getValue(UIFeature.AdvancedSettings)
|
||||||
? new Tab(
|
? new Tab(
|
||||||
SpaceSettingsTab.Advanced,
|
SpaceSettingsTab.Advanced,
|
||||||
_td("Advanced"),
|
_td("Advanced"),
|
||||||
"mx_RoomSettingsDialog_warningIcon",
|
"mx_RoomSettingsDialog_warningIcon",
|
||||||
<AdvancedRoomSettingsTab roomId={space.roomId} closeSettingsFn={onFinished} />,
|
<AdvancedRoomSettingsTab room={space} closeSettingsFn={onFinished} />,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
].filter(Boolean) as NonEmptyArray<Tab>;
|
].filter(Boolean) as NonEmptyArray<Tab>;
|
||||||
|
|
|
@ -16,9 +16,9 @@ limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
|
import { Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { _t } from "../../../../../languageHandler";
|
import { _t } from "../../../../../languageHandler";
|
||||||
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
|
|
||||||
import AccessibleButton, { ButtonEvent } from "../../../elements/AccessibleButton";
|
import AccessibleButton, { ButtonEvent } from "../../../elements/AccessibleButton";
|
||||||
import RoomUpgradeDialog from "../../../dialogs/RoomUpgradeDialog";
|
import RoomUpgradeDialog from "../../../dialogs/RoomUpgradeDialog";
|
||||||
import Modal from "../../../../../Modal";
|
import Modal from "../../../../../Modal";
|
||||||
|
@ -29,7 +29,7 @@ import { ViewRoomPayload } from "../../../../../dispatcher/payloads/ViewRoomPayl
|
||||||
import SettingsStore from "../../../../../settings/SettingsStore";
|
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
roomId: string;
|
room: Room;
|
||||||
closeSettingsFn(): void;
|
closeSettingsFn(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,8 +64,8 @@ export default class AdvancedRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
this.state = {};
|
this.state = {};
|
||||||
|
|
||||||
// we handle lack of this object gracefully later, so don't worry about it failing here.
|
// we handle lack of this object gracefully later, so don't worry about it failing here.
|
||||||
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
const room = this.props.room;
|
||||||
room?.getRecommendedVersion().then((v) => {
|
room.getRecommendedVersion().then((v) => {
|
||||||
const tombstone = room.currentState.getStateEvents(EventType.RoomTombstone, "");
|
const tombstone = room.currentState.getStateEvents(EventType.RoomTombstone, "");
|
||||||
|
|
||||||
const additionalStateChanges: Partial<IState> = {};
|
const additionalStateChanges: Partial<IState> = {};
|
||||||
|
@ -85,8 +85,7 @@ export default class AdvancedRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
}
|
}
|
||||||
|
|
||||||
private upgradeRoom = (): void => {
|
private upgradeRoom = (): void => {
|
||||||
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
Modal.createDialog(RoomUpgradeDialog, { room: this.props.room });
|
||||||
if (room) Modal.createDialog(RoomUpgradeDialog, { room });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private onOldRoomClicked = (e: ButtonEvent): void => {
|
private onOldRoomClicked = (e: ButtonEvent): void => {
|
||||||
|
@ -105,12 +104,11 @@ export default class AdvancedRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
const client = MatrixClientPeg.get();
|
const room = this.props.room;
|
||||||
const room = client.getRoom(this.props.roomId);
|
const isSpace = room.isSpaceRoom();
|
||||||
const isSpace = room?.isSpaceRoom();
|
|
||||||
|
|
||||||
let unfederatableSection: JSX.Element | undefined;
|
let unfederatableSection: JSX.Element | undefined;
|
||||||
if (room?.currentState.getStateEvents(EventType.RoomCreate, "")?.getContent()["m.federate"] === false) {
|
if (room.currentState.getStateEvents(EventType.RoomCreate, "")?.getContent()["m.federate"] === false) {
|
||||||
unfederatableSection = <div>{_t("This room is not accessible by remote Matrix servers")}</div>;
|
unfederatableSection = <div>{_t("This room is not accessible by remote Matrix servers")}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,9 +141,9 @@ export default class AdvancedRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
if (this.state.oldRoomId) {
|
if (this.state.oldRoomId) {
|
||||||
let copy: string;
|
let copy: string;
|
||||||
if (isSpace) {
|
if (isSpace) {
|
||||||
copy = _t("View older version of %(spaceName)s.", { spaceName: room?.name ?? this.state.oldRoomId });
|
copy = _t("View older version of %(spaceName)s.", { spaceName: room.name ?? this.state.oldRoomId });
|
||||||
} else {
|
} else {
|
||||||
copy = _t("View older messages in %(roomName)s.", { roomName: room?.name ?? this.state.oldRoomId });
|
copy = _t("View older messages in %(roomName)s.", { roomName: room.name ?? this.state.oldRoomId });
|
||||||
}
|
}
|
||||||
|
|
||||||
oldRoomLink = (
|
oldRoomLink = (
|
||||||
|
@ -160,11 +158,13 @@ export default class AdvancedRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
<div className="mx_SettingsTab_heading">{_t("Advanced")}</div>
|
<div className="mx_SettingsTab_heading">{_t("Advanced")}</div>
|
||||||
<div className="mx_SettingsTab_section mx_SettingsTab_subsectionText">
|
<div className="mx_SettingsTab_section mx_SettingsTab_subsectionText">
|
||||||
<span className="mx_SettingsTab_subheading">
|
<span className="mx_SettingsTab_subheading">
|
||||||
{room?.isSpaceRoom() ? _t("Space information") : _t("Room information")}
|
{room.isSpaceRoom() ? _t("Space information") : _t("Room information")}
|
||||||
</span>
|
</span>
|
||||||
<div>
|
<div>
|
||||||
<span>{_t("Internal room ID")}</span>
|
<span>{_t("Internal room ID")}</span>
|
||||||
<CopyableText getTextToCopy={() => this.props.roomId}>{this.props.roomId}</CopyableText>
|
<CopyableText getTextToCopy={() => this.props.room.roomId}>
|
||||||
|
{this.props.room.roomId}
|
||||||
|
</CopyableText>
|
||||||
</div>
|
</div>
|
||||||
{unfederatableSection}
|
{unfederatableSection}
|
||||||
</div>
|
</div>
|
||||||
|
@ -172,7 +172,7 @@ export default class AdvancedRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
<span className="mx_SettingsTab_subheading">{_t("Room version")}</span>
|
<span className="mx_SettingsTab_subheading">{_t("Room version")}</span>
|
||||||
<div>
|
<div>
|
||||||
<span>{_t("Room version:")}</span>
|
<span>{_t("Room version:")}</span>
|
||||||
{room?.getVersion()}
|
{room.getVersion()}
|
||||||
</div>
|
</div>
|
||||||
{oldRoomLink}
|
{oldRoomLink}
|
||||||
{roomUpgradeButton}
|
{roomUpgradeButton}
|
||||||
|
|
|
@ -30,7 +30,7 @@ const BRIDGE_EVENT_TYPES = [
|
||||||
const BRIDGES_LINK = "https://matrix.org/bridges/";
|
const BRIDGES_LINK = "https://matrix.org/bridges/";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
roomId: string;
|
room: Room;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class BridgeSettingsTab extends React.Component<IProps> {
|
export default class BridgeSettingsTab extends React.Component<IProps> {
|
||||||
|
@ -51,9 +51,8 @@ export default class BridgeSettingsTab extends React.Component<IProps> {
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
// This settings tab will only be invoked if the following function returns more
|
// This settings tab will only be invoked if the following function returns more
|
||||||
// than 0 events, so no validation is needed at this stage.
|
// than 0 events, so no validation is needed at this stage.
|
||||||
const bridgeEvents = BridgeSettingsTab.getBridgeStateEvents(this.props.roomId);
|
const bridgeEvents = BridgeSettingsTab.getBridgeStateEvents(this.props.room.roomId);
|
||||||
const client = MatrixClientPeg.get();
|
const room = this.props.room;
|
||||||
const room = client.getRoom(this.props.roomId);
|
|
||||||
|
|
||||||
let content: JSX.Element;
|
let content: JSX.Element;
|
||||||
if (bridgeEvents.length > 0) {
|
if (bridgeEvents.length > 0) {
|
||||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { ContextType } from "react";
|
import React, { ContextType } from "react";
|
||||||
|
import { Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { _t } from "../../../../../languageHandler";
|
import { _t } from "../../../../../languageHandler";
|
||||||
import RoomProfileSettings from "../../../room_settings/RoomProfileSettings";
|
import RoomProfileSettings from "../../../room_settings/RoomProfileSettings";
|
||||||
|
@ -28,7 +29,7 @@ import AliasSettings from "../../../room_settings/AliasSettings";
|
||||||
import PosthogTrackers from "../../../../../PosthogTrackers";
|
import PosthogTrackers from "../../../../../PosthogTrackers";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
roomId: string;
|
room: Room;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -50,7 +51,7 @@ export default class GeneralRoomSettingsTab extends React.Component<IProps, ISta
|
||||||
private onLeaveClick = (ev: ButtonEvent): void => {
|
private onLeaveClick = (ev: ButtonEvent): void => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "leave_room",
|
action: "leave_room",
|
||||||
room_id: this.props.roomId,
|
room_id: this.props.room.roomId,
|
||||||
});
|
});
|
||||||
|
|
||||||
PosthogTrackers.trackInteraction("WebRoomSettingsLeaveButton", ev);
|
PosthogTrackers.trackInteraction("WebRoomSettingsLeaveButton", ev);
|
||||||
|
@ -58,17 +59,18 @@ export default class GeneralRoomSettingsTab extends React.Component<IProps, ISta
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
const client = this.context;
|
const client = this.context;
|
||||||
const room = client.getRoom(this.props.roomId);
|
const room = this.props.room;
|
||||||
|
|
||||||
const canSetAliases = true; // Previously, we arbitrarily only allowed admins to do this
|
const canSetAliases = true; // Previously, we arbitrarily only allowed admins to do this
|
||||||
const canSetCanonical = room?.currentState.mayClientSendStateEvent("m.room.canonical_alias", client);
|
const canSetCanonical = room.currentState.mayClientSendStateEvent("m.room.canonical_alias", client);
|
||||||
const canonicalAliasEv = room?.currentState.getStateEvents("m.room.canonical_alias", "") ?? undefined;
|
const canonicalAliasEv = room.currentState.getStateEvents("m.room.canonical_alias", "") ?? undefined;
|
||||||
|
|
||||||
const urlPreviewSettings =
|
const urlPreviewSettings = SettingsStore.getValue(UIFeature.URLPreviews) ? (
|
||||||
room && SettingsStore.getValue(UIFeature.URLPreviews) ? <UrlPreviewSettings room={room} /> : null;
|
<UrlPreviewSettings room={room} />
|
||||||
|
) : null;
|
||||||
|
|
||||||
let leaveSection;
|
let leaveSection;
|
||||||
if (room?.getMyMembership() === "join") {
|
if (room.getMyMembership() === "join") {
|
||||||
leaveSection = (
|
leaveSection = (
|
||||||
<>
|
<>
|
||||||
<span className="mx_SettingsTab_subheading">{_t("Leave room")}</span>
|
<span className="mx_SettingsTab_subheading">{_t("Leave room")}</span>
|
||||||
|
@ -85,12 +87,12 @@ export default class GeneralRoomSettingsTab extends React.Component<IProps, ISta
|
||||||
<div className="mx_SettingsTab mx_GeneralRoomSettingsTab">
|
<div className="mx_SettingsTab mx_GeneralRoomSettingsTab">
|
||||||
<div className="mx_SettingsTab_heading">{_t("General")}</div>
|
<div className="mx_SettingsTab_heading">{_t("General")}</div>
|
||||||
<div className="mx_SettingsTab_section mx_GeneralRoomSettingsTab_profileSection">
|
<div className="mx_SettingsTab_section mx_GeneralRoomSettingsTab_profileSection">
|
||||||
<RoomProfileSettings roomId={this.props.roomId} />
|
<RoomProfileSettings roomId={room.roomId} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mx_SettingsTab_heading">{_t("Room Addresses")}</div>
|
<div className="mx_SettingsTab_heading">{_t("Room Addresses")}</div>
|
||||||
<AliasSettings
|
<AliasSettings
|
||||||
roomId={this.props.roomId}
|
roomId={room.roomId}
|
||||||
canSetCanonicalAlias={canSetCanonical}
|
canSetCanonicalAlias={canSetCanonical}
|
||||||
canSetAliases={canSetAliases}
|
canSetAliases={canSetAliases}
|
||||||
canonicalAliasEvent={canonicalAliasEv}
|
canonicalAliasEvent={canonicalAliasEv}
|
||||||
|
|
|
@ -53,7 +53,7 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
|
||||||
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
|
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this.roomProps = EchoChamber.forRoom(context.getRoom(this.props.roomId));
|
this.roomProps = EchoChamber.forRoom(context.getRoom(this.props.roomId)!);
|
||||||
|
|
||||||
let currentSound = "default";
|
let currentSound = "default";
|
||||||
const soundData = Notifier.getSoundForRoom(this.props.roomId);
|
const soundData = Notifier.getSoundForRoom(this.props.roomId);
|
||||||
|
|
|
@ -15,23 +15,20 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useContext } from "react";
|
import React, { useContext } from "react";
|
||||||
|
import { Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
|
||||||
import { PollHistory } from "../../../polls/pollHistory/PollHistory";
|
import { PollHistory } from "../../../polls/pollHistory/PollHistory";
|
||||||
import { RoomPermalinkCreator } from "../../../../../utils/permalinks/Permalinks";
|
import { RoomPermalinkCreator } from "../../../../../utils/permalinks/Permalinks";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
roomId: string;
|
room: Room;
|
||||||
onFinished: () => void;
|
onFinished: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PollHistoryTab: React.FC<IProps> = ({ roomId, onFinished }) => {
|
export const PollHistoryTab: React.FC<IProps> = ({ room, onFinished }) => {
|
||||||
const matrixClient = useContext(MatrixClientContext);
|
const matrixClient = useContext(MatrixClientContext);
|
||||||
const room = matrixClient.getRoom(roomId);
|
const permalinkCreator = new RoomPermalinkCreator(room, room.roomId);
|
||||||
if (!room) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const permalinkCreator = new RoomPermalinkCreator(room, roomId);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_SettingsTab">
|
<div className="mx_SettingsTab">
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { throttle, get } from "lodash";
|
import { throttle, get } from "lodash";
|
||||||
import { compare } from "matrix-js-sdk/src/utils";
|
import { compare } from "matrix-js-sdk/src/utils";
|
||||||
import { IContent } from "matrix-js-sdk/src/models/event";
|
import { IContent } from "matrix-js-sdk/src/models/event";
|
||||||
|
import { Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { _t, _td } from "../../../../../languageHandler";
|
import { _t, _td } from "../../../../../languageHandler";
|
||||||
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
|
||||||
|
@ -129,7 +130,7 @@ export class BannedUser extends React.Component<IBannedUserProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
roomId: string;
|
room: Room;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
||||||
|
@ -145,7 +146,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private onRoomStateUpdate = (state: RoomState): void => {
|
private onRoomStateUpdate = (state: RoomState): void => {
|
||||||
if (state.roomId !== this.props.roomId) return;
|
if (state.roomId !== this.props.room.roomId) return;
|
||||||
this.onThisRoomMembership();
|
this.onThisRoomMembership();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -171,8 +172,8 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
||||||
|
|
||||||
private onPowerLevelsChanged = (value: number, powerLevelKey: string): void => {
|
private onPowerLevelsChanged = (value: number, powerLevelKey: string): void => {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const room = client.getRoom(this.props.roomId);
|
const room = this.props.room;
|
||||||
const plEvent = room?.currentState.getStateEvents(EventType.RoomPowerLevels, "");
|
const plEvent = room.currentState.getStateEvents(EventType.RoomPowerLevels, "");
|
||||||
let plContent = plEvent?.getContent() ?? {};
|
let plContent = plEvent?.getContent() ?? {};
|
||||||
|
|
||||||
// Clone the power levels just in case
|
// Clone the power levels just in case
|
||||||
|
@ -186,7 +187,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
||||||
plContent["events"][powerLevelKey.slice(eventsLevelPrefix.length)] = value;
|
plContent["events"][powerLevelKey.slice(eventsLevelPrefix.length)] = value;
|
||||||
} else {
|
} else {
|
||||||
const keyPath = powerLevelKey.split(".");
|
const keyPath = powerLevelKey.split(".");
|
||||||
let parentObj: IContent | undefined;
|
let parentObj: IContent = {};
|
||||||
let currentObj = plContent;
|
let currentObj = plContent;
|
||||||
for (const key of keyPath) {
|
for (const key of keyPath) {
|
||||||
if (!currentObj[key]) {
|
if (!currentObj[key]) {
|
||||||
|
@ -198,7 +199,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
||||||
parentObj[keyPath[keyPath.length - 1]] = value;
|
parentObj[keyPath[keyPath.length - 1]] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
client.sendStateEvent(this.props.roomId, EventType.RoomPowerLevels, plContent).catch((e) => {
|
client.sendStateEvent(this.props.room.roomId, EventType.RoomPowerLevels, plContent).catch((e) => {
|
||||||
logger.error(e);
|
logger.error(e);
|
||||||
|
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
@ -213,8 +214,8 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
||||||
|
|
||||||
private onUserPowerLevelChanged = (value: number, powerLevelKey: string): void => {
|
private onUserPowerLevelChanged = (value: number, powerLevelKey: string): void => {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const room = client.getRoom(this.props.roomId);
|
const room = this.props.room;
|
||||||
const plEvent = room?.currentState.getStateEvents(EventType.RoomPowerLevels, "");
|
const plEvent = room.currentState.getStateEvents(EventType.RoomPowerLevels, "");
|
||||||
let plContent = plEvent?.getContent() ?? {};
|
let plContent = plEvent?.getContent() ?? {};
|
||||||
|
|
||||||
// Clone the power levels just in case
|
// Clone the power levels just in case
|
||||||
|
@ -224,7 +225,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
||||||
if (!plContent["users"]) plContent["users"] = {};
|
if (!plContent["users"]) plContent["users"] = {};
|
||||||
plContent["users"][powerLevelKey] = value;
|
plContent["users"][powerLevelKey] = value;
|
||||||
|
|
||||||
client.sendStateEvent(this.props.roomId, EventType.RoomPowerLevels, plContent).catch((e) => {
|
client.sendStateEvent(this.props.room.roomId, EventType.RoomPowerLevels, plContent).catch((e) => {
|
||||||
logger.error(e);
|
logger.error(e);
|
||||||
|
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
@ -239,12 +240,12 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const room = client.getRoom(this.props.roomId);
|
const room = this.props.room;
|
||||||
const isSpaceRoom = room?.isSpaceRoom();
|
const isSpaceRoom = room.isSpaceRoom();
|
||||||
|
|
||||||
const plEvent = room?.currentState.getStateEvents(EventType.RoomPowerLevels, "");
|
const plEvent = room.currentState.getStateEvents(EventType.RoomPowerLevels, "");
|
||||||
const plContent = plEvent ? plEvent.getContent() || {} : {};
|
const plContent = plEvent ? plEvent.getContent() || {} : {};
|
||||||
const canChangeLevels = room?.currentState.mayClientSendStateEvent(EventType.RoomPowerLevels, client);
|
const canChangeLevels = room.currentState.mayClientSendStateEvent(EventType.RoomPowerLevels, client);
|
||||||
|
|
||||||
const plEventsToLabels: Record<EventType | string, string | null> = {
|
const plEventsToLabels: Record<EventType | string, string | null> = {
|
||||||
// These will be translated for us later.
|
// These will be translated for us later.
|
||||||
|
@ -392,7 +393,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const banned = room?.getMembersWithMembership("ban");
|
const banned = room.getMembersWithMembership("ban");
|
||||||
let bannedUsersSection: JSX.Element | undefined;
|
let bannedUsersSection: JSX.Element | undefined;
|
||||||
if (banned?.length) {
|
if (banned?.length) {
|
||||||
const canBanUsers = currentUserLevel >= banLevel;
|
const canBanUsers = currentUserLevel >= banLevel;
|
||||||
|
@ -401,16 +402,16 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
||||||
<ul>
|
<ul>
|
||||||
{banned.map((member) => {
|
{banned.map((member) => {
|
||||||
const banEvent = member.events.member?.getContent();
|
const banEvent = member.events.member?.getContent();
|
||||||
const sender = room?.getMember(member.events.member.getSender());
|
const bannedById = member.events.member?.getSender();
|
||||||
let bannedBy = member.events.member?.getSender(); // start by falling back to mxid
|
const sender = bannedById ? room.getMember(bannedById) : undefined;
|
||||||
if (sender) bannedBy = sender.name;
|
const bannedBy = sender?.name || bannedById; // fallback to mxid
|
||||||
return (
|
return (
|
||||||
<BannedUser
|
<BannedUser
|
||||||
key={member.userId}
|
key={member.userId}
|
||||||
canUnban={canBanUsers}
|
canUnban={canBanUsers}
|
||||||
member={member}
|
member={member}
|
||||||
reason={banEvent?.reason}
|
reason={banEvent?.reason}
|
||||||
by={bannedBy}
|
by={bannedBy!}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -443,7 +444,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
// hide the power level selector for enabling E2EE if it the room is already encrypted
|
// hide the power level selector for enabling E2EE if it the room is already encrypted
|
||||||
if (client.isRoomEncrypted(this.props.roomId)) {
|
if (client.isRoomEncrypted(this.props.room.roomId)) {
|
||||||
delete eventsLevels[EventType.RoomEncryption];
|
delete eventsLevels[EventType.RoomEncryption];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -481,9 +482,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
||||||
<div className="mx_SettingsTab mx_RolesRoomSettingsTab">
|
<div className="mx_SettingsTab mx_RolesRoomSettingsTab">
|
||||||
<div className="mx_SettingsTab_heading">{_t("Roles & Permissions")}</div>
|
<div className="mx_SettingsTab_heading">{_t("Roles & Permissions")}</div>
|
||||||
{privilegedUsersSection}
|
{privilegedUsersSection}
|
||||||
{canChangeLevels && room !== null && (
|
{canChangeLevels && <AddPrivilegedUsers room={room} defaultUserLevel={defaultUserLevel} />}
|
||||||
<AddPrivilegedUsers room={room} defaultUserLevel={defaultUserLevel} />
|
|
||||||
)}
|
|
||||||
{mutedUsersSection}
|
{mutedUsersSection}
|
||||||
{bannedUsersSection}
|
{bannedUsersSection}
|
||||||
<SettingsFieldset
|
<SettingsFieldset
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
|
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { Icon as WarningIcon } from "../../../../../../res/img/warning.svg";
|
import { Icon as WarningIcon } from "../../../../../../res/img/warning.svg";
|
||||||
import { _t } from "../../../../../languageHandler";
|
import { _t } from "../../../../../languageHandler";
|
||||||
|
@ -42,7 +43,7 @@ import PosthogTrackers from "../../../../../PosthogTrackers";
|
||||||
import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
roomId: string;
|
room: Room;
|
||||||
closeSettingsFn: () => void;
|
closeSettingsFn: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +62,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
|
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
const state = context.getRoom(this.props.roomId)?.currentState;
|
const state = this.props.room.currentState;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
guestAccess: this.pullContentPropertyFromEvent<GuestAccess>(
|
guestAccess: this.pullContentPropertyFromEvent<GuestAccess>(
|
||||||
|
@ -75,7 +76,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
HistoryVisibility.Shared,
|
HistoryVisibility.Shared,
|
||||||
),
|
),
|
||||||
hasAliases: false, // async loaded in componentDidMount
|
hasAliases: false, // async loaded in componentDidMount
|
||||||
encrypted: context.isRoomEncrypted(this.props.roomId),
|
encrypted: context.isRoomEncrypted(this.props.room.roomId),
|
||||||
showAdvancedSection: false,
|
showAdvancedSection: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -104,7 +105,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
};
|
};
|
||||||
|
|
||||||
private onEncryptionChange = async (): Promise<void> => {
|
private onEncryptionChange = async (): Promise<void> => {
|
||||||
if (this.context.getRoom(this.props.roomId)?.getJoinRule() === JoinRule.Public) {
|
if (this.props.room.getJoinRule() === JoinRule.Public) {
|
||||||
const dialog = Modal.createDialog(QuestionDialog, {
|
const dialog = Modal.createDialog(QuestionDialog, {
|
||||||
title: _t("Are you sure you want to add encryption to this public room?"),
|
title: _t("Are you sure you want to add encryption to this public room?"),
|
||||||
description: (
|
description: (
|
||||||
|
@ -172,7 +173,9 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
const beforeEncrypted = this.state.encrypted;
|
const beforeEncrypted = this.state.encrypted;
|
||||||
this.setState({ encrypted: true });
|
this.setState({ encrypted: true });
|
||||||
this.context
|
this.context
|
||||||
.sendStateEvent(this.props.roomId, EventType.RoomEncryption, { algorithm: "m.megolm.v1.aes-sha2" })
|
.sendStateEvent(this.props.room.roomId, EventType.RoomEncryption, {
|
||||||
|
algorithm: "m.megolm.v1.aes-sha2",
|
||||||
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
logger.error(e);
|
logger.error(e);
|
||||||
this.setState({ encrypted: beforeEncrypted });
|
this.setState({ encrypted: beforeEncrypted });
|
||||||
|
@ -190,7 +193,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
|
|
||||||
this.context
|
this.context
|
||||||
.sendStateEvent(
|
.sendStateEvent(
|
||||||
this.props.roomId,
|
this.props.room.roomId,
|
||||||
EventType.RoomGuestAccess,
|
EventType.RoomGuestAccess,
|
||||||
{
|
{
|
||||||
guest_access: guestAccess,
|
guest_access: guestAccess,
|
||||||
|
@ -222,7 +225,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
this.setState({ history: history });
|
this.setState({ history: history });
|
||||||
this.context
|
this.context
|
||||||
.sendStateEvent(
|
.sendStateEvent(
|
||||||
this.props.roomId,
|
this.props.room.roomId,
|
||||||
EventType.RoomHistoryVisibility,
|
EventType.RoomHistoryVisibility,
|
||||||
{
|
{
|
||||||
history_visibility: history,
|
history_visibility: history,
|
||||||
|
@ -236,22 +239,21 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
};
|
};
|
||||||
|
|
||||||
private updateBlacklistDevicesFlag = (checked: boolean): void => {
|
private updateBlacklistDevicesFlag = (checked: boolean): void => {
|
||||||
this.context.getRoom(this.props.roomId)?.setBlacklistUnverifiedDevices(checked);
|
this.props.room.setBlacklistUnverifiedDevices(checked);
|
||||||
};
|
};
|
||||||
|
|
||||||
private async hasAliases(): Promise<boolean> {
|
private async hasAliases(): Promise<boolean> {
|
||||||
const cli = this.context;
|
const cli = this.context;
|
||||||
const response = await cli.getLocalAliases(this.props.roomId);
|
const response = await cli.getLocalAliases(this.props.room.roomId);
|
||||||
const localAliases = response.aliases;
|
const localAliases = response.aliases;
|
||||||
return Array.isArray(localAliases) && localAliases.length !== 0;
|
return Array.isArray(localAliases) && localAliases.length !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderJoinRule(): JSX.Element {
|
private renderJoinRule(): JSX.Element {
|
||||||
const client = this.context;
|
const room = this.props.room;
|
||||||
const room = client.getRoom(this.props.roomId);
|
|
||||||
|
|
||||||
let aliasWarning: JSX.Element | undefined;
|
let aliasWarning: JSX.Element | undefined;
|
||||||
if (room?.getJoinRule() === JoinRule.Public && !this.state.hasAliases) {
|
if (room.getJoinRule() === JoinRule.Public && !this.state.hasAliases) {
|
||||||
aliasWarning = (
|
aliasWarning = (
|
||||||
<div className="mx_SecurityRoomSettingsTab_warning">
|
<div className="mx_SecurityRoomSettingsTab_warning">
|
||||||
<WarningIcon width={15} height={15} />
|
<WarningIcon width={15} height={15} />
|
||||||
|
@ -260,7 +262,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const description = _t("Decide who can join %(roomName)s.", {
|
const description = _t("Decide who can join %(roomName)s.", {
|
||||||
roomName: room?.name,
|
roomName: room.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -342,7 +344,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
|
|
||||||
const client = this.context;
|
const client = this.context;
|
||||||
const history = this.state.history;
|
const history = this.state.history;
|
||||||
const state = client.getRoom(this.props.roomId)?.currentState;
|
const state = this.props.room.currentState;
|
||||||
const canChangeHistory = state?.mayClientSendStateEvent(EventType.RoomHistoryVisibility, client);
|
const canChangeHistory = state?.mayClientSendStateEvent(EventType.RoomHistoryVisibility, client);
|
||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
|
@ -393,7 +395,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
private renderAdvanced(): JSX.Element {
|
private renderAdvanced(): JSX.Element {
|
||||||
const client = this.context;
|
const client = this.context;
|
||||||
const guestAccess = this.state.guestAccess;
|
const guestAccess = this.state.guestAccess;
|
||||||
const state = client.getRoom(this.props.roomId)?.currentState;
|
const state = this.props.room.currentState;
|
||||||
const canSetGuestAccess = state?.mayClientSendStateEvent(EventType.RoomGuestAccess, client);
|
const canSetGuestAccess = state?.mayClientSendStateEvent(EventType.RoomGuestAccess, client);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -416,9 +418,9 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
const client = this.context;
|
const client = this.context;
|
||||||
const room = client.getRoom(this.props.roomId);
|
const room = this.props.room;
|
||||||
const isEncrypted = this.state.encrypted;
|
const isEncrypted = this.state.encrypted;
|
||||||
const hasEncryptionPermission = room?.currentState.mayClientSendStateEvent(EventType.RoomEncryption, client);
|
const hasEncryptionPermission = room.currentState.mayClientSendStateEvent(EventType.RoomEncryption, client);
|
||||||
const canEnableEncryption = !isEncrypted && hasEncryptionPermission;
|
const canEnableEncryption = !isEncrypted && hasEncryptionPermission;
|
||||||
|
|
||||||
let encryptionSettings: JSX.Element | undefined;
|
let encryptionSettings: JSX.Element | undefined;
|
||||||
|
@ -428,7 +430,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
name="blacklistUnverifiedDevices"
|
name="blacklistUnverifiedDevices"
|
||||||
level={SettingLevel.ROOM_DEVICE}
|
level={SettingLevel.ROOM_DEVICE}
|
||||||
onChange={this.updateBlacklistDevicesFlag}
|
onChange={this.updateBlacklistDevicesFlag}
|
||||||
roomId={this.props.roomId}
|
roomId={this.props.room.roomId}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -436,7 +438,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
const historySection = this.renderHistory();
|
const historySection = this.renderHistory();
|
||||||
|
|
||||||
let advanced: JSX.Element | undefined;
|
let advanced: JSX.Element | undefined;
|
||||||
if (room?.getJoinRule() === JoinRule.Public) {
|
if (room.getJoinRule() === JoinRule.Public) {
|
||||||
advanced = (
|
advanced = (
|
||||||
<div className="mx_SettingsTab_section">
|
<div className="mx_SettingsTab_section">
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
|
|
|
@ -18,6 +18,7 @@ import React, { useCallback, useMemo, useState } from "react";
|
||||||
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { RoomState } from "matrix-js-sdk/src/models/room-state";
|
import { RoomState } from "matrix-js-sdk/src/models/room-state";
|
||||||
|
import { Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { _t } from "../../../../../languageHandler";
|
import { _t } from "../../../../../languageHandler";
|
||||||
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
|
||||||
|
@ -29,14 +30,13 @@ import { useRoomState } from "../../../../../hooks/useRoomState";
|
||||||
import SdkConfig, { DEFAULTS } from "../../../../../SdkConfig";
|
import SdkConfig, { DEFAULTS } from "../../../../../SdkConfig";
|
||||||
|
|
||||||
interface ElementCallSwitchProps {
|
interface ElementCallSwitchProps {
|
||||||
roomId: string;
|
room: Room;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ElementCallSwitch: React.FC<ElementCallSwitchProps> = ({ roomId }) => {
|
const ElementCallSwitch: React.FC<ElementCallSwitchProps> = ({ room }) => {
|
||||||
const room = useMemo(() => MatrixClientPeg.get().getRoom(roomId), [roomId]);
|
const isPublic = useMemo(() => room.getJoinRule() === JoinRule.Public, [room]);
|
||||||
const isPublic = useMemo(() => room?.getJoinRule() === JoinRule.Public, [room]);
|
|
||||||
const [content, events, maySend] = useRoomState(
|
const [content, events, maySend] = useRoomState(
|
||||||
room ?? undefined,
|
room,
|
||||||
useCallback((state: RoomState) => {
|
useCallback((state: RoomState) => {
|
||||||
const content = state?.getStateEvents(EventType.RoomPowerLevels, "")?.getContent();
|
const content = state?.getStateEvents(EventType.RoomPowerLevels, "")?.getContent();
|
||||||
return [
|
return [
|
||||||
|
@ -68,12 +68,12 @@ const ElementCallSwitch: React.FC<ElementCallSwitchProps> = ({ roomId }) => {
|
||||||
events[ElementCall.MEMBER_EVENT_TYPE.name] = adminLevel;
|
events[ElementCall.MEMBER_EVENT_TYPE.name] = adminLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
MatrixClientPeg.get().sendStateEvent(roomId, EventType.RoomPowerLevels, {
|
MatrixClientPeg.get().sendStateEvent(room.roomId, EventType.RoomPowerLevels, {
|
||||||
events: events,
|
events: events,
|
||||||
...content,
|
...content,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[roomId, content, events, isPublic],
|
[room.roomId, content, events, isPublic],
|
||||||
);
|
);
|
||||||
|
|
||||||
const brand = SdkConfig.get("element_call").brand ?? DEFAULTS.element_call.brand;
|
const brand = SdkConfig.get("element_call").brand ?? DEFAULTS.element_call.brand;
|
||||||
|
@ -95,14 +95,14 @@ const ElementCallSwitch: React.FC<ElementCallSwitchProps> = ({ roomId }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
roomId: string;
|
room: Room;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VoipRoomSettingsTab: React.FC<Props> = ({ roomId }) => {
|
export const VoipRoomSettingsTab: React.FC<Props> = ({ room }) => {
|
||||||
return (
|
return (
|
||||||
<SettingsTab heading={_t("Voice & Video")}>
|
<SettingsTab heading={_t("Voice & Video")}>
|
||||||
<SettingsSubsection heading={_t("Call type")}>
|
<SettingsSubsection heading={_t("Call type")}>
|
||||||
<ElementCallSwitch roomId={roomId} />
|
<ElementCallSwitch room={room} />
|
||||||
</SettingsSubsection>
|
</SettingsSubsection>
|
||||||
</SettingsTab>
|
</SettingsTab>
|
||||||
);
|
);
|
||||||
|
|
|
@ -38,24 +38,46 @@ describe("<RoomSettingsDialog />", () => {
|
||||||
|
|
||||||
const roomId = "!room:server.org";
|
const roomId = "!room:server.org";
|
||||||
const room = new Room(roomId, mockClient, userId);
|
const room = new Room(roomId, mockClient, userId);
|
||||||
|
room.name = "Test Room";
|
||||||
|
const room2 = new Room("!room2:server.org", mockClient, userId);
|
||||||
|
room2.name = "Another Room";
|
||||||
|
|
||||||
jest.spyOn(SettingsStore, "getValue");
|
jest.spyOn(SettingsStore, "getValue");
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
mockClient.getRoom.mockReturnValue(room);
|
mockClient.getRoom.mockImplementation((roomId) => {
|
||||||
|
if (roomId === room.roomId) return room;
|
||||||
|
if (roomId === room2.roomId) return room2;
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
jest.spyOn(SettingsStore, "getValue").mockReset().mockReturnValue(false);
|
jest.spyOn(SettingsStore, "getValue").mockReset().mockReturnValue(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
const getComponent = (onFinished = jest.fn()) =>
|
const getComponent = (onFinished = jest.fn(), propRoomId = roomId) =>
|
||||||
render(<RoomSettingsDialog roomId={roomId} onFinished={onFinished} />, {
|
render(<RoomSettingsDialog roomId={propRoomId} onFinished={onFinished} />, {
|
||||||
wrapper: ({ children }) => (
|
wrapper: ({ children }) => (
|
||||||
<MatrixClientContext.Provider value={mockClient}>{children}</MatrixClientContext.Provider>
|
<MatrixClientContext.Provider value={mockClient}>{children}</MatrixClientContext.Provider>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("catches errors when room is not found", () => {
|
||||||
|
getComponent(undefined, "!room-that-does-not-exist");
|
||||||
|
expect(screen.getByText("Something went wrong!")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates when roomId prop changes", () => {
|
||||||
|
const { rerender, getByText } = getComponent(undefined, roomId);
|
||||||
|
|
||||||
|
expect(getByText(`Room Settings - ${room.name}`)).toBeInTheDocument();
|
||||||
|
|
||||||
|
rerender(<RoomSettingsDialog roomId={room2.roomId} onFinished={jest.fn()} />);
|
||||||
|
|
||||||
|
expect(getByText(`Room Settings - ${room2.name}`)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
describe("Settings tabs", () => {
|
describe("Settings tabs", () => {
|
||||||
it("renders default tabs correctly", () => {
|
it("renders default tabs correctly", () => {
|
||||||
const { container } = getComponent();
|
const { container } = getComponent();
|
||||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { fireEvent, render, RenderResult } from "@testing-library/react";
|
import { fireEvent, render, RenderResult, screen } from "@testing-library/react";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { mocked } from "jest-mock";
|
import { mocked } from "jest-mock";
|
||||||
|
@ -36,7 +36,7 @@ describe("AdvancedRoomSettingsTab", () => {
|
||||||
let room: Room;
|
let room: Room;
|
||||||
|
|
||||||
const renderTab = (): RenderResult => {
|
const renderTab = (): RenderResult => {
|
||||||
return render(<AdvancedRoomSettingsTab roomId={roomId} closeSettingsFn={jest.fn()} />);
|
return render(<AdvancedRoomSettingsTab room={room} closeSettingsFn={jest.fn()} />);
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -69,6 +69,22 @@ describe("AdvancedRoomSettingsTab", () => {
|
||||||
tab.getByText("custom_room_version_1");
|
tab.getByText("custom_room_version_1");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("displays message when room cannot federate", () => {
|
||||||
|
const createEvent = new MatrixEvent({
|
||||||
|
sender: "@a:b.com",
|
||||||
|
type: EventType.RoomCreate,
|
||||||
|
content: { "m.federate": false },
|
||||||
|
room_id: room.roomId,
|
||||||
|
state_key: "",
|
||||||
|
});
|
||||||
|
jest.spyOn(room.currentState, "getStateEvents").mockImplementation((type) =>
|
||||||
|
type === EventType.RoomCreate ? createEvent : null,
|
||||||
|
);
|
||||||
|
|
||||||
|
renderTab();
|
||||||
|
expect(screen.getByText("This room is not accessible by remote Matrix servers")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
function mockStateEvents(room: Room) {
|
function mockStateEvents(room: Room) {
|
||||||
const createEvent = mkEvent({
|
const createEvent = mkEvent({
|
||||||
event: true,
|
event: true,
|
||||||
|
@ -143,5 +159,16 @@ describe("AdvancedRoomSettingsTab", () => {
|
||||||
metricsViaKeyboard: false,
|
metricsViaKeyboard: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("handles when room is a space", async () => {
|
||||||
|
mockStateEvents(room);
|
||||||
|
jest.spyOn(room, "isSpaceRoom").mockReturnValue(true);
|
||||||
|
|
||||||
|
mockStateEvents(room);
|
||||||
|
const tab = renderTab();
|
||||||
|
const link = await tab.findByText("View older version of test room.");
|
||||||
|
expect(link).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Space information")).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
|
import BridgeSettingsTab from "../../../../../../src/components/views/settings/tabs/room/BridgeSettingsTab";
|
||||||
|
import { getMockClientWithEventEmitter } from "../../../../../test-utils";
|
||||||
|
|
||||||
|
describe("<BridgeSettingsTab />", () => {
|
||||||
|
const userId = "@alice:server.org";
|
||||||
|
const client = getMockClientWithEventEmitter({
|
||||||
|
getRoom: jest.fn(),
|
||||||
|
});
|
||||||
|
const roomId = "!room:server.org";
|
||||||
|
|
||||||
|
const getComponent = (room: Room) => render(<BridgeSettingsTab room={room} />);
|
||||||
|
|
||||||
|
it("renders when room is not bridging messages to any platform", () => {
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
|
||||||
|
const { container } = getComponent(room);
|
||||||
|
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders when room is bridging messages", () => {
|
||||||
|
const bridgeEvent = new MatrixEvent({
|
||||||
|
type: "uk.half-shot.bridge",
|
||||||
|
content: {
|
||||||
|
channel: { id: "channel-test" },
|
||||||
|
protocol: { id: "protocol-test" },
|
||||||
|
bridgebot: "test",
|
||||||
|
},
|
||||||
|
room_id: roomId,
|
||||||
|
state_key: "1",
|
||||||
|
});
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
room.currentState.setStateEvents([bridgeEvent]);
|
||||||
|
client.getRoom.mockReturnValue(room);
|
||||||
|
|
||||||
|
const { container } = getComponent(room);
|
||||||
|
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -15,12 +15,13 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { fireEvent, render, RenderResult } from "@testing-library/react";
|
import { fireEvent, render, RenderResult, screen } from "@testing-library/react";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { mocked } from "jest-mock";
|
import { mocked } from "jest-mock";
|
||||||
|
import { RoomMember } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import RolesRoomSettingsTab from "../../../../../../src/components/views/settings/tabs/room/RolesRoomSettingsTab";
|
import RolesRoomSettingsTab from "../../../../../../src/components/views/settings/tabs/room/RolesRoomSettingsTab";
|
||||||
import { mkStubRoom, stubClient } from "../../../../../test-utils";
|
import { mkStubRoom, stubClient } from "../../../../../test-utils";
|
||||||
|
@ -30,12 +31,13 @@ import SettingsStore from "../../../../../../src/settings/SettingsStore";
|
||||||
import { ElementCall } from "../../../../../../src/models/Call";
|
import { ElementCall } from "../../../../../../src/models/Call";
|
||||||
|
|
||||||
describe("RolesRoomSettingsTab", () => {
|
describe("RolesRoomSettingsTab", () => {
|
||||||
|
const userId = "@alice:server.org";
|
||||||
const roomId = "!room:example.com";
|
const roomId = "!room:example.com";
|
||||||
let cli: MatrixClient;
|
let cli: MatrixClient;
|
||||||
let room: Room;
|
let room: Room;
|
||||||
|
|
||||||
const renderTab = (): RenderResult => {
|
const renderTab = (propRoom: Room = room): RenderResult => {
|
||||||
return render(<RolesRoomSettingsTab roomId={roomId} />);
|
return render(<RolesRoomSettingsTab room={propRoom} />);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getVoiceBroadcastsSelect = (): HTMLElement => {
|
const getVoiceBroadcastsSelect = (): HTMLElement => {
|
||||||
|
@ -183,4 +185,54 @@ describe("RolesRoomSettingsTab", () => {
|
||||||
expect(getJoinCallSelectedOption(tab)).toBeFalsy();
|
expect(getJoinCallSelectedOption(tab)).toBeFalsy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Banned users", () => {
|
||||||
|
it("should not render banned section when no banned users", () => {
|
||||||
|
const room = new Room(roomId, cli, userId);
|
||||||
|
renderTab(room);
|
||||||
|
|
||||||
|
expect(screen.queryByText("Banned users")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders banned users", () => {
|
||||||
|
const bannedMember = new RoomMember(roomId, "@bob:server.org");
|
||||||
|
bannedMember.setMembershipEvent(
|
||||||
|
new MatrixEvent({
|
||||||
|
type: EventType.RoomMember,
|
||||||
|
content: {
|
||||||
|
membership: "ban",
|
||||||
|
reason: "just testing",
|
||||||
|
},
|
||||||
|
sender: userId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const room = new Room(roomId, cli, userId);
|
||||||
|
jest.spyOn(room, "getMembersWithMembership").mockReturnValue([bannedMember]);
|
||||||
|
renderTab(room);
|
||||||
|
|
||||||
|
expect(screen.getByText("Banned users").parentElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses banners display name when available", () => {
|
||||||
|
const bannedMember = new RoomMember(roomId, "@bob:server.org");
|
||||||
|
const senderMember = new RoomMember(roomId, "@alice:server.org");
|
||||||
|
senderMember.name = "Alice";
|
||||||
|
bannedMember.setMembershipEvent(
|
||||||
|
new MatrixEvent({
|
||||||
|
type: EventType.RoomMember,
|
||||||
|
content: {
|
||||||
|
membership: "ban",
|
||||||
|
reason: "just testing",
|
||||||
|
},
|
||||||
|
sender: userId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const room = new Room(roomId, cli, userId);
|
||||||
|
jest.spyOn(room, "getMembersWithMembership").mockReturnValue([bannedMember]);
|
||||||
|
jest.spyOn(room, "getMember").mockReturnValue(senderMember);
|
||||||
|
renderTab(room);
|
||||||
|
|
||||||
|
expect(screen.getByTitle("Banned by Alice")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,402 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { fireEvent, render, screen, within } from "@testing-library/react";
|
||||||
|
import { EventType, GuestAccess, HistoryVisibility, JoinRule, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
|
import SecurityRoomSettingsTab from "../../../../../../src/components/views/settings/tabs/room/SecurityRoomSettingsTab";
|
||||||
|
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
|
||||||
|
import SettingsStore from "../../../../../../src/settings/SettingsStore";
|
||||||
|
import { flushPromises, getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../../../test-utils";
|
||||||
|
import { filterBoolean } from "../../../../../../src/utils/arrays";
|
||||||
|
|
||||||
|
describe("<SecurityRoomSettingsTab />", () => {
|
||||||
|
const userId = "@alice:server.org";
|
||||||
|
const client = getMockClientWithEventEmitter({
|
||||||
|
...mockClientMethodsUser(userId),
|
||||||
|
getRoom: jest.fn(),
|
||||||
|
isRoomEncrypted: jest.fn(),
|
||||||
|
getLocalAliases: jest.fn().mockReturnValue([]),
|
||||||
|
sendStateEvent: jest.fn(),
|
||||||
|
});
|
||||||
|
const roomId = "!room:server.org";
|
||||||
|
|
||||||
|
const getComponent = (room: Room, closeSettingsFn = jest.fn()) =>
|
||||||
|
render(<SecurityRoomSettingsTab room={room} closeSettingsFn={closeSettingsFn} />, {
|
||||||
|
wrapper: ({ children }) => (
|
||||||
|
<MatrixClientContext.Provider value={client}>{children}</MatrixClientContext.Provider>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const setRoomStateEvents = (
|
||||||
|
room: Room,
|
||||||
|
joinRule?: JoinRule,
|
||||||
|
guestAccess?: GuestAccess,
|
||||||
|
history?: HistoryVisibility,
|
||||||
|
): void => {
|
||||||
|
const events = filterBoolean<MatrixEvent>([
|
||||||
|
new MatrixEvent({
|
||||||
|
type: EventType.RoomCreate,
|
||||||
|
content: { version: "test" },
|
||||||
|
sender: userId,
|
||||||
|
state_key: "",
|
||||||
|
room_id: room.roomId,
|
||||||
|
}),
|
||||||
|
guestAccess &&
|
||||||
|
new MatrixEvent({
|
||||||
|
type: EventType.RoomGuestAccess,
|
||||||
|
content: { guest_access: guestAccess },
|
||||||
|
sender: userId,
|
||||||
|
state_key: "",
|
||||||
|
room_id: room.roomId,
|
||||||
|
}),
|
||||||
|
history &&
|
||||||
|
new MatrixEvent({
|
||||||
|
type: EventType.RoomHistoryVisibility,
|
||||||
|
content: { history_visibility: history },
|
||||||
|
sender: userId,
|
||||||
|
state_key: "",
|
||||||
|
room_id: room.roomId,
|
||||||
|
}),
|
||||||
|
joinRule &&
|
||||||
|
new MatrixEvent({
|
||||||
|
type: EventType.RoomJoinRules,
|
||||||
|
content: { join_rule: joinRule },
|
||||||
|
sender: userId,
|
||||||
|
state_key: "",
|
||||||
|
room_id: room.roomId,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
room.currentState.setStateEvents(events);
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
client.sendStateEvent.mockReset().mockResolvedValue({ event_id: "test" });
|
||||||
|
client.isRoomEncrypted.mockReturnValue(false);
|
||||||
|
jest.spyOn(SettingsStore, "getValue").mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("join rule", () => {
|
||||||
|
it("warns when trying to make an encrypted room public", async () => {
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
client.isRoomEncrypted.mockReturnValue(true);
|
||||||
|
setRoomStateEvents(room, JoinRule.Invite);
|
||||||
|
|
||||||
|
getComponent(room);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByLabelText("Public"));
|
||||||
|
|
||||||
|
const modal = await screen.findByRole("dialog");
|
||||||
|
|
||||||
|
expect(modal).toMatchSnapshot();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText("Cancel"));
|
||||||
|
|
||||||
|
// join rule not updated
|
||||||
|
expect(screen.getByLabelText("Private (invite only)").hasAttribute("checked")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates join rule", async () => {
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
setRoomStateEvents(room, JoinRule.Invite);
|
||||||
|
|
||||||
|
getComponent(room);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByLabelText("Public"));
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(client.sendStateEvent).toHaveBeenCalledWith(
|
||||||
|
room.roomId,
|
||||||
|
EventType.RoomJoinRules,
|
||||||
|
{
|
||||||
|
join_rule: JoinRule.Public,
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles error when updating join rule fails", async () => {
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
client.sendStateEvent.mockRejectedValue("oups");
|
||||||
|
setRoomStateEvents(room, JoinRule.Invite);
|
||||||
|
|
||||||
|
getComponent(room);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByLabelText("Public"));
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
const dialog = await screen.findByRole("dialog");
|
||||||
|
|
||||||
|
expect(dialog).toMatchSnapshot();
|
||||||
|
|
||||||
|
fireEvent.click(within(dialog).getByText("OK"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("displays advanced section toggle when join rule is public", () => {
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
setRoomStateEvents(room, JoinRule.Public);
|
||||||
|
|
||||||
|
getComponent(room);
|
||||||
|
|
||||||
|
expect(screen.getByText("Show advanced")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not display advanced section toggle when join rule is not public", () => {
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
setRoomStateEvents(room, JoinRule.Invite);
|
||||||
|
|
||||||
|
getComponent(room);
|
||||||
|
|
||||||
|
expect(screen.queryByText("Show advanced")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("guest access", () => {
|
||||||
|
it("uses forbidden by default when room has no guest access event", () => {
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
setRoomStateEvents(room, JoinRule.Public);
|
||||||
|
|
||||||
|
getComponent(room);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText("Show advanced"));
|
||||||
|
|
||||||
|
expect(screen.getByLabelText("Enable guest access").getAttribute("aria-checked")).toBe("false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates guest access on toggle", () => {
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
setRoomStateEvents(room, JoinRule.Public);
|
||||||
|
getComponent(room);
|
||||||
|
fireEvent.click(screen.getByText("Show advanced"));
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByLabelText("Enable guest access"));
|
||||||
|
|
||||||
|
// toggle set immediately
|
||||||
|
expect(screen.getByLabelText("Enable guest access").getAttribute("aria-checked")).toBe("true");
|
||||||
|
|
||||||
|
expect(client.sendStateEvent).toHaveBeenCalledWith(
|
||||||
|
room.roomId,
|
||||||
|
EventType.RoomGuestAccess,
|
||||||
|
{ guest_access: GuestAccess.CanJoin },
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("logs error and resets state when updating guest access fails", async () => {
|
||||||
|
client.sendStateEvent.mockRejectedValue("oups");
|
||||||
|
jest.spyOn(logger, "error").mockImplementation(() => {});
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
setRoomStateEvents(room, JoinRule.Public, GuestAccess.CanJoin);
|
||||||
|
getComponent(room);
|
||||||
|
fireEvent.click(screen.getByText("Show advanced"));
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByLabelText("Enable guest access"));
|
||||||
|
|
||||||
|
// toggle set immediately
|
||||||
|
expect(screen.getByLabelText("Enable guest access").getAttribute("aria-checked")).toBe("false");
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
expect(client.sendStateEvent).toHaveBeenCalled();
|
||||||
|
expect(logger.error).toHaveBeenCalledWith("oups");
|
||||||
|
|
||||||
|
// toggle reset to old value
|
||||||
|
expect(screen.getByLabelText("Enable guest access").getAttribute("aria-checked")).toBe("true");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("history visibility", () => {
|
||||||
|
it("does not render section when RoomHistorySettings feature is disabled", () => {
|
||||||
|
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
setRoomStateEvents(room);
|
||||||
|
|
||||||
|
getComponent(room);
|
||||||
|
|
||||||
|
expect(screen.queryByText("Who can read history")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses shared as default history visibility when no state event found", () => {
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
setRoomStateEvents(room);
|
||||||
|
|
||||||
|
getComponent(room);
|
||||||
|
|
||||||
|
expect(screen.getByText("Who can read history?").parentElement).toMatchSnapshot();
|
||||||
|
expect(screen.getByDisplayValue(HistoryVisibility.Shared)).toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not render world readable option when room is encrypted", () => {
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
client.isRoomEncrypted.mockReturnValue(true);
|
||||||
|
setRoomStateEvents(room);
|
||||||
|
|
||||||
|
getComponent(room);
|
||||||
|
|
||||||
|
expect(screen.queryByDisplayValue(HistoryVisibility.WorldReadable)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders world readable option when room is encrypted and history is already set to world readable", () => {
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
client.isRoomEncrypted.mockReturnValue(true);
|
||||||
|
setRoomStateEvents(room, undefined, undefined, HistoryVisibility.WorldReadable);
|
||||||
|
|
||||||
|
getComponent(room);
|
||||||
|
|
||||||
|
expect(screen.getByDisplayValue(HistoryVisibility.WorldReadable)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates history visibility", () => {
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
|
||||||
|
getComponent(room);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByDisplayValue(HistoryVisibility.Invited));
|
||||||
|
|
||||||
|
// toggle updated immediately
|
||||||
|
expect(screen.getByDisplayValue(HistoryVisibility.Invited)).toBeChecked();
|
||||||
|
|
||||||
|
expect(client.sendStateEvent).toHaveBeenCalledWith(
|
||||||
|
room.roomId,
|
||||||
|
EventType.RoomHistoryVisibility,
|
||||||
|
{
|
||||||
|
history_visibility: HistoryVisibility.Invited,
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles error when updating history visibility", async () => {
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
client.sendStateEvent.mockRejectedValue("oups");
|
||||||
|
jest.spyOn(logger, "error").mockImplementation(() => {});
|
||||||
|
|
||||||
|
getComponent(room);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByDisplayValue(HistoryVisibility.Invited));
|
||||||
|
|
||||||
|
// toggle updated immediately
|
||||||
|
expect(screen.getByDisplayValue(HistoryVisibility.Invited)).toBeChecked();
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
// reset to before updated value
|
||||||
|
expect(screen.getByDisplayValue(HistoryVisibility.Shared)).toBeChecked();
|
||||||
|
expect(logger.error).toHaveBeenCalledWith("oups");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("encryption", () => {
|
||||||
|
it("displays encryption as enabled", () => {
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
client.isRoomEncrypted.mockReturnValue(true);
|
||||||
|
setRoomStateEvents(room);
|
||||||
|
getComponent(room);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText("Encrypted")).toBeChecked();
|
||||||
|
// can't disable encryption once enabled
|
||||||
|
expect(screen.getByLabelText("Encrypted").getAttribute("aria-disabled")).toEqual("true");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("asks users to confirm when setting room to encrypted", async () => {
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
setRoomStateEvents(room);
|
||||||
|
getComponent(room);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText("Encrypted")).not.toBeChecked();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByLabelText("Encrypted"));
|
||||||
|
|
||||||
|
const dialog = await screen.findByRole("dialog");
|
||||||
|
|
||||||
|
fireEvent.click(within(dialog).getByText("Cancel"));
|
||||||
|
|
||||||
|
expect(client.sendStateEvent).not.toHaveBeenCalled();
|
||||||
|
expect(screen.getByLabelText("Encrypted")).not.toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("enables encryption after confirmation", async () => {
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
setRoomStateEvents(room);
|
||||||
|
getComponent(room);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText("Encrypted")).not.toBeChecked();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByLabelText("Encrypted"));
|
||||||
|
|
||||||
|
const dialog = await screen.findByRole("dialog");
|
||||||
|
|
||||||
|
fireEvent.click(within(dialog).getByText("OK"));
|
||||||
|
|
||||||
|
expect(client.sendStateEvent).toHaveBeenCalledWith(room.roomId, EventType.RoomEncryption, {
|
||||||
|
algorithm: "m.megolm.v1.aes-sha2",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders world readable option when room is encrypted and history is already set to world readable", () => {
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
client.isRoomEncrypted.mockReturnValue(true);
|
||||||
|
setRoomStateEvents(room, undefined, undefined, HistoryVisibility.WorldReadable);
|
||||||
|
|
||||||
|
getComponent(room);
|
||||||
|
|
||||||
|
expect(screen.getByDisplayValue(HistoryVisibility.WorldReadable)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates history visibility", () => {
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
|
||||||
|
getComponent(room);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByDisplayValue(HistoryVisibility.Invited));
|
||||||
|
|
||||||
|
// toggle updated immediately
|
||||||
|
expect(screen.getByDisplayValue(HistoryVisibility.Invited)).toBeChecked();
|
||||||
|
|
||||||
|
expect(client.sendStateEvent).toHaveBeenCalledWith(
|
||||||
|
room.roomId,
|
||||||
|
EventType.RoomHistoryVisibility,
|
||||||
|
{
|
||||||
|
history_visibility: HistoryVisibility.Invited,
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles error when updating history visibility", async () => {
|
||||||
|
const room = new Room(roomId, client, userId);
|
||||||
|
client.sendStateEvent.mockRejectedValue("oups");
|
||||||
|
jest.spyOn(logger, "error").mockImplementation(() => {});
|
||||||
|
|
||||||
|
getComponent(room);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByDisplayValue(HistoryVisibility.Invited));
|
||||||
|
|
||||||
|
// toggle updated immediately
|
||||||
|
expect(screen.getByDisplayValue(HistoryVisibility.Invited)).toBeChecked();
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
// reset to before updated value
|
||||||
|
expect(screen.getByDisplayValue(HistoryVisibility.Shared)).toBeChecked();
|
||||||
|
expect(logger.error).toHaveBeenCalledWith("oups");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -27,13 +27,13 @@ import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
|
||||||
import { VoipRoomSettingsTab } from "../../../../../../src/components/views/settings/tabs/room/VoipRoomSettingsTab";
|
import { VoipRoomSettingsTab } from "../../../../../../src/components/views/settings/tabs/room/VoipRoomSettingsTab";
|
||||||
import { ElementCall } from "../../../../../../src/models/Call";
|
import { ElementCall } from "../../../../../../src/models/Call";
|
||||||
|
|
||||||
describe("RolesRoomSettingsTab", () => {
|
describe("VoipRoomSettingsTab", () => {
|
||||||
const roomId = "!room:example.com";
|
const roomId = "!room:example.com";
|
||||||
let cli: MatrixClient;
|
let cli: MatrixClient;
|
||||||
let room: Room;
|
let room: Room;
|
||||||
|
|
||||||
const renderTab = (): RenderResult => {
|
const renderTab = (): RenderResult => {
|
||||||
return render(<VoipRoomSettingsTab roomId={roomId} />);
|
return render(<VoipRoomSettingsTab room={room} />);
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<BridgeSettingsTab /> renders when room is bridging messages 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="mx_SettingsTab"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_SettingsTab_heading"
|
||||||
|
>
|
||||||
|
Bridges
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_SettingsTab_section mx_SettingsTab_subsectionText"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<span>
|
||||||
|
This room is bridging messages to the following platforms.
|
||||||
|
<a
|
||||||
|
href="https://matrix.org/bridges/"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Learn more.
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<ul
|
||||||
|
class="mx_RoomSettingsDialog_BridgeList"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="mx_RoomSettingsDialog_BridgeList_listItem"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_RoomSettingsDialog_column_icon"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_RoomSettingsDialog_noProtocolIcon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_RoomSettingsDialog_column_data"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="mx_RoomSettingsDialog_column_data_protocolName"
|
||||||
|
>
|
||||||
|
protocol-test
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
class="mx_RoomSettingsDialog_column_data_details mx_RoomSettingsDialog_workspace_channel_details"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_RoomSettingsDialog_channel"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Channel:
|
||||||
|
<span>
|
||||||
|
channel-test
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<ul
|
||||||
|
class="mx_RoomSettingsDialog_column_data_metadata mx_RoomSettingsDialog_metadata"
|
||||||
|
>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<span>
|
||||||
|
This bridge is managed by
|
||||||
|
.
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<BridgeSettingsTab /> renders when room is not bridging messages to any platform 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="mx_SettingsTab"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_SettingsTab_heading"
|
||||||
|
>
|
||||||
|
Bridges
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_SettingsTab_section mx_SettingsTab_subsectionText"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<span>
|
||||||
|
This room isn't bridging messages to any platforms.
|
||||||
|
<a
|
||||||
|
href="https://matrix.org/bridges/"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Learn more.
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`RolesRoomSettingsTab Banned users renders banned users 1`] = `
|
||||||
|
<fieldset
|
||||||
|
class="mx_SettingsFieldset"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="mx_SettingsFieldset_legend"
|
||||||
|
>
|
||||||
|
Banned users
|
||||||
|
</legend>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span
|
||||||
|
title="Banned by @alice:server.org"
|
||||||
|
>
|
||||||
|
<strong>
|
||||||
|
@bob:server.org
|
||||||
|
</strong>
|
||||||
|
|
||||||
|
Reason: just testing
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</fieldset>
|
||||||
|
`;
|
|
@ -0,0 +1,227 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<SecurityRoomSettingsTab /> history visibility uses shared as default history visibility when no state event found 1`] = `
|
||||||
|
<fieldset
|
||||||
|
class="mx_SettingsFieldset"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="mx_SettingsFieldset_legend"
|
||||||
|
>
|
||||||
|
Who can read history?
|
||||||
|
</legend>
|
||||||
|
<div
|
||||||
|
class="mx_SettingsFieldset_description"
|
||||||
|
>
|
||||||
|
Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.
|
||||||
|
</div>
|
||||||
|
<label
|
||||||
|
class="mx_StyledRadioButton mx_StyledRadioButton_enabled"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="historyVis-world_readable"
|
||||||
|
name="historyVis"
|
||||||
|
type="radio"
|
||||||
|
value="world_readable"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_StyledRadioButton_content"
|
||||||
|
>
|
||||||
|
Anyone
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_StyledRadioButton_spacer"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class="mx_StyledRadioButton mx_StyledRadioButton_enabled mx_StyledRadioButton_checked"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked=""
|
||||||
|
id="historyVis-shared"
|
||||||
|
name="historyVis"
|
||||||
|
type="radio"
|
||||||
|
value="shared"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_StyledRadioButton_content"
|
||||||
|
>
|
||||||
|
Members only (since the point in time of selecting this option)
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_StyledRadioButton_spacer"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class="mx_StyledRadioButton mx_StyledRadioButton_enabled"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="historyVis-invited"
|
||||||
|
name="historyVis"
|
||||||
|
type="radio"
|
||||||
|
value="invited"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_StyledRadioButton_content"
|
||||||
|
>
|
||||||
|
Members only (since they were invited)
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_StyledRadioButton_spacer"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class="mx_StyledRadioButton mx_StyledRadioButton_enabled"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="historyVis-joined"
|
||||||
|
name="historyVis"
|
||||||
|
type="radio"
|
||||||
|
value="joined"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_StyledRadioButton_content"
|
||||||
|
>
|
||||||
|
Members only (since they joined)
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_StyledRadioButton_spacer"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<SecurityRoomSettingsTab /> join rule handles error when updating join rule fails 1`] = `
|
||||||
|
<div
|
||||||
|
aria-describedby="mx_Dialog_content"
|
||||||
|
aria-labelledby="mx_BaseDialog_title"
|
||||||
|
class="mx_ErrorDialog mx_Dialog_fixedWidth"
|
||||||
|
data-focus-lock-disabled="false"
|
||||||
|
role="dialog"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_Dialog_header mx_Dialog_headerWithCancel"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
class="mx_Heading_h2 mx_Dialog_title"
|
||||||
|
id="mx_BaseDialog_title"
|
||||||
|
>
|
||||||
|
Failed to update the join rules
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
aria-label="Close dialog"
|
||||||
|
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_Dialog_content"
|
||||||
|
id="mx_Dialog_content"
|
||||||
|
>
|
||||||
|
Unknown failure
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_Dialog_buttons"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="mx_Dialog_primary"
|
||||||
|
>
|
||||||
|
OK
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<SecurityRoomSettingsTab /> join rule warns when trying to make an encrypted room public 1`] = `
|
||||||
|
<div
|
||||||
|
aria-describedby="mx_Dialog_content"
|
||||||
|
aria-labelledby="mx_BaseDialog_title"
|
||||||
|
class="mx_QuestionDialog mx_Dialog_fixedWidth"
|
||||||
|
data-focus-lock-disabled="false"
|
||||||
|
role="dialog"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_Dialog_header mx_Dialog_headerWithCancel"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
class="mx_Heading_h2 mx_Dialog_title"
|
||||||
|
id="mx_BaseDialog_title"
|
||||||
|
>
|
||||||
|
Are you sure you want to make this encrypted room public?
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
aria-label="Close dialog"
|
||||||
|
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_Dialog_content"
|
||||||
|
id="mx_Dialog_content"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<b>
|
||||||
|
It's not recommended to make encrypted rooms public.
|
||||||
|
</b>
|
||||||
|
It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.
|
||||||
|
</span>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
To avoid these issues, create a
|
||||||
|
<div
|
||||||
|
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
|
||||||
|
new public room
|
||||||
|
|
||||||
|
</div>
|
||||||
|
for the conversation you plan to have.
|
||||||
|
</span>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_Dialog_buttons"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_Dialog_buttons_row"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
data-testid="dialog-cancel-button"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="mx_Dialog_primary"
|
||||||
|
data-testid="dialog-primary-button"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
OK
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
Loading…
Add table
Add a link
Reference in a new issue