diff --git a/src/components/views/messages/TextualBody.tsx b/src/components/views/messages/TextualBody.tsx index 890f6eceb6..dc7cc4dc23 100644 --- a/src/components/views/messages/TextualBody.tsx +++ b/src/components/views/messages/TextualBody.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef, SyntheticEvent, MouseEvent, ReactNode } from "react"; +import React, { createRef, SyntheticEvent, MouseEvent } from "react"; import ReactDOM from "react-dom"; import highlight from "highlight.js"; import { MsgType } from "matrix-js-sdk/src/@types/event"; @@ -86,21 +86,21 @@ export default class TextualBody extends React.Component { } private applyFormatting(): void { - const showLineNumbers = SettingsStore.getValue("showCodeLineNumbers"); - this.activateSpoilers([this.contentRef.current]); + // Function is only called from render / componentDidMount → contentRef is set + const content = this.contentRef.current!; - // pillifyLinks BEFORE linkifyElement because plain room/user URLs in the composer - // are still sent as plaintext URLs. If these are ever pillified in the composer, - // we should be pillify them here by doing the linkifying BEFORE the pillifying. - pillifyLinks([this.contentRef.current], this.props.mxEvent, this.pills); - HtmlUtils.linkifyElement(this.contentRef.current); + const showLineNumbers = SettingsStore.getValue("showCodeLineNumbers"); + this.activateSpoilers([content]); + + HtmlUtils.linkifyElement(content); + pillifyLinks([content], this.props.mxEvent, this.pills); this.calculateUrlPreview(); // tooltipifyLinks AFTER calculateUrlPreview because the DOM inside the tooltip // container is empty before the internal component has mounted so calculateUrlPreview // won't find any anchors - tooltipifyLinks([this.contentRef.current], this.pills, this.tooltips); + tooltipifyLinks([content], this.pills, this.tooltips); if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") { // Handle expansion and add buttons @@ -578,18 +578,16 @@ export default class TextualBody extends React.Component { // only strip reply if this is the original replying event, edits thereafter do not have the fallback const stripReply = !mxEvent.replacingEvent() && !!getParentEventId(mxEvent); - let body: ReactNode; - if (!body) { - isEmote = content.msgtype === MsgType.Emote; - isNotice = content.msgtype === MsgType.Notice; - body = HtmlUtils.bodyToHtml(content, this.props.highlights, { - disableBigEmoji: isEmote || !SettingsStore.getValue("TextualBody.enableBigEmoji"), - // Part of Replies fallback support - stripReplyFallback: stripReply, - ref: this.contentRef, - returnString: false, - }); - } + isEmote = content.msgtype === MsgType.Emote; + isNotice = content.msgtype === MsgType.Notice; + let body = HtmlUtils.bodyToHtml(content, this.props.highlights, { + disableBigEmoji: isEmote || !SettingsStore.getValue("TextualBody.enableBigEmoji"), + // Part of Replies fallback support + stripReplyFallback: stripReply, + ref: this.contentRef, + returnString: false, + }); + if (this.props.replacingEventId) { body = ( <> diff --git a/test/components/views/messages/TextualBody-test.tsx b/test/components/views/messages/TextualBody-test.tsx index e01a5f809f..149cb7ae47 100644 --- a/test/components/views/messages/TextualBody-test.tsx +++ b/test/components/views/messages/TextualBody-test.tsx @@ -19,7 +19,7 @@ import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix"; import { MockedObject } from "jest-mock"; import { render } from "@testing-library/react"; -import { getMockClientWithEventEmitter, mkEvent, mkStubRoom } from "../../../test-utils"; +import { getMockClientWithEventEmitter, mkEvent, mkMessage, mkStubRoom } from "../../../test-utils"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import * as languageHandler from "../../../../src/languageHandler"; import DMRoomMap from "../../../../src/utils/DMRoomMap"; @@ -28,6 +28,15 @@ import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks"; import { MediaEventHelper } from "../../../../src/utils/MediaEventHelper"; +const mkRoomTextMessage = (body: string): MatrixEvent => { + return mkMessage({ + msg: body, + room: "room_id", + user: "sender", + event: true, + }); +}; + describe("", () => { afterEach(() => { jest.spyOn(MatrixClientPeg, "get").mockRestore(); @@ -38,9 +47,11 @@ describe("", () => { beforeEach(() => { defaultMatrixClient = getMockClientWithEventEmitter({ getRoom: () => defaultRoom, + getRooms: () => [defaultRoom], getAccountData: (): MatrixEvent | undefined => undefined, isGuest: () => false, mxcUrlToHttp: (s: string) => s, + getUserId: () => "@user:example.com", }); }); @@ -116,17 +127,7 @@ describe("", () => { }); it("simple message renders as expected", () => { - const ev = mkEvent({ - type: "m.room.message", - room: "room_id", - user: "sender", - content: { - body: "this is a plaintext message", - msgtype: "m.text", - }, - event: true, - }); - + const ev = mkRoomTextMessage("this is a plaintext message"); const { container } = getComponent({ mxEvent: ev }); expect(container).toHaveTextContent(ev.getContent().body); const content = container.querySelector(".mx_EventTile_body"); @@ -135,17 +136,7 @@ describe("", () => { // If pills were rendered within a Portal/same shadow DOM then it'd be easier to test it("linkification get applied correctly into the DOM", () => { - const ev = mkEvent({ - type: "m.room.message", - room: "room_id", - user: "sender", - content: { - body: "Visit https://matrix.org/", - msgtype: "m.text", - }, - event: true, - }); - + const ev = mkRoomTextMessage("Visit https://matrix.org/"); const { container } = getComponent({ mxEvent: ev }); expect(container).toHaveTextContent(ev.getContent().body); const content = container.querySelector(".mx_EventTile_body"); @@ -155,6 +146,24 @@ describe("", () => { "https://matrix.org/", ); }); + + it("pillification of MXIDs get applied correctly into the DOM", () => { + const ev = mkRoomTextMessage("Chat with @user:example.com"); + const { container } = getComponent({ mxEvent: ev }); + const content = container.querySelector(".mx_EventTile_body"); + expect(content.innerHTML).toMatchInlineSnapshot( + `"Chat with Member"`, + ); + }); + + it("pillification of room aliases get applied correctly into the DOM", () => { + const ev = mkRoomTextMessage("Visit #room:example.com"); + const { container } = getComponent({ mxEvent: ev }); + const content = container.querySelector(".mx_EventTile_body"); + expect(content.innerHTML).toMatchInlineSnapshot( + `"Visit #room:example.com"`, + ); + }); }); describe("renders formatted m.text correctly", () => { @@ -382,17 +391,7 @@ describe("", () => { }); DMRoomMap.makeShared(); - const ev = mkEvent({ - type: "m.room.message", - room: "room_id", - user: "sender", - content: { - body: "Visit https://matrix.org/", - msgtype: "m.text", - }, - event: true, - }); - + const ev = mkRoomTextMessage("Visit https://matrix.org/"); const { container, rerender } = getComponent( { mxEvent: ev, showUrlPreview: true, onHeightChanged: jest.fn() }, matrixClient,