d35fce198c
* Ask the user to change the room access settings if they click the create link button. Signed-off-by: Timo K <toger5@hotmail.de> * disable call button if appropriate. Signed-off-by: Timo K <toger5@hotmail.de> * Add tests Refactor tests to be in CallGuestLinkButton-test instead of the RoomHeader Signed-off-by: Timo K <toger5@hotmail.de> * add test for: no button if cannot change join rule and room not public nor knock Signed-off-by: Timo K <toger5@hotmail.de> * fix tests Signed-off-by: Timo K <toger5@hotmail.de> * add JoinRuleDialog tests Signed-off-by: Timo K <toger5@hotmail.de> * move spy into before each Signed-off-by: Timo K <toger5@hotmail.de> * Update src/i18n/strings/en_EN.json Co-authored-by: Robin <robin@robin.town> * remove inline css and update modal style Signed-off-by: Timo K <toger5@hotmail.de> * Update src/i18n/strings/en_EN.json Co-authored-by: Robin <robin@robin.town> * Update src/i18n/strings/en_EN.json Co-authored-by: Robin <robin@robin.town> * Invite state was not reactive. Changing power level did not update the ui. Signed-off-by: Timo K <toger5@hotmail.de> * linter Signed-off-by: Timo K <toger5@hotmail.de> * make useGuestAccessInformation use useRoomState Signed-off-by: Timo K <toger5@hotmail.de> * fix tests and simplify logic * fix tests * review Signed-off-by: Timo K <toger5@hotmail.de> --------- Signed-off-by: Timo K <toger5@hotmail.de> Co-authored-by: Robin <robin@robin.town>
290 lines
13 KiB
TypeScript
290 lines
13 KiB
TypeScript
/*
|
|
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.
|
|
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 { TooltipProvider } from "@vector-im/compound-web";
|
|
import { fireEvent, getByLabelText, getByText, render, screen, waitFor } from "@testing-library/react";
|
|
import { EventTimeline, JoinRule, Room } from "matrix-js-sdk/src/matrix";
|
|
import { KnownMembership } from "matrix-js-sdk/src/types";
|
|
|
|
import { SDKContext, SdkContextClass } from "../../../../../src/contexts/SDKContext";
|
|
import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../../test-utils";
|
|
import {
|
|
CallGuestLinkButton,
|
|
JoinRuleDialog,
|
|
} from "../../../../../src/components/views/rooms/RoomHeader/CallGuestLinkButton";
|
|
import Modal from "../../../../../src/Modal";
|
|
import SdkConfig from "../../../../../src/SdkConfig";
|
|
import ShareDialog from "../../../../../src/components/views/dialogs/ShareDialog";
|
|
import { _t } from "../../../../../src/languageHandler";
|
|
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
|
|
|
describe("<CallGuestLinkButton />", () => {
|
|
const roomId = "!room:server.org";
|
|
let sdkContext!: SdkContextClass;
|
|
let modalSpy: jest.SpyInstance;
|
|
let modalResolve: (value: unknown[] | PromiseLike<unknown[]>) => void;
|
|
let room: Room;
|
|
|
|
const targetUnencrypted =
|
|
"https://guest_spa_url.com/room/#/!room:server.org?roomId=%21room%3Aserver.org&viaServers=example.org";
|
|
const targetEncrypted =
|
|
"https://guest_spa_url.com/room/#/!room:server.org?roomId=%21room%3Aserver.org&perParticipantE2EE=true&viaServers=example.org";
|
|
const expectedShareDialogProps = {
|
|
target: targetEncrypted,
|
|
customTitle: "Conference invite link",
|
|
subtitle: "Link for external users to join the call without a matrix account:",
|
|
};
|
|
|
|
/**
|
|
* Create a room using mocked client
|
|
* And mock isElementVideoRoom
|
|
*/
|
|
const makeRoom = (isVideoRoom = true): Room => {
|
|
const room = new Room(roomId, sdkContext.client!, sdkContext.client!.getSafeUserId());
|
|
jest.spyOn(room, "isElementVideoRoom").mockReturnValue(isVideoRoom);
|
|
// stub
|
|
jest.spyOn(room, "getPendingEvents").mockReturnValue([]);
|
|
return room;
|
|
};
|
|
function mockRoomMembers(room: Room, count: number) {
|
|
const members = Array(count)
|
|
.fill(0)
|
|
.map((_, index) => ({
|
|
userId: `@user-${index}:example.org`,
|
|
roomId: room.roomId,
|
|
membership: KnownMembership.Join,
|
|
}));
|
|
|
|
room.currentState.setJoinedMemberCount(members.length);
|
|
room.getJoinedMembers = jest.fn().mockReturnValue(members);
|
|
}
|
|
|
|
const getComponent = (room: Room) =>
|
|
render(<CallGuestLinkButton room={room} />, {
|
|
wrapper: ({ children }) => (
|
|
<SDKContext.Provider value={sdkContext}>
|
|
<TooltipProvider>{children}</TooltipProvider>
|
|
</SDKContext.Provider>
|
|
),
|
|
});
|
|
|
|
const oldGet = SdkConfig.get;
|
|
beforeEach(() => {
|
|
const client = getMockClientWithEventEmitter({
|
|
...mockClientMethodsUser(),
|
|
sendStateEvent: jest.fn(),
|
|
});
|
|
sdkContext = new SdkContextClass();
|
|
sdkContext.client = client;
|
|
const modalPromise = new Promise<unknown[]>((resolve) => {
|
|
modalResolve = resolve;
|
|
});
|
|
modalSpy = jest.spyOn(Modal, "createDialog").mockReturnValue({ finished: modalPromise, close: jest.fn() });
|
|
room = makeRoom();
|
|
mockRoomMembers(room, 3);
|
|
|
|
jest.spyOn(SdkConfig, "get").mockImplementation((key) => {
|
|
if (key === "element_call") {
|
|
return { guest_spa_url: "https://guest_spa_url.com", url: "https://spa_url.com" };
|
|
}
|
|
return oldGet(key);
|
|
});
|
|
jest.spyOn(room, "hasEncryptionStateEvent").mockReturnValue(true);
|
|
jest.spyOn(SdkContextClass.instance.roomViewStore, "isViewingCall").mockReturnValue(true);
|
|
});
|
|
afterEach(() => {
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
it("shows the JoinRuleDialog on click with private join rules", async () => {
|
|
getComponent(room);
|
|
fireEvent.click(screen.getByLabelText("Share call link"));
|
|
expect(modalSpy).toHaveBeenCalledWith(JoinRuleDialog, { room, canInvite: false });
|
|
// pretend public was selected
|
|
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Public);
|
|
modalResolve([]);
|
|
await new Promise(process.nextTick);
|
|
const callParams = modalSpy.mock.calls[1];
|
|
expect(callParams[0]).toEqual(ShareDialog);
|
|
expect(callParams[1].target.toString()).toEqual(expectedShareDialogProps.target);
|
|
expect(callParams[1].subtitle).toEqual(expectedShareDialogProps.subtitle);
|
|
expect(callParams[1].customTitle).toEqual(expectedShareDialogProps.customTitle);
|
|
});
|
|
|
|
it("shows the ShareDialog on click with public join rules", () => {
|
|
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Public);
|
|
getComponent(room);
|
|
fireEvent.click(screen.getByLabelText("Share call link"));
|
|
const callParams = modalSpy.mock.calls[0];
|
|
expect(callParams[0]).toEqual(ShareDialog);
|
|
expect(callParams[1].target.toString()).toEqual(expectedShareDialogProps.target);
|
|
expect(callParams[1].subtitle).toEqual(expectedShareDialogProps.subtitle);
|
|
expect(callParams[1].customTitle).toEqual(expectedShareDialogProps.customTitle);
|
|
});
|
|
|
|
it("shows the ShareDialog on click with knock join rules", () => {
|
|
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Knock);
|
|
jest.spyOn(room, "canInvite").mockReturnValue(true);
|
|
getComponent(room);
|
|
fireEvent.click(screen.getByLabelText("Share call link"));
|
|
const callParams = modalSpy.mock.calls[0];
|
|
expect(callParams[0]).toEqual(ShareDialog);
|
|
expect(callParams[1].target.toString()).toEqual(expectedShareDialogProps.target);
|
|
expect(callParams[1].subtitle).toEqual(expectedShareDialogProps.subtitle);
|
|
expect(callParams[1].customTitle).toEqual(expectedShareDialogProps.customTitle);
|
|
});
|
|
|
|
it("don't show external conference button if room not public nor knock and the user cannot change join rules", () => {
|
|
// preparation for if we refactor the related code to not use currentState.
|
|
jest.spyOn(room, "getLiveTimeline").mockReturnValue({
|
|
getState: jest.fn().mockReturnValue({
|
|
maySendStateEvent: jest.fn().mockReturnValue(false),
|
|
}),
|
|
} as unknown as EventTimeline);
|
|
jest.spyOn(room.currentState, "maySendStateEvent").mockReturnValue(false);
|
|
getComponent(room);
|
|
expect(screen.queryByLabelText("Share call link")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("don't show external conference button if now guest spa link is configured", () => {
|
|
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Public);
|
|
jest.spyOn(SdkContextClass.instance.roomViewStore, "isViewingCall").mockReturnValue(true);
|
|
|
|
jest.spyOn(SdkConfig, "get").mockImplementation((key) => {
|
|
if (key === "element_call") {
|
|
return { url: "https://example2.com" };
|
|
}
|
|
return oldGet(key);
|
|
});
|
|
|
|
getComponent(room);
|
|
// We only change the SdkConfig and show that this everything else is
|
|
// configured so that the call link button is shown.
|
|
expect(screen.queryByLabelText("Share call link")).not.toBeInTheDocument();
|
|
|
|
jest.spyOn(SdkConfig, "get").mockImplementation((key) => {
|
|
if (key === "element_call") {
|
|
return { guest_spa_url: "https://guest_spa_url.com", url: "https://example2.com" };
|
|
}
|
|
return oldGet(key);
|
|
});
|
|
|
|
const { container } = getComponent(room);
|
|
expect(getByLabelText(container, "Share call link")).toBeInTheDocument();
|
|
});
|
|
|
|
it("opens the share dialog with the correct share link in an encrypted room", () => {
|
|
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Public);
|
|
jest.spyOn(SdkContextClass.instance.roomViewStore, "isViewingCall").mockReturnValue(true);
|
|
|
|
const { container } = getComponent(room);
|
|
const modalSpy = jest.spyOn(Modal, "createDialog");
|
|
fireEvent.click(getByLabelText(container, _t("voip|get_call_link")));
|
|
// const target =
|
|
// "https://guest_spa_url.com/room/#/!room:server.org?roomId=%21room%3Aserver.org&perParticipantE2EE=true&viaServers=example.org";
|
|
expect(modalSpy).toHaveBeenCalled();
|
|
const arg0 = modalSpy.mock.calls[0][0];
|
|
const arg1 = modalSpy.mock.calls[0][1] as any;
|
|
expect(arg0).toEqual(ShareDialog);
|
|
const { customTitle, subtitle } = arg1;
|
|
expect({ customTitle, subtitle }).toEqual({
|
|
customTitle: "Conference invite link",
|
|
subtitle: _t("share|share_call_subtitle"),
|
|
});
|
|
expect(arg1.target.toString()).toEqual(targetEncrypted);
|
|
});
|
|
|
|
it("share dialog has correct link in an unencrypted room", () => {
|
|
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Public);
|
|
jest.spyOn(room, "hasEncryptionStateEvent").mockReturnValue(false);
|
|
jest.spyOn(SdkContextClass.instance.roomViewStore, "isViewingCall").mockReturnValue(true);
|
|
|
|
const { container } = getComponent(room);
|
|
const modalSpy = jest.spyOn(Modal, "createDialog");
|
|
fireEvent.click(getByLabelText(container, _t("voip|get_call_link")));
|
|
const arg1 = modalSpy.mock.calls[0][1] as any;
|
|
expect(arg1.target.toString()).toEqual(targetUnencrypted);
|
|
});
|
|
|
|
describe("<JoinRuleDialog />", () => {
|
|
const onFinished = jest.fn();
|
|
|
|
const getComponent = (room: Room, canInvite: boolean = true) =>
|
|
render(<JoinRuleDialog room={room} canInvite={canInvite} onFinished={onFinished} />, {
|
|
wrapper: ({ children }) => (
|
|
<SDKContext.Provider value={sdkContext}>
|
|
<TooltipProvider>{children}</TooltipProvider>
|
|
</SDKContext.Provider>
|
|
),
|
|
});
|
|
|
|
beforeEach(() => {
|
|
// feature_ask_to_join enabled
|
|
jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
|
|
});
|
|
|
|
it("shows ask to join if feature is enabled", () => {
|
|
const { container } = getComponent(room);
|
|
expect(getByText(container, "Ask to join")).toBeInTheDocument();
|
|
});
|
|
it("font show ask to join if feature is enabled but cannot invite", () => {
|
|
getComponent(room, false);
|
|
expect(screen.queryByText("Ask to join")).not.toBeInTheDocument();
|
|
});
|
|
it("doesn't show ask to join if feature is disabled", () => {
|
|
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
|
getComponent(room);
|
|
expect(screen.queryByText("Ask to join")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("sends correct state event on click", async () => {
|
|
const sendStateSpy = jest.spyOn(sdkContext.client!, "sendStateEvent");
|
|
let container;
|
|
container = getComponent(room).container;
|
|
fireEvent.click(getByText(container, "Ask to join"));
|
|
expect(sendStateSpy).toHaveBeenCalledWith(
|
|
"!room:server.org",
|
|
"m.room.join_rules",
|
|
{ join_rule: "knock" },
|
|
"",
|
|
);
|
|
expect(sendStateSpy).toHaveBeenCalledTimes(1);
|
|
await waitFor(() => expect(onFinished).toHaveBeenCalledTimes(1));
|
|
onFinished.mockClear();
|
|
sendStateSpy.mockClear();
|
|
|
|
container = getComponent(room).container;
|
|
fireEvent.click(getByText(container, "Public"));
|
|
expect(sendStateSpy).toHaveBeenLastCalledWith(
|
|
"!room:server.org",
|
|
"m.room.join_rules",
|
|
{ join_rule: "public" },
|
|
"",
|
|
);
|
|
expect(sendStateSpy).toHaveBeenCalledTimes(1);
|
|
container = getComponent(room).container;
|
|
await waitFor(() => expect(onFinished).toHaveBeenCalledTimes(1));
|
|
onFinished.mockClear();
|
|
sendStateSpy.mockClear();
|
|
|
|
fireEvent.click(getByText(container, _t("update_room_access_modal|no_change")));
|
|
await waitFor(() => expect(onFinished).toHaveBeenCalledTimes(1));
|
|
// Don't call sendStateEvent if no change is clicked.
|
|
expect(sendStateSpy).toHaveBeenCalledTimes(0);
|
|
});
|
|
});
|
|
});
|