diff --git a/src/components/views/rooms/NewRoomIntro.tsx b/src/components/views/rooms/NewRoomIntro.tsx index f12b03e288..67daec115d 100644 --- a/src/components/views/rooms/NewRoomIntro.tsx +++ b/src/components/views/rooms/NewRoomIntro.tsx @@ -30,6 +30,8 @@ import { privateShouldBeEncrypted } from "../../../utils/rooms"; import { LocalRoom } from "../../../models/LocalRoom"; import { shouldEncryptRoomWithSingle3rdPartyInvite } from "../../../utils/room/shouldEncryptRoomWithSingle3rdPartyInvite"; import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx"; +import { useTopic } from "../../../hooks/room/useTopic"; +import { topicToHtml, Linkify } from "../../../HtmlUtils"; function hasExpectedEncryptionSettings(matrixClient: MatrixClient, room: Room): boolean { const isEncrypted: boolean = matrixClient.isRoomEncrypted(room.roomId); @@ -52,6 +54,7 @@ const determineIntroMessage = (room: Room, encryptedSingle3rdPartyInvite: boolea const NewRoomIntro: React.FC = () => { const cli = useContext(MatrixClientContext); const { room, roomId } = useScopedRoomContext("room", "roomId"); + const topic = useTopic(room); if (!room || !roomId) { throw new Error("Unable to create a NewRoomIntro without room and roomId"); @@ -106,7 +109,6 @@ const NewRoomIntro: React.FC = () => { ); } else { const inRoom = room && room.getMyMembership() === KnownMembership.Join; - const topic = room.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic; const canAddTopic = inRoom && room.currentState.maySendStateEvent(EventType.RoomTopic, cli.getSafeUserId()); const onTopicClick = (): void => { @@ -126,18 +128,23 @@ const NewRoomIntro: React.FC = () => { let topicText; if (canAddTopic && topic) { topicText = _t( - "room|intro|topic_edit", - { topic }, + "room|intro|edit_topic", + {}, { a: (sub) => ( {sub} ), + topic: () => {topicToHtml(topic?.text, topic?.html)}, }, ); } else if (topic) { - topicText = _t("room|intro|topic", { topic }); + topicText = _t( + "room|intro|display_topic", + {}, + { topic: () => {topicToHtml(topic?.text, topic?.html)} }, + ); } else if (canAddTopic) { topicText = _t( "room|intro|no_topic", @@ -245,7 +252,7 @@ const NewRoomIntro: React.FC = () => { }, )}

-

{topicText}

+

{topicText}

