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" ;
2025-02-05 13:25:06 +00:00
import { mocked , type MockedObject } from "jest-mock" ;
2024-10-18 15:57:39 +01:00
import { render , waitFor } from "jest-matrix-react" ;
2025-03-21 11:34:06 +00:00
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor" ;
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 { MatrixClientPeg } from "../../../../../src/MatrixClientPeg" ;
import * as languageHandler from "../../../../../src/languageHandler" ;
import DMRoomMap from "../../../../../src/utils/DMRoomMap" ;
import TextualBody from "../../../../../src/components/views/messages/TextualBody" ;
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext" ;
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks" ;
2025-02-05 13:25:06 +00:00
import { type MediaEventHelper } from "../../../../../src/utils/MediaEventHelper" ;
2020-01-06 13:28:29 +00:00
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 (() => {
2022-04-28 11:32:50 +02:00
jest . spyOn ( MatrixClientPeg , "get" ). mockRestore ();
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 );
defaultProps . permalinkCreator = new RoomPermalinkCreator ( defaultRoom );
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
});
2022-12-29 16:21:00 +00:00
const getComponent = ( props = {}, matrixClient : MatrixClient = defaultMatrixClient , renderingFn? : any ) =>
( renderingFn ?? render )(
< MatrixClientContext.Provider value = { matrixClient }>
< TextualBody { ...defaultProps } { ...props } />
</ MatrixClientContext.Provider >,
);
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
});
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" );
expect ( content . innerHTML ). toMatchInlineSnapshot (
2025-03-26 20:25:03 +00:00
`"Chat with <a href="https://matrix.to/#/@user:example.com" rel="noreferrer noopener" class="linkified">@user:example.com</a>"` ,
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" );
expect ( content . innerHTML ). toMatchInlineSnapshot (
2025-04-09 19:03:09 +00:00
`"Chat with <bdi><a class="mx_Pill mx_UserPill mx_UserPill_me" href="https://matrix.to/#/@user:example.com"><span aria-label="Profile picture" aria-hidden="true" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_1qbcf_8 mx_BaseAvatar" style="--cpd-avatar-size: 16px;"><img loading="lazy" alt="" referrerpolicy="no-referrer" class="_image_1qbcf_41" data-type="round" width="16px" height="16px" src="mxc://avatar.url/image.png"></span><span class="mx_Pill_text">Member</span></a></bdi>"` ,
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" );
expect ( content . innerHTML ). toMatchInlineSnapshot (
2025-03-26 20:25:03 +00:00
`"Visit <a href="https://matrix.to/#/#room:example.com" rel="noreferrer noopener" class="linkified">#room:example.com</a>"` ,
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" );
expect ( content . innerHTML ). toMatchInlineSnapshot (
2025-03-26 20:25:03 +00:00
`"Visit <bdi><a class="mx_Pill mx_RoomPill" href="https://matrix.to/#/#room:example.com"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 24 24" class="mx_Pill_LinkIcon mx_BaseAvatar"><path d="M12 19.071q-1.467 1.467-3.536 1.467-2.067 0-3.535-1.467t-1.467-3.535q0-2.07 1.467-3.536L7.05 9.879q.3-.3.707-.3t.707.3.301.707-.3.707l-2.122 2.121a2.9 2.9 0 0 0-.884 2.122q0 1.237.884 2.12.884.885 2.121.885t2.122-.884l2.121-2.121q.3-.3.707-.3t.707.3.3.707q0 .405-.3.707zm-1.414-4.243q-.3.3-.707.301a.97.97 0 0 1-.707-.3q-.3-.3-.301-.708 0-.405.3-.707l4.243-4.242q.3-.3.707-.3t.707.3.3.707-.3.707zm6.364-.707q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.301-.707 0-.405.3-.707l2.122-2.121q.884-.885.884-2.121 0-1.238-.884-2.122a2.9 2.9 0 0 0-2.121-.884q-1.237 0-2.122.884l-2.121 2.122q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.3-.708 0-.405.3-.707L12 4.93q1.467-1.467 3.536-1.467t3.535 1.467 1.467 3.536T19.071 12z"></path></svg><span class="mx_Pill_text">#room:example.com</span></a></bdi>"` ,
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" );
expect ( content . innerHTML ). toMatchInlineSnapshot (
2025-03-26 20:25:03 +00:00
`"foo <bdi><span tabindex="0"><span class="mx_Pill mx_KeywordPill"><span class="mx_Pill_text">bar</span></span></span></bdi> baz"` ,
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 (() => {
languageHandler . setMissingEntryGenerator (( key ) => key . split ( "|" , 2 )[ 1 ]);
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 );
2024-12-18 17:42:37 +01:00
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
});