", () => {
- const aliceId = "@alice:server.org";
- const room1Id = "$room1:server.org";
- const room2Id = "$room2:server.org";
- const room3Id = "$room3:server.org";
- const mockClient = getMockClientWithEventEmitter({
- getVisibleRooms: jest.fn().mockReturnValue([]),
- getUserId: jest.fn().mockReturnValue(aliceId),
- getSafeUserId: jest.fn().mockReturnValue(aliceId),
- unstable_setLiveBeacon: jest.fn().mockResolvedValue({ event_id: "1" }),
- sendEvent: jest.fn(),
- isGuest: jest.fn().mockReturnValue(false),
- });
-
- // 14.03.2022 16:15
- const now = 1647270879403;
- const MINUTE_MS = 60000;
- const HOUR_MS = 3600000;
- // mock the date so events are stable for snapshots etc
- jest.spyOn(global.Date, "now").mockReturnValue(now);
- const room1Beacon1 = makeBeaconInfoEvent(
- aliceId,
- room1Id,
- {
- isLive: true,
- timeout: HOUR_MS,
- },
- "$0",
- );
- const room2Beacon1 = makeBeaconInfoEvent(aliceId, room2Id, { isLive: true, timeout: HOUR_MS }, "$1");
- const room2Beacon2 = makeBeaconInfoEvent(aliceId, room2Id, { isLive: true, timeout: HOUR_MS * 12 }, "$2");
- const room3Beacon1 = makeBeaconInfoEvent(aliceId, room3Id, { isLive: true, timeout: HOUR_MS }, "$3");
-
- // make fresh rooms every time
- // as we update room state
- const makeRoomsWithStateEvents = (stateEvents: MatrixEvent[] = []): [Room, Room] => {
- const room1 = new Room(room1Id, mockClient, aliceId);
- const room2 = new Room(room2Id, mockClient, aliceId);
-
- room1.currentState.setStateEvents(stateEvents);
- room2.currentState.setStateEvents(stateEvents);
- mockClient.getVisibleRooms.mockReturnValue([room1, room2]);
-
- return [room1, room2];
- };
-
- const makeOwnBeaconStore = async () => {
- const store = OwnBeaconStore.instance;
-
- await setupAsyncStoreWithClient(store, mockClient);
- return store;
- };
-
- const defaultProps = {
- roomId: room1Id,
- };
- const getComponent = (props = {}) => {
- return render(
);
- };
-
- const localStorageSpy = jest.spyOn(localStorage.__proto__, "getItem").mockReturnValue(undefined);
-
- beforeEach(() => {
- mockGeolocation();
- jest.spyOn(global.Date, "now").mockReturnValue(now);
- mockClient.unstable_setLiveBeacon.mockReset().mockResolvedValue({ event_id: "1" });
-
- // assume all beacons were created on this device
- localStorageSpy.mockReturnValue(
- JSON.stringify([room1Beacon1.getId(), room2Beacon1.getId(), room2Beacon2.getId(), room3Beacon1.getId()]),
- );
- });
-
- afterEach(async () => {
- jest.spyOn(OwnBeaconStore.instance, "beaconHasLocationPublishError").mockRestore();
- await resetAsyncStoreWithClient(OwnBeaconStore.instance);
- });
-
- afterAll(() => {
- jest.spyOn(global.Date, "now").mockRestore();
- localStorageSpy.mockRestore();
- jest.spyOn(defaultDispatcher, "dispatch").mockRestore();
- });
-
- it("renders nothing when user has no live beacons at all", async () => {
- await makeOwnBeaconStore();
- const { asFragment } = getComponent();
- expect(asFragment()).toMatchInlineSnapshot(`
`);
- });
-
- it("renders nothing when user has no live beacons in room", async () => {
- await act(async () => {
- await makeRoomsWithStateEvents([room2Beacon1]);
- await makeOwnBeaconStore();
- });
- const { asFragment } = getComponent({ roomId: room1Id });
- expect(asFragment()).toMatchInlineSnapshot(`
`);
- });
-
- it("does not render when geolocation is not working", async () => {
- jest.spyOn(logger, "error").mockImplementation(() => {});
- // @ts-ignore
- navigator.geolocation = undefined;
- await act(async () => {
- await makeRoomsWithStateEvents([room1Beacon1, room2Beacon1, room2Beacon2]);
- await makeOwnBeaconStore();
- });
- const { asFragment } = getComponent({ roomId: room1Id });
-
- expect(asFragment()).toMatchInlineSnapshot(`
`);
- });
- describe("when user has live beacons and geolocation is available", () => {
- beforeEach(async () => {
- await act(async () => {
- await makeRoomsWithStateEvents([room1Beacon1, room2Beacon1, room2Beacon2]);
- await makeOwnBeaconStore();
- });
- });
-
- it("renders correctly with one live beacon in room", () => {
- const { asFragment } = getComponent({ roomId: room1Id });
- // beacons have generated ids that break snapshots
- // assert on html
- expect(asFragment()).toMatchSnapshot();
- });
-
- it("renders correctly with two live beacons in room", () => {
- const { asFragment, container } = getComponent({ roomId: room2Id });
- // beacons have generated ids that break snapshots
- // assert on html
- expect(asFragment()).toMatchSnapshot();
- // later expiry displayed
- expect(container).toHaveTextContent("12h left");
- });
-
- it("removes itself when user stops having live beacons", async () => {
- const { container } = getComponent({ roomId: room1Id });
- // started out rendered
- expect(container.firstChild).toBeTruthy();
-
- // time travel until room1Beacon1 is expired
- act(() => {
- advanceDateAndTime(HOUR_MS + 1);
- });
- act(() => {
- mockClient.emit(BeaconEvent.LivenessChange, false, new Beacon(room1Beacon1));
- });
-
- await waitFor(() => expect(container.firstChild).toBeFalsy());
- });
-
- it("removes itself when user stops monitoring live position", async () => {
- const { container } = getComponent({ roomId: room1Id });
- // started out rendered
- expect(container.firstChild).toBeTruthy();
-
- act(() => {
- // cheat to clear this
- // @ts-ignore
- OwnBeaconStore.instance.clearPositionWatch = undefined;
- OwnBeaconStore.instance.emit(OwnBeaconStoreEvent.MonitoringLivePosition);
- });
-
- await waitFor(() => expect(container.firstChild).toBeFalsy());
- });
-
- it("renders when user adds a live beacon", async () => {
- const { container } = getComponent({ roomId: room3Id });
- // started out not rendered
- expect(container.firstChild).toBeFalsy();
- act(() => {
- mockClient.emit(BeaconEvent.New, room3Beacon1, new Beacon(room3Beacon1));
- });
-
- await waitFor(() => expect(container.firstChild).toBeTruthy());
- });
-
- it("updates beacon time left periodically", () => {
- const { container } = getComponent({ roomId: room1Id });
- expect(container).toHaveTextContent("1h left");
-
- act(() => {
- advanceDateAndTime(MINUTE_MS * 25);
- });
-
- expect(container).toHaveTextContent("35m left");
- });
-
- it("updates beacon time left when beacon updates", () => {
- const { container } = getComponent({ roomId: room1Id });
- expect(container).toHaveTextContent("1h left");
-
- act(() => {
- const beacon = OwnBeaconStore.instance.getBeaconById(getBeaconInfoIdentifier(room1Beacon1));
- const room1Beacon1Update = makeBeaconInfoEvent(
- aliceId,
- room1Id,
- {
- isLive: true,
- timeout: 3 * HOUR_MS,
- },
- "$0",
- );
- beacon?.update(room1Beacon1Update);
- });
-
- // update to expiry of new beacon
- expect(container).toHaveTextContent("3h left");
- });
-
- it("clears expiry time interval on unmount", () => {
- const clearIntervalSpy = jest.spyOn(global, "clearInterval");
- const { container, unmount } = getComponent({ roomId: room1Id });
- expect(container).toHaveTextContent("1h left");
-
- unmount();
-
- expect(clearIntervalSpy).toHaveBeenCalled();
- });
-
- it("navigates to beacon tile on click", () => {
- const dispatcherSpy = jest.spyOn(defaultDispatcher, "dispatch");
- const { container } = getComponent({ roomId: room1Id });
-
- act(() => {
- fireEvent.click(container.firstChild! as Node);
- });
-
- expect(dispatcherSpy).toHaveBeenCalledWith({
- action: Action.ViewRoom,
- event_id: room1Beacon1.getId(),
- room_id: room1Id,
- highlighted: true,
- scroll_into_view: true,
- metricsTrigger: undefined,
- });
- });
-
- describe("stopping beacons", () => {
- it("stops beacon on stop sharing click", async () => {
- const { container } = getComponent({ roomId: room2Id });
-
- const btn = getByTestId(container, "room-live-share-primary-button");
-
- fireEvent.click(btn);
-
- expect(mockClient.unstable_setLiveBeacon).toHaveBeenCalled();
-
- await waitFor(() => expect(screen.queryByTestId("spinner")).toBeInTheDocument());
-
- expect(btn.hasAttribute("disabled")).toBe(true);
- });
-
- it("displays error when stop sharing fails", async () => {
- const { container, asFragment } = getComponent({ roomId: room1Id });
- const btn = getByTestId(container, "room-live-share-primary-button");
-
- // fail first time
- mockClient.unstable_setLiveBeacon
- .mockRejectedValueOnce(new Error("oups"))
- .mockResolvedValue({ event_id: "1" });
-
- await act(async () => {
- fireEvent.click(btn);
- await flushPromisesWithFakeTimers();
- });
-
- expect(asFragment()).toMatchSnapshot();
-
- act(() => {
- fireEvent.click(btn);
- });
-
- expect(mockClient.unstable_setLiveBeacon).toHaveBeenCalledTimes(2);
- });
-
- it("displays again with correct state after stopping a beacon", () => {
- // make sure the loading state is reset correctly after removing a beacon
- const { container } = getComponent({ roomId: room1Id });
- const btn = getByTestId(container, "room-live-share-primary-button");
-
- // stop the beacon
- act(() => {
- fireEvent.click(btn);
- });
- // time travel until room1Beacon1 is expired
- act(() => {
- advanceDateAndTime(HOUR_MS + 1);
- });
- act(() => {
- mockClient.emit(BeaconEvent.LivenessChange, false, new Beacon(room1Beacon1));
- });
-
- const newLiveBeacon = makeBeaconInfoEvent(aliceId, room1Id, { isLive: true });
- act(() => {
- mockClient.emit(BeaconEvent.New, newLiveBeacon, new Beacon(newLiveBeacon));
- });
-
- // button not disabled and expiry time shown
- expect(btn.hasAttribute("disabled")).toBe(true);
- });
- });
-
- describe("with location publish errors", () => {
- it("displays location publish error when mounted with location publish errors", async () => {
- const locationPublishErrorSpy = jest
- .spyOn(OwnBeaconStore.instance, "beaconHasLocationPublishError")
- .mockReturnValue(true);
- const { asFragment } = getComponent({ roomId: room2Id });
-
- expect(asFragment()).toMatchSnapshot();
- expect(locationPublishErrorSpy).toHaveBeenCalledWith(getBeaconInfoIdentifier(room2Beacon1), 0, [
- getBeaconInfoIdentifier(room2Beacon1),
- ]);
- });
-
- it(
- "displays location publish error when locationPublishError event is emitted" +
- " and beacons have errors",
- async () => {
- const locationPublishErrorSpy = jest
- .spyOn(OwnBeaconStore.instance, "beaconHasLocationPublishError")
- .mockReturnValue(false);
- const { container } = getComponent({ roomId: room2Id });
-
- // update mock and emit event
- act(() => {
- locationPublishErrorSpy.mockReturnValue(true);
- OwnBeaconStore.instance.emit(
- OwnBeaconStoreEvent.LocationPublishError,
- getBeaconInfoIdentifier(room2Beacon1),
- );
- });
-
- // renders wire error ui
- expect(container).toHaveTextContent(
- "An error occurred whilst sharing your live location, please try again",
- );
-
- expect(screen.queryByTestId("room-live-share-wire-error-close-button")).toBeInTheDocument();
- },
- );
-
- it("stops displaying wire error when errors are cleared", async () => {
- const locationPublishErrorSpy = jest
- .spyOn(OwnBeaconStore.instance, "beaconHasLocationPublishError")
- .mockReturnValue(true);
- const { container } = getComponent({ roomId: room2Id });
-
- // update mock and emit event
- act(() => {
- locationPublishErrorSpy.mockReturnValue(false);
- OwnBeaconStore.instance.emit(
- OwnBeaconStoreEvent.LocationPublishError,
- getBeaconInfoIdentifier(room2Beacon1),
- );
- });
-
- // renders error-free ui
- expect(container).toHaveTextContent("You are sharing your live location");
- expect(screen.queryByTestId("room-live-share-wire-error-close-button")).not.toBeInTheDocument();
- });
-
- it("clicking retry button resets location publish errors", async () => {
- jest.spyOn(OwnBeaconStore.instance, "beaconHasLocationPublishError").mockReturnValue(true);
- const resetErrorSpy = jest.spyOn(OwnBeaconStore.instance, "resetLocationPublishError");
-
- const { container } = getComponent({ roomId: room2Id });
- const btn = getByTestId(container, "room-live-share-primary-button");
-
- act(() => {
- fireEvent.click(btn);
- });
-
- expect(resetErrorSpy).toHaveBeenCalledWith(getBeaconInfoIdentifier(room2Beacon1));
- });
-
- it("clicking close button stops beacons", async () => {
- jest.spyOn(OwnBeaconStore.instance, "beaconHasLocationPublishError").mockReturnValue(true);
- const stopBeaconSpy = jest.spyOn(OwnBeaconStore.instance, "stopBeacon");
-
- const { container } = getComponent({ roomId: room2Id });
- const btn = getByTestId(container, "room-live-share-wire-error-close-button");
- act(() => {
- fireEvent.click(btn);
- });
-
- expect(stopBeaconSpy).toHaveBeenCalledWith(getBeaconInfoIdentifier(room2Beacon1));
- });
- });
- });
-});
diff --git a/test/components/views/beacon/__snapshots__/RoomLiveShareWarning-test.tsx.snap b/test/components/views/beacon/__snapshots__/RoomLiveShareWarning-test.tsx.snap
deleted file mode 100644
index c2bb48e33c..0000000000
--- a/test/components/views/beacon/__snapshots__/RoomLiveShareWarning-test.tsx.snap
+++ /dev/null
@@ -1,133 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`
when user has live beacons and geolocation is available renders correctly with one live beacon in room 1`] = `
-
-
-
-
- You are sharing your live location
-
-
- 1h left
-
-
-
-
-`;
-
-exports[`
when user has live beacons and geolocation is available renders correctly with two live beacons in room 1`] = `
-
-
-
-
- You are sharing your live location
-
-
- 12h left
-
-
-
-
-`;
-
-exports[`
when user has live beacons and geolocation is available stopping beacons displays error when stop sharing fails 1`] = `
-
-
-
-
- An error occurred while stopping your live location, please try again
-
-
-
-
-`;
-
-exports[`
when user has live beacons and geolocation is available with location publish errors displays location publish error when mounted with location publish errors 1`] = `
-
-
-
-
- An error occurred whilst sharing your live location, please try again
-
-
-
-
-
-`;
diff --git a/test/components/views/context_menus/RoomContextMenu-test.tsx b/test/components/views/context_menus/RoomContextMenu-test.tsx
deleted file mode 100644
index 3644d2c325..0000000000
--- a/test/components/views/context_menus/RoomContextMenu-test.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2023 Mikhail Aheichyk
-Copyright 2023 Nordeck IT + Consulting GmbH.
-Copyright 2023 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
-Please see LICENSE files in the repository root for full details.
-*/
-
-import { render, screen } from "@testing-library/react";
-import React, { ComponentProps } from "react";
-import { mocked } from "jest-mock";
-import { MatrixClient, PendingEventOrdering, Room } from "matrix-js-sdk/src/matrix";
-import { KnownMembership } from "matrix-js-sdk/src/types";
-
-import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
-import RoomContextMenu from "../../../../src/components/views/context_menus/RoomContextMenu";
-import { shouldShowComponent } from "../../../../src/customisations/helpers/UIComponents";
-import { stubClient } from "../../../test-utils";
-import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
-import DMRoomMap from "../../../../src/utils/DMRoomMap";
-import SettingsStore from "../../../../src/settings/SettingsStore";
-import { EchoChamber } from "../../../../src/stores/local-echo/EchoChamber";
-import { RoomNotifState } from "../../../../src/RoomNotifs";
-
-jest.mock("../../../../src/customisations/helpers/UIComponents", () => ({
- shouldShowComponent: jest.fn(),
-}));
-
-describe("RoomContextMenu", () => {
- const ROOM_ID = "!123:matrix.org";
-
- let room: Room;
- let mockClient: MatrixClient;
-
- let onFinished: () => void;
-
- beforeEach(() => {
- jest.clearAllMocks();
-
- stubClient();
- mockClient = mocked(MatrixClientPeg.safeGet());
-
- room = new Room(ROOM_ID, mockClient, mockClient.getUserId() ?? "", {
- pendingEventOrdering: PendingEventOrdering.Detached,
- });
-
- const dmRoomMap = {
- getUserIdForRoomId: jest.fn(),
- } as unknown as DMRoomMap;
- DMRoomMap.setShared(dmRoomMap);
-
- onFinished = jest.fn();
- });
-
- function renderComponent(props: Partial
> = {}) {
- render(
-
-
- ,
- );
- }
-
- it("does not render invite menu item when UIComponent customisations disable invite", () => {
- jest.spyOn(room, "canInvite").mockReturnValue(true);
- mocked(shouldShowComponent).mockReturnValue(false);
-
- renderComponent();
-
- expect(screen.queryByRole("menuitem", { name: "Invite" })).not.toBeInTheDocument();
- });
-
- it("renders invite menu item when UIComponent customisations enable invite", () => {
- jest.spyOn(room, "canInvite").mockReturnValue(true);
- mocked(shouldShowComponent).mockReturnValue(true);
-
- renderComponent();
-
- expect(screen.getByRole("menuitem", { name: "Invite" })).toBeInTheDocument();
- });
-
- it("when developer mode is disabled, it should not render the developer tools option", () => {
- renderComponent();
- expect(screen.queryByText("Developer tools")).not.toBeInTheDocument();
- });
-
- describe("when developer mode is enabled", () => {
- beforeEach(() => {
- jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => setting === "developerMode");
- });
-
- it("should render the developer tools option", () => {
- renderComponent();
- expect(screen.getByText("Developer tools")).toBeInTheDocument();
- });
- });
-
- it("should render notification option for joined rooms", () => {
- const chamber = EchoChamber.forRoom(room);
- chamber.notificationVolume = RoomNotifState.Mute;
- jest.spyOn(room, "getMyMembership").mockReturnValue(KnownMembership.Join);
- renderComponent();
-
- expect(
- screen.getByRole("menuitem", { name: "Notifications" }).querySelector(".mx_IconizedContextMenu_sublabel"),
- ).toHaveTextContent("Mute");
- });
-});
diff --git a/test/components/views/right_panel/LegacyRoomHeaderButtons-test.tsx b/test/components/views/right_panel/LegacyRoomHeaderButtons-test.tsx
deleted file mode 100644
index 89e9c330d0..0000000000
--- a/test/components/views/right_panel/LegacyRoomHeaderButtons-test.tsx
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2022, 2023 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
-Please see LICENSE files in the repository root for full details.
-*/
-
-import { render, waitFor } from "@testing-library/react";
-import {
- MatrixEvent,
- MsgType,
- RelationType,
- NotificationCountType,
- Room,
- MatrixClient,
- PendingEventOrdering,
- ReceiptType,
-} from "matrix-js-sdk/src/matrix";
-import React from "react";
-
-import LegacyRoomHeaderButtons from "../../../../src/components/views/right_panel/LegacyRoomHeaderButtons";
-import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
-import { mkEvent, stubClient } from "../../../test-utils";
-import { mkThread } from "../../../test-utils/threads";
-
-describe("LegacyRoomHeaderButtons-test.tsx", function () {
- const ROOM_ID = "!roomId:example.org";
- let room: Room;
- let client: MatrixClient;
-
- beforeEach(() => {
- jest.clearAllMocks();
-
- stubClient();
- client = MatrixClientPeg.safeGet();
- client.supportsThreads = () => true;
- room = new Room(ROOM_ID, client, client.getUserId() ?? "", {
- pendingEventOrdering: PendingEventOrdering.Detached,
- });
- });
-
- function getComponent(room?: Room) {
- return render();
- }
-
- function getThreadButton(container: HTMLElement) {
- return container.querySelector(".mx_RightPanel_threadsButton");
- }
-
- function isIndicatorOfType(container: HTMLElement, type: "highlight" | "notification" | "activity") {
- return container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")!.className.includes(type);
- }
-
- it("should render", () => {
- const { asFragment } = getComponent(room);
- expect(asFragment()).toMatchSnapshot();
- });
-
- it("shows the thread button", () => {
- const { container } = getComponent(room);
- expect(getThreadButton(container)).not.toBeNull();
- });
-
- it("room wide notification does not change the thread button", () => {
- room.setUnreadNotificationCount(NotificationCountType.Highlight, 1);
- room.setUnreadNotificationCount(NotificationCountType.Total, 1);
-
- const { container } = getComponent(room);
-
- expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull();
- });
-
- it("thread notification does change the thread button", async () => {
- const { container } = getComponent(room);
- expect(getThreadButton(container)!.className.includes("mx_LegacyRoomHeader_button--unread")).toBeFalsy();
-
- room.setThreadUnreadNotificationCount("$123", NotificationCountType.Total, 1);
- await waitFor(() => {
- expect(getThreadButton(container)!.className.includes("mx_LegacyRoomHeader_button--unread")).toBeTruthy();
- expect(isIndicatorOfType(container, "notification")).toBe(true);
- });
-
- room.setThreadUnreadNotificationCount("$123", NotificationCountType.Highlight, 1);
- await waitFor(() => expect(isIndicatorOfType(container, "highlight")).toBe(true));
-
- room.setThreadUnreadNotificationCount("$123", NotificationCountType.Total, 0);
- room.setThreadUnreadNotificationCount("$123", NotificationCountType.Highlight, 0);
-
- await waitFor(() => expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull());
- });
-
- it("thread activity does change the thread button", async () => {
- const { container } = getComponent(room);
-
- // Thread activity should appear on the icon.
- const { rootEvent, events } = mkThread({
- room,
- client,
- authorId: client.getUserId()!,
- participantUserIds: ["@alice:example.org"],
- length: 5,
- });
- // We need some receipt, otherwise we treat this thread as
- // "older than all threaded receipts" and consider it read.
- let receipt = new MatrixEvent({
- type: "m.receipt",
- room_id: room.roomId,
- content: {
- [events[1].getId()!]: {
- // Receipt for the first event in the thread
- [ReceiptType.Read]: {
- [client.getUserId()!]: { ts: 1, thread_id: rootEvent.getId() },
- },
- },
- },
- });
- room.addReceipt(receipt);
- await waitFor(() => expect(isIndicatorOfType(container, "activity")).toBe(true));
-
- // Sending the last event should clear the notification.
- let event = mkEvent({
- event: true,
- type: "m.room.message",
- user: client.getUserId()!,
- room: room.roomId,
- content: {
- "msgtype": MsgType.Text,
- "body": "Test",
- "m.relates_to": {
- event_id: rootEvent.getId(),
- rel_type: RelationType.Thread,
- },
- },
- });
- room.addLiveEvents([event]);
- await waitFor(() => expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull());
-
- // Mark it as unread again.
- event = mkEvent({
- event: true,
- type: "m.room.message",
- user: "@alice:example.org",
- room: room.roomId,
- content: {
- "msgtype": MsgType.Text,
- "body": "Test",
- "m.relates_to": {
- event_id: rootEvent.getId(),
- rel_type: RelationType.Thread,
- },
- },
- });
- room.addLiveEvents([event]);
- await waitFor(() => expect(isIndicatorOfType(container, "activity")).toBe(true));
-
- // Sending a read receipt on an earlier event shouldn't do anything.
- receipt = new MatrixEvent({
- type: "m.receipt",
- room_id: room.roomId,
- content: {
- [events.at(-1)!.getId()!]: {
- [ReceiptType.Read]: {
- [client.getUserId()!]: { ts: 1, thread_id: rootEvent.getId() },
- },
- },
- },
- });
- room.addReceipt(receipt);
- await waitFor(() => expect(isIndicatorOfType(container, "activity")).toBe(true));
-
- // Sending a receipt on the latest event should clear the notification.
- receipt = new MatrixEvent({
- type: "m.receipt",
- room_id: room.roomId,
- content: {
- [event.getId()!]: {
- [ReceiptType.Read]: {
- [client.getUserId()!]: { ts: 1, thread_id: rootEvent.getId() },
- },
- },
- },
- });
- room.addReceipt(receipt);
- await waitFor(() => expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull());
- });
-});
diff --git a/test/components/views/right_panel/__snapshots__/LegacyRoomHeaderButtons-test.tsx.snap b/test/components/views/right_panel/__snapshots__/LegacyRoomHeaderButtons-test.tsx.snap
deleted file mode 100644
index 6dbe9ed2e3..0000000000
--- a/test/components/views/right_panel/__snapshots__/LegacyRoomHeaderButtons-test.tsx.snap
+++ /dev/null
@@ -1,28 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`LegacyRoomHeaderButtons-test.tsx should render 1`] = `
-
-
-
-
-
-`;
diff --git a/test/components/views/rooms/LegacyRoomHeader-test.tsx b/test/components/views/rooms/LegacyRoomHeader-test.tsx
deleted file mode 100644
index 16dbefbab0..0000000000
--- a/test/components/views/rooms/LegacyRoomHeader-test.tsx
+++ /dev/null
@@ -1,917 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2022 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
-Please see LICENSE files in the repository root for full details.
-*/
-
-import React from "react";
-import { render, screen, act, fireEvent, waitFor, getByRole, RenderResult } from "@testing-library/react";
-import { mocked, Mocked } from "jest-mock";
-import {
- EventType,
- RoomType,
- Room,
- RoomStateEvent,
- PendingEventOrdering,
- ISearchResults,
- IContent,
-} from "matrix-js-sdk/src/matrix";
-import { KnownMembership } from "matrix-js-sdk/src/types";
-import { CallType } from "matrix-js-sdk/src/webrtc/call";
-import { ClientWidgetApi, Widget } from "matrix-widget-api";
-import EventEmitter from "events";
-import { setupJestCanvasMock } from "jest-canvas-mock";
-import { ViewRoomOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
-import { MatrixRTCSession, MatrixRTCSessionManagerEvents } from "matrix-js-sdk/src/matrixrtc";
-
-import type { MatrixClient, MatrixEvent, RoomMember } from "matrix-js-sdk/src/matrix";
-import type { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
-import {
- stubClient,
- mkRoomMember,
- setupAsyncStoreWithClient,
- resetAsyncStoreWithClient,
- mockPlatformPeg,
- mkEvent,
- filterConsole,
-} from "../../../test-utils";
-import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
-import DMRoomMap from "../../../../src/utils/DMRoomMap";
-import RoomHeader, { IProps as RoomHeaderProps } from "../../../../src/components/views/rooms/LegacyRoomHeader";
-import { E2EStatus } from "../../../../src/utils/ShieldUtils";
-import { IRoomState } from "../../../../src/components/structures/RoomView";
-import RoomContext from "../../../../src/contexts/RoomContext";
-import SdkConfig from "../../../../src/SdkConfig";
-import SettingsStore from "../../../../src/settings/SettingsStore";
-import { ElementCall, JitsiCall } from "../../../../src/models/Call";
-import { CallStore } from "../../../../src/stores/CallStore";
-import LegacyCallHandler from "../../../../src/LegacyCallHandler";
-import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
-import { Action } from "../../../../src/dispatcher/actions";
-import WidgetStore from "../../../../src/stores/WidgetStore";
-import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore";
-import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../../src/MediaDeviceHandler";
-import { shouldShowComponent } from "../../../../src/customisations/helpers/UIComponents";
-import { UIComponent } from "../../../../src/settings/UIFeature";
-import WidgetUtils from "../../../../src/utils/WidgetUtils";
-import { ElementWidgetActions } from "../../../../src/stores/widgets/ElementWidgetActions";
-import { SearchScope } from "../../../../src/Searching";
-
-jest.mock("../../../../src/customisations/helpers/UIComponents", () => ({
- shouldShowComponent: jest.fn(),
-}));
-
-describe("LegacyRoomHeader", () => {
- let client: Mocked;
- let room: Room;
- let alice: RoomMember;
- let bob: RoomMember;
- let carol: RoomMember;
-
- filterConsole(
- "Age for event was not available, using `now - origin_server_ts` as a fallback. If the device clock is not correct issues might occur.",
- );
-
- beforeEach(async () => {
- // some of our tests rely on the jest canvas mock, and `afterEach` will have reset the mock, so we need to
- // restore it.
- setupJestCanvasMock();
-
- mockPlatformPeg({ supportsJitsiScreensharing: () => true });
-
- stubClient();
- client = mocked(MatrixClientPeg.safeGet());
- client.getUserId.mockReturnValue("@alice:example.org");
-
- room = new Room("!1:example.org", client, "@alice:example.org", {
- pendingEventOrdering: PendingEventOrdering.Detached,
- });
- room.currentState.setStateEvents([mkCreationEvent(room.roomId, "@alice:example.org")]);
-
- client.getRoom.mockImplementation((roomId) => (roomId === room.roomId ? room : null));
- client.getRooms.mockReturnValue([room]);
- client.reEmitter.reEmit(room, [RoomStateEvent.Events]);
- client.sendStateEvent.mockImplementation(async (roomId, eventType, content, stateKey = "") => {
- if (roomId !== room.roomId) throw new Error("Unknown room");
- const event = mkEvent({
- event: true,
- type: eventType,
- room: roomId,
- user: alice.userId,
- skey: stateKey,
- content: content as IContent,
- });
- room.addLiveEvents([event]);
- return { event_id: event.getId()! };
- });
-
- alice = mkRoomMember(room.roomId, "@alice:example.org");
- bob = mkRoomMember(room.roomId, "@bob:example.org");
- carol = mkRoomMember(room.roomId, "@carol:example.org");
-
- client.getRoom.mockImplementation((roomId) => (roomId === room.roomId ? room : null));
- client.getRooms.mockReturnValue([room]);
- client.reEmitter.reEmit(room, [RoomStateEvent.Events]);
-
- await Promise.all(
- [CallStore.instance, WidgetStore.instance].map((store) => setupAsyncStoreWithClient(store, client)),
- );
-
- jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({
- [MediaDeviceKindEnum.AudioInput]: [],
- [MediaDeviceKindEnum.VideoInput]: [],
- [MediaDeviceKindEnum.AudioOutput]: [],
- });
-
- DMRoomMap.makeShared(client);
- jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(carol.userId);
- });
-
- afterEach(async () => {
- await Promise.all([CallStore.instance, WidgetStore.instance].map(resetAsyncStoreWithClient));
- client.reEmitter.stopReEmitting(room, [RoomStateEvent.Events]);
- jest.restoreAllMocks();
- SdkConfig.reset();
- });
-
- const mockRoomType = (type: string) => {
- jest.spyOn(room, "getType").mockReturnValue(type);
- };
- const mockRoomMembers = (members: RoomMember[]) => {
- jest.spyOn(room, "getJoinedMembers").mockReturnValue(members);
- jest.spyOn(room, "getMember").mockImplementation(
- (userId) => members.find((member) => member.userId === userId) ?? null,
- );
- };
- const mockEnabledSettings = (settings: string[]) => {
- jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName) => settings.includes(settingName));
- };
- const mockEventPowerLevels = (events: { [eventType: string]: number }) => {
- room.currentState.setStateEvents([
- mkEvent({
- event: true,
- type: EventType.RoomPowerLevels,
- room: room.roomId,
- user: alice.userId,
- skey: "",
- content: { events, state_default: 0 },
- }),
- ]);
- };
- const mockLegacyCall = () => {
- jest.spyOn(LegacyCallHandler.instance, "getCallForRoom").mockReturnValue({} as unknown as MatrixCall);
- };
- const withCall = async (fn: (call: ElementCall) => void | Promise): Promise => {
- await ElementCall.create(room);
- const call = CallStore.instance.getCall(room.roomId);
- if (!(call instanceof ElementCall)) throw new Error("Failed to create call");
-
- const widget = new Widget(call.widget);
-
- const eventEmitter = new EventEmitter();
- const messaging = {
- on: eventEmitter.on.bind(eventEmitter),
- off: eventEmitter.off.bind(eventEmitter),
- once: eventEmitter.once.bind(eventEmitter),
- emit: eventEmitter.emit.bind(eventEmitter),
- stop: jest.fn(),
- transport: {
- send: jest.fn(),
- reply: jest.fn(),
- },
- } as unknown as Mocked;
- WidgetMessagingStore.instance.storeMessaging(widget, call.roomId, messaging);
-
- await fn(call);
-
- call.destroy();
- WidgetMessagingStore.instance.stopMessaging(widget, call.roomId);
- };
-
- const renderHeader = (props: Partial = {}, roomContext: Partial = {}) => {
- render(
-
- {}}
- onInviteClick={null}
- onForgetClick={() => {}}
- onAppsClick={() => {}}
- e2eStatus={E2EStatus.Normal}
- appsShown={true}
- searchInfo={{
- searchId: Math.random(),
- promise: new Promise(() => {}),
- term: "",
- scope: SearchScope.Room,
- count: 0,
- }}
- viewingCall={false}
- activeCall={null}
- {...props}
- />
- ,
- );
- };
-
- it("hides call buttons in video rooms", () => {
- mockRoomType(RoomType.UnstableCall);
- mockEnabledSettings(["showCallButtonsInComposer", "feature_video_rooms", "feature_element_call_video_rooms"]);
-
- renderHeader();
- expect(screen.queryByRole("button", { name: /call/i })).toBeNull();
- });
-
- it("hides call buttons if showCallButtonsInComposer is disabled", () => {
- mockEnabledSettings([]);
-
- renderHeader();
- expect(screen.queryByRole("button", { name: /call/i })).toBeNull();
- });
-
- it(
- "hides the voice call button and disables the video call button if configured to use Element Call exclusively " +
- "and there's an ongoing call",
- async () => {
- mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
- SdkConfig.put({
- element_call: { url: "https://call.element.io", use_exclusively: true, brand: "Element Call" },
- });
- await ElementCall.create(room);
-
- renderHeader();
- expect(screen.queryByRole("button", { name: "Voice call" })).toBeNull();
- expect(screen.getByRole("button", { name: "Video call" })).toHaveAttribute("aria-disabled", "true");
- },
- );
-
- it(
- "hides the voice call button and starts an Element call when the video call button is pressed if configured to " +
- "use Element Call exclusively",
- async () => {
- mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
- SdkConfig.put({
- element_call: { url: "https://call.element.io", use_exclusively: true, brand: "Element Call" },
- });
-
- renderHeader();
- expect(screen.queryByRole("button", { name: "Voice call" })).toBeNull();
-
- const dispatcherSpy = jest.fn();
- const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
- fireEvent.click(screen.getByRole("button", { name: "Video call" }));
- await waitFor(() =>
- expect(dispatcherSpy).toHaveBeenCalledWith({
- action: Action.ViewRoom,
- room_id: room.roomId,
- skipLobby: false,
- view_call: true,
- }),
- );
- defaultDispatcher.unregister(dispatcherRef);
- },
- );
-
- it(
- "hides the voice call button and disables the video call button if configured to use Element Call exclusively " +
- "and the user lacks permission",
- () => {
- mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
- SdkConfig.put({
- element_call: { url: "https://call.element.io", use_exclusively: true, brand: "Element Call" },
- });
- mockEventPowerLevels({ [ElementCall.CALL_EVENT_TYPE.name]: 100 });
-
- renderHeader();
- expect(screen.queryByRole("button", { name: "Voice call" })).toBeNull();
- expect(screen.getByRole("button", { name: "Video call" })).toHaveAttribute("aria-disabled", "true");
- },
- );
-
- it("disables call buttons in the new group call experience if there's an ongoing Element call", async () => {
- mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
- await ElementCall.create(room);
-
- renderHeader();
- expect(screen.getByRole("button", { name: "Voice call" })).toHaveAttribute("aria-disabled", "true");
- expect(screen.getByRole("button", { name: "Video call" })).toHaveAttribute("aria-disabled", "true");
- });
-
- it("disables call buttons in the new group call experience if there's an ongoing legacy 1:1 call", () => {
- mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
- mockLegacyCall();
-
- renderHeader();
- expect(screen.getByRole("button", { name: "Voice call" })).toHaveAttribute("aria-disabled", "true");
- expect(screen.getByRole("button", { name: "Video call" })).toHaveAttribute("aria-disabled", "true");
- });
-
- it("disables call buttons in the new group call experience if there's an existing Jitsi widget", async () => {
- mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
- await JitsiCall.create(room);
-
- renderHeader();
- expect(screen.getByRole("button", { name: "Voice call" })).toHaveAttribute("aria-disabled", "true");
- expect(screen.getByRole("button", { name: "Video call" })).toHaveAttribute("aria-disabled", "true");
- });
-
- it("disables call buttons in the new group call experience if there's no other members", () => {
- mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
-
- renderHeader();
- expect(screen.getByRole("button", { name: "Voice call" })).toHaveAttribute("aria-disabled", "true");
- expect(screen.getByRole("button", { name: "Video call" })).toHaveAttribute("aria-disabled", "true");
- });
-
- it(
- "starts a legacy 1:1 call when call buttons are pressed in the new group call experience if there's 1 other " +
- "member",
- async () => {
- mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
- mockRoomMembers([alice, bob]);
-
- renderHeader();
-
- const placeCallSpy = jest.spyOn(LegacyCallHandler.instance, "placeCall").mockResolvedValue(undefined);
- fireEvent.click(screen.getByRole("button", { name: "Voice call" }));
- await act(() => Promise.resolve()); // Allow effects to settle
- expect(placeCallSpy).toHaveBeenCalledWith(room.roomId, CallType.Voice);
-
- placeCallSpy.mockClear();
- fireEvent.click(screen.getByRole("button", { name: "Video call" }));
- await act(() => Promise.resolve()); // Allow effects to settle
- fireEvent.click(screen.getByRole("menuitem", { name: "Legacy video call" }));
- await act(() => Promise.resolve()); // Allow effects to settle
- expect(placeCallSpy).toHaveBeenCalledWith(room.roomId, CallType.Video);
- },
- );
-
- it(
- "creates a Jitsi widget when call buttons are pressed in the new group call experience if the user lacks " +
- "permission to start Element calls",
- async () => {
- mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
- mockRoomMembers([alice, bob, carol]);
- mockEventPowerLevels({ [ElementCall.CALL_EVENT_TYPE.name]: 100 });
-
- renderHeader();
-
- const placeCallSpy = jest.spyOn(LegacyCallHandler.instance, "placeCall").mockResolvedValue(undefined);
- fireEvent.click(screen.getByRole("button", { name: "Voice call" }));
- await act(() => Promise.resolve()); // Allow effects to settle
- expect(placeCallSpy).toHaveBeenCalledWith(room.roomId, CallType.Voice);
-
- placeCallSpy.mockClear();
- fireEvent.click(screen.getByRole("button", { name: "Video call" }));
- await act(() => Promise.resolve()); // Allow effects to settle
- expect(placeCallSpy).toHaveBeenCalledWith(room.roomId, CallType.Video);
- },
- );
-
- it(
- "creates a Jitsi widget when the voice call button is pressed and shows a menu when the video call button is " +
- "pressed in the new group call experience",
- async () => {
- mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
- mockRoomMembers([alice, bob, carol]);
-
- renderHeader();
-
- const placeCallSpy = jest.spyOn(LegacyCallHandler.instance, "placeCall").mockResolvedValue(undefined);
- fireEvent.click(screen.getByRole("button", { name: "Voice call" }));
- await act(() => Promise.resolve()); // Allow effects to settle
- expect(placeCallSpy).toHaveBeenCalledWith(room.roomId, CallType.Voice);
-
- // First try creating a Jitsi widget from the menu
- placeCallSpy.mockClear();
- fireEvent.click(screen.getByRole("button", { name: "Video call" }));
- fireEvent.click(getByRole(screen.getByRole("menu"), "menuitem", { name: /jitsi/i }));
- await act(() => Promise.resolve()); // Allow effects to settle
- expect(placeCallSpy).toHaveBeenCalledWith(room.roomId, CallType.Video);
-
- // Then try starting an Element call from the menu
- const dispatcherSpy = jest.fn();
- const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
- fireEvent.click(screen.getByRole("button", { name: "Video call" }));
- fireEvent.click(getByRole(screen.getByRole("menu"), "menuitem", { name: /element/i }));
- await waitFor(() =>
- expect(dispatcherSpy).toHaveBeenCalledWith({
- action: Action.ViewRoom,
- room_id: room.roomId,
- skipLobby: false,
- view_call: true,
- }),
- );
- defaultDispatcher.unregister(dispatcherRef);
- },
- );
-
- it(
- "disables the voice call button and starts an Element call when the video call button is pressed in the new " +
- "group call experience if the user lacks permission to edit widgets",
- async () => {
- mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
- mockRoomMembers([alice, bob, carol]);
- mockEventPowerLevels({ "im.vector.modular.widgets": 100 });
-
- renderHeader();
- expect(screen.getByRole("button", { name: "Voice call" })).toHaveAttribute("aria-disabled", "true");
-
- const dispatcherSpy = jest.fn();
- const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
- fireEvent.click(screen.getByRole("button", { name: "Video call" }));
- await waitFor(() =>
- expect(dispatcherSpy).toHaveBeenCalledWith({
- action: Action.ViewRoom,
- room_id: room.roomId,
- skipLobby: false,
- view_call: true,
- }),
- );
- defaultDispatcher.unregister(dispatcherRef);
- },
- );
-
- it("disables call buttons in the new group call experience if the user lacks permission", () => {
- mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
- mockRoomMembers([alice, bob, carol]);
- mockEventPowerLevels({ [ElementCall.CALL_EVENT_TYPE.name]: 100, "im.vector.modular.widgets": 100 });
-
- renderHeader();
- expect(screen.getByRole("button", { name: "Voice call" })).toHaveAttribute("aria-disabled", "true");
- expect(screen.getByRole("button", { name: "Video call" })).toHaveAttribute("aria-disabled", "true");
- });
-
- it("disables call buttons if there's an ongoing legacy 1:1 call", () => {
- mockEnabledSettings(["showCallButtonsInComposer"]);
- mockLegacyCall();
-
- renderHeader();
- expect(screen.getByRole("button", { name: "Voice call" })).toHaveAttribute("aria-disabled", "true");
- expect(screen.getByRole("button", { name: "Video call" })).toHaveAttribute("aria-disabled", "true");
- });
-
- it("disables call buttons if there's an existing Jitsi widget", async () => {
- mockEnabledSettings(["showCallButtonsInComposer"]);
- await JitsiCall.create(room);
-
- renderHeader();
- expect(screen.getByRole("button", { name: "Voice call" })).toHaveAttribute("aria-disabled", "true");
- expect(screen.getByRole("button", { name: "Video call" })).toHaveAttribute("aria-disabled", "true");
- });
-
- it("disables call buttons if there's no other members", () => {
- mockEnabledSettings(["showCallButtonsInComposer"]);
-
- renderHeader();
- expect(screen.getByRole("button", { name: "Voice call" })).toHaveAttribute("aria-disabled", "true");
- expect(screen.getByRole("button", { name: "Video call" })).toHaveAttribute("aria-disabled", "true");
- });
-
- it("starts a legacy 1:1 call when call buttons are pressed if there's 1 other member", async () => {
- mockEnabledSettings(["showCallButtonsInComposer"]);
- mockRoomMembers([alice, bob]);
- mockEventPowerLevels({ "im.vector.modular.widgets": 100 }); // Just to verify that it doesn't try to use Jitsi
-
- renderHeader();
-
- const placeCallSpy = jest.spyOn(LegacyCallHandler.instance, "placeCall").mockResolvedValue(undefined);
- fireEvent.click(screen.getByRole("button", { name: "Voice call" }));
- await act(() => Promise.resolve()); // Allow effects to settle
- expect(placeCallSpy).toHaveBeenCalledWith(room.roomId, CallType.Voice);
-
- placeCallSpy.mockClear();
- fireEvent.click(screen.getByRole("button", { name: "Video call" }));
- await act(() => Promise.resolve()); // Allow effects to settle
- expect(placeCallSpy).toHaveBeenCalledWith(room.roomId, CallType.Video);
- });
-
- it("creates a Jitsi widget when call buttons are pressed", async () => {
- mockEnabledSettings(["showCallButtonsInComposer"]);
- mockRoomMembers([alice, bob, carol]);
-
- renderHeader();
-
- const placeCallSpy = jest.spyOn(LegacyCallHandler.instance, "placeCall").mockResolvedValue(undefined);
- fireEvent.click(screen.getByRole("button", { name: "Voice call" }));
- await act(() => Promise.resolve()); // Allow effects to settle
- expect(placeCallSpy).toHaveBeenCalledWith(room.roomId, CallType.Voice);
-
- placeCallSpy.mockClear();
- fireEvent.click(screen.getByRole("button", { name: "Video call" }));
- await act(() => Promise.resolve()); // Allow effects to settle
- expect(placeCallSpy).toHaveBeenCalledWith(room.roomId, CallType.Video);
- });
-
- it("disables call buttons if the user lacks permission", () => {
- mockEnabledSettings(["showCallButtonsInComposer"]);
- mockRoomMembers([alice, bob, carol]);
- mockEventPowerLevels({ "im.vector.modular.widgets": 100 });
-
- renderHeader();
- expect(screen.getByRole("button", { name: "Voice call" })).toHaveAttribute("aria-disabled", "true");
- expect(screen.getByRole("button", { name: "Video call" })).toHaveAttribute("aria-disabled", "true");
- });
-
- it("shows a close button when viewing a call lobby that returns to the timeline when pressed", async () => {
- mockEnabledSettings(["feature_group_calls"]);
-
- renderHeader({ viewingCall: true });
-
- const dispatcherSpy = jest.fn();
- const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
- fireEvent.click(screen.getByRole("button", { name: /close/i }));
- await waitFor(() =>
- expect(dispatcherSpy).toHaveBeenCalledWith({
- action: Action.ViewRoom,
- room_id: room.roomId,
- view_call: false,
- }),
- );
- defaultDispatcher.unregister(dispatcherRef);
- });
-
- it("shows a reduce button when viewing a call that returns to the timeline when pressed", async () => {
- mockEnabledSettings(["feature_group_calls"]);
-
- await withCall(async (call) => {
- renderHeader({ viewingCall: true, activeCall: call });
-
- const dispatcherSpy = jest.fn();
- const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
- fireEvent.click(screen.getByRole("button", { name: /timeline/i }));
- await waitFor(() =>
- expect(dispatcherSpy).toHaveBeenCalledWith({
- action: Action.ViewRoom,
- room_id: room.roomId,
- view_call: false,
- }),
- );
- defaultDispatcher.unregister(dispatcherRef);
- });
- });
-
- it("shows a layout button when viewing a call that shows a menu when pressed", async () => {
- mockEnabledSettings(["feature_group_calls"]);
-
- await withCall(async (call) => {
- // We set the call to skip lobby because otherwise the connection will wait until
- // the user clicks the "join" button, inside the widget lobby which is hard to mock.
- call.widget.data = { ...call.widget.data, skipLobby: true };
- // The connect method will wait until the session actually connected. Otherwise it will timeout.
- // Emitting SessionStarted will trigger the connect method to resolve.
- setTimeout(
- () =>
- client.matrixRTC.emit(MatrixRTCSessionManagerEvents.SessionStarted, room.roomId, {
- room,
- } as MatrixRTCSession),
- 100,
- );
- await call.start();
-
- const messaging = WidgetMessagingStore.instance.getMessagingForUid(WidgetUtils.getWidgetUid(call.widget))!;
- renderHeader({ viewingCall: true, activeCall: call });
-
- // Should start with Freedom selected
- fireEvent.click(screen.getByRole("button", { name: /layout/i }));
- screen.getByRole("menuitemradio", { name: "Freedom", checked: true });
-
- // Clicking Spotlight should tell the widget to switch and close the menu
- fireEvent.click(screen.getByRole("menuitemradio", { name: "Spotlight" }));
- expect(mocked(messaging.transport).send).toHaveBeenCalledWith(ElementWidgetActions.SpotlightLayout, {});
- expect(screen.queryByRole("menu")).toBeNull();
-
- // When the widget responds and the user reopens the menu, they should see Spotlight selected
- act(() => {
- messaging.emit(
- `action:${ElementWidgetActions.SpotlightLayout}`,
- new CustomEvent("widgetapirequest", { detail: { data: {} } }),
- );
- });
- fireEvent.click(screen.getByRole("button", { name: /layout/i }));
- screen.getByRole("menuitemradio", { name: "Spotlight", checked: true });
-
- // Now try switching back to Freedom
- fireEvent.click(screen.getByRole("menuitemradio", { name: "Freedom" }));
- expect(mocked(messaging.transport).send).toHaveBeenCalledWith(ElementWidgetActions.TileLayout, {});
- expect(screen.queryByRole("menu")).toBeNull();
-
- // When the widget responds and the user reopens the menu, they should see Freedom selected
- act(() => {
- messaging.emit(
- `action:${ElementWidgetActions.TileLayout}`,
- new CustomEvent("widgetapirequest", { detail: { data: {} } }),
- );
- });
- fireEvent.click(screen.getByRole("button", { name: /layout/i }));
- screen.getByRole("menuitemradio", { name: "Freedom", checked: true });
- });
- });
-
- it("shows an invite button in video rooms", () => {
- mockEnabledSettings(["feature_video_rooms", "feature_element_call_video_rooms"]);
- mockRoomType(RoomType.UnstableCall);
-
- const onInviteClick = jest.fn();
- renderHeader({ onInviteClick, viewingCall: true });
-
- fireEvent.click(screen.getByRole("button", { name: /invite/i }));
- expect(onInviteClick).toHaveBeenCalled();
- });
-
- it("hides the invite button in non-video rooms when viewing a call", () => {
- renderHeader({ onInviteClick: () => {}, viewingCall: true });
-
- expect(screen.queryByRole("button", { name: /invite/i })).toBeNull();
- });
-
- it("shows the room avatar in a room with only ourselves", () => {
- // When we render a non-DM room with 1 person in it
- const room = createRoom({ name: "X Room", isDm: false, userIds: [] });
- const rendered = mountHeader(room);
-
- // Then the room's avatar is the initial of its name
- const initial = rendered.container.querySelector(".mx_BaseAvatar");
- expect(initial).toHaveTextContent("X");
- });
-
- it("shows the room avatar in a room with 2 people", () => {
- // When we render a non-DM room with 2 people in it
- const room = createRoom({ name: "Y Room", isDm: false, userIds: ["other"] });
- const rendered = mountHeader(room);
-
- // Then the room's avatar is the initial of its name
- const initial = rendered.container.querySelector(".mx_BaseAvatar");
- expect(initial).toHaveTextContent("Y");
- });
-
- it("shows the room avatar in a room with >2 people", () => {
- // When we render a non-DM room with 3 people in it
- const room = createRoom({ name: "Z Room", isDm: false, userIds: ["other1", "other2"] });
- const rendered = mountHeader(room);
-
- // Then the room's avatar is the initial of its name
- const initial = rendered.container.querySelector(".mx_BaseAvatar");
- expect(initial).toHaveTextContent("Z");
- });
-
- it("shows the room avatar in a DM with only ourselves", () => {
- // When we render a non-DM room with 1 person in it
- const room = createRoom({ name: "Z Room", isDm: true, userIds: [] });
- const rendered = mountHeader(room);
-
- // Then the room's avatar is the initial of its name
- const initial = rendered.container.querySelector(".mx_BaseAvatar");
- expect(initial).toHaveTextContent("Z");
- });
-
- it("shows the user avatar in a DM with 2 people", () => {
- // Note: this is the interesting case - this is the ONLY
- // time we should use the user's avatar.
-
- // When we render a DM room with only 2 people in it
- const room = createRoom({ name: "Y Room", isDm: true, userIds: ["other"] });
- const rendered = mountHeader(room);
-
- // Then we use the other user's avatar as our room's image avatar
- const image = rendered.container.querySelector(".mx_BaseAvatar img");
- expect(image).toHaveAttribute("src", "http://this.is.a.url/example.org/other");
- });
-
- it("shows the room avatar in a DM with >2 people", () => {
- // When we render a DM room with 3 people in it
- const room = createRoom({
- name: "Z Room",
- isDm: true,
- userIds: ["other1", "other2"],
- });
- const rendered = mountHeader(room);
-
- // Then the room's avatar is the initial of its name
- const initial = rendered.container.querySelector(".mx_BaseAvatar");
- expect(initial).toHaveTextContent("Z");
- });
-
- it("renders call buttons normally", () => {
- const room = createRoom({ name: "Room", isDm: false, userIds: ["other"] });
- const wrapper = mountHeader(room);
-
- expect(wrapper.container.querySelector('[aria-label="Voice call"]')).toBeDefined();
- expect(wrapper.container.querySelector('[aria-label="Video call"]')).toBeDefined();
- });
-
- it("hides call buttons when the room is tombstoned", () => {
- const room = createRoom({ name: "Room", isDm: false, userIds: [] });
- const wrapper = mountHeader(
- room,
- {},
- {
- tombstone: mkEvent({
- event: true,
- type: "m.room.tombstone",
- room: room.roomId,
- user: "@user1:server",
- skey: "",
- content: {},
- ts: Date.now(),
- }),
- },
- );
-
- expect(wrapper.container.querySelector('[aria-label="Voice call"]')).toBeFalsy();
- expect(wrapper.container.querySelector('[aria-label="Video call"]')).toBeFalsy();
- });
-
- it("should render buttons if not passing showButtons (default true)", () => {
- const room = createRoom({ name: "Room", isDm: false, userIds: [] });
- const wrapper = mountHeader(room);
- expect(wrapper.container.querySelector(".mx_LegacyRoomHeader_button")).toBeDefined();
- });
-
- it("should not render buttons if passing showButtons = false", () => {
- const room = createRoom({ name: "Room", isDm: false, userIds: [] });
- const wrapper = mountHeader(room, { showButtons: false });
- expect(wrapper.container.querySelector(".mx_LegacyRoomHeader_button")).toBeFalsy();
- });
-
- 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_LegacyRoomHeader_name.mx_AccessibleButton")).toBeDefined();
- });
-
- 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_LegacyRoomHeader_name.mx_AccessibleButton")).toBeFalsy();
- },
- );
-
- it("renders additionalButtons", async () => {
- const additionalButtons: ViewRoomOpts["buttons"] = [
- {
- icon: () => <>test-icon>,
- id: "test-id",
- label: () => "test-label",
- onClick: () => {},
- },
- ];
- renderHeader({ additionalButtons });
- expect(screen.getByRole("button", { name: "test-icon" })).toBeInTheDocument();
- });
-
- it("calls onClick-callback on additionalButtons", () => {
- const callback = jest.fn();
- const additionalButtons: ViewRoomOpts["buttons"] = [
- {
- icon: () => <>test-icon>,
- id: "test-id",
- label: () => "test-label",
- onClick: callback,
- },
- ];
- renderHeader({ additionalButtons });
- fireEvent.click(screen.getByRole("button", { name: "test-icon" }));
- expect(callback).toHaveBeenCalled();
- });
-});
-
-interface IRoomCreationInfo {
- name: string;
- isDm: boolean;
- userIds: string[];
-}
-
-function createRoom(info: IRoomCreationInfo) {
- stubClient();
- const client: MatrixClient = MatrixClientPeg.safeGet();
-
- const roomId = "!1234567890:domain";
- const userId = client.getUserId()!;
- if (info.isDm) {
- client.getAccountData = (eventType) => {
- if (eventType === "m.direct") {
- return mkDirectEvent(roomId, userId, info.userIds);
- } else {
- return undefined;
- }
- };
- }
-
- DMRoomMap.makeShared(client).start();
-
- const room = new Room(roomId, client, userId, {
- pendingEventOrdering: PendingEventOrdering.Detached,
- });
-
- const otherJoinEvents: MatrixEvent[] = [];
- for (const otherUserId of info.userIds) {
- otherJoinEvents.push(mkJoinEvent(roomId, otherUserId));
- }
-
- room.currentState.setStateEvents([
- mkCreationEvent(roomId, userId),
- mkNameEvent(roomId, userId, info.name),
- mkJoinEvent(roomId, userId),
- ...otherJoinEvents,
- ]);
- room.recalculate();
-
- return room;
-}
-
-function mountHeader(room: Room, propsOverride = {}, roomContext?: Partial): RenderResult {
- const props: RoomHeaderProps = {
- room,
- inRoom: true,
- onSearchClick: () => {},
- onInviteClick: null,
- onForgetClick: () => {},
- onAppsClick: () => {},
- e2eStatus: E2EStatus.Normal,
- appsShown: true,
- searchInfo: {
- searchId: Math.random(),
- promise: new Promise(() => {}),
- term: "",
- scope: SearchScope.Room,
- count: 0,
- },
- viewingCall: false,
- activeCall: null,
- ...propsOverride,
- };
-
- return render(
-
-
- ,
- );
-}
-
-function mkCreationEvent(roomId: string, userId: string): MatrixEvent {
- return mkEvent({
- event: true,
- type: "m.room.create",
- room: roomId,
- user: userId,
- content: {
- creator: userId,
- room_version: "5",
- predecessor: {
- room_id: "!prevroom",
- event_id: "$someevent",
- },
- },
- });
-}
-
-function mkNameEvent(roomId: string, userId: string, name: string): MatrixEvent {
- return mkEvent({
- event: true,
- type: "m.room.name",
- room: roomId,
- user: userId,
- content: { name },
- });
-}
-
-function mkJoinEvent(roomId: string, userId: string) {
- const ret = mkEvent({
- event: true,
- type: "m.room.member",
- room: roomId,
- user: userId,
- content: {
- membership: KnownMembership.Join,
- avatar_url: "mxc://example.org/" + userId,
- },
- });
- ret.event.state_key = userId;
- return ret;
-}
-
-function mkDirectEvent(roomId: string, userId: string, otherUsers: string[]): MatrixEvent {
- const content: Record = {};
- for (const otherUserId of otherUsers) {
- content[otherUserId] = [roomId];
- }
- return mkEvent({
- event: true,
- type: "m.direct",
- room: roomId,
- user: userId,
- content,
- });
-}
diff --git a/test/components/views/settings/tabs/user/__snapshots__/LabsUserSettingsTab-test.tsx.snap b/test/components/views/settings/tabs/user/__snapshots__/LabsUserSettingsTab-test.tsx.snap
index 51ab465503..4e65f7b61f 100644
--- a/test/components/views/settings/tabs/user/__snapshots__/LabsUserSettingsTab-test.tsx.snap
+++ b/test/components/views/settings/tabs/user/__snapshots__/LabsUserSettingsTab-test.tsx.snap
@@ -129,61 +129,6 @@ exports[` renders settings marked as beta as beta cards 1