diff --git a/modules/tchap-translations/tchap_translations.json b/modules/tchap-translations/tchap_translations.json index d6da46660..0ef3a3004 100644 --- a/modules/tchap-translations/tchap_translations.json +++ b/modules/tchap-translations/tchap_translations.json @@ -900,7 +900,7 @@ }, "room_settings|security|link_sharing_modal_confirmation": { "en": "

If you activate the access by link, everyone having the link will be able to share it and join the room without invitation.

External users will still have to be invited manually to the room.

", - "fr": "

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.

Les agents externes à la fonction publique devront tout de même être invités manuellement.

" + "fr": "

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.

Les personnes externes à la fonction publique devront tout de même être invitées manuellement.

" }, "room_settings|security|link_sharing_title": { "en": "Activate access by link for this room", diff --git a/src/tchap/components/views/rooms/TchapRoomLinkAccess.tsx b/src/tchap/components/views/rooms/TchapRoomLinkAccess.tsx index 375e844b6..0b1c17463 100644 --- a/src/tchap/components/views/rooms/TchapRoomLinkAccess.tsx +++ b/src/tchap/components/views/rooms/TchapRoomLinkAccess.tsx @@ -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(""); @@ -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(() => { @@ -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 @@ -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 => { + const dialog = Modal.createDialog(QuestionDialog, { + title: _t("room_settings|security|link_sharing_title"), + description: ( +
+

+ {_t("room_settings|security|link_sharing_modal_confirmation", null, { + p: (sub) =>

{sub}

, + },)} +

+
+ ), + }); + const { finished } = dialog; + const [confirm] = await finished; + return !!confirm + } + return (
- + disabled={disableLinkSharing} + data-testid="share_link_switch" + /> { isLinkSharingActivated ? linkSharingUrl} aria-labelledby="shared_room_link"> diff --git a/src/tchap/components/views/settings/TchapJoinRuleSettings.tsx b/src/tchap/components/views/settings/TchapJoinRuleSettings.tsx index 3b10e32a9..8270079c7 100644 --- a/src/tchap/components/views/settings/TchapJoinRuleSettings.tsx +++ b/src/tchap/components/views/settings/TchapJoinRuleSettings.tsx @@ -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; @@ -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 => { - const dialog = Modal.createDialog(QuestionDialog, { - title: _t("room_settings|security|link_sharing_title"), - description: ( -
-

- {_t("room_settings|security|link_sharing_modal_confirmation", null, { - p: (sub) =>

{sub}

, - },)} -

-
- ), - }); - const { finished } = dialog; - const [confirm] = await finished; - return !!confirm } const renderLinkSharing = () => { - return + return } return ( diff --git a/test/unit-tests/tchap/components/views/rooms/TchapRoomLinkAccess-test.tsx b/test/unit-tests/tchap/components/views/rooms/TchapRoomLinkAccess-test.tsx new file mode 100644 index 000000000..294f59277 --- /dev/null +++ b/test/unit-tests/tchap/components/views/rooms/TchapRoomLinkAccess-test.tsx @@ -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(); + + 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"); + }); + }); +}); diff --git a/test/unit-tests/tchap/components/views/rooms/__snapshots__/TchapRoomLinkAccess-test.tsx.snap b/test/unit-tests/tchap/components/views/rooms/__snapshots__/TchapRoomLinkAccess-test.tsx.snap new file mode 100644 index 000000000..4d199d140 --- /dev/null +++ b/test/unit-tests/tchap/components/views/rooms/__snapshots__/TchapRoomLinkAccess-test.tsx.snap @@ -0,0 +1,59 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TchapRoomLinkAccess should activate link when clicking on the switch 1`] = ` +