Skip to content

Commit

Permalink
test(room_access_link): add test for room access by link
Browse files Browse the repository at this point in the history
  • Loading branch information
marc.sirisak committed Nov 6, 2024
1 parent db2cd18 commit 7e77bfe
Show file tree
Hide file tree
Showing 5 changed files with 272 additions and 53 deletions.
2 changes: 1 addition & 1 deletion modules/tchap-translations/tchap_translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -900,7 +900,7 @@
},
"room_settings|security|link_sharing_modal_confirmation": {
"en": "<p>If you activate the access by link, everyone having the link will be able to share it and join the room without invitation.</p> <p>External users will still have to be invited manually to the room.</p>",
"fr": "<p>L'activation de l'accès au salon par lien permettra à n'importe qui en sa possession, de le partager et de rejoindre le salon sans invitation.</p> <p>Les agents externes à la fonction publique devront tout de même être invités manuellement.</p>"
"fr": "<p>L'activation de l'accès au salon par lien permettra à n'importe qui en sa possession, de le partager et de rejoindre le salon sans invitation.</p> <p>Les personnes externes à la fonction publique devront tout de même être invitées manuellement.</p>"
},
"room_settings|security|link_sharing_title": {
"en": "Activate access by link for this room",
Expand Down
66 changes: 43 additions & 23 deletions src/tchap/components/views/rooms/TchapRoomLinkAccess.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@ import { EventType, GuestAccess, JoinRule, Room } from "matrix-js-sdk/src/matrix
import React, { useEffect, useState } from 'react';
import LabelledToggleSwitch from 'matrix-react-sdk/src/components/views/elements/LabelledToggleSwitch';
import { _t } from 'matrix-react-sdk/src/languageHandler';
import { MatrixClientPeg } from 'matrix-react-sdk/src/MatrixClientPeg';
import { randomString } from 'matrix-js-sdk/src/randomstring';
import TchapRoomUtils from '../../../util/TchapRoomUtils';
import { makeRoomPermalink } from "matrix-react-sdk/src/utils/permalinks/Permalinks";
import { TchapRoomType } from "../../../@types/tchap";
import { RoomJoinRulesEventContent } from "matrix-js-sdk/lib/types";
import CopyableText from "matrix-react-sdk/src/components/views/elements/CopyableText";
import Modal from "matrix-react-sdk/src/Modal";
import QuestionDialog from "matrix-react-sdk/src/components/views/dialogs/QuestionDialog";

interface ITchapRoomLinkAccessProps {
room: Room,
onBeforeChangeCallback: Function
onUpdateParentView: Function
}

export default function TchapRoomLinkAccess({room, onBeforeChangeCallback}: ITchapRoomLinkAccessProps) : JSX.Element {
export default function TchapRoomLinkAccess({room, onUpdateParentView}: ITchapRoomLinkAccessProps) : JSX.Element {

const [isLinkSharingActivated, setIsLinkSharingActivated] = useState(false);
const [linkSharingUrl, setLinkSharingUrl] = useState("");
Expand All @@ -39,7 +40,7 @@ export default function TchapRoomLinkAccess({room, onBeforeChangeCallback}: ITch
}
setIsLinkSharingActivated(isActivated)
// updating the parent join rule options
onBeforeChangeCallback(isActivated, true);
onUpdateParentView(isActivated, true);
}

useEffect(() => {
Expand All @@ -48,14 +49,13 @@ export default function TchapRoomLinkAccess({room, onBeforeChangeCallback}: ITch

// Create the permalink to share
const _setUpRoomByLink = async () => {
const client = MatrixClientPeg.get()!;
try {
// create an alias if not existing
if (!room.getCanonicalAlias()) {
const aliasName = (room.name?.replace(/[^a-z0-9]/gi, "") ?? "") + randomString(11);
const fullAlias = `#${aliasName}:${client.getDomain()}`;
await client.createAlias(fullAlias, room.roomId)
await client.sendStateEvent(room.roomId, EventType.RoomCanonicalAlias, { alias: fullAlias }, "")
const fullAlias = `#${aliasName}:${room.client.getDomain()}`;
await room.client.createAlias(fullAlias, room.roomId)
await room.client.sendStateEvent(room.roomId, EventType.RoomCanonicalAlias, { alias: fullAlias }, "")
}

// it will take the new alias created previously or the existing one to make a link
Expand Down Expand Up @@ -99,34 +99,54 @@ export default function TchapRoomLinkAccess({room, onBeforeChangeCallback}: ITch
// if the link sharing is deactivated we also need to update the joinrule parent view to show the other options
if (!checked) {
await _setJoinRules(newJoinRule);
onBeforeChangeCallback(checked)
onUpdateParentView(checked)
return;
}

// call parent join rule access, to confirm we want to change to public access and hide the other join options
onBeforeChangeCallback(checked, false, async (actionConfirmed: boolean) => {
// Show modal for confirmation
const activationIsConfirmed = await activateLinksharingModal();

if (activationIsConfirmed) {
// create link if we activate the sharing, otherwise change nothing
if (actionConfirmed) {
if (TchapRoomUtils.getRoomGuessAccessRule(room) === GuestAccess.CanJoin) {
await _setGuestAccessRules(GuestAccess.Forbidden)
}
_setUpRoomByLink();
_setJoinRules(JoinRule.Public)
} else {
// we revert because the action was not confirmed
setIsLinkSharingActivated(!checked);
if (TchapRoomUtils.getRoomGuessAccessRule(room) === GuestAccess.CanJoin) {
await _setGuestAccessRules(GuestAccess.Forbidden)
}
})
await Promise.all([_setUpRoomByLink(), _setJoinRules(JoinRule.Public)]);
onUpdateParentView(checked, false);
} else {
// we revert because the action was not confirmed
setIsLinkSharingActivated(!checked);
}
};


const activateLinksharingModal = async (): Promise<boolean> => {
const dialog = Modal.createDialog(QuestionDialog, {
title: _t("room_settings|security|link_sharing_title"),
description: (
<div>
<p>
{_t("room_settings|security|link_sharing_modal_confirmation", null, {
p: (sub) => <p>{sub}</p>,
},)}
</p>
</div>
),
});
const { finished } = dialog;
const [confirm] = await finished;
return !!confirm
}

return (
<div>
<LabelledToggleSwitch value={isLinkSharingActivated}
onChange={ _onLinkSharingSwitchChange }
label={_t("room_settings|security|link_sharing_title")}
caption={_t("room_settings|security|link_sharing_caption")}
disabled={disableLinkSharing}/>

disabled={disableLinkSharing}
data-testid="share_link_switch"
/>
{
isLinkSharingActivated ?
<CopyableText getTextToCopy={() => linkSharingUrl} aria-labelledby="shared_room_link">
Expand Down
33 changes: 4 additions & 29 deletions src/tchap/components/views/settings/TchapJoinRuleSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -426,8 +426,10 @@ const JoinRuleSettings = ({ room, promptUpgrade, aliasWarning, onError, beforeCh

// This is a callback function used by the child link sharing component
// It will indicate wether or not to hide the joinrule options or not
const activateLinkSharingChange = async (checked: boolean, init: boolean, cb?: Function) => {
const activateLinkSharingChange = async (checked: boolean, init: boolean) => {
// hide or display the join rules
setIsLinkSharingActivated(checked);

// if its the initialisation phase we dont need to do anything more other than hide or not the join options
if (init) {
return;
Expand All @@ -438,37 +440,10 @@ const JoinRuleSettings = ({ room, promptUpgrade, aliasWarning, onError, beforeCh
const currentJoinRule = TchapRoomUtils.getRoomJoinRule(room);
setContent(currentJoinRule ? { join_rule: JoinRule.Invite } : {} as IJoinRuleEventContent);
}

// getting the callback from the link sharing component
if (checked && cb) {
// showing the modal to confirm we want to turn the room to public
const resultConfirmed = await activateLinksharingModal();
setIsLinkSharingActivated(resultConfirmed);
// if we cancel the modal, we need to revert back the switch and values
cb(resultConfirmed);
};
}

const activateLinksharingModal = async (): Promise<boolean> => {
const dialog = Modal.createDialog(QuestionDialog, {
title: _t("room_settings|security|link_sharing_title"),
description: (
<div>
<p>
{_t("room_settings|security|link_sharing_modal_confirmation", null, {
p: (sub) => <p>{sub}</p>,
},)}
</p>
</div>
),
});
const { finished } = dialog;
const [confirm] = await finished;
return !!confirm
}

const renderLinkSharing = () => {
return <TchapRoomLinkAccess room={room} onBeforeChangeCallback={activateLinkSharingChange}></TchapRoomLinkAccess>
return <TchapRoomLinkAccess room={room} onUpdateParentView={activateLinkSharingChange}></TchapRoomLinkAccess>
}

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import React from "react";
import { render, screen, waitFor, waitForElementToBeRemoved } from "@testing-library/react";
import { mocked } from "jest-mock";
import { mkStubRoom, stubClient } from "matrix-react-sdk/test/test-utils/test-utils";
import { EventType, GuestAccess, JoinRule, MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import { makeRoomPermalink } from "matrix-react-sdk/src/utils/permalinks/Permalinks";
import userEvent from "@testing-library/user-event";

import TchapRoomLinkAccess from "../../../../../../src/tchap/components/views/rooms/TchapRoomLinkAccess";
import { TchapRoomType } from "../../../../../../src/tchap/@types/tchap";

import TchapRoomUtils from "~tchap-web/src/tchap/util/TchapRoomUtils";
import {
flushPromises,
waitEnoughCyclesForModal,
} from "~tchap-web/linked-dependencies/matrix-react-sdk/test/test-utils";

jest.mock("~tchap-web/src/tchap/util/TchapRoomUtils");
jest.mock("matrix-react-sdk/src/utils/permalinks/Permalinks");

describe("TchapRoomLinkAccess", () => {
const client: MatrixClient = stubClient();
const room: Room = mkStubRoom("roomId", "test", client);

const mockedTchapRoomUtils = mocked(TchapRoomUtils);
const mockedMakeRoomPermalink = mocked(makeRoomPermalink);
const onUpdateParentView = jest.fn().mockImplementation(() => {});

const mockedLinked = "https://testmocked.matrix.org";

const getComponent = () => render(<TchapRoomLinkAccess room={room} onUpdateParentView={onUpdateParentView} />);

beforeEach(() => {
mockedTchapRoomUtils.getTchapRoomType.mockImplementation(() => TchapRoomType.Private);
mockedTchapRoomUtils.getRoomJoinRule.mockImplementation(() => JoinRule.Invite);
mockedTchapRoomUtils.isUserAdmin.mockImplementation(() => true);
mockedTchapRoomUtils.getRoomGuessAccessRule.mockImplementation(() => GuestAccess.CanJoin);
mockedMakeRoomPermalink.mockImplementation(() => mockedLinked);

client.createAlias = jest.fn().mockResolvedValue("alias");

jest.spyOn(client, "sendStateEvent").mockResolvedValue(Promise.resolve({ event_id: "" }));
jest.spyOn(client, "sendStateEvent").mockResolvedValue(Promise.resolve({ event_id: "" }));
});

afterEach(() => {
jest.clearAllMocks();
});

it("should render correct initial value when joinrule is public", async () => {
mockedTchapRoomUtils.getRoomJoinRule.mockImplementation(() => JoinRule.Public);
mockedTchapRoomUtils.getTchapRoomType.mockImplementation(() => TchapRoomType.Forum);

getComponent();

await flushPromises();

const switchLink = screen.queryByRole("switch");

const linkDisplay = screen.queryByText(mockedLinked);

expect(linkDisplay).toBeDefined();

// should be disable because we dont change this settings for forum room
expect(switchLink).toHaveAttribute("aria-disabled", "true");
});

it("should render correct initial value when joinrule is invite", async () => {
mockedTchapRoomUtils.getRoomJoinRule.mockImplementation(() => JoinRule.Invite);

getComponent();

await flushPromises();

const switchLink = screen.queryByRole("switch");

const linkDisplay = screen.queryByText(mockedLinked);

// linked should not appear because the share link is deactivated
expect(linkDisplay).toBe(null);

// we should be able to click on the link to activate it
expect(switchLink).toHaveAttribute("aria-disabled", "false");
});

it("should disable link if user is not admin", async () => {
mockedTchapRoomUtils.getRoomJoinRule.mockImplementation(() => JoinRule.Invite);
mockedTchapRoomUtils.isUserAdmin.mockImplementation(() => false);

getComponent();

await flushPromises();

const switchLink = screen.queryByRole("switch");

const linkDisplay = screen.queryByText(mockedLinked);

// linked should not appear because the share link is deactivated
expect(linkDisplay).toBe(null);

// the user should not be able to click on the button
expect(switchLink).toHaveAttribute("aria-disabled", "true");
});

it("should activate link when clicking on the switch", async () => {
// jest.useFakeTimers();

mockedTchapRoomUtils.getRoomJoinRule.mockImplementation(() => JoinRule.Invite);
getComponent();

await flushPromises();

const switchLink = screen.getByRole("switch");

// should open dialog
userEvent.click(switchLink);

await waitEnoughCyclesForModal({
useFakeTimers: true,
});

const dialog = await screen.findByRole("dialog");

expect(dialog).toMatchSnapshot();

const confirmButton = screen.getByTestId("dialog-primary-button");

userEvent.click(confirmButton);

await waitForElementToBeRemoved(dialog);
// should activate the switch with public join rule value
expect(mockedMakeRoomPermalink).toHaveBeenCalledTimes(1);
// joinrule to public, guest access to forbiden and canonical alias
expect(room.client.sendStateEvent).toHaveBeenCalledTimes(3);
expect(switchLink).toHaveAttribute("aria-checked", "true");
});

it("should deactivate link when clicking on the switch", async () => {
mockedTchapRoomUtils.getRoomJoinRule.mockImplementation(() => JoinRule.Public);

getComponent();

await flushPromises();

const switchLink = screen.getByRole("switch");

userEvent.click(switchLink);

await waitFor(() => {
const linkDisplay = screen.queryByText(mockedLinked);

// no link since we deactivated the switch
expect(linkDisplay).toBe(null);
// should deactivate the switch
expect(room.client.sendStateEvent).toHaveBeenCalledTimes(1);
expect(room.client.sendStateEvent).toHaveBeenCalledWith(
room.roomId,
EventType.RoomJoinRules,
{ join_rule: JoinRule.Invite },
"",
);
expect(switchLink).toHaveAttribute("aria-checked", "false");
});
});
});
Loading

0 comments on commit 7e77bfe

Please sign in to comment.