From 798f13a071eacc16b62b563594de954ab8b27c2a Mon Sep 17 00:00:00 2001 From: Charly Nguyen <charly.nguyen@nordeck.net> Date: Mon, 21 Aug 2023 09:27:47 +0200 Subject: [PATCH] Allow setting knock room visibility Signed-off-by: Charly Nguyen <charly.nguyen@nordeck.net> --- res/css/views/dialogs/_CreateRoomDialog.pcss | 5 ++ res/css/views/settings/_JoinRuleSettings.pcss | 5 ++ .../views/dialogs/CreateRoomDialog.tsx | 19 ++++ .../dialogs/spotlight/SpotlightDialog.tsx | 36 +++++++- .../views/elements/LabelledCheckbox.tsx | 7 +- .../views/settings/JoinRuleSettings.tsx | 46 +++++++++- src/i18n/strings/en_EN.json | 3 + .../views/dialogs/CreateRoomDialog-test.tsx | 87 ++++++++++++------- .../views/dialogs/SpotlightDialog-test.tsx | 73 ++++++++++++++++ .../views/elements/LabelledCheckbox-test.tsx | 12 +++ .../views/settings/JoinRuleSettings-test.tsx | 73 +++++++++++++++- 11 files changed, 325 insertions(+), 41 deletions(-) diff --git a/res/css/views/dialogs/_CreateRoomDialog.pcss b/res/css/views/dialogs/_CreateRoomDialog.pcss index 437044cc8fe..4e3d90cffe6 100644 --- a/res/css/views/dialogs/_CreateRoomDialog.pcss +++ b/res/css/views/dialogs/_CreateRoomDialog.pcss @@ -113,3 +113,8 @@ limitations under the License. font-size: $font-12px; } } + +.mx_CreateRoomDialog_labelledCheckbox { + color: $muted-fg-color; + margin-top: var(--cpd-space-6x); +} diff --git a/res/css/views/settings/_JoinRuleSettings.pcss b/res/css/views/settings/_JoinRuleSettings.pcss index de06580ea7e..8841dd522bb 100644 --- a/res/css/views/settings/_JoinRuleSettings.pcss +++ b/res/css/views/settings/_JoinRuleSettings.pcss @@ -79,3 +79,8 @@ limitations under the License. } } } + +.mx_JoinRuleSettings_labelledCheckbox { + font: var(--cpd-font-body-md-regular); + margin-top: var(--cpd-space-2x); +} diff --git a/src/components/views/dialogs/CreateRoomDialog.tsx b/src/components/views/dialogs/CreateRoomDialog.tsx index 276f1195b3d..6546d06a43e 100644 --- a/src/components/views/dialogs/CreateRoomDialog.tsx +++ b/src/components/views/dialogs/CreateRoomDialog.tsx @@ -33,6 +33,7 @@ import { getKeyBindingsManager } from "../../../KeyBindingsManager"; import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; import { privateShouldBeEncrypted } from "../../../utils/rooms"; import SettingsStore from "../../../settings/SettingsStore"; +import LabelledCheckbox from "../elements/LabelledCheckbox"; interface IProps { type?: RoomType; @@ -129,6 +130,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> { if (this.state.joinRule === JoinRule.Knock) { opts.joinRule = JoinRule.Knock; + createOpts.visibility = this.state.isPublic ? Visibility.Public : Visibility.Private; } return opts; @@ -215,6 +217,10 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> { return result; }; + private onIsPublicChange = (isPublic: boolean): void => { + this.setState({ isPublic }); + }; + private static validateRoomName = withValidation({ rules: [ { @@ -298,6 +304,18 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> { ); } + let visibilitySection: JSX.Element | undefined; + if (this.state.joinRule === JoinRule.Knock) { + visibilitySection = ( + <LabelledCheckbox + className="mx_CreateRoomDialog_labelledCheckbox" + label={_t("Make this room visible in the public room directory.")} + onChange={this.onIsPublicChange} + value={this.state.isPublic} + /> + ); + } + let e2eeSection: JSX.Element | undefined; if (this.state.joinRule !== JoinRule.Public) { let microcopy: string; @@ -383,6 +401,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> { /> {publicPrivateLabel} + {visibilitySection} {e2eeSection} {aliasField} <details onToggle={this.onDetailsToggled} className="mx_CreateRoomDialog_details"> diff --git a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx index ac6365b8776..11bf7ee8a08 100644 --- a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx +++ b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx @@ -24,6 +24,7 @@ import { RoomType, Room, HierarchyRoom, + JoinRule, } from "matrix-js-sdk/src/matrix"; import { normalize } from "matrix-js-sdk/src/utils"; import React, { ChangeEvent, RefObject, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; @@ -276,6 +277,10 @@ const roomAriaUnreadLabel = (room: Room, notification: RoomNotificationState): s } }; +const canAskToJoin = (joinRule?: JoinRule): boolean => { + return SettingsStore.getValue("feature_ask_to_join") && JoinRule.Knock === joinRule; +}; + interface IDirectoryOpts { limit: number; query: string; @@ -514,7 +519,14 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n }, [results, filter]); const viewRoom = ( - room: { roomId: string; roomAlias?: string; autoJoin?: boolean; shouldPeek?: boolean; viaServers?: string[] }, + room: { + roomId: string; + roomAlias?: string; + autoJoin?: boolean; + shouldPeek?: boolean; + viaServers?: string[]; + joinRule?: IPublicRoomsChunkRoom["join_rule"]; + }, persist = false, viaKeyboard = false, ): void => { @@ -538,10 +550,15 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n metricsViaKeyboard: viaKeyboard, room_id: room.roomId, room_alias: room.roomAlias, - auto_join: room.autoJoin, + auto_join: room.autoJoin && !canAskToJoin(room.joinRule), should_peek: room.shouldPeek, via_servers: room.viaServers, }); + + if (canAskToJoin(room.joinRule)) { + defaultDispatcher.dispatch({ action: Action.PromptAskToJoin }); + } + onFinished(); }; @@ -651,12 +668,15 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n } if (isPublicRoomResult(result)) { const clientRoom = cli.getRoom(result.publicRoom.room_id); + const joinRule = result.publicRoom.join_rule; // Element Web currently does not allow guests to join rooms, so we // instead show them view buttons for all rooms. If the room is not // world readable, a modal will appear asking you to register first. If // it is readable, the preview appears as normal. const showViewButton = - clientRoom?.getMyMembership() === "join" || result.publicRoom.world_readable || cli.isGuest(); + clientRoom?.getMyMembership() === "join" || + (result.publicRoom.world_readable && !canAskToJoin(joinRule)) || + cli.isGuest(); const listener = (ev: ButtonEvent): void => { ev.stopPropagation(); @@ -669,12 +689,20 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n autoJoin: !result.publicRoom.world_readable && !cli.isGuest(), shouldPeek: result.publicRoom.world_readable || cli.isGuest(), viaServers: config ? [config.roomServer] : undefined, + joinRule, }, true, ev.type !== "click", ); }; + let buttonLabel; + if (showViewButton) { + buttonLabel = _t("action|view"); + } else { + buttonLabel = canAskToJoin(joinRule) ? _t("action|ask_to_join") : _t("action|join"); + } + return ( <Option id={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}`} @@ -687,7 +715,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n onClick={listener} tabIndex={-1} > - {showViewButton ? _t("action|view") : _t("action|join")} + {buttonLabel} </AccessibleButton> } aria-labelledby={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_name`} diff --git a/src/components/views/elements/LabelledCheckbox.tsx b/src/components/views/elements/LabelledCheckbox.tsx index 86604d0989a..72eded8203a 100644 --- a/src/components/views/elements/LabelledCheckbox.tsx +++ b/src/components/views/elements/LabelledCheckbox.tsx @@ -15,6 +15,7 @@ limitations under the License. */ import React from "react"; +import classnames from "classnames"; import StyledCheckbox from "./StyledCheckbox"; @@ -29,11 +30,13 @@ interface IProps { disabled?: boolean; // The function to call when the value changes onChange(checked: boolean): void; + // Optional CSS class to apply to the label + className?: string; } -const LabelledCheckbox: React.FC<IProps> = ({ value, label, byline, disabled, onChange }) => { +const LabelledCheckbox: React.FC<IProps> = ({ value, label, byline, disabled, onChange, className }) => { return ( - <label className="mx_LabelledCheckbox"> + <label className={classnames("mx_LabelledCheckbox", className)}> <StyledCheckbox disabled={disabled} checked={value} onChange={(e) => onChange(e.target.checked)} /> <div className="mx_LabelledCheckbox_labels"> <span className="mx_LabelledCheckbox_label">{label}</span> diff --git a/src/components/views/settings/JoinRuleSettings.tsx b/src/components/views/settings/JoinRuleSettings.tsx index 9d33c114175..085fc4d1527 100644 --- a/src/components/views/settings/JoinRuleSettings.tsx +++ b/src/components/views/settings/JoinRuleSettings.tsx @@ -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"; @@ -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; @@ -76,6 +84,23 @@ const JoinRuleSettings: React.FC<JoinRuleSettingsProps> = ({ ? content?.allow?.filter((o) => o.type === RestrictedAllowType.RoomMembership).map((o) => o.room_id) : undefined; + const [visibility, setVisibility] = useState(Visibility.Private); + + useEffect(() => { + if (joinRule === JoinRule.Knock) { + cli.getRoomDirectoryVisibility(room.roomId) + .then(({ visibility }) => setVisibility(visibility)) + .catch(onError); + } + }, [cli, joinRule, onError, room.roomId]); + + const onVisibilityChange = (checked: boolean): void => { + const visibility = checked ? Visibility.Public : Visibility.Private; + cli.setRoomDirectoryVisibility(room.roomId, visibility) + .then(() => setVisibility(visibility)) + .catch(onError); + }; + const editRestrictedRoomIds = async (): Promise<string[] | undefined> => { let selected = restrictedAllowRoomIds; if (!selected?.length && SpaceStore.instance.activeSpaceRoom) { @@ -297,7 +322,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={!!preferredKnockVersion || 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={onVisibilityChange} + value={visibility === Visibility.Public} + /> + </> + ), }); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index cf1863c8442..6bb9f24780a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -74,6 +74,7 @@ "resend": "Resend", "next": "Next", "view": "View", + "ask_to_join": "Ask to join", "forward": "Forward", "copy_link": "Copy link", "logout": "Logout", @@ -1192,6 +1193,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)", diff --git a/test/components/views/dialogs/CreateRoomDialog-test.tsx b/test/components/views/dialogs/CreateRoomDialog-test.tsx index 9a43cc63dea..d51b9b298da 100644 --- a/test/components/views/dialogs/CreateRoomDialog-test.tsx +++ b/test/components/views/dialogs/CreateRoomDialog-test.tsx @@ -210,45 +210,72 @@ describe("<CreateRoomDialog />", () => { }); describe("for a knock room", () => { - it("should not have the option to create a knock room", async () => { - jest.spyOn(SettingsStore, "getValue").mockReturnValue(false); - getComponent(); - fireEvent.click(screen.getByLabelText("Room visibility")); - - expect(screen.queryByRole("option", { name: "Ask to join" })).not.toBeInTheDocument(); + describe("when disabling feature", () => { + it("should not have the option to create a knock room", async () => { + jest.spyOn(SettingsStore, "getValue").mockReturnValue(false); + getComponent(); + fireEvent.click(screen.getByLabelText("Room visibility")); + expect(screen.queryByRole("option", { name: "Ask to join" })).not.toBeInTheDocument(); + }); }); - it("should create a knock room", async () => { - jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => setting === "feature_ask_to_join"); + describe("when enabling feature", () => { const onFinished = jest.fn(); - getComponent({ onFinished }); - await flushPromises(); - const roomName = "Test Room Name"; - fireEvent.change(screen.getByLabelText("Name"), { target: { value: roomName } }); - fireEvent.click(screen.getByLabelText("Room visibility")); - fireEvent.click(screen.getByRole("option", { name: "Ask to join" })); + beforeEach(async () => { + jest.spyOn(SettingsStore, "getValue").mockImplementation( + (setting) => setting === "feature_ask_to_join", + ); + getComponent({ onFinished }); + fireEvent.change(screen.getByLabelText("Name"), { target: { value: roomName } }); + fireEvent.click(screen.getByLabelText("Room visibility")); + fireEvent.click(screen.getByRole("option", { name: "Ask to join" })); + }); - fireEvent.click(screen.getByText("Create room")); - await flushPromises(); + it("should have a heading", () => { + expect(screen.getByRole("heading")).toHaveTextContent("Create a room"); + }); - expect(screen.getByText("Create a room")).toBeInTheDocument(); + it("should have a hint", () => { + expect( + screen.getByText( + "Anyone can request to join, but admins or moderators need to grant access. You can change this later.", + ), + ).toBeInTheDocument(); + }); - expect( - screen.getByText( - "Anyone can request to join, but admins or moderators need to grant access. You can change this later.", - ), - ).toBeInTheDocument(); + it("should create a private knock room", async () => { + fireEvent.click(screen.getByText("Create room")); + await flushPromises(); + expect(onFinished).toHaveBeenCalledWith(true, { + createOpts: { + name: roomName, + visibility: Visibility.Private, + }, + encryption: true, + joinRule: JoinRule.Knock, + parentSpace: undefined, + roomType: undefined, + }); + }); - expect(onFinished).toHaveBeenCalledWith(true, { - createOpts: { - name: roomName, - }, - encryption: true, - joinRule: JoinRule.Knock, - parentSpace: undefined, - roomType: undefined, + it("should create a public knock room", async () => { + fireEvent.click( + screen.getByRole("checkbox", { name: "Make this room visible in the public room directory." }), + ); + fireEvent.click(screen.getByText("Create room")); + await flushPromises(); + expect(onFinished).toHaveBeenCalledWith(true, { + createOpts: { + name: roomName, + visibility: Visibility.Public, + }, + encryption: true, + joinRule: JoinRule.Knock, + parentSpace: undefined, + roomType: undefined, + }); }); }); }); diff --git a/test/components/views/dialogs/SpotlightDialog-test.tsx b/test/components/views/dialogs/SpotlightDialog-test.tsx index 44fd98c9f60..5ec1108bc4b 100644 --- a/test/components/views/dialogs/SpotlightDialog-test.tsx +++ b/test/components/views/dialogs/SpotlightDialog-test.tsx @@ -20,6 +20,7 @@ import { ConnectionError, IProtocol, IPublicRoomsChunkRoom, + JoinRule, MatrixClient, Room, RoomMember, @@ -38,6 +39,7 @@ import SettingsStore from "../../../../src/settings/SettingsStore"; import { SettingLevel } from "../../../../src/settings/SettingLevel"; import defaultDispatcher from "../../../../src/dispatcher/dispatcher"; import SdkConfig from "../../../../src/SdkConfig"; +import { Action } from "../../../../src/dispatcher/actions"; jest.useFakeTimers(); @@ -574,4 +576,75 @@ describe("Spotlight Dialog", () => { expect(screen.getByText("Failed to query public rooms")).toBeInTheDocument(); }); + + describe("knock rooms", () => { + const knockRoom: IPublicRoomsChunkRoom = { + guest_can_join: false, + join_rule: JoinRule.Knock, + num_joined_members: 0, + room_id: "some-room-id", + world_readable: false, + }; + + const viewRoomParams = { + action: Action.ViewRoom, + metricsTrigger: "WebUnifiedSearch", + metricsViaKeyboard: false, + room_alias: undefined, + room_id: knockRoom.room_id, + should_peek: false, + via_servers: ["example.tld"], + }; + + beforeEach(() => (mockedClient = mockClient({ rooms: [knockRoom] }))); + + describe("when disabling feature", () => { + beforeEach(async () => { + jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => + setting === "feature_ask_to_join" ? false : [], + ); + + render(<SpotlightDialog initialFilter={Filter.PublicRooms} onFinished={() => {}} />); + + // search is debounced + jest.advanceTimersByTime(200); + await flushPromisesWithFakeTimers(); + + fireEvent.click(screen.getByRole("button", { name: "View" })); + }); + + it("should not skip to auto join", async () => { + expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ ...viewRoomParams, auto_join: true }); + }); + + it("should not prompt ask to join", async () => { + expect(defaultDispatcher.dispatch).not.toHaveBeenCalledWith({ action: Action.PromptAskToJoin }); + }); + }); + + describe("when enabling feature", () => { + beforeEach(async () => { + jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => + setting === "feature_ask_to_join" ? true : [], + ); + jest.spyOn(mockedClient, "getRoom").mockReturnValue(null); + + render(<SpotlightDialog initialFilter={Filter.PublicRooms} onFinished={() => {}} />); + + // search is debounced + jest.advanceTimersByTime(200); + await flushPromisesWithFakeTimers(); + + fireEvent.click(screen.getByRole("button", { name: "Ask to join" })); + }); + + it("should skip to auto join", async () => { + expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ ...viewRoomParams, auto_join: false }); + }); + + it("should prompt ask to join", async () => { + expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: Action.PromptAskToJoin }); + }); + }); + }); }); diff --git a/test/components/views/elements/LabelledCheckbox-test.tsx b/test/components/views/elements/LabelledCheckbox-test.tsx index c619794d90e..3e5917cb522 100644 --- a/test/components/views/elements/LabelledCheckbox-test.tsx +++ b/test/components/views/elements/LabelledCheckbox-test.tsx @@ -89,4 +89,16 @@ describe("<LabelledCheckbox />", () => { expect(checkbox).toBeChecked(); expect(checkbox).toBeDisabled(); }); + + it("should render with a custom class name", () => { + const className = "some class name"; + const props: CompProps = { + label: "Hello world", + value: false, + onChange: jest.fn(), + className, + }; + const { container } = render(getComponent(props)); + expect(container.firstElementChild?.className).toContain(className); + }); }); diff --git a/test/components/views/settings/JoinRuleSettings-test.tsx b/test/components/views/settings/JoinRuleSettings-test.tsx index 2b25da37116..3acaa4ca042 100644 --- a/test/components/views/settings/JoinRuleSettings-test.tsx +++ b/test/components/views/settings/JoinRuleSettings-test.tsx @@ -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, @@ -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"; @@ -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"; @@ -270,10 +274,75 @@ describe("<JoinRuleSettings />", () => { }); }); + describe("knock rooms visibility", () => { + const getCheckbox = () => screen.getByRole("checkbox"); + const room = new Room(roomId, client, userId); + + describe("when join rule is knock", () => { + const error = new MatrixError(); + 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 () => { + 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.Public); + getComponent({ promptUpgrade: true, room }); + expect(getCheckbox()).toBeDisabled(); + }); + }); + + describe("when join rule is not knock", () => { + beforeEach(() => { + setRoomStateEvents(room, PreferredRoomVersions.KnockRooms, JoinRule.Public); + getComponent({ room }); + }); + + it("should disable the checkbox", async () => { + expect(getCheckbox()).toBeDisabled(); + }); + + it("should set the visibility to private by default", async () => { + 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(); }); });