From c06ffddfa9ea67013da7a481864d843f09646bf5 Mon Sep 17 00:00:00 2001 From: Mikhail Aheichyk Date: Tue, 14 Mar 2023 10:31:23 +0300 Subject: [PATCH 1/2] Show room options menu if "UIComponent.roomOptionsMenu" is enabled Signed-off-by: Mikhail Aheichyk --- .../spotlight/RoomResultContextMenus.tsx | 26 ++++---- src/components/views/rooms/RoomHeader.tsx | 4 +- src/components/views/rooms/RoomTile.tsx | 4 +- src/settings/UIFeature.ts | 5 ++ .../spotlight/RoomResultContextMenus-test.tsx | 66 +++++++++++++++++++ .../views/rooms/RoomHeader-test.tsx | 27 ++++++-- test/components/views/rooms/RoomTile-test.tsx | 34 ++++++++-- 7 files changed, 141 insertions(+), 25 deletions(-) create mode 100644 test/components/views/dialogs/spotlight/RoomResultContextMenus-test.tsx diff --git a/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx b/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx index 8d95860ef8a..14b19fce658 100644 --- a/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx +++ b/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx @@ -27,6 +27,8 @@ import { RoomNotificationContextMenu } from "../../context_menus/RoomNotificatio import SpaceContextMenu from "../../context_menus/SpaceContextMenu"; import { ButtonEvent } from "../../elements/AccessibleButton"; import { contextMenuBelow } from "../../rooms/RoomTile"; +import { shouldShowComponent } from "../../../../customisations/helpers/UIComponents"; +import { UIComponent } from "../../../../settings/UIFeature"; interface Props { room: Room; @@ -80,18 +82,20 @@ export function RoomResultContextMenus({ room }: Props): JSX.Element { return ( - { - ev.preventDefault(); - ev.stopPropagation(); + {shouldShowComponent(UIComponent.RoomOptionsMenu) && ( + { + ev.preventDefault(); + ev.stopPropagation(); - const target = ev.target as HTMLElement; - setGeneralMenuPosition(target.getBoundingClientRect()); - }} - title={room.isSpaceRoom() ? _t("Space options") : _t("Room options")} - isExpanded={generalMenuPosition !== null} - /> + const target = ev.target as HTMLElement; + setGeneralMenuPosition(target.getBoundingClientRect()); + }} + title={room.isSpaceRoom() ? _t("Space options") : _t("Room options")} + isExpanded={generalMenuPosition !== null} + /> + )} {!room.isSpaceRoom() && ( { ); - if (this.props.enableRoomOptionsMenu) { + if (this.props.enableRoomOptionsMenu && shouldShowComponent(UIComponent.RoomOptionsMenu)) { return ( { }; private get showContextMenu(): boolean { - return this.props.tag !== DefaultTagID.Invite; + return this.props.tag !== DefaultTagID.Invite && shouldShowComponent(UIComponent.RoomOptionsMenu); } private get showMessagePreview(): boolean { diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 89cbe203aaa..9ea7c42b116 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -64,4 +64,9 @@ export enum UIComponent { * and integrations to the room, such as from the room information card. */ AddIntegrations = "UIComponent.addIntegrations", + + /** + * Components that lead the user to room options menu. + */ + RoomOptionsMenu = "UIComponent.roomOptionsMenu", } diff --git a/test/components/views/dialogs/spotlight/RoomResultContextMenus-test.tsx b/test/components/views/dialogs/spotlight/RoomResultContextMenus-test.tsx new file mode 100644 index 00000000000..0ead500a072 --- /dev/null +++ b/test/components/views/dialogs/spotlight/RoomResultContextMenus-test.tsx @@ -0,0 +1,66 @@ +/* +Copyright 2023 Mikhail Aheichyk +Copyright 2023 Nordeck IT + Consulting GmbH. + +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, screen, RenderResult } from "@testing-library/react"; +import { mocked } from "jest-mock"; +import { Room } from "matrix-js-sdk/src/models/room"; +import { MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/client"; + +import { RoomResultContextMenus } from "../../../../../src/components/views/dialogs/spotlight/RoomResultContextMenus"; +import { filterConsole, stubClient } from "../../../../test-utils"; +import { shouldShowComponent } from "../../../../../src/customisations/helpers/UIComponents"; +import { UIComponent } from "../../../../../src/settings/UIFeature"; + +jest.mock("../../../../../src/customisations/helpers/UIComponents", () => ({ + shouldShowComponent: jest.fn(), +})); + +describe("RoomResultContextMenus", () => { + let client: MatrixClient; + let room: Room; + + const renderRoomResultContextMenus = (): RenderResult => { + return render(); + }; + + filterConsole( + // irrelevant for this test + "Room !1:example.org does not have an m.room.create event", + ); + + beforeEach(() => { + client = stubClient(); + room = new Room("!1:example.org", client, "@alice:example.org", { + pendingEventOrdering: PendingEventOrdering.Detached, + }); + }); + + it("does not render the room options context menu when UIComponent customisations disable room options", () => { + mocked(shouldShowComponent).mockReturnValue(false); + renderRoomResultContextMenus(); + expect(shouldShowComponent).toHaveBeenCalledWith(UIComponent.RoomOptionsMenu); + expect(screen.queryByRole("button", { name: "Room options" })).not.toBeInTheDocument(); + }); + + it("renders the room options context menu when UIComponent customisations enable room options", () => { + mocked(shouldShowComponent).mockReturnValue(true); + renderRoomResultContextMenus(); + expect(shouldShowComponent).toHaveBeenCalledWith(UIComponent.RoomOptionsMenu); + expect(screen.queryByRole("button", { name: "Room options" })).toBeInTheDocument(); + }); +}); diff --git a/test/components/views/rooms/RoomHeader-test.tsx b/test/components/views/rooms/RoomHeader-test.tsx index 35cb5d200c9..6943d52710e 100644 --- a/test/components/views/rooms/RoomHeader-test.tsx +++ b/test/components/views/rooms/RoomHeader-test.tsx @@ -57,6 +57,12 @@ import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessa import WidgetUtils from "../../../../src/utils/WidgetUtils"; import { ElementWidgetActions } from "../../../../src/stores/widgets/ElementWidgetActions"; import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../../src/MediaDeviceHandler"; +import { shouldShowComponent } from "../../../../src/customisations/helpers/UIComponents"; +import { UIComponent } from "../../../../src/settings/UIFeature"; + +jest.mock("../../../../src/customisations/helpers/UIComponents", () => ({ + shouldShowComponent: jest.fn(), +})); describe("RoomHeader", () => { let client: Mocked; @@ -729,17 +735,26 @@ describe("RoomHeader", () => { expect(wrapper.container.querySelector(".mx_RoomHeader_button")).toBeFalsy(); }); - it("should render the room options context menu if not passing enableRoomOptionsMenu (default true)", () => { + it("should render the room options context menu if not passing enableRoomOptionsMenu (default true) and UIComponent customisations room options enabled", () => { + mocked(shouldShowComponent).mockReturnValue(true); const room = createRoom({ name: "Room", isDm: false, userIds: [] }); const wrapper = mountHeader(room); + expect(shouldShowComponent).toHaveBeenCalledWith(UIComponent.RoomOptionsMenu); expect(wrapper.container.querySelector(".mx_RoomHeader_name.mx_AccessibleButton")).toBeDefined(); }); - it("should not render the room options context menu if passing enableRoomOptionsMenu = false", () => { - const room = createRoom({ name: "Room", isDm: false, userIds: [] }); - const wrapper = mountHeader(room, { enableRoomOptionsMenu: false }); - expect(wrapper.container.querySelector(".mx_RoomHeader_name.mx_AccessibleButton")).toBeFalsy(); - }); + it.each([ + [false, true], + [true, false], + ])( + "should not render the room options context menu if passing enableRoomOptionsMenu = %s and UIComponent customisations room options enable = %s", + (enableRoomOptionsMenu, showRoomOptionsMenu) => { + mocked(shouldShowComponent).mockReturnValue(showRoomOptionsMenu); + const room = createRoom({ name: "Room", isDm: false, userIds: [] }); + const wrapper = mountHeader(room, { enableRoomOptionsMenu }); + expect(wrapper.container.querySelector(".mx_RoomHeader_name.mx_AccessibleButton")).toBeFalsy(); + }, + ); }); interface IRoomCreationInfo { diff --git a/test/components/views/rooms/RoomTile-test.tsx b/test/components/views/rooms/RoomTile-test.tsx index b4bfde243c1..dcff896cad0 100644 --- a/test/components/views/rooms/RoomTile-test.tsx +++ b/test/components/views/rooms/RoomTile-test.tsx @@ -45,6 +45,12 @@ import { VoiceBroadcastInfoState } from "../../../../src/voice-broadcast"; import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils"; import { TestSdkContext } from "../../../TestSdkContext"; import { SDKContext } from "../../../../src/contexts/SDKContext"; +import { shouldShowComponent } from "../../../../src/customisations/helpers/UIComponents"; +import { UIComponent } from "../../../../src/settings/UIFeature"; + +jest.mock("../../../../src/customisations/helpers/UIComponents", () => ({ + shouldShowComponent: jest.fn(), +})); describe("RoomTile", () => { jest.spyOn(PlatformPeg, "get").mockReturnValue({ @@ -66,8 +72,8 @@ describe("RoomTile", () => { }); }; - const renderRoomTile = (): void => { - renderResult = render( + const renderRoomTile = (): RenderResult => { + return render( , @@ -77,7 +83,6 @@ describe("RoomTile", () => { let client: Mocked; let voiceBroadcastInfoEvent: MatrixEvent; let room: Room; - let renderResult: RenderResult; let sdkContext: TestSdkContext; filterConsole( @@ -99,8 +104,6 @@ describe("RoomTile", () => { client.getRoom.mockImplementation((roomId) => (roomId === room.roomId ? room : null)); client.getRooms.mockReturnValue([room]); client.reEmitter.reEmit(room, [RoomStateEvent.Events]); - - renderRoomTile(); }); afterEach(() => { @@ -108,9 +111,25 @@ describe("RoomTile", () => { }); it("should render the room", () => { + mocked(shouldShowComponent).mockReturnValue(true); + const renderResult: RenderResult = renderRoomTile(); expect(renderResult.container).toMatchSnapshot(); }); + it("does not render the room options context menu when UIComponent customisations disable room options", () => { + mocked(shouldShowComponent).mockReturnValue(false); + renderRoomTile(); + expect(shouldShowComponent).toHaveBeenCalledWith(UIComponent.RoomOptionsMenu); + expect(screen.queryByRole("button", { name: "Room options" })).not.toBeInTheDocument(); + }); + + it("renders the room options context menu when UIComponent customisations enable room options", () => { + mocked(shouldShowComponent).mockReturnValue(true); + renderRoomTile(); + expect(shouldShowComponent).toHaveBeenCalledWith(UIComponent.RoomOptionsMenu); + expect(screen.queryByRole("button", { name: "Room options" })).toBeInTheDocument(); + }); + describe("when a call starts", () => { let call: MockedCall; let widget: Widget; @@ -131,13 +150,13 @@ describe("RoomTile", () => { }); afterEach(() => { - renderResult.unmount(); call.destroy(); client.reEmitter.stopReEmitting(room, [RoomStateEvent.Events]); WidgetMessagingStore.instance.stopMessaging(widget, room.roomId); }); it("tracks connection state", async () => { + renderRoomTile(); screen.getByText("Video"); // Insert an await point in the connection method so we can inspect @@ -160,6 +179,7 @@ describe("RoomTile", () => { }); it("tracks participants", () => { + renderRoomTile(); const alice: [RoomMember, Set] = [mkRoomMember(room.roomId, "@alice:example.org"), new Set(["a"])]; const bob: [RoomMember, Set] = [ mkRoomMember(room.roomId, "@bob:example.org"), @@ -187,6 +207,7 @@ describe("RoomTile", () => { describe("and a live broadcast starts", () => { beforeEach(async () => { + renderRoomTile(); await setUpVoiceBroadcast(VoiceBroadcastInfoState.Started); }); @@ -199,6 +220,7 @@ describe("RoomTile", () => { describe("when a live voice broadcast starts", () => { beforeEach(async () => { + renderRoomTile(); await setUpVoiceBroadcast(VoiceBroadcastInfoState.Started); }); From a5784b86775281eda58daaa5844f9045fcbaf416 Mon Sep 17 00:00:00 2001 From: Mikhail Aheichyk Date: Fri, 9 Jun 2023 14:57:08 +0300 Subject: [PATCH 2/2] Explicit type is removed. Signed-off-by: Mikhail Aheichyk --- test/components/views/rooms/RoomTile-test.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/components/views/rooms/RoomTile-test.tsx b/test/components/views/rooms/RoomTile-test.tsx index d4aa02c29a8..51f06f00058 100644 --- a/test/components/views/rooms/RoomTile-test.tsx +++ b/test/components/views/rooms/RoomTile-test.tsx @@ -155,7 +155,7 @@ describe("RoomTile", () => { describe("when message previews are not enabled", () => { it("should render the room", () => { mocked(shouldShowComponent).mockReturnValue(true); - const renderResult: RenderResult = renderRoomTile(); + const renderResult = renderRoomTile(); expect(renderResult.container).toMatchSnapshot(); }); @@ -305,7 +305,7 @@ describe("RoomTile", () => { }); it("should render a room without a message as expected", async () => { - const renderResult: RenderResult = renderRoomTile(); + const renderResult = renderRoomTile(); // flush promises here because the preview is created asynchronously await flushPromises(); expect(renderResult.asFragment()).toMatchSnapshot(); @@ -317,7 +317,7 @@ describe("RoomTile", () => { }); it("should render as expected", async () => { - const renderResult: RenderResult = renderRoomTile(); + const renderResult = renderRoomTile(); expect(await screen.findByText("test message")).toBeInTheDocument(); expect(renderResult.asFragment()).toMatchSnapshot(); }); @@ -329,7 +329,7 @@ describe("RoomTile", () => { }); it("should render as expected", async () => { - const renderResult: RenderResult = renderRoomTile(); + const renderResult = renderRoomTile(); expect(await screen.findByText("test thread reply")).toBeInTheDocument(); expect(renderResult.asFragment()).toMatchSnapshot(); });