Show room options menu if "UIComponent.roomOptionsMenu" is enabled (#10365)
* Show room options menu if "UIComponent.roomOptionsMenu" is enabled Signed-off-by: Mikhail Aheichyk <mikhail.aheichyk@nordeck.net> * Explicit type is removed. Signed-off-by: Mikhail Aheichyk <mikhail.aheichyk@nordeck.net> --------- Signed-off-by: Mikhail Aheichyk <mikhail.aheichyk@nordeck.net> Co-authored-by: Mikhail Aheichyk <mikhail.aheichyk@nordeck.net>
This commit is contained in:
parent
53415bfdfe
commit
53b42e3217
7 changed files with 144 additions and 30 deletions
|
@ -27,6 +27,8 @@ import { RoomNotificationContextMenu } from "../../context_menus/RoomNotificatio
|
||||||
import SpaceContextMenu from "../../context_menus/SpaceContextMenu";
|
import SpaceContextMenu from "../../context_menus/SpaceContextMenu";
|
||||||
import { ButtonEvent } from "../../elements/AccessibleButton";
|
import { ButtonEvent } from "../../elements/AccessibleButton";
|
||||||
import { contextMenuBelow } from "../../rooms/RoomTile";
|
import { contextMenuBelow } from "../../rooms/RoomTile";
|
||||||
|
import { shouldShowComponent } from "../../../../customisations/helpers/UIComponents";
|
||||||
|
import { UIComponent } from "../../../../settings/UIFeature";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -80,18 +82,20 @@ export function RoomResultContextMenus({ room }: Props): JSX.Element {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<ContextMenuTooltipButton
|
{shouldShowComponent(UIComponent.RoomOptionsMenu) && (
|
||||||
className="mx_SpotlightDialog_option--menu"
|
<ContextMenuTooltipButton
|
||||||
onClick={(ev: ButtonEvent) => {
|
className="mx_SpotlightDialog_option--menu"
|
||||||
ev.preventDefault();
|
onClick={(ev: ButtonEvent) => {
|
||||||
ev.stopPropagation();
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
const target = ev.target as HTMLElement;
|
const target = ev.target as HTMLElement;
|
||||||
setGeneralMenuPosition(target.getBoundingClientRect());
|
setGeneralMenuPosition(target.getBoundingClientRect());
|
||||||
}}
|
}}
|
||||||
title={room.isSpaceRoom() ? _t("Space options") : _t("Room options")}
|
title={room.isSpaceRoom() ? _t("Space options") : _t("Room options")}
|
||||||
isExpanded={generalMenuPosition !== null}
|
isExpanded={generalMenuPosition !== null}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
{!room.isSpaceRoom() && (
|
{!room.isSpaceRoom() && (
|
||||||
<ContextMenuTooltipButton
|
<ContextMenuTooltipButton
|
||||||
className={notificationMenuClasses}
|
className={notificationMenuClasses}
|
||||||
|
|
|
@ -69,6 +69,8 @@ import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||||
import { GroupCallDuration } from "../voip/CallDuration";
|
import { GroupCallDuration } from "../voip/CallDuration";
|
||||||
import { Alignment } from "../elements/Tooltip";
|
import { Alignment } from "../elements/Tooltip";
|
||||||
import RoomCallBanner from "../beacon/RoomCallBanner";
|
import RoomCallBanner from "../beacon/RoomCallBanner";
|
||||||
|
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
|
||||||
|
import { UIComponent } from "../../../settings/UIFeature";
|
||||||
|
|
||||||
class DisabledWithReason {
|
class DisabledWithReason {
|
||||||
public constructor(public readonly reason: string) {}
|
public constructor(public readonly reason: string) {}
|
||||||
|
@ -697,7 +699,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
||||||
</RoomName>
|
</RoomName>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.props.enableRoomOptionsMenu) {
|
if (this.props.enableRoomOptionsMenu && shouldShowComponent(UIComponent.RoomOptionsMenu)) {
|
||||||
return (
|
return (
|
||||||
<ContextMenuTooltipButton
|
<ContextMenuTooltipButton
|
||||||
className="mx_RoomHeader_name"
|
className="mx_RoomHeader_name"
|
||||||
|
|
|
@ -49,6 +49,8 @@ import { CallStore, CallStoreEvent } from "../../../stores/CallStore";
|
||||||
import { SdkContextClass } from "../../../contexts/SDKContext";
|
import { SdkContextClass } from "../../../contexts/SDKContext";
|
||||||
import { useHasRoomLiveVoiceBroadcast } from "../../../voice-broadcast";
|
import { useHasRoomLiveVoiceBroadcast } from "../../../voice-broadcast";
|
||||||
import { RoomTileSubtitle } from "./RoomTileSubtitle";
|
import { RoomTileSubtitle } from "./RoomTileSubtitle";
|
||||||
|
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
|
||||||
|
import { UIComponent } from "../../../settings/UIFeature";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -118,7 +120,7 @@ export class RoomTile extends React.PureComponent<ClassProps, State> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private get showContextMenu(): boolean {
|
private get showContextMenu(): boolean {
|
||||||
return this.props.tag !== DefaultTagID.Invite;
|
return this.props.tag !== DefaultTagID.Invite && shouldShowComponent(UIComponent.RoomOptionsMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
private get showMessagePreview(): boolean {
|
private get showMessagePreview(): boolean {
|
||||||
|
|
|
@ -70,4 +70,9 @@ export enum UIComponent {
|
||||||
* Component that lead to the user being able to search, dial, explore rooms
|
* Component that lead to the user being able to search, dial, explore rooms
|
||||||
*/
|
*/
|
||||||
FilterContainer = "UIComponent.filterContainer",
|
FilterContainer = "UIComponent.filterContainer",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Components that lead the user to room options menu.
|
||||||
|
*/
|
||||||
|
RoomOptionsMenu = "UIComponent.roomOptionsMenu",
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(<RoomResultContextMenus room={room} />);
|
||||||
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
|
@ -57,6 +57,12 @@ import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessa
|
||||||
import WidgetUtils from "../../../../src/utils/WidgetUtils";
|
import WidgetUtils from "../../../../src/utils/WidgetUtils";
|
||||||
import { ElementWidgetActions } from "../../../../src/stores/widgets/ElementWidgetActions";
|
import { ElementWidgetActions } from "../../../../src/stores/widgets/ElementWidgetActions";
|
||||||
import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../../src/MediaDeviceHandler";
|
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", () => {
|
describe("RoomHeader", () => {
|
||||||
let client: Mocked<MatrixClient>;
|
let client: Mocked<MatrixClient>;
|
||||||
|
@ -729,17 +735,26 @@ describe("RoomHeader", () => {
|
||||||
expect(wrapper.container.querySelector(".mx_RoomHeader_button")).toBeFalsy();
|
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 room = createRoom({ name: "Room", isDm: false, userIds: [] });
|
||||||
const wrapper = mountHeader(room);
|
const wrapper = mountHeader(room);
|
||||||
|
expect(shouldShowComponent).toHaveBeenCalledWith(UIComponent.RoomOptionsMenu);
|
||||||
expect(wrapper.container.querySelector(".mx_RoomHeader_name.mx_AccessibleButton")).toBeDefined();
|
expect(wrapper.container.querySelector(".mx_RoomHeader_name.mx_AccessibleButton")).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not render the room options context menu if passing enableRoomOptionsMenu = false", () => {
|
it.each([
|
||||||
const room = createRoom({ name: "Room", isDm: false, userIds: [] });
|
[false, true],
|
||||||
const wrapper = mountHeader(room, { enableRoomOptionsMenu: false });
|
[true, false],
|
||||||
expect(wrapper.container.querySelector(".mx_RoomHeader_name.mx_AccessibleButton")).toBeFalsy();
|
])(
|
||||||
});
|
"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 {
|
interface IRoomCreationInfo {
|
||||||
|
|
|
@ -47,8 +47,14 @@ import { VoiceBroadcastInfoState } from "../../../../src/voice-broadcast";
|
||||||
import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils";
|
import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils";
|
||||||
import { TestSdkContext } from "../../../TestSdkContext";
|
import { TestSdkContext } from "../../../TestSdkContext";
|
||||||
import { SDKContext } from "../../../../src/contexts/SDKContext";
|
import { SDKContext } from "../../../../src/contexts/SDKContext";
|
||||||
|
import { shouldShowComponent } from "../../../../src/customisations/helpers/UIComponents";
|
||||||
|
import { UIComponent } from "../../../../src/settings/UIFeature";
|
||||||
import { MessagePreviewStore } from "../../../../src/stores/room-list/MessagePreviewStore";
|
import { MessagePreviewStore } from "../../../../src/stores/room-list/MessagePreviewStore";
|
||||||
|
|
||||||
|
jest.mock("../../../../src/customisations/helpers/UIComponents", () => ({
|
||||||
|
shouldShowComponent: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
describe("RoomTile", () => {
|
describe("RoomTile", () => {
|
||||||
jest.spyOn(PlatformPeg, "get").mockReturnValue({
|
jest.spyOn(PlatformPeg, "get").mockReturnValue({
|
||||||
overrideBrowserShortcuts: () => false,
|
overrideBrowserShortcuts: () => false,
|
||||||
|
@ -69,8 +75,8 @@ describe("RoomTile", () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderRoomTile = (): void => {
|
const renderRoomTile = (): RenderResult => {
|
||||||
renderResult = render(
|
return render(
|
||||||
<SDKContext.Provider value={sdkContext}>
|
<SDKContext.Provider value={sdkContext}>
|
||||||
<RoomTile
|
<RoomTile
|
||||||
room={room}
|
room={room}
|
||||||
|
@ -85,7 +91,6 @@ describe("RoomTile", () => {
|
||||||
let client: Mocked<MatrixClient>;
|
let client: Mocked<MatrixClient>;
|
||||||
let voiceBroadcastInfoEvent: MatrixEvent;
|
let voiceBroadcastInfoEvent: MatrixEvent;
|
||||||
let room: Room;
|
let room: Room;
|
||||||
let renderResult: RenderResult;
|
|
||||||
let sdkContext: TestSdkContext;
|
let sdkContext: TestSdkContext;
|
||||||
let showMessagePreview = false;
|
let showMessagePreview = false;
|
||||||
|
|
||||||
|
@ -148,12 +153,24 @@ describe("RoomTile", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when message previews are not enabled", () => {
|
describe("when message previews are not enabled", () => {
|
||||||
beforeEach(() => {
|
it("should render the room", () => {
|
||||||
renderRoomTile();
|
mocked(shouldShowComponent).mockReturnValue(true);
|
||||||
|
const renderResult = renderRoomTile();
|
||||||
|
expect(renderResult.container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render the room", () => {
|
it("does not render the room options context menu when UIComponent customisations disable room options", () => {
|
||||||
expect(renderResult.container).toMatchSnapshot();
|
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", () => {
|
describe("when a call starts", () => {
|
||||||
|
@ -176,13 +193,13 @@ describe("RoomTile", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
renderResult.unmount();
|
|
||||||
call.destroy();
|
call.destroy();
|
||||||
client.reEmitter.stopReEmitting(room, [RoomStateEvent.Events]);
|
client.reEmitter.stopReEmitting(room, [RoomStateEvent.Events]);
|
||||||
WidgetMessagingStore.instance.stopMessaging(widget, room.roomId);
|
WidgetMessagingStore.instance.stopMessaging(widget, room.roomId);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("tracks connection state", async () => {
|
it("tracks connection state", async () => {
|
||||||
|
renderRoomTile();
|
||||||
screen.getByText("Video");
|
screen.getByText("Video");
|
||||||
|
|
||||||
// Insert an await point in the connection method so we can inspect
|
// Insert an await point in the connection method so we can inspect
|
||||||
|
@ -205,6 +222,7 @@ describe("RoomTile", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("tracks participants", () => {
|
it("tracks participants", () => {
|
||||||
|
renderRoomTile();
|
||||||
const alice: [RoomMember, Set<string>] = [
|
const alice: [RoomMember, Set<string>] = [
|
||||||
mkRoomMember(room.roomId, "@alice:example.org"),
|
mkRoomMember(room.roomId, "@alice:example.org"),
|
||||||
new Set(["a"]),
|
new Set(["a"]),
|
||||||
|
@ -238,6 +256,7 @@ describe("RoomTile", () => {
|
||||||
|
|
||||||
describe("and a live broadcast starts", () => {
|
describe("and a live broadcast starts", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
renderRoomTile();
|
||||||
await setUpVoiceBroadcast(VoiceBroadcastInfoState.Started);
|
await setUpVoiceBroadcast(VoiceBroadcastInfoState.Started);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -250,6 +269,7 @@ describe("RoomTile", () => {
|
||||||
|
|
||||||
describe("when a live voice broadcast starts", () => {
|
describe("when a live voice broadcast starts", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
renderRoomTile();
|
||||||
await setUpVoiceBroadcast(VoiceBroadcastInfoState.Started);
|
await setUpVoiceBroadcast(VoiceBroadcastInfoState.Started);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -285,7 +305,7 @@ describe("RoomTile", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render a room without a message as expected", async () => {
|
it("should render a room without a message as expected", async () => {
|
||||||
renderRoomTile();
|
const renderResult = renderRoomTile();
|
||||||
// flush promises here because the preview is created asynchronously
|
// flush promises here because the preview is created asynchronously
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
expect(renderResult.asFragment()).toMatchSnapshot();
|
expect(renderResult.asFragment()).toMatchSnapshot();
|
||||||
|
@ -297,7 +317,7 @@ describe("RoomTile", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render as expected", async () => {
|
it("should render as expected", async () => {
|
||||||
renderRoomTile();
|
const renderResult = renderRoomTile();
|
||||||
expect(await screen.findByText("test message")).toBeInTheDocument();
|
expect(await screen.findByText("test message")).toBeInTheDocument();
|
||||||
expect(renderResult.asFragment()).toMatchSnapshot();
|
expect(renderResult.asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -309,7 +329,7 @@ describe("RoomTile", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render as expected", async () => {
|
it("should render as expected", async () => {
|
||||||
renderRoomTile();
|
const renderResult = renderRoomTile();
|
||||||
expect(await screen.findByText("test thread reply")).toBeInTheDocument();
|
expect(await screen.findByText("test thread reply")).toBeInTheDocument();
|
||||||
expect(renderResult.asFragment()).toMatchSnapshot();
|
expect(renderResult.asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue