Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Allow setting knock room directory visibility #11529

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions res/css/views/settings/_JoinRuleSettings.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,8 @@ limitations under the License.
}
}
}

.mx_JoinRuleSettings_labelledCheckbox {
font: var(--cpd-font-body-md-regular);
margin-top: var(--cpd-space-2x);
}
45 changes: 42 additions & 3 deletions src/components/views/settings/JoinRuleSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import React, { ReactNode } from "react";
import { IJoinRuleEventContent, JoinRule, RestrictedAllowType, Room, EventType } from "matrix-js-sdk/src/matrix";
import React, { ReactNode, useEffect, useState } from "react";
import {
IJoinRuleEventContent,
JoinRule,
RestrictedAllowType,
Room,
EventType,
Visibility,
} from "matrix-js-sdk/src/matrix";

import StyledRadioGroup, { IDefinition } from "../elements/StyledRadioGroup";
import { _t } from "../../../languageHandler";
Expand All @@ -34,6 +41,7 @@ import { Action } from "../../../dispatcher/actions";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { doesRoomVersionSupport, PreferredRoomVersions } from "../../../utils/PreferredRoomVersions";
import SettingsStore from "../../../settings/SettingsStore";
import LabelledCheckbox from "../elements/LabelledCheckbox";

export interface JoinRuleSettingsProps {
room: Room;
Expand Down Expand Up @@ -76,6 +84,22 @@ const JoinRuleSettings: React.FC<JoinRuleSettingsProps> = ({
? content?.allow?.filter((o) => o.type === RestrictedAllowType.RoomMembership).map((o) => o.room_id)
: undefined;

const [isPublicKnockRoom, setIsPublicKnockRoom] = useState(false);

useEffect(() => {
if (joinRule === JoinRule.Knock) {
cli.getRoomDirectoryVisibility(room.roomId)
.then(({ visibility }) => setIsPublicKnockRoom(visibility === Visibility.Public))
.catch(onError);
}
}, [cli, joinRule, onError, room.roomId]);

const onIsPublicKnockRoomChange = (checked: boolean): void => {
cli.setRoomDirectoryVisibility(room.roomId, checked ? Visibility.Public : Visibility.Private)
.then(() => setIsPublicKnockRoom(checked))
.catch(onError);
};

const editRestrictedRoomIds = async (): Promise<string[] | undefined> => {
let selected = restrictedAllowRoomIds;
if (!selected?.length && SpaceStore.instance.activeSpaceRoom) {
Expand Down Expand Up @@ -297,7 +321,22 @@ const JoinRuleSettings: React.FC<JoinRuleSettingsProps> = ({
{preferredKnockVersion && upgradeRequiredPill}
</>
),
description: _t("People cannot join unless access is granted."),
description: (
<>
{_t("People cannot join unless access is granted.")}
<LabelledCheckbox
className="mx_JoinRuleSettings_labelledCheckbox"
disabled={joinRule !== JoinRule.Knock}
label={
room.isSpaceRoom()
? _t("Make this space visible in the public room directory.")
: _t("Make this room visible in the public room directory.")
}
onChange={onIsPublicKnockRoomChange}
value={isPublicKnockRoom}
/>
</>
),
});
}

Expand Down
3 changes: 2 additions & 1 deletion src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -1291,6 +1291,8 @@
"Space members": "Space members",
"Ask to join": "Ask to join",
"People cannot join unless access is granted.": "People cannot join unless access is granted.",
"Make this space visible in the public room directory.": "Make this space visible in the public room directory.",
"Make this room visible in the public room directory.": "Make this room visible in the public room directory.",
"This room is in some spaces you're not an admin of. In those spaces, the old room will still be shown, but people will be prompted to join the new one.": "This room is in some spaces you're not an admin of. In those spaces, the old room will still be shown, but people will be prompted to join the new one.",
"This upgrade will allow members of selected spaces access to this room without an invite.": "This upgrade will allow members of selected spaces access to this room without an invite.",
"IRC (Experimental)": "IRC (Experimental)",
Expand Down Expand Up @@ -2708,7 +2710,6 @@
"Anyone will be able to find and join this room.": "Anyone will be able to find and join this room.",
"Only people invited will be able to find and join this room.": "Only people invited will be able to find and join this room.",
"Anyone can request to join, but admins or moderators need to grant access. You can change this later.": "Anyone can request to join, but admins or moderators need to grant access. You can change this later.",
"Make this room visible in the public room directory.": "Make this room visible in the public room directory.",
"You can't disable this later. The room will be encrypted but the embedded call will not.": "You can't disable this later. The room will be encrypted but the embedded call will not.",
"You can't disable this later. Bridges & most bots won't work yet.": "You can't disable this later. Bridges & most bots won't work yet.",
"Your server requires encryption to be enabled in private rooms.": "Your server requires encryption to be enabled in private rooms.",
Expand Down
75 changes: 73 additions & 2 deletions test/components/views/settings/JoinRuleSettings-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ limitations under the License.
*/

import React from "react";
import { fireEvent, render, screen, within } from "@testing-library/react";
import { act, fireEvent, render, screen, within } from "@testing-library/react";
import {
EventType,
GuestAccess,
Expand All @@ -25,6 +25,8 @@ import {
Room,
ClientEvent,
RoomMember,
MatrixError,
Visibility,
} from "matrix-js-sdk/src/matrix";
import { defer, IDeferred } from "matrix-js-sdk/src/utils";

Expand All @@ -51,6 +53,8 @@ describe("<JoinRuleSettings />", () => {
getProfileInfo: jest.fn(),
invite: jest.fn().mockResolvedValue(undefined),
isRoomEncrypted: jest.fn().mockReturnValue(false),
getRoomDirectoryVisibility: jest.fn(),
setRoomDirectoryVisibility: jest.fn(),
});
const roomId = "!room:server.org";
const newRoomId = "!roomUpgraded:server.org";
Expand Down Expand Up @@ -270,10 +274,77 @@ describe("<JoinRuleSettings />", () => {
});
});

describe("knock rooms directory visibility", () => {
const getCheckbox = () => screen.getByRole("checkbox");
let room: Room;

beforeEach(() => (room = new Room(roomId, client, userId)));

describe("when join rule is knock", () => {
beforeEach(() => setRoomStateEvents(room, PreferredRoomVersions.KnockRooms, JoinRule.Knock));

it("should set the visibility to public", async () => {
jest.spyOn(client, "getRoomDirectoryVisibility").mockResolvedValue({ visibility: Visibility.Private });
jest.spyOn(client, "setRoomDirectoryVisibility").mockResolvedValue({});
getComponent({ room });
fireEvent.click(getCheckbox());
await act(async () => await flushPromises());
expect(client.setRoomDirectoryVisibility).toHaveBeenCalledWith(roomId, Visibility.Public);
expect(getCheckbox()).toBeChecked();
});

it("should set the visibility to private", async () => {
jest.spyOn(client, "getRoomDirectoryVisibility").mockResolvedValue({ visibility: Visibility.Public });
jest.spyOn(client, "setRoomDirectoryVisibility").mockResolvedValue({});
getComponent({ room });
await act(async () => await flushPromises());
fireEvent.click(getCheckbox());
await act(async () => await flushPromises());
expect(client.setRoomDirectoryVisibility).toHaveBeenCalledWith(roomId, Visibility.Private);
expect(getCheckbox()).not.toBeChecked();
});

it("should call onError if setting visibility fails", async () => {
const error = new MatrixError();
jest.spyOn(client, "getRoomDirectoryVisibility").mockResolvedValue({ visibility: Visibility.Private });
jest.spyOn(client, "setRoomDirectoryVisibility").mockRejectedValue(error);
getComponent({ room });
fireEvent.click(getCheckbox());
await act(async () => await flushPromises());
expect(getCheckbox()).not.toBeChecked();
expect(defaultProps.onError).toHaveBeenCalledWith(error);
});
});

describe("when the room version is unsupported and upgrade is enabled", () => {
it("should disable the checkbox", () => {
setRoomStateEvents(room, "6", JoinRule.Invite);
getComponent({ promptUpgrade: true, room });
expect(getCheckbox()).toBeDisabled();
});
});

describe("when join rule is not knock", () => {
beforeEach(() => {
setRoomStateEvents(room, PreferredRoomVersions.KnockRooms, JoinRule.Invite);
getComponent({ room });
});

it("should disable the checkbox", () => {
expect(getCheckbox()).toBeDisabled();
});

it("should set the visibility to private by default", () => {
expect(getCheckbox()).not.toBeChecked();
});
});
});

it("should not show knock room join rule", async () => {
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
const room = new Room(newRoomId, client, userId);
getComponent({ room: room });
setRoomStateEvents(room, PreferredRoomVersions.KnockRooms);
getComponent({ room });
expect(screen.queryByText("Ask to join")).not.toBeInTheDocument();
});
});