2020-01-06 14:52:08 +00:00
/*
2024-09-09 14:57:16 +01:00
Copyright 2024 New Vector Ltd.
Copyright 2019-2022 The Matrix.org Foundation C.I.C.
2020-01-06 14:52:08 +00:00
2025-01-06 11:18:54 +00:00
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
2024-09-09 14:57:16 +01:00
Please see LICENSE files in the repository root for full details.
2020-01-06 14:52:08 +00:00
*/
2025-03-26 20:25:03 +00:00
import React , { type ComponentProps } from "react" ;
import { type MatrixClient , type MatrixEvent , PushRuleKind , type Room } from "matrix-js-sdk/src/matrix" ;
2026-06-19 16:34:18 +01:00
import { mocked , type MockedObject } from "jest-mock-vitest-adapter" ;
2026-05-08 11:33:23 +02:00
import { act , render , waitFor } from "jest-matrix-react" ;
2025-03-21 11:34:06 +00:00
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor" ;
2026-06-24 10:49:49 +01:00
import { setMissingEntryGenerator } from "@element-hq/web-shared-components" ;
2020-01-06 13:28:29 +00:00
2025-03-26 20:25:03 +00:00
import {
getMockClientWithEventEmitter ,
mkEvent ,
mkMessage ,
mkStubRoom ,
mockClientPushProcessor ,
} from "../../../../test-utils" ;
2024-10-15 14:57:26 +01:00
import DMRoomMap from "../../../../../src/utils/DMRoomMap" ;
2026-04-28 09:07:19 +02:00
import { TextualBodyFactory as TextualBody } from "../../../../../src/components/views/messages/TextualBodyFactory" ;
2024-10-15 14:57:26 +01:00
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext" ;
2026-03-09 10:58:05 +01:00
import RoomContext from "../../../../../src/contexts/RoomContext" ;
2024-10-15 14:57:26 +01:00
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks" ;
2025-02-05 13:25:06 +00:00
import { type MediaEventHelper } from "../../../../../src/utils/MediaEventHelper" ;
2026-03-09 10:58:05 +01:00
import { getRoomContext } from "../../../../test-utils/room" ;
2020-01-06 13:28:29 +00:00
2026-04-28 09:07:19 +02:00
jest . mock ( "../../../../../src/hooks/useMediaVisible" , () => ({
__esModule : true ,
useMediaVisible : () => [ true , jest . fn ()],
}));
2023-03-22 13:27:24 +01:00
const room1Id = "!room1:example.com" ;
const room2Id = "!room2:example.com" ;
const room2Name = "Room 2" ;
interface MkRoomTextMessageOpts {
roomId? : string ;
}
const mkRoomTextMessage = ( body : string , mkRoomTextMessageOpts? : MkRoomTextMessageOpts ) : MatrixEvent => {
2023-03-02 11:41:17 +01:00
return mkMessage ({
msg : body ,
2023-03-22 13:27:24 +01:00
room : mkRoomTextMessageOpts?.roomId ?? room1Id ,
2023-03-02 11:41:17 +01:00
user : "sender" ,
event : true ,
});
};
2023-03-16 15:01:09 +01:00
const mkFormattedMessage = ( body : string , formattedBody : string ) : MatrixEvent => {
return mkMessage ({
msg : body ,
formattedMsg : formattedBody ,
format : "org.matrix.custom.html" ,
2023-03-22 13:27:24 +01:00
room : room1Id ,
2023-03-16 15:01:09 +01:00
user : "sender" ,
event : true ,
});
};
2020-01-06 13:28:29 +00:00
describe ( "<TextualBody />" , () => {
afterEach (() => {
2023-05-05 09:26:11 +01:00
jest . spyOn ( global . Math , "random" ). mockRestore ();
2020-01-06 13:28:29 +00:00
});
2025-03-26 20:25:03 +00:00
let defaultRoom : Room ;
let otherRoom : Room ;
2022-04-28 11:32:50 +02:00
let defaultMatrixClient : MockedObject < MatrixClient >;
2023-03-22 13:27:24 +01:00
const defaultEvent = mkEvent ({
type : "m.room.message" ,
room : room1Id ,
user : "sender" ,
content : {
body : "winks" ,
msgtype : "m.emote" ,
},
event : true ,
});
2025-03-26 20:25:03 +00:00
const defaultProps : ComponentProps < typeof TextualBody > = {
mxEvent : defaultEvent ,
highlights : [] as string [],
highlightLink : "" ,
onMessageAllowed : jest.fn (),
mediaEventHelper : {} as MediaEventHelper ,
};
2022-04-28 11:32:50 +02:00
beforeEach (() => {
defaultMatrixClient = getMockClientWithEventEmitter ({
2023-03-22 13:27:24 +01:00
getRoom : ( roomId : string | undefined ) => {
if ( roomId === room1Id ) return defaultRoom ;
if ( roomId === room2Id ) return otherRoom ;
return null ;
},
getRooms : () => [ defaultRoom , otherRoom ],
2023-02-13 11:39:16 +00:00
getAccountData : () : MatrixEvent | undefined => undefined ,
2020-10-29 13:22:09 +00:00
isGuest : () => false ,
2023-02-13 11:39:16 +00:00
mxcUrlToHttp : ( s : string ) => s ,
2023-03-02 11:41:17 +01:00
getUserId : () => "@user:example.com" ,
2023-03-21 10:23:20 +01:00
fetchRoomEvent : () => {
throw new Error ( "MockClient event not found" );
},
2022-04-28 11:32:50 +02:00
});
2025-03-21 11:34:06 +00:00
// @ts-expect-error
defaultMatrixClient . pushProcessor = new PushProcessor ( defaultMatrixClient );
2023-03-22 13:27:24 +01:00
2025-03-26 20:25:03 +00:00
defaultRoom = mkStubRoom ( room1Id , "test room" , defaultMatrixClient );
otherRoom = mkStubRoom ( room2Id , room2Name , defaultMatrixClient );
2023-03-22 13:27:24 +01:00
mocked ( defaultRoom ). findEventById . mockImplementation (( eventId : string ) => {
if ( eventId === defaultEvent . getId ()) return defaultEvent ;
return undefined ;
});
2023-05-05 09:26:11 +01:00
jest . spyOn ( global . Math , "random" ). mockReturnValue ( 0.123456 );
2022-04-28 11:32:50 +02:00
});
2026-03-09 10:58:05 +01:00
const getComponent = ( props = {}, matrixClient : MatrixClient = defaultMatrixClient , renderingFn? : any ) => {
const mergedProps = { ... defaultProps , ... props };
const room = matrixClient . getRoom ( mergedProps . mxEvent . getRoomId ()) ?? defaultRoom ;
2026-04-28 09:07:19 +02:00
const finalProps = {
... mergedProps ,
permalinkCreator : mergedProps.permalinkCreator ?? new RoomPermalinkCreator ( room ),
};
2026-03-09 10:58:05 +01:00
return ( renderingFn ?? render )(
2022-12-29 16:21:00 +00:00
< MatrixClientContext.Provider value = { matrixClient }>
2026-03-09 10:58:05 +01:00
< RoomContext.Provider value = { getRoomContext ( room , {})}>
2026-04-28 09:07:19 +02:00
< TextualBody { ...finalProps } />
2026-03-09 10:58:05 +01:00
</ RoomContext.Provider >
2022-12-29 16:21:00 +00:00
</ MatrixClientContext.Provider >,
);
2026-03-09 10:58:05 +01:00
};
2022-04-28 11:32:50 +02:00
it ( "renders m.emote correctly" , () => {
2023-05-23 16:24:12 +01:00
DMRoomMap . makeShared ( defaultMatrixClient );
2020-01-06 13:28:29 +00:00
const ev = mkEvent ({
type : "m.room.message" ,
2023-03-22 13:27:24 +01:00
room : room1Id ,
2020-01-06 13:28:29 +00:00
user : "sender" ,
content : {
body : "winks" ,
msgtype : "m.emote" ,
},
event : true ,
});
2022-12-29 16:21:00 +00:00
const { container } = getComponent ({ mxEvent : ev });
expect ( container ). toHaveTextContent ( "* sender winks" );
const content = container . querySelector ( ".mx_EventTile_body" );
2024-07-25 18:58:33 +02:00
expect ( content ). toMatchSnapshot ();
2020-01-06 13:28:29 +00:00
});
2026-04-09 14:13:02 +02:00
it ( "keeps edited emote bodies inline with the sender" , () => {
DMRoomMap . makeShared ( defaultMatrixClient );
const ev = mkEvent ({
type : "m.room.message" ,
room : room1Id ,
user : "sender" ,
content : {
body : "winks" ,
msgtype : "m.emote" ,
},
event : true ,
});
jest . spyOn ( ev , "replacingEventDate" ). mockReturnValue ( new Date ( 1993 , 7 , 3 ));
const { container } = getComponent ({ mxEvent : ev , replacingEventId : ev.getId () });
2026-04-28 09:07:19 +02:00
expect ( container ). toHaveTextContent ( "* sender winks(edited)" );
2026-04-09 14:13:02 +02:00
});
2026-05-08 11:33:23 +02:00
it ( "updates the body kind when an edit changes msgtype to m.emote" , async () => {
DMRoomMap . makeShared ( defaultMatrixClient );
const ev = mkEvent ({
type : "m.room.message" ,
room : room1Id ,
user : "sender" ,
content : {
body : "hello" ,
msgtype : "m.text" ,
},
event : true ,
});
const { container } = getComponent ({ mxEvent : ev });
expect ( container ). toHaveTextContent ( "hello" );
const edit = mkEvent ({
type : "m.room.message" ,
room : room1Id ,
user : "sender" ,
content : {
"body" : "* waves" ,
"msgtype" : "m.emote" ,
"m.new_content" : {
body : "waves" ,
msgtype : "m.emote" ,
},
},
event : true ,
});
act (() => {
ev . makeReplaced ( edit );
});
await waitFor (() => expect ( container ). toHaveTextContent ( "* sender waves(edited)" ));
});
2020-01-09 17:24:13 -07:00
it ( "renders m.notice correctly" , () => {
2023-05-23 16:24:12 +01:00
DMRoomMap . makeShared ( defaultMatrixClient );
2020-01-06 13:28:29 +00:00
const ev = mkEvent ({
type : "m.room.message" ,
2023-03-22 13:27:24 +01:00
room : room1Id ,
2020-01-06 13:28:29 +00:00
user : "bot_sender" ,
content : {
body : "this is a notice, probably from a bot" ,
msgtype : "m.notice" ,
},
event : true ,
});
2022-12-29 16:21:00 +00:00
const { container } = getComponent ({ mxEvent : ev });
expect ( container ). toHaveTextContent ( ev . getContent (). body );
const content = container . querySelector ( ".mx_EventTile_body" );
2024-07-25 18:58:33 +02:00
expect ( content ). toMatchSnapshot ();
2020-01-06 13:28:29 +00:00
});
describe ( "renders plain-text m.text correctly" , () => {
2020-01-09 17:24:13 -07:00
beforeEach (() => {
2023-05-23 16:24:12 +01:00
DMRoomMap . makeShared ( defaultMatrixClient );
2020-01-09 17:24:13 -07:00
});
2020-01-06 13:28:29 +00:00
2020-01-09 17:24:13 -07:00
it ( "simple message renders as expected" , () => {
2023-03-02 11:41:17 +01:00
const ev = mkRoomTextMessage ( "this is a plaintext message" );
2022-12-29 16:21:00 +00:00
const { container } = getComponent ({ mxEvent : ev });
expect ( container ). toHaveTextContent ( ev . getContent (). body );
const content = container . querySelector ( ".mx_EventTile_body" );
2024-07-25 18:58:33 +02:00
expect ( content ). toMatchSnapshot ();
2020-01-06 13:28:29 +00:00
});
// If pills were rendered within a Portal/same shadow DOM then it'd be easier to test
2020-01-09 17:24:13 -07:00
it ( "linkification get applied correctly into the DOM" , () => {
2023-03-02 11:41:17 +01:00
const ev = mkRoomTextMessage ( "Visit https://matrix.org/" );
2022-12-29 16:21:00 +00:00
const { container } = getComponent ({ mxEvent : ev });
expect ( container ). toHaveTextContent ( ev . getContent (). body );
const content = container . querySelector ( ".mx_EventTile_body" );
2024-07-25 18:58:33 +02:00
expect ( content ). toMatchSnapshot ();
2020-01-06 13:28:29 +00:00
});
2023-03-02 11:41:17 +01:00
2023-03-14 11:56:14 +01:00
it ( "should not pillify MXIDs" , () => {
2023-03-02 11:41:17 +01:00
const ev = mkRoomTextMessage ( "Chat with @user:example.com" );
const { container } = getComponent ({ mxEvent : ev });
const content = container . querySelector ( ".mx_EventTile_body" );
2026-04-10 16:52:59 +01:00
expect ( content . innerHTML ). toMatchSnapshot ();
2023-03-02 11:41:17 +01:00
});
2023-03-16 15:01:09 +01:00
it ( "should pillify an MXID permalink" , () => {
const ev = mkRoomTextMessage ( "Chat with https://matrix.to/#/@user:example.com" );
const { container } = getComponent ({ mxEvent : ev });
const content = container . querySelector ( ".mx_EventTile_body" );
2026-04-10 16:52:59 +01:00
expect ( content . innerHTML ). toMatchSnapshot ();
2023-03-16 15:01:09 +01:00
});
2023-03-14 11:56:14 +01:00
it ( "should not pillify room aliases" , () => {
2023-03-02 11:41:17 +01:00
const ev = mkRoomTextMessage ( "Visit #room:example.com" );
const { container } = getComponent ({ mxEvent : ev });
const content = container . querySelector ( ".mx_EventTile_body" );
2026-04-10 16:52:59 +01:00
expect ( content . innerHTML ). toMatchSnapshot ();
2023-03-02 11:41:17 +01:00
});
2023-03-16 15:01:09 +01:00
it ( "should pillify a room alias permalink" , () => {
const ev = mkRoomTextMessage ( "Visit https://matrix.to/#/#room:example.com" );
const { container } = getComponent ({ mxEvent : ev });
const content = container . querySelector ( ".mx_EventTile_body" );
2026-04-10 16:52:59 +01:00
expect ( content . innerHTML ). toMatchSnapshot ();
2023-03-16 15:01:09 +01:00
});
2023-03-22 13:27:24 +01:00
it ( "should pillify a permalink to a message in the same room with the label »Message from Member«" , () => {
const ev = mkRoomTextMessage ( `Visit https://matrix.to/#/ ${ room1Id } / ${ defaultEvent . getId () } ` );
const { container } = getComponent ({ mxEvent : ev });
const content = container . querySelector ( ".mx_EventTile_body" );
2024-01-02 18:56:39 +00:00
expect ( content . innerHTML . replace ( defaultEvent . getId (), "%event_id%" )). toMatchSnapshot ();
2023-03-22 13:27:24 +01:00
});
it ( "should pillify a permalink to an unknown message in the same room with the label »Message«" , () => {
const ev = mkRoomTextMessage ( `Visit https://matrix.to/#/ ${ room1Id } /!abc123` );
const { container } = getComponent ({ mxEvent : ev });
const content = container . querySelector ( ".mx_EventTile_body" );
expect ( content ). toMatchSnapshot ();
});
it ( "should pillify a permalink to an event in another room with the label »Message in Room 2«" , () => {
const ev = mkRoomTextMessage ( `Visit https://matrix.to/#/ ${ room2Id } / ${ defaultEvent . getId () } ` );
const { container } = getComponent ({ mxEvent : ev });
const content = container . querySelector ( ".mx_EventTile_body" );
2024-01-02 18:56:39 +00:00
expect ( content . innerHTML . replace ( defaultEvent . getId (), "%event_id%" )). toMatchSnapshot ();
2023-03-22 13:27:24 +01:00
});
2025-01-22 13:42:40 -08:00
it ( "should pillify a keyword responsible for triggering a notification" , () => {
const ev = mkRoomTextMessage ( "foo bar baz" );
ev . setPushDetails ( undefined , {
actions : [],
pattern : "bar" ,
rule_id : "bar" ,
default : false ,
enabled : true ,
kind : PushRuleKind.ContentSpecific ,
});
const { container } = getComponent ({ mxEvent : ev });
const content = container . querySelector ( ".mx_EventTile_body" );
2026-04-10 16:52:59 +01:00
expect ( content . innerHTML ). toMatchSnapshot ();
2025-01-22 13:42:40 -08:00
});
2020-01-06 13:28:29 +00:00
});
describe ( "renders formatted m.text correctly" , () => {
2023-02-13 11:39:16 +00:00
let matrixClient : MatrixClient ;
2020-01-09 17:24:13 -07:00
beforeEach (() => {
2022-04-28 11:32:50 +02:00
matrixClient = getMockClientWithEventEmitter ({
2025-03-26 20:25:03 +00:00
getRoom : jest.fn (),
... mockClientPushProcessor (),
2023-02-13 11:39:16 +00:00
getAccountData : () : MatrixEvent | undefined => undefined ,
2020-01-09 17:24:13 -07:00
getUserId : () => "@me:my_server" ,
getHomeserverUrl : () => "https://my_server/" ,
2023-02-13 11:39:16 +00:00
on : () : void => undefined ,
removeListener : () : void => undefined ,
2020-10-29 13:22:09 +00:00
isGuest : () => false ,
2023-02-13 11:39:16 +00:00
mxcUrlToHttp : ( s : string ) => s ,
2022-04-28 11:32:50 +02:00
});
2025-03-26 20:25:03 +00:00
mocked ( matrixClient . getRoom ). mockReturnValue ( mkStubRoom ( room1Id , "room name" , matrixClient ));
2023-05-23 16:24:12 +01:00
DMRoomMap . makeShared ( defaultMatrixClient );
2020-01-09 17:24:13 -07:00
});
2020-01-06 13:28:29 +00:00
2020-01-09 17:24:13 -07:00
it ( "italics, bold, underline and strikethrough render as expected" , () => {
2023-03-16 15:01:09 +01:00
const ev = mkFormattedMessage (
"foo *baz* __bar__ <del>del</del> <u>u</u>" ,
"foo <em>baz</em> <strong>bar</strong> <del>del</del> <u>u</u>" ,
);
2022-12-29 16:21:00 +00:00
const { container } = getComponent ({ mxEvent : ev }, matrixClient );
expect ( container ). toHaveTextContent ( "foo baz bar del u" );
const content = container . querySelector ( ".mx_EventTile_body" );
2024-07-25 18:58:33 +02:00
expect ( content ). toMatchSnapshot ();
2020-01-06 13:28:29 +00:00
});
2020-01-09 17:24:13 -07:00
it ( "spoilers get injected properly into the DOM" , () => {
2023-03-16 15:01:09 +01:00
const ev = mkFormattedMessage (
"Hey [Spoiler for movie](mxc://someserver/somefile)" ,
'Hey <span data-mx-spoiler="movie">the movie was awesome</span>' ,
);
2022-12-29 16:21:00 +00:00
const { container } = getComponent ({ mxEvent : ev }, matrixClient );
expect ( container ). toHaveTextContent ( "Hey (movie) the movie was awesome" );
const content = container . querySelector ( ".mx_EventTile_body" );
2024-07-25 18:58:33 +02:00
expect ( content ). toMatchSnapshot ();
2020-01-06 13:28:29 +00:00
});
2022-05-02 20:26:37 -04:00
it ( "linkification is not applied to code blocks" , () => {
2023-03-16 15:01:09 +01:00
const ev = mkFormattedMessage (
"Visit `https://matrix.org/`\n```\nhttps://matrix.org/\n```" ,
"<p>Visit <code>https://matrix.org/</code></p>\n<pre>https://matrix.org/\n</pre>\n" ,
);
2022-12-29 16:21:00 +00:00
const { container } = getComponent ({ mxEvent : ev }, matrixClient );
expect ( container ). toHaveTextContent ( "Visit https://matrix.org/ 1https://matrix.org/" );
const content = container . querySelector ( ".mx_EventTile_body" );
expect ( content ). toMatchSnapshot ();
2022-05-02 20:26:37 -04:00
});
2024-10-18 15:57:39 +01:00
it ( "should syntax highlight code blocks" , async () => {
const ev = mkFormattedMessage (
"```py\n# Python Program to calculate the square root\n\n# Note: change this value for a different result\nnum = 8 \n\n# To take the input from the user\n#num = float(input('Enter a number: '))\n\nnum_sqrt = num ** 0.5\nprint('The square root of %0.3f is %0.3f'%(num ,num_sqrt))" ,
"<pre><code class=\"language-py\"># Python Program to calculate the square root\n\n# Note: change this value for a different result\nnum = 8 \n\n# To take the input from the user\n#num = float(input('Enter a number: '))\n\nnum_sqrt = num ** 0.5\nprint('The square root of %0.3f is %0.3f'%(num ,num_sqrt))\n</code></pre>\n" ,
);
const { container } = getComponent ({ mxEvent : ev }, matrixClient );
await waitFor (() => expect ( container . querySelector ( ".hljs-built_in" )). toBeInTheDocument ());
const content = container . querySelector ( ".mx_EventTile_body" );
expect ( content ). toMatchSnapshot ();
});
2020-01-06 13:28:29 +00:00
// If pills were rendered within a Portal/same shadow DOM then it'd be easier to test
2020-01-09 17:24:13 -07:00
it ( "pills get injected correctly into the DOM" , () => {
2023-03-16 15:01:09 +01:00
const ev = mkFormattedMessage ( "Hey User" , 'Hey <a href="https://matrix.to/#/@user:server">Member</a>' );
2022-12-29 16:21:00 +00:00
const { container } = getComponent ({ mxEvent : ev }, matrixClient );
expect ( container ). toHaveTextContent ( "Hey Member" );
const content = container . querySelector ( ".mx_EventTile_body" );
expect ( content ). toMatchSnapshot ();
2020-01-06 13:28:29 +00:00
});
2021-01-27 11:46:20 +00:00
2022-02-22 06:46:34 -05:00
it ( "pills do not appear in code blocks" , () => {
2023-03-16 15:01:09 +01:00
const ev = mkFormattedMessage (
"`@room`\n```\n@room\n```" ,
"<p><code>@room</code></p>\n<pre><code>@room\n</code></pre>\n" ,
);
2022-12-29 16:21:00 +00:00
const { container } = getComponent ({ mxEvent : ev });
expect ( container ). toHaveTextContent ( "@room 1@room" );
const content = container . querySelector ( ".mx_EventTile_body" );
expect ( content ). toMatchSnapshot ();
2022-02-22 06:46:34 -05:00
});
2023-03-21 10:23:20 +01:00
it ( "pills do not appear for event permalinks with a custom label" , () => {
2023-03-16 15:01:09 +01:00
const ev = mkFormattedMessage (
"An [event link](https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com/" +
"$16085560162aNpaH:example.com?via=example.com) with text" ,
'An <a href="https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com/' +
'$16085560162aNpaH:example.com?via=example.com">event link</a> with text' ,
);
2023-03-21 10:23:20 +01:00
const { asFragment , container } = getComponent ({ mxEvent : ev }, matrixClient );
2022-12-29 16:21:00 +00:00
expect ( container ). toHaveTextContent ( "An event link with text" );
2023-03-21 10:23:20 +01:00
expect ( asFragment ()). toMatchSnapshot ();
});
it ( "pills appear for event permalinks without a custom label" , () => {
const ev = mkFormattedMessage (
"See this message https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com/$16085560162aNpaH:example.com?via=example.com" ,
'See this message <a href="https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com/$16085560162aNpaH:example.com?via=example.com">' +
"https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com/$16085560162aNpaH:example.com?via=example.com</a>" ,
2021-01-27 11:46:20 +00:00
);
2023-03-21 10:23:20 +01:00
const { asFragment } = getComponent ({ mxEvent : ev }, matrixClient );
expect ( asFragment ()). toMatchSnapshot ();
2021-01-27 11:46:20 +00:00
});
2021-02-03 15:18:19 +00:00
it ( "pills appear for room links with vias" , () => {
2023-03-16 15:01:09 +01:00
const ev = mkFormattedMessage (
"A [room link](https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com" +
"?via=example.com&via=bob.com) with vias" ,
'A <a href="https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com' +
'?via=example.com&via=bob.com">room link</a> with vias' ,
);
2023-03-21 10:23:20 +01:00
const { asFragment , container } = getComponent ({ mxEvent : ev }, matrixClient );
2022-12-29 16:21:00 +00:00
expect ( container ). toHaveTextContent ( "A room name with vias" );
2023-03-21 10:23:20 +01:00
expect ( asFragment ()). toMatchSnapshot ();
2021-02-03 15:18:19 +00:00
});
2021-12-02 06:25:12 -03:00
2023-03-16 15:01:09 +01:00
it ( "pills appear for an MXID permalink" , () => {
const ev = mkFormattedMessage (
"Chat with [@user:example.com](https://matrix.to/#/@user:example.com)" ,
'Chat with <a href="https://matrix.to/#/@user:example.com">@user:example.com</a>' ,
);
const { container } = getComponent ({ mxEvent : ev }, matrixClient );
const content = container . querySelector ( ".mx_EventTile_body" );
expect ( content ). toMatchSnapshot ();
});
2024-07-25 18:58:33 +02:00
it ( "renders formatted body without html correctly" , () => {
2021-12-02 06:25:12 -03:00
const ev = mkEvent ({
type : "m.room.message" ,
room : "room_id" ,
user : "sender" ,
content : {
body : "escaped \\*markdown\\*" ,
msgtype : "m.text" ,
format : "org.matrix.custom.html" ,
formatted_body : "escaped *markdown*" ,
},
event : true ,
});
2022-12-29 16:21:00 +00:00
const { container } = getComponent ({ mxEvent : ev }, matrixClient );
2021-12-02 06:25:12 -03:00
2022-12-29 16:21:00 +00:00
const content = container . querySelector ( ".mx_EventTile_body" );
2024-07-25 18:58:33 +02:00
expect ( content ). toMatchSnapshot ();
2021-12-02 06:25:12 -03:00
});
2020-01-06 13:28:29 +00:00
});
2020-01-06 14:38:21 +00:00
2024-12-18 17:42:37 +01:00
describe ( "url preview" , () => {
let matrixClient : MatrixClient ;
2020-01-06 14:38:21 +00:00
2024-12-18 17:42:37 +01:00
beforeEach (() => {
2026-06-24 10:49:49 +01:00
setMissingEntryGenerator (( key ) => key . split ( "|" , 2 )[ 1 ]);
2024-12-18 17:42:37 +01:00
matrixClient = getMockClientWithEventEmitter ({
2025-03-26 20:25:03 +00:00
getRoom : jest.fn (),
getUserId : jest.fn (),
... mockClientPushProcessor (),
2024-12-18 17:42:37 +01:00
getAccountData : () : MatrixClient | undefined => undefined ,
getUrlPreview : ( url : string ) => new Promise (() => {}),
isGuest : () => false ,
mxcUrlToHttp : ( s : string ) => s ,
});
2025-03-26 20:25:03 +00:00
mocked ( matrixClient . getRoom ). mockReturnValue ( mkStubRoom ( "room_id" , "room name" , matrixClient ));
2024-12-18 17:42:37 +01:00
DMRoomMap . makeShared ( defaultMatrixClient );
2022-04-28 11:32:50 +02:00
});
2020-01-06 14:38:21 +00:00
2024-12-18 17:42:37 +01:00
it ( "renders url previews correctly" , () => {
const ev = mkRoomTextMessage ( "Visit https://matrix.org/" );
2025-03-28 10:36:10 +00:00
const { container , rerender } = getComponent ({ mxEvent : ev , showUrlPreview : true }, matrixClient );
2020-01-06 14:38:21 +00:00
2024-12-18 17:42:37 +01:00
expect ( container ). toHaveTextContent ( ev . getContent (). body );
expect ( container . querySelector ( "a" )). toHaveAttribute ( "href" , "https://matrix.org/" );
2020-01-06 14:38:21 +00:00
2024-12-18 17:42:37 +01:00
// simulate an event edit and check the transition from the old URL preview to the new one
const ev2 = mkEvent ({
type : "m.room.message" ,
room : "room_id" ,
user : "sender" ,
content : {
"m.new_content" : {
body : "Visit https://vector.im/ and https://riot.im/" ,
msgtype : "m.text" ,
},
2020-01-06 14:38:21 +00:00
},
2024-12-18 17:42:37 +01:00
event : true ,
});
jest . spyOn ( ev , "replacingEventDate" ). mockReturnValue ( new Date ( 1993 , 7 , 3 ));
ev . makeReplaced ( ev2 );
2025-03-28 10:36:10 +00:00
getComponent ({ mxEvent : ev , showUrlPreview : true , replacingEventId : ev.getId () }, matrixClient , rerender );
2024-12-18 17:42:37 +01:00
expect ( container ). toHaveTextContent ( ev2 . getContent ()[ "m.new_content" ]. body + "(edited)" );
const links = [ "https://vector.im/" , "https://riot.im/" ];
const anchorNodes = container . querySelectorAll ( "a" );
Array . from ( anchorNodes ). forEach (( node , index ) => {
expect ( node ). toHaveAttribute ( "href" , links [ index ]);
});
2020-01-06 14:38:21 +00:00
});
2024-12-18 17:42:37 +01:00
it ( "should listen to showUrlPreview change" , () => {
const ev = mkRoomTextMessage ( "Visit https://matrix.org/" );
2022-12-29 16:21:00 +00:00
2025-03-28 10:36:10 +00:00
const { container , rerender } = getComponent ({ mxEvent : ev , showUrlPreview : false }, matrixClient );
2024-12-18 17:42:37 +01:00
expect ( container . querySelector ( ".mx_LinkPreviewGroup" )). toBeNull ();
2022-12-29 16:21:00 +00:00
2025-03-28 10:36:10 +00:00
getComponent ({ mxEvent : ev , showUrlPreview : true }, matrixClient , rerender );
2026-03-16 10:05:34 +00:00
waitFor (() => {
// Asynchronous check since the VM needs to recalcuate.
expect ( container . querySelector ( ".mx_LinkPreviewGroup" )). toBeTruthy ();
});
2022-12-29 16:21:00 +00:00
});
2020-01-06 14:38:21 +00:00
});
2020-01-06 13:28:29 +00:00
});