Add feature flag 'feature_new_room_decoration_ui' and segrate legacy UI component (#11345)
* Move RoomHeader to LegacyRoomHeader * Create new RoomHeader component
This commit is contained in:
parent
89a92c6351
commit
6ae7c033d5
30 changed files with 2309 additions and 2103 deletions
|
@ -6,13 +6,13 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1
|
|||
class="mx_RoomView mx_RoomView--local"
|
||||
>
|
||||
<header
|
||||
class="mx_RoomHeader light-panel"
|
||||
class="mx_LegacyRoomHeader light-panel"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomHeader_wrapper"
|
||||
class="mx_LegacyRoomHeader_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomHeader_avatar"
|
||||
class="mx_LegacyRoomHeader_avatar"
|
||||
>
|
||||
<div
|
||||
class="mx_DecoratedRoomAvatar"
|
||||
|
@ -41,11 +41,11 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_RoomHeader_name mx_RoomHeader_name--textonly"
|
||||
class="mx_LegacyRoomHeader_name mx_LegacyRoomHeader_name--textonly"
|
||||
>
|
||||
<div
|
||||
aria-level="1"
|
||||
class="mx_RoomHeader_nametext"
|
||||
class="mx_LegacyRoomHeader_nametext"
|
||||
dir="auto"
|
||||
role="heading"
|
||||
title="@user:example.com"
|
||||
|
@ -55,7 +55,7 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1
|
|||
</div>
|
||||
<div
|
||||
aria-describedby="mx_TooltipTarget_abdefghi"
|
||||
class="mx_RoomHeader_topic mx_RoomTopic"
|
||||
class="mx_LegacyRoomHeader_topic mx_RoomTopic"
|
||||
dir="auto"
|
||||
tabindex="0"
|
||||
>
|
||||
|
@ -99,13 +99,13 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
|
|||
class="mx_RoomView mx_RoomView--local"
|
||||
>
|
||||
<header
|
||||
class="mx_RoomHeader light-panel"
|
||||
class="mx_LegacyRoomHeader light-panel"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomHeader_wrapper"
|
||||
class="mx_LegacyRoomHeader_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomHeader_avatar"
|
||||
class="mx_LegacyRoomHeader_avatar"
|
||||
>
|
||||
<div
|
||||
class="mx_DecoratedRoomAvatar"
|
||||
|
@ -134,11 +134,11 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_RoomHeader_name mx_RoomHeader_name--textonly"
|
||||
class="mx_LegacyRoomHeader_name mx_LegacyRoomHeader_name--textonly"
|
||||
>
|
||||
<div
|
||||
aria-level="1"
|
||||
class="mx_RoomHeader_nametext"
|
||||
class="mx_LegacyRoomHeader_nametext"
|
||||
dir="auto"
|
||||
role="heading"
|
||||
title="@user:example.com"
|
||||
|
@ -148,7 +148,7 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
|
|||
</div>
|
||||
<div
|
||||
aria-describedby="mx_TooltipTarget_abdefghi"
|
||||
class="mx_RoomHeader_topic mx_RoomTopic"
|
||||
class="mx_LegacyRoomHeader_topic mx_RoomTopic"
|
||||
dir="auto"
|
||||
tabindex="0"
|
||||
>
|
||||
|
@ -289,13 +289,13 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
|
|||
class="mx_RoomView mx_RoomView--local"
|
||||
>
|
||||
<header
|
||||
class="mx_RoomHeader light-panel"
|
||||
class="mx_LegacyRoomHeader light-panel"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomHeader_wrapper"
|
||||
class="mx_LegacyRoomHeader_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomHeader_avatar"
|
||||
class="mx_LegacyRoomHeader_avatar"
|
||||
>
|
||||
<div
|
||||
class="mx_DecoratedRoomAvatar"
|
||||
|
@ -324,11 +324,11 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_RoomHeader_name mx_RoomHeader_name--textonly"
|
||||
class="mx_LegacyRoomHeader_name mx_LegacyRoomHeader_name--textonly"
|
||||
>
|
||||
<div
|
||||
aria-level="1"
|
||||
class="mx_RoomHeader_nametext"
|
||||
class="mx_LegacyRoomHeader_nametext"
|
||||
dir="auto"
|
||||
role="heading"
|
||||
title="@user:example.com"
|
||||
|
@ -338,7 +338,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
|
|||
</div>
|
||||
<div
|
||||
aria-describedby="mx_TooltipTarget_abdefghi"
|
||||
class="mx_RoomHeader_topic mx_RoomTopic"
|
||||
class="mx_LegacyRoomHeader_topic mx_RoomTopic"
|
||||
dir="auto"
|
||||
tabindex="0"
|
||||
>
|
||||
|
@ -554,13 +554,13 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
|
|||
class="mx_RoomView mx_RoomView--local"
|
||||
>
|
||||
<header
|
||||
class="mx_RoomHeader light-panel"
|
||||
class="mx_LegacyRoomHeader light-panel"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomHeader_wrapper"
|
||||
class="mx_LegacyRoomHeader_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomHeader_avatar"
|
||||
class="mx_LegacyRoomHeader_avatar"
|
||||
>
|
||||
<div
|
||||
class="mx_DecoratedRoomAvatar"
|
||||
|
@ -590,14 +590,14 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
|
|||
</div>
|
||||
<div
|
||||
aria-label="This room is end-to-end encrypted"
|
||||
class="mx_E2EIcon mx_E2EIcon_normal mx_RoomHeader_icon"
|
||||
class="mx_E2EIcon mx_E2EIcon_normal mx_LegacyRoomHeader_icon"
|
||||
/>
|
||||
<div
|
||||
class="mx_RoomHeader_name mx_RoomHeader_name--textonly"
|
||||
class="mx_LegacyRoomHeader_name mx_LegacyRoomHeader_name--textonly"
|
||||
>
|
||||
<div
|
||||
aria-level="1"
|
||||
class="mx_RoomHeader_nametext"
|
||||
class="mx_LegacyRoomHeader_nametext"
|
||||
dir="auto"
|
||||
role="heading"
|
||||
title="@user:example.com"
|
||||
|
@ -607,7 +607,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
|
|||
</div>
|
||||
<div
|
||||
aria-describedby="mx_TooltipTarget_abdefghi"
|
||||
class="mx_RoomHeader_topic mx_RoomTopic"
|
||||
class="mx_LegacyRoomHeader_topic mx_RoomTopic"
|
||||
dir="auto"
|
||||
tabindex="0"
|
||||
>
|
||||
|
|
|
@ -21,12 +21,12 @@ import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
|
|||
import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts";
|
||||
import React from "react";
|
||||
|
||||
import RoomHeaderButtons from "../../../../src/components/views/right_panel/RoomHeaderButtons";
|
||||
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("RoomHeaderButtons-test.tsx", function () {
|
||||
describe("LegacyRoomHeaderButtons-test.tsx", function () {
|
||||
const ROOM_ID = "!roomId:example.org";
|
||||
let room: Room;
|
||||
let client: MatrixClient;
|
||||
|
@ -43,7 +43,7 @@ describe("RoomHeaderButtons-test.tsx", function () {
|
|||
});
|
||||
|
||||
function getComponent(room?: Room) {
|
||||
return render(<RoomHeaderButtons room={room} excludedRightPanelPhaseButtons={[]} />);
|
||||
return render(<LegacyRoomHeaderButtons room={room} excludedRightPanelPhaseButtons={[]} />);
|
||||
}
|
||||
|
||||
function getThreadButton(container: HTMLElement) {
|
||||
|
@ -75,10 +75,10 @@ describe("RoomHeaderButtons-test.tsx", function () {
|
|||
|
||||
it("thread notification does change the thread button", () => {
|
||||
const { container } = getComponent(room);
|
||||
expect(getThreadButton(container)!.className.includes("mx_RoomHeader_button--unread")).toBeFalsy();
|
||||
expect(getThreadButton(container)!.className.includes("mx_LegacyRoomHeader_button--unread")).toBeFalsy();
|
||||
|
||||
room.setThreadUnreadNotificationCount("$123", NotificationCountType.Total, 1);
|
||||
expect(getThreadButton(container)!.className.includes("mx_RoomHeader_button--unread")).toBeTruthy();
|
||||
expect(getThreadButton(container)!.className.includes("mx_LegacyRoomHeader_button--unread")).toBeTruthy();
|
||||
expect(isIndicatorOfType(container, "gray")).toBe(true);
|
||||
|
||||
room.setThreadUnreadNotificationCount("$123", NotificationCountType.Highlight, 1);
|
|
@ -1,18 +1,18 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`RoomHeaderButtons-test.tsx should render 1`] = `
|
||||
exports[`LegacyRoomHeaderButtons-test.tsx should render 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
aria-current="false"
|
||||
aria-label="Chat"
|
||||
class="mx_AccessibleButton mx_RoomHeader_button mx_RightPanel_timelineCardButton"
|
||||
class="mx_AccessibleButton mx_LegacyRoomHeader_button mx_RightPanel_timelineCardButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-current="false"
|
||||
aria-label="Threads"
|
||||
class="mx_AccessibleButton mx_RoomHeader_button mx_RightPanel_threadsButton"
|
||||
class="mx_AccessibleButton mx_LegacyRoomHeader_button mx_RightPanel_threadsButton"
|
||||
data-testid="threadsButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
|
@ -20,14 +20,14 @@ exports[`RoomHeaderButtons-test.tsx should render 1`] = `
|
|||
<div
|
||||
aria-current="false"
|
||||
aria-label="Notifications"
|
||||
class="mx_AccessibleButton mx_RoomHeader_button mx_RightPanel_notifsButton"
|
||||
class="mx_AccessibleButton mx_LegacyRoomHeader_button mx_RightPanel_notifsButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-current="false"
|
||||
aria-label="Room info"
|
||||
class="mx_AccessibleButton mx_RoomHeader_button mx_RightPanel_roomSummaryButton"
|
||||
class="mx_AccessibleButton mx_LegacyRoomHeader_button mx_RightPanel_roomSummaryButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
884
test/components/views/rooms/LegacyRoomHeader-test.tsx
Normal file
884
test/components/views/rooms/LegacyRoomHeader-test.tsx
Normal file
|
@ -0,0 +1,884 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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, act, fireEvent, waitFor, getByRole, RenderResult } from "@testing-library/react";
|
||||
import { mocked, Mocked } from "jest-mock";
|
||||
import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
|
||||
import { PendingEventOrdering } from "matrix-js-sdk/src/client";
|
||||
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { ClientWidgetApi, Widget } from "matrix-widget-api";
|
||||
import EventEmitter from "events";
|
||||
import { ISearchResults } from "matrix-js-sdk/src/@types/search";
|
||||
|
||||
import type { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import type { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import type { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
import type { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
||||
import {
|
||||
stubClient,
|
||||
mkRoomMember,
|
||||
setupAsyncStoreWithClient,
|
||||
resetAsyncStoreWithClient,
|
||||
mockPlatformPeg,
|
||||
} 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 { SearchScope } from "../../../../src/components/views/rooms/SearchBar";
|
||||
import { E2EStatus } from "../../../../src/utils/ShieldUtils";
|
||||
import { mkEvent } from "../../../test-utils";
|
||||
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 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("LegacyRoomHeader", () => {
|
||||
let client: Mocked<MatrixClient>;
|
||||
let room: Room;
|
||||
let alice: RoomMember;
|
||||
let bob: RoomMember;
|
||||
let carol: RoomMember;
|
||||
|
||||
beforeEach(async () => {
|
||||
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,
|
||||
});
|
||||
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<void>): Promise<void> => {
|
||||
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<ClientWidgetApi>;
|
||||
WidgetMessagingStore.instance.storeMessaging(widget, call.roomId, messaging);
|
||||
|
||||
await fn(call);
|
||||
|
||||
call.destroy();
|
||||
WidgetMessagingStore.instance.stopMessaging(widget, call.roomId);
|
||||
};
|
||||
|
||||
const renderHeader = (props: Partial<RoomHeaderProps> = {}, roomContext: Partial<IRoomState> = {}) => {
|
||||
render(
|
||||
<RoomContext.Provider value={{ ...roomContext, room } as IRoomState}>
|
||||
<RoomHeader
|
||||
room={room}
|
||||
inRoom={true}
|
||||
onSearchClick={() => {}}
|
||||
onInviteClick={null}
|
||||
onForgetClick={() => {}}
|
||||
onAppsClick={() => {}}
|
||||
e2eStatus={E2EStatus.Normal}
|
||||
appsShown={true}
|
||||
searchInfo={{
|
||||
searchId: Math.random(),
|
||||
promise: new Promise<ISearchResults>(() => {}),
|
||||
term: "",
|
||||
scope: SearchScope.Room,
|
||||
count: 0,
|
||||
}}
|
||||
viewingCall={false}
|
||||
activeCall={null}
|
||||
{...props}
|
||||
/>
|
||||
</RoomContext.Provider>,
|
||||
);
|
||||
};
|
||||
|
||||
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,
|
||||
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
|
||||
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,
|
||||
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,
|
||||
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) => {
|
||||
await call.connect();
|
||||
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_initial");
|
||||
expect(initial).toHaveTextContent("X");
|
||||
|
||||
// And there is no image avatar (because it's not set on this room)
|
||||
const image = rendered.container.querySelector(".mx_BaseAvatar_image");
|
||||
expect(image).toHaveAttribute("src", "data:image/png;base64,00");
|
||||
});
|
||||
|
||||
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_initial");
|
||||
expect(initial).toHaveTextContent("Y");
|
||||
|
||||
// And there is no image avatar (because it's not set on this room)
|
||||
const image = rendered.container.querySelector(".mx_BaseAvatar_image");
|
||||
expect(image).toHaveAttribute("src", "data:image/png;base64,00");
|
||||
});
|
||||
|
||||
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_initial");
|
||||
expect(initial).toHaveTextContent("Z");
|
||||
|
||||
// And there is no image avatar (because it's not set on this room)
|
||||
const image = rendered.container.querySelector(".mx_BaseAvatar_image");
|
||||
expect(image).toHaveAttribute("src", "data:image/png;base64,00");
|
||||
});
|
||||
|
||||
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_initial");
|
||||
expect(initial).toHaveTextContent("Z");
|
||||
|
||||
// And there is no image avatar (because it's not set on this room)
|
||||
const image = rendered.container.querySelector(".mx_BaseAvatar_image");
|
||||
expect(image).toHaveAttribute("src", "data:image/png;base64,00");
|
||||
});
|
||||
|
||||
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_image");
|
||||
expect(image).toHaveAttribute("src", "http://this.is.a.url/example.org/other");
|
||||
|
||||
// And there is no initial avatar
|
||||
expect(rendered.container.querySelector(".mx_BaseAvatar_initial")).toBeFalsy();
|
||||
});
|
||||
|
||||
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_initial");
|
||||
expect(initial).toHaveTextContent("Z");
|
||||
|
||||
// And there is no image avatar (because it's not set on this room)
|
||||
const image = rendered.container.querySelector(".mx_BaseAvatar_image");
|
||||
expect(image).toHaveAttribute("src", "data:image/png;base64,00");
|
||||
});
|
||||
|
||||
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();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
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) => {
|
||||
expect(eventType).toEqual("m.direct");
|
||||
return mkDirectEvent(roomId, userId, info.userIds);
|
||||
};
|
||||
}
|
||||
|
||||
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<IRoomState>): RenderResult {
|
||||
const props: RoomHeaderProps = {
|
||||
room,
|
||||
inRoom: true,
|
||||
onSearchClick: () => {},
|
||||
onInviteClick: null,
|
||||
onForgetClick: () => {},
|
||||
onAppsClick: () => {},
|
||||
e2eStatus: E2EStatus.Normal,
|
||||
appsShown: true,
|
||||
searchInfo: {
|
||||
searchId: Math.random(),
|
||||
promise: new Promise<ISearchResults>(() => {}),
|
||||
term: "",
|
||||
scope: SearchScope.Room,
|
||||
count: 0,
|
||||
},
|
||||
viewingCall: false,
|
||||
activeCall: null,
|
||||
...propsOverride,
|
||||
};
|
||||
|
||||
return render(
|
||||
<RoomContext.Provider value={{ ...roomContext, room } as IRoomState}>
|
||||
<RoomHeader {...props} />
|
||||
</RoomContext.Provider>,
|
||||
);
|
||||
}
|
||||
|
||||
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: "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<string, string[]> = {};
|
||||
for (const otherUserId of otherUsers) {
|
||||
content[otherUserId] = [roomId];
|
||||
}
|
||||
return mkEvent({
|
||||
event: true,
|
||||
type: "m.direct",
|
||||
room: roomId,
|
||||
user: userId,
|
||||
content,
|
||||
});
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,870 +15,32 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
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 } from "matrix-js-sdk/src/@types/event";
|
||||
import { Mocked } from "jest-mock";
|
||||
import { render } from "@testing-library/react";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
|
||||
import { PendingEventOrdering } from "matrix-js-sdk/src/client";
|
||||
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { ClientWidgetApi, Widget } from "matrix-widget-api";
|
||||
import EventEmitter from "events";
|
||||
import { ISearchResults } from "matrix-js-sdk/src/@types/search";
|
||||
|
||||
import { stubClient } from "../../../test-utils";
|
||||
import RoomHeader from "../../../../src/components/views/rooms/RoomHeader";
|
||||
import type { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import type { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import type { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
import type { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
||||
import {
|
||||
stubClient,
|
||||
mkRoomMember,
|
||||
setupAsyncStoreWithClient,
|
||||
resetAsyncStoreWithClient,
|
||||
mockPlatformPeg,
|
||||
} from "../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import DMRoomMap from "../../../../src/utils/DMRoomMap";
|
||||
import RoomHeader, { IProps as RoomHeaderProps } from "../../../../src/components/views/rooms/RoomHeader";
|
||||
import { SearchScope } from "../../../../src/components/views/rooms/SearchBar";
|
||||
import { E2EStatus } from "../../../../src/utils/ShieldUtils";
|
||||
import { mkEvent } from "../../../test-utils";
|
||||
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 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", () => {
|
||||
describe("Roomeader", () => {
|
||||
let client: Mocked<MatrixClient>;
|
||||
let room: Room;
|
||||
let alice: RoomMember;
|
||||
let bob: RoomMember;
|
||||
let carol: RoomMember;
|
||||
|
||||
const ROOM_ID = "!1:example.org";
|
||||
|
||||
beforeEach(async () => {
|
||||
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,
|
||||
});
|
||||
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);
|
||||
room = new Room(ROOM_ID, client, "@alice:example.org");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all([CallStore.instance, WidgetStore.instance].map(resetAsyncStoreWithClient));
|
||||
client.reEmitter.stopReEmitting(room, [RoomStateEvent.Events]);
|
||||
jest.restoreAllMocks();
|
||||
SdkConfig.reset();
|
||||
it("renders with no props", () => {
|
||||
const { asFragment } = render(<RoomHeader />);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
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<void>): Promise<void> => {
|
||||
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<ClientWidgetApi>;
|
||||
WidgetMessagingStore.instance.storeMessaging(widget, call.roomId, messaging);
|
||||
|
||||
await fn(call);
|
||||
|
||||
call.destroy();
|
||||
WidgetMessagingStore.instance.stopMessaging(widget, call.roomId);
|
||||
};
|
||||
|
||||
const renderHeader = (props: Partial<RoomHeaderProps> = {}, roomContext: Partial<IRoomState> = {}) => {
|
||||
render(
|
||||
<RoomContext.Provider value={{ ...roomContext, room } as IRoomState}>
|
||||
<RoomHeader
|
||||
room={room}
|
||||
inRoom={true}
|
||||
onSearchClick={() => {}}
|
||||
onInviteClick={null}
|
||||
onForgetClick={() => {}}
|
||||
onAppsClick={() => {}}
|
||||
e2eStatus={E2EStatus.Normal}
|
||||
appsShown={true}
|
||||
searchInfo={{
|
||||
searchId: Math.random(),
|
||||
promise: new Promise<ISearchResults>(() => {}),
|
||||
term: "",
|
||||
scope: SearchScope.Room,
|
||||
count: 0,
|
||||
}}
|
||||
viewingCall={false}
|
||||
activeCall={null}
|
||||
{...props}
|
||||
/>
|
||||
</RoomContext.Provider>,
|
||||
);
|
||||
};
|
||||
|
||||
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("renders the room header", () => {
|
||||
const { container } = render(<RoomHeader room={room} />);
|
||||
expect(container).toHaveTextContent(ROOM_ID);
|
||||
});
|
||||
|
||||
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,
|
||||
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
|
||||
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,
|
||||
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,
|
||||
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) => {
|
||||
await call.connect();
|
||||
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_initial");
|
||||
expect(initial).toHaveTextContent("X");
|
||||
|
||||
// And there is no image avatar (because it's not set on this room)
|
||||
const image = rendered.container.querySelector(".mx_BaseAvatar_image");
|
||||
expect(image).toHaveAttribute("src", "data:image/png;base64,00");
|
||||
});
|
||||
|
||||
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_initial");
|
||||
expect(initial).toHaveTextContent("Y");
|
||||
|
||||
// And there is no image avatar (because it's not set on this room)
|
||||
const image = rendered.container.querySelector(".mx_BaseAvatar_image");
|
||||
expect(image).toHaveAttribute("src", "data:image/png;base64,00");
|
||||
});
|
||||
|
||||
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_initial");
|
||||
expect(initial).toHaveTextContent("Z");
|
||||
|
||||
// And there is no image avatar (because it's not set on this room)
|
||||
const image = rendered.container.querySelector(".mx_BaseAvatar_image");
|
||||
expect(image).toHaveAttribute("src", "data:image/png;base64,00");
|
||||
});
|
||||
|
||||
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_initial");
|
||||
expect(initial).toHaveTextContent("Z");
|
||||
|
||||
// And there is no image avatar (because it's not set on this room)
|
||||
const image = rendered.container.querySelector(".mx_BaseAvatar_image");
|
||||
expect(image).toHaveAttribute("src", "data:image/png;base64,00");
|
||||
});
|
||||
|
||||
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_image");
|
||||
expect(image).toHaveAttribute("src", "http://this.is.a.url/example.org/other");
|
||||
|
||||
// And there is no initial avatar
|
||||
expect(rendered.container.querySelector(".mx_BaseAvatar_initial")).toBeFalsy();
|
||||
});
|
||||
|
||||
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_initial");
|
||||
expect(initial).toHaveTextContent("Z");
|
||||
|
||||
// And there is no image avatar (because it's not set on this room)
|
||||
const image = rendered.container.querySelector(".mx_BaseAvatar_image");
|
||||
expect(image).toHaveAttribute("src", "data:image/png;base64,00");
|
||||
});
|
||||
|
||||
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_RoomHeader_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_RoomHeader_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_RoomHeader_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_RoomHeader_name.mx_AccessibleButton")).toBeFalsy();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
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) => {
|
||||
expect(eventType).toEqual("m.direct");
|
||||
return mkDirectEvent(roomId, userId, info.userIds);
|
||||
};
|
||||
}
|
||||
|
||||
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<IRoomState>): RenderResult {
|
||||
const props: RoomHeaderProps = {
|
||||
room,
|
||||
inRoom: true,
|
||||
onSearchClick: () => {},
|
||||
onInviteClick: null,
|
||||
onForgetClick: () => {},
|
||||
onAppsClick: () => {},
|
||||
e2eStatus: E2EStatus.Normal,
|
||||
appsShown: true,
|
||||
searchInfo: {
|
||||
searchId: Math.random(),
|
||||
promise: new Promise<ISearchResults>(() => {}),
|
||||
term: "",
|
||||
scope: SearchScope.Room,
|
||||
count: 0,
|
||||
},
|
||||
viewingCall: false,
|
||||
activeCall: null,
|
||||
...propsOverride,
|
||||
};
|
||||
|
||||
return render(
|
||||
<RoomContext.Provider value={{ ...roomContext, room } as IRoomState}>
|
||||
<RoomHeader {...props} />
|
||||
</RoomContext.Provider>,
|
||||
);
|
||||
}
|
||||
|
||||
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: "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<string, string[]> = {};
|
||||
for (const otherUserId of otherUsers) {
|
||||
content[otherUserId] = [roomId];
|
||||
}
|
||||
return mkEvent({
|
||||
event: true,
|
||||
type: "m.direct",
|
||||
room: roomId,
|
||||
user: userId,
|
||||
content,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Roomeader renders with no props 1`] = `
|
||||
<DocumentFragment>
|
||||
<header
|
||||
class="mx_LegacyRoomHeader light-panel"
|
||||
>
|
||||
<div
|
||||
class="mx_LegacyRoomHeader_wrapper"
|
||||
/>
|
||||
</header>
|
||||
</DocumentFragment>
|
||||
`;
|
Loading…
Add table
Add a link
Reference in a new issue