274 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			TypeScript
		
	
	
			
		
		
	
	
			274 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			TypeScript
		
	
	
/*
 | 
						|
Copyright 2024 New Vector Ltd.
 | 
						|
Copyright 2023 The Matrix.org Foundation C.I.C.
 | 
						|
 | 
						|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
 | 
						|
Please see LICENSE files in the repository root for full details.
 | 
						|
*/
 | 
						|
 | 
						|
import React from "react";
 | 
						|
import { fireEvent, getByLabelText, getByText, render, screen, waitFor } from "jest-matrix-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}>{children}</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.getByRole("button", { name: "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.getByRole("button", { name: "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.getByRole("button", { name: "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);
 | 
						|
        });
 | 
						|
 | 
						|
        getComponent(room);
 | 
						|
        expect(getByLabelText(document.body, "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);
 | 
						|
 | 
						|
        getComponent(room);
 | 
						|
        const modalSpy = jest.spyOn(Modal, "createDialog");
 | 
						|
        fireEvent.click(getByLabelText(document.body, _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);
 | 
						|
 | 
						|
        getComponent(room);
 | 
						|
        const modalSpy = jest.spyOn(Modal, "createDialog");
 | 
						|
        fireEvent.click(getByLabelText(document.body, _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}>{children}</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);
 | 
						|
        });
 | 
						|
    });
 | 
						|
});
 |