{buttons} ); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 77335c9e75..90a5ecc683 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1998,7 +1998,9 @@ "inaccessible_subtitle_1": "Try again later, or ask a room or space admin to check if you have access.", "inaccessible_subtitle_2": "%(errcode)s was returned while trying to access the room or space. If you think you're seeing this message in error, please submit a bug report.", "intro": { + "display_topic": "Topic: ", "dm_caption": "Only the two of you are in this conversation, unless either of you invites anyone to join.", + "edit_topic": "Topic: (edit)", "enable_encryption_prompt": "Enable encryption in settings.", "encrypted_3pid_dm_pending_join": "Once everyone has joined, you’ll be able to chat", "no_avatar_label": "Add a photo, so people can easily spot your room.", @@ -2008,8 +2010,6 @@ "send_message_start_dm": "Send your first message to invite to chat", "start_of_dm_history": "This is the beginning of your direct message history with .", "start_of_room": "This is the start of .", - "topic": "Topic: %(topic)s ", - "topic_edit": "Topic: %(topic)s (edit)", "unencrypted_warning": "End-to-end encryption isn't enabled", "user_created": "%(displayName)s created this room.", "you_created": "You created this room." diff --git a/test/unit-tests/components/views/rooms/NewRoomIntro-test.tsx b/test/unit-tests/components/views/rooms/NewRoomIntro-test.tsx index 333e0e1020..59d87bc3cd 100644 --- a/test/unit-tests/components/views/rooms/NewRoomIntro-test.tsx +++ b/test/unit-tests/components/views/rooms/NewRoomIntro-test.tsx @@ -9,16 +9,24 @@ Please see LICENSE files in the repository root for full details. import React from "react"; import { render, screen } from "jest-matrix-react"; -import { type MatrixClient, Room } from "matrix-js-sdk/src/matrix"; +import { EventTimeline, type MatrixClient, Room } from "matrix-js-sdk/src/matrix"; +import { KnownMembership } from "matrix-js-sdk/src/types"; import { LocalRoom } from "../../../../../src/models/LocalRoom"; -import { filterConsole, mkRoomMemberJoinEvent, mkThirdPartyInviteEvent, stubClient } from "../../../../test-utils"; +import { + filterConsole, + mkEvent, + mkRoomMemberJoinEvent, + mkThirdPartyInviteEvent, + stubClient, +} from "../../../../test-utils"; import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext"; import NewRoomIntro from "../../../../../src/components/views/rooms/NewRoomIntro"; import { type IRoomState } from "../../../../../src/components/structures/RoomView"; import DMRoomMap from "../../../../../src/utils/DMRoomMap"; import { DirectoryMember } from "../../../../../src/utils/direct-messages"; import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx"; +import defaultDispatcher from "../../../../../src/dispatcher/dispatcher"; const renderNewRoomIntro = (client: MatrixClient, room: Room | LocalRoom) => { render( @@ -37,11 +45,15 @@ describe("NewRoomIntro", () => { filterConsole("Room !room:example.com does not have an m.room.create event"); - beforeAll(() => { + beforeEach(() => { client = stubClient(); DMRoomMap.makeShared(client); }); + afterEach(() => { + jest.resetAllMocks(); + }); + describe("for a DM Room", () => { beforeEach(() => { jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(userId); @@ -88,4 +100,62 @@ describe("NewRoomIntro", () => { screen.getByText((id, element) => element?.tagName === "SPAN" && element?.textContent === expected); }); }); + + describe("topic", () => { + let room: Room; + + beforeEach(() => { + room = new Room(roomId, client, userId); + room.getLiveTimeline() + .getState(EventTimeline.FORWARDS) + ?.setStateEvents([mkRoomMemberJoinEvent(client.getSafeUserId(), room.roomId)]); + jest.spyOn(DMRoomMap.shared(), "getRoomIds").mockReturnValue(new Set([room.roomId])); + }); + + function addTopicToRoom(topic: string) { + const topicEvent = mkEvent({ + type: "m.room.topic", + room: roomId, + user: userId, + content: { + topic, + }, + ts: 123, + event: true, + }); + + room.addLiveEvents([topicEvent], { addToState: true }); + } + + it("should render the topic", () => { + addTopicToRoom("Test topic"); + renderNewRoomIntro(client, room); + screen.getByText("Test topic"); + }); + + it("should render a link in the topic", () => { + addTopicToRoom("This is a link: https://matrix.org/"); + renderNewRoomIntro(client, room); + expect(screen.getByTestId("topic")).toMatchSnapshot(); + }); + + it("should be able to add a topic", () => { + addTopicToRoom("Test topic"); + jest.spyOn(room, "getMyMembership").mockReturnValue(KnownMembership.Join); + jest.spyOn(room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, "maySendStateEvent").mockReturnValue( + true, + ); + const spyDispatcher = jest.spyOn(defaultDispatcher, "dispatch"); + + renderNewRoomIntro(client, room); + screen.getByRole("button", { name: "edit" }).click(); + expect(spyDispatcher).toHaveBeenCalledWith( + { + action: "open_room_settings", + room_id: room.roomId, + }, + true, + ); + }); + }); }); diff --git a/test/unit-tests/components/views/rooms/__snapshots__/NewRoomIntro-test.tsx.snap b/test/unit-tests/components/views/rooms/__snapshots__/NewRoomIntro-test.tsx.snap new file mode 100644 index 0000000000..cc642fb598 --- /dev/null +++ b/test/unit-tests/components/views/rooms/__snapshots__/NewRoomIntro-test.tsx.snap @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NewRoomIntro topic should render a link in the topic 1`] = ` +

+ + Topic: + + This is a link: + + https://matrix.org/ + + + +

+`;