Call guest access link creation to join calls as a non registered user via the EC SPA (#12259)
* Add externall call link button if in public call room Signed-off-by: Timo K <toger5@hotmail.de> * Allow configuring a spa homeserver url. Signed-off-by: Timo K <toger5@hotmail.de> * temp Signed-off-by: Timo K <toger5@hotmail.de> * remove homeserver url Signed-off-by: Timo K <toger5@hotmail.de> * Add custom title to share dialog. So that we can use it as a "share call" dialog. Signed-off-by: Timo K <toger5@hotmail.de> * - rename config options - only show link button if a guest url is provided - share dialog custom Title - rename call share labels Signed-off-by: Timo K <toger5@hotmail.de> * rename to title_link Signed-off-by: Timo K <toger5@hotmail.de> * add tests for ShareDialog Signed-off-by: Timo K <toger5@hotmail.de> * add tests for share call button Signed-off-by: Timo K <toger5@hotmail.de> * review Signed-off-by: Timo K <toger5@hotmail.de> * remove comment Signed-off-by: Timo K <toger5@hotmail.de> * Update src/components/views/dialogs/ShareDialog.tsx Co-authored-by: David Baker <dbkr@users.noreply.github.com> --------- Signed-off-by: Timo K <toger5@hotmail.de> Co-authored-by: David Baker <dbkr@users.noreply.github.com>pull/28217/head
parent
af51897889
commit
70365c891b
|
@ -119,6 +119,7 @@ export interface IConfigOptions {
|
|||
};
|
||||
element_call: {
|
||||
url?: string;
|
||||
guest_spa_url?: string;
|
||||
use_exclusively?: boolean;
|
||||
participant_limit?: number;
|
||||
brand?: string;
|
||||
|
|
|
@ -62,11 +62,28 @@ const socials = [
|
|||
];
|
||||
|
||||
interface BaseProps {
|
||||
/**
|
||||
* A function that is called when the dialog is dismissed
|
||||
*/
|
||||
onFinished(): void;
|
||||
/**
|
||||
* An optional string to use as the dialog title.
|
||||
* If not provided, an appropriate title for the target type will be used.
|
||||
*/
|
||||
customTitle?: string;
|
||||
/**
|
||||
* An optional string to use as the dialog subtitle
|
||||
*/
|
||||
subtitle?: string;
|
||||
}
|
||||
|
||||
interface Props extends BaseProps {
|
||||
target: Room | User | RoomMember;
|
||||
/**
|
||||
* The target to link to.
|
||||
* This can be a Room, User, RoomMember, or MatrixEvent or an already computed URL.
|
||||
* A <u>matrix.to</u> link will be generated out of it if it's not already a url.
|
||||
*/
|
||||
target: Room | User | RoomMember | URL;
|
||||
permalinkCreator?: RoomPermalinkCreator;
|
||||
}
|
||||
|
||||
|
@ -109,7 +126,9 @@ export default class ShareDialog extends React.PureComponent<XOR<Props, EventPro
|
|||
};
|
||||
|
||||
private getUrl(): string {
|
||||
if (this.props.target instanceof Room) {
|
||||
if (this.props.target instanceof URL) {
|
||||
return this.props.target.toString();
|
||||
} else if (this.props.target instanceof Room) {
|
||||
if (this.state.linkSpecificEvent) {
|
||||
const events = this.props.target.getLiveTimeline().getEvents();
|
||||
return this.state.permalinkCreator!.forEvent(events[events.length - 1].getId()!);
|
||||
|
@ -129,8 +148,10 @@ export default class ShareDialog extends React.PureComponent<XOR<Props, EventPro
|
|||
let title: string | undefined;
|
||||
let checkbox: JSX.Element | undefined;
|
||||
|
||||
if (this.props.target instanceof Room) {
|
||||
title = _t("share|title_room");
|
||||
if (this.props.target instanceof URL) {
|
||||
title = this.props.customTitle ?? _t("share|title_link");
|
||||
} else if (this.props.target instanceof Room) {
|
||||
title = this.props.customTitle ?? _t("share|title_room");
|
||||
|
||||
const events = this.props.target.getLiveTimeline().getEvents();
|
||||
if (events.length > 0) {
|
||||
|
@ -146,9 +167,9 @@ export default class ShareDialog extends React.PureComponent<XOR<Props, EventPro
|
|||
);
|
||||
}
|
||||
} else if (this.props.target instanceof User || this.props.target instanceof RoomMember) {
|
||||
title = _t("share|title_user");
|
||||
title = this.props.customTitle ?? _t("share|title_user");
|
||||
} else if (this.props.target instanceof MatrixEvent) {
|
||||
title = _t("share|title_message");
|
||||
title = this.props.customTitle ?? _t("share|title_message");
|
||||
checkbox = (
|
||||
<div>
|
||||
<StyledCheckbox
|
||||
|
@ -206,6 +227,7 @@ export default class ShareDialog extends React.PureComponent<XOR<Props, EventPro
|
|||
contentId="mx_Dialog_content"
|
||||
onFinished={this.props.onFinished}
|
||||
>
|
||||
{this.props.subtitle && <p>{this.props.subtitle}</p>}
|
||||
<div className="mx_ShareDialog_content">
|
||||
<CopyableText getTextToCopy={() => matrixToUrl}>
|
||||
<a title={_t("share|link_title")} href={matrixToUrl} onClick={ShareDialog.onLinkClick}>
|
||||
|
|
|
@ -18,6 +18,7 @@ import React, { useCallback, useEffect, useMemo, useState } from "react";
|
|||
import { Body as BodyText, Button, IconButton, Menu, MenuItem, Tooltip } from "@vector-im/compound-web";
|
||||
import { Icon as VideoCallIcon } from "@vector-im/compound-design-tokens/icons/video-call-solid.svg";
|
||||
import { Icon as VoiceCallIcon } from "@vector-im/compound-design-tokens/icons/voice-call.svg";
|
||||
import { Icon as ExternalLinkIcon } from "@vector-im/compound-design-tokens/icons/link.svg";
|
||||
import { Icon as CloseCallIcon } from "@vector-im/compound-design-tokens/icons/close.svg";
|
||||
import { Icon as ThreadsIcon } from "@vector-im/compound-design-tokens/icons/threads-solid.svg";
|
||||
import { Icon as NotificationsIcon } from "@vector-im/compound-design-tokens/icons/notifications-solid.svg";
|
||||
|
@ -26,6 +27,7 @@ import { Icon as ErrorIcon } from "@vector-im/compound-design-tokens/icons/error
|
|||
import { Icon as PublicIcon } from "@vector-im/compound-design-tokens/icons/public.svg";
|
||||
import { EventType, JoinRule, type Room } from "matrix-js-sdk/src/matrix";
|
||||
import { ViewRoomOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { useRoomName } from "../../../hooks/useRoomName";
|
||||
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
|
||||
|
@ -54,6 +56,8 @@ import { VideoRoomChatButton } from "./RoomHeader/VideoRoomChatButton";
|
|||
import { RoomKnocksBar } from "./RoomKnocksBar";
|
||||
import { isVideoRoom } from "../../../utils/video-rooms";
|
||||
import { notificationLevelToIndicator } from "../../../utils/notifications";
|
||||
import Modal from "../../../Modal";
|
||||
import ShareDialog from "../dialogs/ShareDialog";
|
||||
|
||||
export default function RoomHeader({
|
||||
room,
|
||||
|
@ -78,6 +82,8 @@ export default function RoomHeader({
|
|||
videoCallClick,
|
||||
toggleCallMaximized: toggleCall,
|
||||
isViewingCall,
|
||||
generateCallLink,
|
||||
canGenerateCallLink,
|
||||
isConnectedToCall,
|
||||
hasActiveCallSession,
|
||||
callOptions,
|
||||
|
@ -118,6 +124,20 @@ export default function RoomHeader({
|
|||
|
||||
const videoClick = useCallback((ev) => videoCallClick(ev, callOptions[0]), [callOptions, videoCallClick]);
|
||||
|
||||
const shareClick = useCallback(() => {
|
||||
try {
|
||||
// generateCallLink throws if the permissions are not met
|
||||
const target = generateCallLink();
|
||||
Modal.createDialog(ShareDialog, {
|
||||
target,
|
||||
customTitle: _t("share|share_call"),
|
||||
subtitle: _t("share|share_call_subtitle"),
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error("Could not generate call link.", e);
|
||||
}
|
||||
}, [generateCallLink]);
|
||||
|
||||
const toggleCallButton = (
|
||||
<Tooltip label={isViewingCall ? _t("voip|minimise_call") : _t("voip|maximise_call")}>
|
||||
<IconButton onClick={toggleCall}>
|
||||
|
@ -125,7 +145,13 @@ export default function RoomHeader({
|
|||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const createExternalLinkButton = (
|
||||
<Tooltip label={_t("voip|get_call_link")}>
|
||||
<IconButton onClick={shareClick} aria-label={_t("voip|get_call_link")}>
|
||||
<ExternalLinkIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
const joinCallButton = (
|
||||
<Tooltip label={videoCallDisabledReason ?? _t("voip|video_call")}>
|
||||
<Button
|
||||
|
@ -309,7 +335,7 @@ export default function RoomHeader({
|
|||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
|
||||
{isViewingCall && canGenerateCallLink && createExternalLinkButton}
|
||||
{((isConnectedToCall && isViewingCall) || isVideoRoom(room)) && <VideoRoomChatButton room={room} />}
|
||||
|
||||
{hasActiveCallSession && !isConnectedToCall && !isViewingCall ? (
|
||||
|
|
|
@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { Room } from "matrix-js-sdk/src/matrix";
|
||||
import { JoinRule, Room } from "matrix-js-sdk/src/matrix";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { useFeatureEnabled } from "../useSettings";
|
||||
import SdkConfig from "../../SdkConfig";
|
||||
|
@ -39,6 +40,7 @@ import defaultDispatcher from "../../dispatcher/dispatcher";
|
|||
import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import { CallStore, CallStoreEvent } from "../../stores/CallStore";
|
||||
import { calculateRoomVia } from "../../utils/permalinks/Permalinks";
|
||||
|
||||
export enum PlatformCallType {
|
||||
ElementCall,
|
||||
|
@ -78,27 +80,35 @@ export const useRoomCall = (
|
|||
videoCallClick(evt: React.MouseEvent | undefined, selectedType: PlatformCallType): void;
|
||||
toggleCallMaximized: () => void;
|
||||
isViewingCall: boolean;
|
||||
generateCallLink: () => URL;
|
||||
canGenerateCallLink: boolean;
|
||||
isConnectedToCall: boolean;
|
||||
hasActiveCallSession: boolean;
|
||||
callOptions: PlatformCallType[];
|
||||
} => {
|
||||
// settings
|
||||
const groupCallsEnabled = useFeatureEnabled("feature_group_calls");
|
||||
const useElementCallExclusively = useMemo(() => {
|
||||
return SdkConfig.get("element_call").use_exclusively;
|
||||
}, []);
|
||||
|
||||
const guestSpaUrl = useMemo(() => {
|
||||
return SdkConfig.get("element_call").guest_spa_url;
|
||||
}, []);
|
||||
|
||||
const hasLegacyCall = useEventEmitterState(
|
||||
LegacyCallHandler.instance,
|
||||
LegacyCallHandlerEvent.CallsChanged,
|
||||
() => LegacyCallHandler.instance.getCallForRoom(room.roomId) !== null,
|
||||
);
|
||||
|
||||
// settings
|
||||
const widgets = useWidgets(room);
|
||||
const jitsiWidget = useMemo(() => widgets.find((widget) => WidgetType.JITSI.matches(widget.type)), [widgets]);
|
||||
const hasJitsiWidget = !!jitsiWidget;
|
||||
const managedHybridWidget = useMemo(() => widgets.find(isManagedHybridWidget), [widgets]);
|
||||
const hasManagedHybridWidget = !!managedHybridWidget;
|
||||
|
||||
// group call
|
||||
const groupCall = useCall(room.roomId);
|
||||
const isConnectedToCall = useConnectionState(groupCall) === ConnectionState.Connected;
|
||||
const hasGroupCall = groupCall !== null;
|
||||
|
@ -107,11 +117,14 @@ export const useRoomCall = (
|
|||
SdkContextClass.instance.roomViewStore.isViewingCall(),
|
||||
);
|
||||
|
||||
// room
|
||||
const memberCount = useRoomMemberCount(room);
|
||||
|
||||
const [mayEditWidgets, mayCreateElementCalls] = useRoomState(room, () => [
|
||||
const [mayEditWidgets, mayCreateElementCalls, canJoinWithoutInvite] = useRoomState(room, () => [
|
||||
room.currentState.mayClientSendStateEvent("im.vector.modular.widgets", room.client),
|
||||
room.currentState.mayClientSendStateEvent(ElementCall.MEMBER_EVENT_TYPE.name, room.client),
|
||||
room.getJoinRule() === "public" || room.getJoinRule() === JoinRule.Knock,
|
||||
/*|| room.getJoinRule() === JoinRule.Restricted <- rule for joining via token?*/
|
||||
]);
|
||||
|
||||
// The options provided to the RoomHeader.
|
||||
|
@ -131,7 +144,7 @@ export const useRoomCall = (
|
|||
return [PlatformCallType.ElementCall];
|
||||
}
|
||||
if (hasGroupCall && WidgetType.CALL.matches(groupCall.widget.type)) {
|
||||
// only allow joining joining the ongoing Element call if there is one.
|
||||
// only allow joining the ongoing Element call if there is one.
|
||||
return [PlatformCallType.ElementCall];
|
||||
}
|
||||
}
|
||||
|
@ -258,6 +271,26 @@ export const useRoomCall = (
|
|||
});
|
||||
}, [isViewingCall, room.roomId]);
|
||||
|
||||
const generateCallLink = useCallback(() => {
|
||||
if (!canJoinWithoutInvite)
|
||||
throw new Error("Cannot create link for room that users can not join without invite.");
|
||||
if (!guestSpaUrl) throw new Error("No guest SPA url for external links provided.");
|
||||
const url = new URL(guestSpaUrl);
|
||||
url.pathname = "/room/";
|
||||
// Set params for the sharable url
|
||||
url.searchParams.set("roomId", room.roomId);
|
||||
url.searchParams.set("perParticipantE2EE", "true");
|
||||
for (const server of calculateRoomVia(room)) {
|
||||
url.searchParams.set("viaServers", server);
|
||||
}
|
||||
|
||||
// Move params into hash
|
||||
url.hash = "/" + room.name + url.search;
|
||||
url.search = "";
|
||||
|
||||
logger.info("Generated element call external url:", url);
|
||||
return url;
|
||||
}, [canJoinWithoutInvite, guestSpaUrl, room]);
|
||||
/**
|
||||
* We've gone through all the steps
|
||||
*/
|
||||
|
@ -268,6 +301,8 @@ export const useRoomCall = (
|
|||
videoCallClick,
|
||||
toggleCallMaximized: toggleCallMaximized,
|
||||
isViewingCall: isViewingCall,
|
||||
generateCallLink,
|
||||
canGenerateCallLink: guestSpaUrl !== undefined && canJoinWithoutInvite,
|
||||
isConnectedToCall: isConnectedToCall,
|
||||
hasActiveCallSession: hasActiveCallSession,
|
||||
callOptions,
|
||||
|
|
|
@ -2896,6 +2896,9 @@
|
|||
"link_title": "Link to room",
|
||||
"permalink_message": "Link to selected message",
|
||||
"permalink_most_recent": "Link to most recent message",
|
||||
"share_call": "Conference invite link",
|
||||
"share_call_subtitle": "Link for external users to join the call without a matrix account:",
|
||||
"title_link": "Share Link",
|
||||
"title_message": "Share Room Message",
|
||||
"title_room": "Share Room",
|
||||
"title_user": "Share User"
|
||||
|
@ -3828,6 +3831,7 @@
|
|||
"expand": "Return to call",
|
||||
"failed_call_live_broadcast_description": "You can’t start a call as you are currently recording a live broadcast. Please end your live broadcast in order to start a call.",
|
||||
"failed_call_live_broadcast_title": "Can’t start a call",
|
||||
"get_call_link": "Share call link",
|
||||
"hangup": "Hangup",
|
||||
"hide_sidebar_button": "Hide sidebar",
|
||||
"input_devices": "Input devices",
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
Copyright 2024 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 { EventTimeline, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { render, RenderOptions } from "@testing-library/react";
|
||||
import { TooltipProvider } from "@vector-im/compound-web";
|
||||
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||
import { _t } from "../../../../src/languageHandler";
|
||||
import ShareDialog from "../../../../src/components/views/dialogs/ShareDialog";
|
||||
import { UIFeature } from "../../../../src/settings/UIFeature";
|
||||
import { stubClient } from "../../../test-utils";
|
||||
jest.mock("../../../../src/utils/ShieldUtils");
|
||||
|
||||
function getWrapper(): RenderOptions {
|
||||
return {
|
||||
wrapper: ({ children }) => (
|
||||
<TooltipProvider>
|
||||
<MatrixClientContext.Provider value={MatrixClientPeg.safeGet()}>
|
||||
{children}
|
||||
</MatrixClientContext.Provider>
|
||||
</TooltipProvider>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
describe("ShareDialog", () => {
|
||||
let room: Room;
|
||||
|
||||
const ROOM_ID = "!1:example.org";
|
||||
|
||||
beforeEach(async () => {
|
||||
stubClient();
|
||||
room = new Room(ROOM_ID, MatrixClientPeg.get()!, "@alice:example.org");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("renders room share dialog", () => {
|
||||
const { container: withoutEvents } = render(<ShareDialog target={room} onFinished={jest.fn()} />, getWrapper());
|
||||
expect(withoutEvents).toHaveTextContent(_t("share|title_room"));
|
||||
|
||||
jest.spyOn(room, "getLiveTimeline").mockReturnValue({ getEvents: () => [{} as MatrixEvent] } as EventTimeline);
|
||||
const { container: withEvents } = render(<ShareDialog target={room} onFinished={jest.fn()} />, getWrapper());
|
||||
expect(withEvents).toHaveTextContent(_t("share|permalink_most_recent"));
|
||||
});
|
||||
|
||||
it("renders user share dialog", () => {
|
||||
mockRoomMembers(room, 1);
|
||||
const { container } = render(
|
||||
<ShareDialog target={room.getJoinedMembers()[0]} onFinished={jest.fn()} />,
|
||||
getWrapper(),
|
||||
);
|
||||
expect(container).toHaveTextContent(_t("share|title_user"));
|
||||
});
|
||||
|
||||
it("renders link share dialog", () => {
|
||||
mockRoomMembers(room, 1);
|
||||
const { container } = render(
|
||||
<ShareDialog target={new URL("https://matrix.org")} onFinished={jest.fn()} />,
|
||||
getWrapper(),
|
||||
);
|
||||
expect(container).toHaveTextContent(_t("share|title_link"));
|
||||
});
|
||||
|
||||
it("renders the QR code if configured", () => {
|
||||
const originalGetValue = SettingsStore.getValue;
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => {
|
||||
if (feature === UIFeature.ShareQRCode) return true;
|
||||
return originalGetValue(feature);
|
||||
});
|
||||
const { container } = render(<ShareDialog target={room} onFinished={jest.fn()} />, getWrapper());
|
||||
const qrCodesVisible = container.getElementsByClassName("mx_ShareDialog_qrcode_container").length > 0;
|
||||
expect(qrCodesVisible).toBe(true);
|
||||
});
|
||||
|
||||
it("renders the social button if configured", () => {
|
||||
const originalGetValue = SettingsStore.getValue;
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => {
|
||||
if (feature === UIFeature.ShareSocial) return true;
|
||||
return originalGetValue(feature);
|
||||
});
|
||||
const { container } = render(<ShareDialog target={room} onFinished={jest.fn()} />, getWrapper());
|
||||
const qrCodesVisible = container.getElementsByClassName("mx_ShareDialog_social_container").length > 0;
|
||||
expect(qrCodesVisible).toBe(true);
|
||||
});
|
||||
it("renders custom title and subtitle", () => {
|
||||
const { container } = render(
|
||||
<ShareDialog
|
||||
target={room}
|
||||
customTitle="test_title_123"
|
||||
subtitle="custom_subtitle_1234"
|
||||
onFinished={jest.fn()}
|
||||
/>,
|
||||
getWrapper(),
|
||||
);
|
||||
expect(container).toHaveTextContent("test_title_123");
|
||||
expect(container).toHaveTextContent("custom_subtitle_1234");
|
||||
});
|
||||
});
|
||||
/**
|
||||
*
|
||||
* @param count the number of users to create
|
||||
*/
|
||||
function mockRoomMembers(room: Room, count: number) {
|
||||
const members = Array(count)
|
||||
.fill(0)
|
||||
.map((_, index) => new RoomMember(room.roomId, "@alice:example.org"));
|
||||
|
||||
room.currentState.setJoinedMemberCount(members.length);
|
||||
room.getJoinedMembers = jest.fn().mockReturnValue(members);
|
||||
}
|
|
@ -55,9 +55,12 @@ import { Call, ElementCall } from "../../../../src/models/Call";
|
|||
import * as ShieldUtils from "../../../../src/utils/ShieldUtils";
|
||||
import { Container, WidgetLayoutStore } from "../../../../src/stores/widgets/WidgetLayoutStore";
|
||||
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||
import { _t } from "../../../../src/languageHandler";
|
||||
import * as UseCall from "../../../../src/hooks/useCall";
|
||||
import { SdkContextClass } from "../../../../src/contexts/SDKContext";
|
||||
import WidgetStore, { IApp } from "../../../../src/stores/WidgetStore";
|
||||
import ShareDialog from "../../../../src/components/views/dialogs/ShareDialog";
|
||||
import Modal from "../../../../src/Modal";
|
||||
jest.mock("../../../../src/utils/ShieldUtils");
|
||||
|
||||
function getWrapper(): RenderOptions {
|
||||
|
@ -491,6 +494,96 @@ describe("RoomHeader", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("External conference", () => {
|
||||
const oldGet = SdkConfig.get;
|
||||
beforeEach(() => {
|
||||
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);
|
||||
});
|
||||
mockRoomMembers(room, 3);
|
||||
jest.spyOn(SdkContextClass.instance.roomViewStore, "isViewingCall").mockReturnValue(true);
|
||||
});
|
||||
|
||||
it("shows the external conference if the room has public join rules", () => {
|
||||
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Public);
|
||||
|
||||
const { container } = render(<RoomHeader room={room} />, getWrapper());
|
||||
expect(getByLabelText(container, _t("voip|get_call_link"))).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows the external conference if the room has Knock join rules", () => {
|
||||
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Knock);
|
||||
|
||||
const { container } = render(<RoomHeader room={room} />, getWrapper());
|
||||
expect(getByLabelText(container, _t("voip|get_call_link"))).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("don't show external conference button if the call is not shown", () => {
|
||||
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Public);
|
||||
jest.spyOn(SdkContextClass.instance.roomViewStore, "isViewingCall").mockReturnValue(false);
|
||||
|
||||
let { container } = render(<RoomHeader room={room} />, getWrapper());
|
||||
expect(screen.queryByLabelText(_t("voip|get_call_link"))).not.toBeInTheDocument();
|
||||
|
||||
jest.spyOn(SdkContextClass.instance.roomViewStore, "isViewingCall").mockReturnValue(true);
|
||||
|
||||
container = render(<RoomHeader room={room} />, getWrapper()).container;
|
||||
|
||||
expect(getByLabelText(container, _t("voip|get_call_link"))).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);
|
||||
});
|
||||
|
||||
render(<RoomHeader room={room} />, getWrapper());
|
||||
|
||||
// We only change the SdkConfig and show that this everything else is
|
||||
// configured so that the call link button is shown.
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
expect(screen.queryByLabelText(_t("voip|get_call_link"))).not.toBeInTheDocument();
|
||||
const { container } = render(<RoomHeader room={room} />, getWrapper());
|
||||
expect(getByLabelText(container, _t("voip|get_call_link"))).toBeInTheDocument();
|
||||
});
|
||||
it("opens the share dialog with the correct share link", () => {
|
||||
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Public);
|
||||
jest.spyOn(SdkContextClass.instance.roomViewStore, "isViewingCall").mockReturnValue(true);
|
||||
|
||||
const { container } = render(<RoomHeader room={room} />, getWrapper());
|
||||
const modalSpy = jest.spyOn(Modal, "createDialog");
|
||||
fireEvent.click(getByLabelText(container, _t("voip|get_call_link")));
|
||||
const target =
|
||||
"https://guest_spa_url.com/room/#/!1:example.org?roomId=%211%3Aexample.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(target);
|
||||
});
|
||||
});
|
||||
|
||||
describe("public room", () => {
|
||||
it("shows a globe", () => {
|
||||
const joinRuleEvent = new MatrixEvent({
|
||||
|
|
Loading…
Reference in New Issue