Switch away from nesting React trees and mangling the DOM (#29586)
* Switch away from nesting React trees and mangling the DOM By parsing HTML events and manipulating the AST before passing it to React Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Use MatrixClientContext in Pill now that we are in the main React tree Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Add missing import Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Break import cycles Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Minimise Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Docs Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
committed by
GitHub
parent
89e22e00fb
commit
3f47487472
@@ -115,6 +115,16 @@ export const mockClientMethodsEvents = () => ({
|
||||
getPushActionsForEvent: jest.fn(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns basic mocked pushProcessor
|
||||
*/
|
||||
export const mockClientPushProcessor = () => ({
|
||||
pushProcessor: {
|
||||
getPushRuleById: jest.fn(),
|
||||
ruleMatchesEvent: jest.fn(),
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns basic mocked client methods related to server support
|
||||
*/
|
||||
|
||||
@@ -303,6 +303,10 @@ export function createTestClient(): MatrixClient {
|
||||
getLocalAliases: jest.fn().mockReturnValue([]),
|
||||
uploadDeviceSigningKeys: jest.fn(),
|
||||
isKeyBackupKeyStored: jest.fn().mockResolvedValue(null),
|
||||
|
||||
pushProcessor: {
|
||||
getPushRuleById: jest.fn(),
|
||||
},
|
||||
} as unknown as MatrixClient;
|
||||
|
||||
client.reEmitter = new ReEmitter(client);
|
||||
|
||||
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type ReactElement } from "react";
|
||||
import React from "react";
|
||||
import { mocked } from "jest-mock";
|
||||
import { render, screen } from "jest-matrix-react";
|
||||
import { type IContent } from "matrix-js-sdk/src/matrix";
|
||||
import parse from "html-react-parser";
|
||||
|
||||
import { bodyToSpan, formatEmojis, topicToHtml } from "../../src/HtmlUtils";
|
||||
import { bodyToHtml, bodyToNode, formatEmojis, topicToHtml } from "../../src/HtmlUtils";
|
||||
import SettingsStore from "../../src/settings/SettingsStore";
|
||||
|
||||
jest.mock("../../src/settings/SettingsStore");
|
||||
@@ -57,12 +57,8 @@ describe("topicToHtml", () => {
|
||||
});
|
||||
|
||||
describe("bodyToHtml", () => {
|
||||
function getHtml(content: IContent, highlights?: string[]): string {
|
||||
return (bodyToSpan(content, highlights, {}) as ReactElement).props.dangerouslySetInnerHTML.__html;
|
||||
}
|
||||
|
||||
it("should apply highlights to HTML messages", () => {
|
||||
const html = getHtml(
|
||||
const html = bodyToHtml(
|
||||
{
|
||||
body: "test **foo** bar",
|
||||
msgtype: "m.text",
|
||||
@@ -76,7 +72,7 @@ describe("bodyToHtml", () => {
|
||||
});
|
||||
|
||||
it("should apply highlights to plaintext messages", () => {
|
||||
const html = getHtml(
|
||||
const html = bodyToHtml(
|
||||
{
|
||||
body: "test foo bar",
|
||||
msgtype: "m.text",
|
||||
@@ -88,7 +84,7 @@ describe("bodyToHtml", () => {
|
||||
});
|
||||
|
||||
it("should not respect HTML tags in plaintext message highlighting", () => {
|
||||
const html = getHtml(
|
||||
const html = bodyToHtml(
|
||||
{
|
||||
body: "test foo <b>bar",
|
||||
msgtype: "m.text",
|
||||
@@ -99,39 +95,12 @@ describe("bodyToHtml", () => {
|
||||
expect(html).toMatchInlineSnapshot(`"<span class="mx_EventTile_searchHighlight">test</span> foo <b>bar"`);
|
||||
});
|
||||
|
||||
it("generates big emoji for emoji made of multiple characters", () => {
|
||||
const { asFragment } = render(bodyToSpan({ body: "👨👩👧👦 ↔️ 🇮🇸", msgtype: "m.text" }, [], {}) as ReactElement);
|
||||
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should generate big emoji for an emoji-only reply to a message", () => {
|
||||
const { asFragment } = render(
|
||||
bodyToSpan(
|
||||
{
|
||||
"body": "> <@sender1:server> Test\n\n🥰",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body":
|
||||
'<mx-reply><blockquote><a href="https://matrix.to/#/!roomId:server/$eventId">In reply to</a> <a href="https://matrix.to/#/@sender1:server">@sender1:server</a><br>Test</blockquote></mx-reply>🥰',
|
||||
"m.relates_to": {
|
||||
"m.in_reply_to": {
|
||||
event_id: "$eventId",
|
||||
},
|
||||
},
|
||||
"msgtype": "m.text",
|
||||
},
|
||||
[],
|
||||
{
|
||||
stripReplyFallback: true,
|
||||
},
|
||||
) as ReactElement,
|
||||
);
|
||||
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("does not mistake characters in text presentation mode for emoji", () => {
|
||||
const { asFragment } = render(bodyToSpan({ body: "↔ ❗︎", msgtype: "m.text" }, [], {}) as ReactElement);
|
||||
const { asFragment } = render(
|
||||
<span className="mx_EventTile_body translate" dir="auto">
|
||||
{parse(bodyToHtml({ body: "↔ ❗︎", msgtype: "m.text" }, [], {}))}
|
||||
</span>,
|
||||
);
|
||||
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
@@ -142,42 +111,54 @@ describe("bodyToHtml", () => {
|
||||
});
|
||||
|
||||
it("should render inline katex", () => {
|
||||
const html = getHtml({
|
||||
body: "hello \\xi world",
|
||||
msgtype: "m.text",
|
||||
formatted_body: 'hello <span data-mx-maths="\\xi"><code>\\xi</code></span> world',
|
||||
format: "org.matrix.custom.html",
|
||||
});
|
||||
const html = bodyToHtml(
|
||||
{
|
||||
body: "hello \\xi world",
|
||||
msgtype: "m.text",
|
||||
formatted_body: 'hello <span data-mx-maths="\\xi"><code>\\xi</code></span> world',
|
||||
format: "org.matrix.custom.html",
|
||||
},
|
||||
[],
|
||||
);
|
||||
expect(html).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render block katex", () => {
|
||||
const html = getHtml({
|
||||
body: "hello \\xi world",
|
||||
msgtype: "m.text",
|
||||
formatted_body: '<p>hello</p><div data-mx-maths="\\xi"><code>\\xi</code></div><p>world</p>',
|
||||
format: "org.matrix.custom.html",
|
||||
});
|
||||
const html = bodyToHtml(
|
||||
{
|
||||
body: "hello \\xi world",
|
||||
msgtype: "m.text",
|
||||
formatted_body: '<p>hello</p><div data-mx-maths="\\xi"><code>\\xi</code></div><p>world</p>',
|
||||
format: "org.matrix.custom.html",
|
||||
},
|
||||
[],
|
||||
);
|
||||
expect(html).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should not mangle code blocks", () => {
|
||||
const html = getHtml({
|
||||
body: "hello \\xi world",
|
||||
msgtype: "m.text",
|
||||
formatted_body: "<p>hello</p><pre><code>$\\xi$</code></pre><p>world</p>",
|
||||
format: "org.matrix.custom.html",
|
||||
});
|
||||
const html = bodyToHtml(
|
||||
{
|
||||
body: "hello \\xi world",
|
||||
msgtype: "m.text",
|
||||
formatted_body: "<p>hello</p><pre><code>$\\xi$</code></pre><p>world</p>",
|
||||
format: "org.matrix.custom.html",
|
||||
},
|
||||
[],
|
||||
);
|
||||
expect(html).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should not mangle divs", () => {
|
||||
const html = getHtml({
|
||||
body: "hello world",
|
||||
msgtype: "m.text",
|
||||
formatted_body: "<p>hello</p><div>world</div>",
|
||||
format: "org.matrix.custom.html",
|
||||
});
|
||||
const html = bodyToHtml(
|
||||
{
|
||||
body: "hello world",
|
||||
msgtype: "m.text",
|
||||
formatted_body: "<p>hello</p><div>world</div>",
|
||||
format: "org.matrix.custom.html",
|
||||
},
|
||||
[],
|
||||
);
|
||||
expect(html).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -198,3 +179,53 @@ describe("formatEmojis", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("bodyToNode", () => {
|
||||
it("generates big emoji for emoji made of multiple characters", () => {
|
||||
const { className, emojiBodyElements } = bodyToNode(
|
||||
{
|
||||
body: "👨👩👧👦 ↔️ 🇮🇸",
|
||||
msgtype: "m.text",
|
||||
},
|
||||
[],
|
||||
{
|
||||
stripReplyFallback: true,
|
||||
},
|
||||
);
|
||||
|
||||
const { asFragment } = render(
|
||||
<span className={className} dir="auto">
|
||||
{emojiBodyElements}
|
||||
</span>,
|
||||
);
|
||||
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should generate big emoji for an emoji-only reply to a message", () => {
|
||||
const { className, formattedBody } = bodyToNode(
|
||||
{
|
||||
"body": "> <@sender1:server> Test\n\n🥰",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body":
|
||||
'<mx-reply><blockquote><a href="https://matrix.to/#/!roomId:server/$eventId">In reply to</a> <a href="https://matrix.to/#/@sender1:server">@sender1:server</a><br>Test</blockquote></mx-reply>🥰',
|
||||
"m.relates_to": {
|
||||
"m.in_reply_to": {
|
||||
event_id: "$eventId",
|
||||
},
|
||||
},
|
||||
"msgtype": "m.text",
|
||||
},
|
||||
[],
|
||||
{
|
||||
stripReplyFallback: true,
|
||||
},
|
||||
);
|
||||
|
||||
const { asFragment } = render(
|
||||
<span className={className} dir="auto" dangerouslySetInnerHTML={{ __html: formattedBody! }} />,
|
||||
);
|
||||
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ exports[`bodyToHtml feature_latex_maths should render block katex 1`] = `"<p>hel
|
||||
|
||||
exports[`bodyToHtml feature_latex_maths should render inline katex 1`] = `"hello <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>ξ</mi></mrow><annotation encoding="application/x-tex">\\xi</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.04601em;">ξ</span></span></span></span> world"`;
|
||||
|
||||
exports[`bodyToHtml generates big emoji for emoji made of multiple characters 1`] = `
|
||||
exports[`bodyToNode generates big emoji for emoji made of multiple characters 1`] = `
|
||||
<DocumentFragment>
|
||||
<span
|
||||
class="mx_EventTile_body mx_EventTile_bigEmoji translate"
|
||||
@@ -49,7 +49,7 @@ exports[`bodyToHtml generates big emoji for emoji made of multiple characters 1`
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`bodyToHtml should generate big emoji for an emoji-only reply to a message 1`] = `
|
||||
exports[`bodyToNode should generate big emoji for an emoji-only reply to a message 1`] = `
|
||||
<DocumentFragment>
|
||||
<span
|
||||
class="mx_EventTile_body mx_EventTile_bigEmoji translate"
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
mockClientMethodsCrypto,
|
||||
mockClientMethodsEvents,
|
||||
mockClientMethodsUser,
|
||||
mockClientPushProcessor,
|
||||
} from "../../../test-utils";
|
||||
import type ResizeNotifier from "../../../../src/utils/ResizeNotifier";
|
||||
import { type IRoomState } from "../../../../src/components/structures/RoomView";
|
||||
@@ -45,6 +46,7 @@ describe("MessagePanel", function () {
|
||||
...mockClientMethodsUser(userId),
|
||||
...mockClientMethodsEvents(),
|
||||
...mockClientMethodsCrypto(),
|
||||
...mockClientPushProcessor(),
|
||||
getAccountData: jest.fn(),
|
||||
isUserIgnored: jest.fn().mockReturnValue(false),
|
||||
isRoomEncrypted: jest.fn().mockReturnValue(false),
|
||||
|
||||
@@ -36,7 +36,14 @@ import TimelinePanel from "../../../../src/components/structures/TimelinePanel";
|
||||
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import { isCallEvent } from "../../../../src/components/structures/LegacyCallEventGrouper";
|
||||
import { filterConsole, flushPromises, mkMembership, mkRoom, stubClient } from "../../../test-utils";
|
||||
import {
|
||||
filterConsole,
|
||||
flushPromises,
|
||||
mkMembership,
|
||||
mkRoom,
|
||||
stubClient,
|
||||
withClientContextRenderOptions,
|
||||
} from "../../../test-utils";
|
||||
import { mkThread } from "../../../test-utils/threads";
|
||||
import { createMessageEventContent } from "../../../test-utils/events";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
@@ -206,6 +213,7 @@ describe("TimelinePanel", () => {
|
||||
manageReadReceipts={true}
|
||||
ref={(ref) => (timelinePanel = ref)}
|
||||
/>,
|
||||
withClientContextRenderOptions(MatrixClientPeg.safeGet()),
|
||||
);
|
||||
await flushPromises();
|
||||
await waitFor(() => expect(timelinePanel).toBeTruthy());
|
||||
@@ -403,7 +411,10 @@ describe("TimelinePanel", () => {
|
||||
setupPagination(client, timeline, eventsPage1, null);
|
||||
|
||||
await withScrollPanelMountSpy(async (mountSpy) => {
|
||||
const { container } = render(<TimelinePanel {...getProps(room, events)} timelineSet={timelineSet} />);
|
||||
const { container } = render(
|
||||
<TimelinePanel {...getProps(room, events)} timelineSet={timelineSet} />,
|
||||
withClientContextRenderOptions(MatrixClientPeg.safeGet()),
|
||||
);
|
||||
|
||||
await waitFor(() => expectEvents(container, [events[1]]));
|
||||
|
||||
@@ -420,7 +431,10 @@ describe("TimelinePanel", () => {
|
||||
const [, room, events] = setupTestData();
|
||||
|
||||
await withScrollPanelMountSpy(async (mountSpy) => {
|
||||
const { container } = render(<TimelinePanel {...getProps(room, events)} />);
|
||||
const { container } = render(
|
||||
<TimelinePanel {...getProps(room, events)} />,
|
||||
withClientContextRenderOptions(MatrixClientPeg.safeGet()),
|
||||
);
|
||||
|
||||
await waitFor(() => expectEvents(container, [events[0], events[1]]));
|
||||
|
||||
@@ -560,6 +574,7 @@ describe("TimelinePanel", () => {
|
||||
overlayTimelineSet={overlayTimelineSet}
|
||||
overlayTimelineSetFilter={isCallEvent}
|
||||
/>,
|
||||
withClientContextRenderOptions(MatrixClientPeg.safeGet()),
|
||||
);
|
||||
await waitFor(() =>
|
||||
expectEvents(container, [
|
||||
@@ -599,6 +614,7 @@ describe("TimelinePanel", () => {
|
||||
|
||||
const { container } = render(
|
||||
<TimelinePanel {...getProps(room, events)} overlayTimelineSet={overlayTimelineSet} />,
|
||||
withClientContextRenderOptions(MatrixClientPeg.safeGet()),
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
@@ -630,6 +646,7 @@ describe("TimelinePanel", () => {
|
||||
|
||||
const { container } = render(
|
||||
<TimelinePanel {...getProps(room, events)} overlayTimelineSet={overlayTimelineSet} />,
|
||||
withClientContextRenderOptions(MatrixClientPeg.safeGet()),
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
@@ -661,6 +678,7 @@ describe("TimelinePanel", () => {
|
||||
|
||||
const { container } = render(
|
||||
<TimelinePanel {...getProps(room, events)} overlayTimelineSet={overlayTimelineSet} />,
|
||||
withClientContextRenderOptions(MatrixClientPeg.safeGet()),
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
@@ -695,6 +713,7 @@ describe("TimelinePanel", () => {
|
||||
timelineSet={timelineSet}
|
||||
overlayTimelineSet={overlayTimelineSet}
|
||||
/>,
|
||||
withClientContextRenderOptions(MatrixClientPeg.safeGet()),
|
||||
);
|
||||
|
||||
await waitFor(() => expectEvents(container, [overlayEvents[0], events[0]]));
|
||||
@@ -768,6 +787,7 @@ describe("TimelinePanel", () => {
|
||||
await withScrollPanelMountSpy(async (mountSpy) => {
|
||||
const { container } = render(
|
||||
<TimelinePanel {...getProps(room, events)} overlayTimelineSet={overlayTimelineSet} />,
|
||||
withClientContextRenderOptions(MatrixClientPeg.safeGet()),
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
@@ -1027,7 +1047,10 @@ describe("TimelinePanel", () => {
|
||||
room.getTimelineSets = jest.fn().mockReturnValue([timelineSet]);
|
||||
|
||||
await withScrollPanelMountSpy(async () => {
|
||||
const { container } = render(<TimelinePanel {...getProps(room, events)} timelineSet={timelineSet} />);
|
||||
const { container } = render(
|
||||
<TimelinePanel {...getProps(room, events)} timelineSet={timelineSet} />,
|
||||
withClientContextRenderOptions(MatrixClientPeg.safeGet()),
|
||||
);
|
||||
|
||||
await waitFor(() => expectEvents(container, [events[1]]));
|
||||
});
|
||||
|
||||
@@ -21,11 +21,14 @@ import {
|
||||
mkRoomCanonicalAliasEvent,
|
||||
mkRoomMemberJoinEvent,
|
||||
stubClient,
|
||||
withClientContextRenderOptions,
|
||||
} from "../../../../test-utils";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import { type ButtonEvent } from "../../../../../src/components/views/elements/AccessibleButton";
|
||||
import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
|
||||
import { SDKContext, SdkContextClass } from "../../../../../src/contexts/SDKContext";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg.ts";
|
||||
import { TestSdkContext } from "../../../TestSdkContext.ts";
|
||||
|
||||
describe("<Pill>", () => {
|
||||
let client: Mocked<MatrixClient>;
|
||||
@@ -45,6 +48,10 @@ describe("<Pill>", () => {
|
||||
let pillParentClickHandler: (e: ButtonEvent) => void;
|
||||
|
||||
const renderPill = (props: PillProps): void => {
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
const mockSdkContext = new TestSdkContext();
|
||||
mockSdkContext.client = cli;
|
||||
|
||||
const withDefault = {
|
||||
inMessage: true,
|
||||
shouldShowPillAvatar: true,
|
||||
@@ -53,9 +60,12 @@ describe("<Pill>", () => {
|
||||
// wrap Pill with a div to allow testing of event bubbling
|
||||
renderResult = render(
|
||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
|
||||
<div onClick={pillParentClickHandler}>
|
||||
<Pill {...withDefault} />
|
||||
</div>,
|
||||
<SDKContext.Provider value={mockSdkContext}>
|
||||
<div onClick={pillParentClickHandler}>
|
||||
<Pill {...withDefault} />
|
||||
</div>
|
||||
</SDKContext.Provider>,
|
||||
withClientContextRenderOptions(cli),
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,13 +6,19 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { type MatrixClient, type MatrixEvent, PushRuleKind } from "matrix-js-sdk/src/matrix";
|
||||
import React, { type ComponentProps } from "react";
|
||||
import { type MatrixClient, type MatrixEvent, PushRuleKind, type Room } from "matrix-js-sdk/src/matrix";
|
||||
import { mocked, type MockedObject } from "jest-mock";
|
||||
import { render, waitFor } from "jest-matrix-react";
|
||||
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
|
||||
|
||||
import { getMockClientWithEventEmitter, mkEvent, mkMessage, mkStubRoom } from "../../../../test-utils";
|
||||
import {
|
||||
getMockClientWithEventEmitter,
|
||||
mkEvent,
|
||||
mkMessage,
|
||||
mkStubRoom,
|
||||
mockClientPushProcessor,
|
||||
} from "../../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import * as languageHandler from "../../../../../src/languageHandler";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
@@ -55,8 +61,8 @@ describe("<TextualBody />", () => {
|
||||
jest.spyOn(global.Math, "random").mockRestore();
|
||||
});
|
||||
|
||||
const defaultRoom = mkStubRoom(room1Id, "test room", undefined);
|
||||
const otherRoom = mkStubRoom(room2Id, room2Name, undefined);
|
||||
let defaultRoom: Room;
|
||||
let otherRoom: Room;
|
||||
let defaultMatrixClient: MockedObject<MatrixClient>;
|
||||
|
||||
const defaultEvent = mkEvent({
|
||||
@@ -70,6 +76,15 @@ describe("<TextualBody />", () => {
|
||||
event: true,
|
||||
});
|
||||
|
||||
const defaultProps: ComponentProps<typeof TextualBody> = {
|
||||
mxEvent: defaultEvent,
|
||||
highlights: [] as string[],
|
||||
highlightLink: "",
|
||||
onMessageAllowed: jest.fn(),
|
||||
onHeightChanged: jest.fn(),
|
||||
mediaEventHelper: {} as MediaEventHelper,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
defaultMatrixClient = getMockClientWithEventEmitter({
|
||||
getRoom: (roomId: string | undefined) => {
|
||||
@@ -89,6 +104,10 @@ describe("<TextualBody />", () => {
|
||||
// @ts-expect-error
|
||||
defaultMatrixClient.pushProcessor = new PushProcessor(defaultMatrixClient);
|
||||
|
||||
defaultRoom = mkStubRoom(room1Id, "test room", defaultMatrixClient);
|
||||
defaultProps.permalinkCreator = new RoomPermalinkCreator(defaultRoom);
|
||||
otherRoom = mkStubRoom(room2Id, room2Name, defaultMatrixClient);
|
||||
|
||||
mocked(defaultRoom).findEventById.mockImplementation((eventId: string) => {
|
||||
if (eventId === defaultEvent.getId()) return defaultEvent;
|
||||
return undefined;
|
||||
@@ -96,16 +115,6 @@ describe("<TextualBody />", () => {
|
||||
jest.spyOn(global.Math, "random").mockReturnValue(0.123456);
|
||||
});
|
||||
|
||||
const defaultProps = {
|
||||
mxEvent: defaultEvent,
|
||||
highlights: [] as string[],
|
||||
highlightLink: "",
|
||||
onMessageAllowed: jest.fn(),
|
||||
onHeightChanged: jest.fn(),
|
||||
permalinkCreator: new RoomPermalinkCreator(defaultRoom),
|
||||
mediaEventHelper: {} as MediaEventHelper,
|
||||
};
|
||||
|
||||
const getComponent = (props = {}, matrixClient: MatrixClient = defaultMatrixClient, renderingFn?: any) =>
|
||||
(renderingFn ?? render)(
|
||||
<MatrixClientContext.Provider value={matrixClient}>
|
||||
@@ -180,7 +189,7 @@ describe("<TextualBody />", () => {
|
||||
const { container } = getComponent({ mxEvent: ev });
|
||||
const content = container.querySelector(".mx_EventTile_body");
|
||||
expect(content.innerHTML).toMatchInlineSnapshot(
|
||||
`"Chat with <a href="https://matrix.to/#/@user:example.com" class="linkified" rel="noreferrer noopener">@user:example.com</a>"`,
|
||||
`"Chat with <a href="https://matrix.to/#/@user:example.com" rel="noreferrer noopener" class="linkified">@user:example.com</a>"`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -189,7 +198,7 @@ describe("<TextualBody />", () => {
|
||||
const { container } = getComponent({ mxEvent: ev });
|
||||
const content = container.querySelector(".mx_EventTile_body");
|
||||
expect(content.innerHTML).toMatchInlineSnapshot(
|
||||
`"Chat with <span><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="" src="mxc://avatar.url/image.png" referrerpolicy="no-referrer" class="_image_1qbcf_41" data-type="round" width="16px" height="16px"></span><span class="mx_Pill_text">Member</span></a></bdi></span>"`,
|
||||
`"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="" src="mxc://avatar.url/image.png" referrerpolicy="no-referrer" class="_image_1qbcf_41" data-type="round" width="16px" height="16px"></span><span class="mx_Pill_text">Member</span></a></bdi>"`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -198,7 +207,7 @@ describe("<TextualBody />", () => {
|
||||
const { container } = getComponent({ mxEvent: ev });
|
||||
const content = container.querySelector(".mx_EventTile_body");
|
||||
expect(content.innerHTML).toMatchInlineSnapshot(
|
||||
`"Visit <a href="https://matrix.to/#/#room:example.com" class="linkified" rel="noreferrer noopener">#room:example.com</a>"`,
|
||||
`"Visit <a href="https://matrix.to/#/#room:example.com" rel="noreferrer noopener" class="linkified">#room:example.com</a>"`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -207,7 +216,7 @@ describe("<TextualBody />", () => {
|
||||
const { container } = getComponent({ mxEvent: ev });
|
||||
const content = container.querySelector(".mx_EventTile_body");
|
||||
expect(content.innerHTML).toMatchInlineSnapshot(
|
||||
`"Visit <span><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></span>"`,
|
||||
`"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>"`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -245,7 +254,7 @@ describe("<TextualBody />", () => {
|
||||
const { container } = getComponent({ mxEvent: ev });
|
||||
const content = container.querySelector(".mx_EventTile_body");
|
||||
expect(content.innerHTML).toMatchInlineSnapshot(
|
||||
`"<span>foo <bdi><span tabindex="0"><span class="mx_Pill mx_KeywordPill"><span class="mx_Pill_text">bar</span></span></span></bdi> baz</span>"`,
|
||||
`"foo <bdi><span tabindex="0"><span class="mx_Pill mx_KeywordPill"><span class="mx_Pill_text">bar</span></span></span></bdi> baz"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -254,7 +263,8 @@ describe("<TextualBody />", () => {
|
||||
let matrixClient: MatrixClient;
|
||||
beforeEach(() => {
|
||||
matrixClient = getMockClientWithEventEmitter({
|
||||
getRoom: () => mkStubRoom(room1Id, "room name", undefined),
|
||||
getRoom: jest.fn(),
|
||||
...mockClientPushProcessor(),
|
||||
getAccountData: (): MatrixEvent | undefined => undefined,
|
||||
getUserId: () => "@me:my_server",
|
||||
getHomeserverUrl: () => "https://my_server/",
|
||||
@@ -263,6 +273,7 @@ describe("<TextualBody />", () => {
|
||||
isGuest: () => false,
|
||||
mxcUrlToHttp: (s: string) => s,
|
||||
});
|
||||
mocked(matrixClient.getRoom).mockReturnValue(mkStubRoom(room1Id, "room name", matrixClient));
|
||||
DMRoomMap.makeShared(defaultMatrixClient);
|
||||
});
|
||||
|
||||
@@ -401,12 +412,15 @@ describe("<TextualBody />", () => {
|
||||
beforeEach(() => {
|
||||
languageHandler.setMissingEntryGenerator((key) => key.split("|", 2)[1]);
|
||||
matrixClient = getMockClientWithEventEmitter({
|
||||
getRoom: () => mkStubRoom("room_id", "room name", undefined),
|
||||
getRoom: jest.fn(),
|
||||
getUserId: jest.fn(),
|
||||
...mockClientPushProcessor(),
|
||||
getAccountData: (): MatrixClient | undefined => undefined,
|
||||
getUrlPreview: (url: string) => new Promise(() => {}),
|
||||
isGuest: () => false,
|
||||
mxcUrlToHttp: (s: string) => s,
|
||||
});
|
||||
mocked(matrixClient.getRoom).mockReturnValue(mkStubRoom("room_id", "room name", matrixClient));
|
||||
DMRoomMap.makeShared(defaultMatrixClient);
|
||||
});
|
||||
|
||||
|
||||
@@ -77,40 +77,38 @@ exports[`<TextualBody /> renders formatted m.text correctly pills appear for an
|
||||
dir="auto"
|
||||
>
|
||||
Chat with
|
||||
<span>
|
||||
<bdi>
|
||||
<a
|
||||
class="mx_Pill mx_UserPill"
|
||||
href="https://matrix.to/#/@user:example.com"
|
||||
<bdi>
|
||||
<a
|
||||
class="mx_Pill mx_UserPill"
|
||||
href="https://matrix.to/#/@user:example.com"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
aria-label="Profile picture"
|
||||
class="_avatar_1qbcf_8 mx_BaseAvatar"
|
||||
data-color="2"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
style="--cpd-avatar-size: 16px;"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
aria-label="Profile picture"
|
||||
class="_avatar_1qbcf_8 mx_BaseAvatar"
|
||||
data-color="2"
|
||||
data-testid="avatar-img"
|
||||
<img
|
||||
alt=""
|
||||
class="_image_1qbcf_41"
|
||||
data-type="round"
|
||||
style="--cpd-avatar-size: 16px;"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
class="_image_1qbcf_41"
|
||||
data-type="round"
|
||||
height="16px"
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer"
|
||||
src="mxc://avatar.url/image.png"
|
||||
width="16px"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="mx_Pill_text"
|
||||
>
|
||||
Member
|
||||
</span>
|
||||
</a>
|
||||
</bdi>
|
||||
</span>
|
||||
height="16px"
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer"
|
||||
src="mxc://avatar.url/image.png"
|
||||
width="16px"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="mx_Pill_text"
|
||||
>
|
||||
Member
|
||||
</span>
|
||||
</a>
|
||||
</bdi>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -124,40 +122,38 @@ exports[`<TextualBody /> renders formatted m.text correctly pills appear for eve
|
||||
dir="auto"
|
||||
>
|
||||
See this message
|
||||
<span>
|
||||
<bdi>
|
||||
<a
|
||||
class="mx_Pill mx_EventPill"
|
||||
href="https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com/$16085560162aNpaH:example.com?via=example.com"
|
||||
<bdi>
|
||||
<a
|
||||
class="mx_Pill mx_EventPill"
|
||||
href="https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com/$16085560162aNpaH:example.com?via=example.com"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
aria-label="Avatar"
|
||||
class="_avatar_1qbcf_8 mx_BaseAvatar"
|
||||
data-color="1"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
style="--cpd-avatar-size: 16px;"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
aria-label="Avatar"
|
||||
class="_avatar_1qbcf_8 mx_BaseAvatar"
|
||||
data-color="1"
|
||||
data-testid="avatar-img"
|
||||
<img
|
||||
alt=""
|
||||
class="_image_1qbcf_41"
|
||||
data-type="round"
|
||||
style="--cpd-avatar-size: 16px;"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
class="_image_1qbcf_41"
|
||||
data-type="round"
|
||||
height="16px"
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer"
|
||||
src="mxc://avatar.url/room.png"
|
||||
width="16px"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="mx_Pill_text"
|
||||
>
|
||||
Message in room name
|
||||
</span>
|
||||
</a>
|
||||
</bdi>
|
||||
</span>
|
||||
height="16px"
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer"
|
||||
src="mxc://avatar.url/room.png"
|
||||
width="16px"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="mx_Pill_text"
|
||||
>
|
||||
Message in room name
|
||||
</span>
|
||||
</a>
|
||||
</bdi>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
@@ -173,40 +169,38 @@ exports[`<TextualBody /> renders formatted m.text correctly pills appear for roo
|
||||
dir="auto"
|
||||
>
|
||||
A
|
||||
<span>
|
||||
<bdi>
|
||||
<a
|
||||
class="mx_Pill mx_RoomPill"
|
||||
href="https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com?via=example.com&via=bob.com"
|
||||
<bdi>
|
||||
<a
|
||||
class="mx_Pill mx_RoomPill"
|
||||
href="https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com?via=example.com&via=bob.com"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
aria-label="Avatar"
|
||||
class="_avatar_1qbcf_8 mx_BaseAvatar"
|
||||
data-color="1"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
style="--cpd-avatar-size: 16px;"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
aria-label="Avatar"
|
||||
class="_avatar_1qbcf_8 mx_BaseAvatar"
|
||||
data-color="1"
|
||||
data-testid="avatar-img"
|
||||
<img
|
||||
alt=""
|
||||
class="_image_1qbcf_41"
|
||||
data-type="round"
|
||||
style="--cpd-avatar-size: 16px;"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
class="_image_1qbcf_41"
|
||||
data-type="round"
|
||||
height="16px"
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer"
|
||||
src="mxc://avatar.url/room.png"
|
||||
width="16px"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="mx_Pill_text"
|
||||
>
|
||||
room name
|
||||
</span>
|
||||
</a>
|
||||
</bdi>
|
||||
</span>
|
||||
height="16px"
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer"
|
||||
src="mxc://avatar.url/room.png"
|
||||
width="16px"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="mx_Pill_text"
|
||||
>
|
||||
room name
|
||||
</span>
|
||||
</a>
|
||||
</bdi>
|
||||
with vias
|
||||
</div>
|
||||
</div>
|
||||
@@ -287,40 +281,38 @@ exports[`<TextualBody /> renders formatted m.text correctly pills get injected c
|
||||
dir="auto"
|
||||
>
|
||||
Hey
|
||||
<span>
|
||||
<bdi>
|
||||
<a
|
||||
class="mx_Pill mx_UserPill"
|
||||
href="https://matrix.to/#/@user:server"
|
||||
<bdi>
|
||||
<a
|
||||
class="mx_Pill mx_UserPill"
|
||||
href="https://matrix.to/#/@user:server"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
aria-label="Profile picture"
|
||||
class="_avatar_1qbcf_8 mx_BaseAvatar"
|
||||
data-color="2"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
style="--cpd-avatar-size: 16px;"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
aria-label="Profile picture"
|
||||
class="_avatar_1qbcf_8 mx_BaseAvatar"
|
||||
data-color="2"
|
||||
data-testid="avatar-img"
|
||||
<img
|
||||
alt=""
|
||||
class="_image_1qbcf_41"
|
||||
data-type="round"
|
||||
style="--cpd-avatar-size: 16px;"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
class="_image_1qbcf_41"
|
||||
data-type="round"
|
||||
height="16px"
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer"
|
||||
src="mxc://avatar.url/image.png"
|
||||
width="16px"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="mx_Pill_text"
|
||||
>
|
||||
Member
|
||||
</span>
|
||||
</a>
|
||||
</bdi>
|
||||
</span>
|
||||
height="16px"
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer"
|
||||
src="mxc://avatar.url/image.png"
|
||||
width="16px"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="mx_Pill_text"
|
||||
>
|
||||
Member
|
||||
</span>
|
||||
</a>
|
||||
</bdi>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -466,25 +458,21 @@ exports[`<TextualBody /> renders formatted m.text correctly spoilers get injecte
|
||||
dir="auto"
|
||||
>
|
||||
Hey
|
||||
<span>
|
||||
<button
|
||||
class="mx_EventTile_spoiler"
|
||||
<button
|
||||
class="mx_EventTile_spoiler"
|
||||
>
|
||||
<span
|
||||
class="mx_EventTile_spoiler_reason"
|
||||
>
|
||||
<span
|
||||
class="mx_EventTile_spoiler_reason"
|
||||
>
|
||||
(movie)
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="mx_EventTile_spoiler_content"
|
||||
>
|
||||
<span>
|
||||
the movie was awesome
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
(movie)
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="mx_EventTile_spoiler_content"
|
||||
>
|
||||
the movie was awesome
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -522,9 +510,9 @@ exports[`<TextualBody /> renders plain-text m.text correctly linkification get a
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to a message in the same room with the label »Message from Member« 1`] = `"Visit <span><bdi><a class="mx_Pill mx_EventPill" href="https://matrix.to/#/!room1:example.com/%event_id%"><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="" src="mxc://avatar.url/image.png" referrerpolicy="no-referrer" class="_image_1qbcf_41" data-type="round" width="16px" height="16px"></span><span class="mx_Pill_text">Message from Member</span></a></bdi></span>"`;
|
||||
exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to a message in the same room with the label »Message from Member« 1`] = `"Visit <bdi><a class="mx_Pill mx_EventPill" href="https://matrix.to/#/!room1:example.com/%event_id%"><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="" src="mxc://avatar.url/image.png" referrerpolicy="no-referrer" class="_image_1qbcf_41" data-type="round" width="16px" height="16px"></span><span class="mx_Pill_text">Message from Member</span></a></bdi>"`;
|
||||
|
||||
exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to an event in another room with the label »Message in Room 2« 1`] = `"Visit <span><bdi><a class="mx_Pill mx_EventPill" href="https://matrix.to/#/!room2:example.com/%event_id%"><span aria-label="Avatar" 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="" src="mxc://avatar.url/room.png" referrerpolicy="no-referrer" class="_image_1qbcf_41" data-type="round" width="16px" height="16px"></span><span class="mx_Pill_text">Message in Room 2</span></a></bdi></span>"`;
|
||||
exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to an event in another room with the label »Message in Room 2« 1`] = `"Visit <bdi><a class="mx_Pill mx_EventPill" href="https://matrix.to/#/!room2:example.com/%event_id%"><span aria-label="Avatar" 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="" src="mxc://avatar.url/room.png" referrerpolicy="no-referrer" class="_image_1qbcf_41" data-type="round" width="16px" height="16px"></span><span class="mx_Pill_text">Message in Room 2</span></a></bdi>"`;
|
||||
|
||||
exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to an unknown message in the same room with the label »Message« 1`] = `
|
||||
<div
|
||||
@@ -532,32 +520,30 @@ exports[`<TextualBody /> renders plain-text m.text correctly should pillify a pe
|
||||
dir="auto"
|
||||
>
|
||||
Visit
|
||||
<span>
|
||||
<bdi>
|
||||
<a
|
||||
class="mx_Pill mx_EventPill"
|
||||
href="https://matrix.to/#/!room1:example.com/!abc123"
|
||||
<bdi>
|
||||
<a
|
||||
class="mx_Pill mx_EventPill"
|
||||
href="https://matrix.to/#/!room1:example.com/!abc123"
|
||||
>
|
||||
<svg
|
||||
class="mx_Pill_LinkIcon mx_BaseAvatar"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<svg
|
||||
class="mx_Pill_LinkIcon mx_BaseAvatar"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<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"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="mx_Pill_text"
|
||||
>
|
||||
Message
|
||||
</span>
|
||||
</a>
|
||||
</bdi>
|
||||
</span>
|
||||
<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"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="mx_Pill_text"
|
||||
>
|
||||
Message
|
||||
</span>
|
||||
</a>
|
||||
</bdi>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import React from "react";
|
||||
import { MatrixEvent, Room, EventType } from "matrix-js-sdk/src/matrix";
|
||||
import { render, type RenderResult } from "jest-matrix-react";
|
||||
|
||||
import { stubClient } from "../../../../test-utils";
|
||||
import { stubClient, withClientContextRenderOptions } from "../../../../test-utils";
|
||||
import SearchResultTile from "../../../../../src/components/views/rooms/SearchResultTile";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
|
||||
@@ -28,7 +28,10 @@ describe("SearchResultTile", () => {
|
||||
});
|
||||
|
||||
function renderComponent(props: Partial<Props>): RenderResult {
|
||||
return render(<SearchResultTile timeline={[]} ourEventsIndexes={[1]} {...props} />);
|
||||
return render(
|
||||
<SearchResultTile timeline={[]} ourEventsIndexes={[1]} {...props} />,
|
||||
withClientContextRenderOptions(MatrixClientPeg.safeGet()),
|
||||
);
|
||||
}
|
||||
|
||||
it("Sets up appropriate callEventGrouper for m.call. events", () => {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`link-tooltip does nothing for empty element 1`] = `
|
||||
<DocumentFragment>
|
||||
<div />
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`link-tooltip wraps single anchor 1`] = `
|
||||
<DocumentFragment>
|
||||
|
||||
|
||||
<div>
|
||||
|
||||
|
||||
<span
|
||||
aria-labelledby=":r0:"
|
||||
tabindex="0"
|
||||
>
|
||||
<a
|
||||
href="/foo"
|
||||
>
|
||||
click
|
||||
</a>
|
||||
</span>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</DocumentFragment>
|
||||
`;
|
||||
103
test/unit-tests/renderer/__snapshots__/pill-test.tsx.snap
Normal file
103
test/unit-tests/renderer/__snapshots__/pill-test.tsx.snap
Normal file
@@ -0,0 +1,103 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`keyword pills should do nothing for empty element 1`] = `
|
||||
<DocumentFragment>
|
||||
<div />
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`keyword pills should pillify 1`] = `
|
||||
<DocumentFragment>
|
||||
<div>
|
||||
Foo
|
||||
<bdi>
|
||||
<span
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="mx_Pill mx_KeywordPill"
|
||||
>
|
||||
<span
|
||||
class="mx_Pill_text"
|
||||
>
|
||||
TeST
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</bdi>
|
||||
Bar
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`mention pills should do nothing for empty element 1`] = `
|
||||
<DocumentFragment>
|
||||
<div />
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`mention pills should pillify @room 1`] = `
|
||||
<DocumentFragment>
|
||||
<div>
|
||||
<bdi>
|
||||
<span
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="mx_Pill mx_AtRoomPill"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
|
||||
data-color="4"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="presentation"
|
||||
style="--cpd-avatar-size: 16px;"
|
||||
>
|
||||
!
|
||||
</span>
|
||||
<span
|
||||
class="mx_Pill_text"
|
||||
>
|
||||
@room
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</bdi>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`mention pills should pillify @room in an intentional mentions world 1`] = `
|
||||
<DocumentFragment>
|
||||
<div>
|
||||
<bdi>
|
||||
<span
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="mx_Pill mx_AtRoomPill"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
|
||||
data-color="4"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="presentation"
|
||||
style="--cpd-avatar-size: 16px;"
|
||||
>
|
||||
!
|
||||
</span>
|
||||
<span
|
||||
class="mx_Pill_text"
|
||||
>
|
||||
@room
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</bdi>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
47
test/unit-tests/renderer/link-tooltip-test.tsx
Normal file
47
test/unit-tests/renderer/link-tooltip-test.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2024-2025 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { screen, fireEvent, render, type RenderResult } from "jest-matrix-react";
|
||||
import parse from "html-react-parser";
|
||||
|
||||
import { ambiguousLinkTooltipRenderer, combineRenderers } from "../../../src/renderer";
|
||||
import PlatformPeg from "../../../src/PlatformPeg";
|
||||
import type BasePlatform from "../../../src/BasePlatform";
|
||||
|
||||
describe("link-tooltip", () => {
|
||||
jest.spyOn(PlatformPeg, "get").mockReturnValue({ needsUrlTooltips: () => true } as unknown as BasePlatform);
|
||||
|
||||
function renderTooltips(input: string): RenderResult {
|
||||
return render(
|
||||
<>
|
||||
{parse(input, {
|
||||
replace: combineRenderers(ambiguousLinkTooltipRenderer)({ isHtml: true }),
|
||||
})}
|
||||
</>,
|
||||
);
|
||||
}
|
||||
|
||||
it("does nothing for empty element", () => {
|
||||
const { asFragment } = renderTooltips("<div></div>");
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("wraps single anchor", () => {
|
||||
const { container, asFragment } = renderTooltips(`
|
||||
<div>
|
||||
<a href="/foo">click</a>
|
||||
</div>
|
||||
`);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
const anchor = container.querySelector("a")!;
|
||||
expect(anchor.getAttribute("href")).toEqual("/foo");
|
||||
fireEvent.focus(anchor.parentElement!);
|
||||
expect(screen.getByLabelText("http://localhost/foo")).toBe(anchor.parentElement!);
|
||||
});
|
||||
});
|
||||
228
test/unit-tests/renderer/pill-test.tsx
Normal file
228
test/unit-tests/renderer/pill-test.tsx
Normal file
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
Copyright 2024-2025 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render, type RenderResult } from "jest-matrix-react";
|
||||
import {
|
||||
MatrixEvent,
|
||||
ConditionKind,
|
||||
EventType,
|
||||
PushRuleActionName,
|
||||
Room,
|
||||
TweakName,
|
||||
type MatrixClient,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { mocked } from "jest-mock";
|
||||
import parse from "html-react-parser";
|
||||
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
|
||||
|
||||
import { keywordPillRenderer, mentionPillRenderer, combineRenderers } from "../../../src/renderer";
|
||||
import { stubClient, withClientContextRenderOptions } from "../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||
import DMRoomMap from "../../../src/utils/DMRoomMap";
|
||||
|
||||
describe("mention pills", () => {
|
||||
let cli: MatrixClient;
|
||||
let room: Room;
|
||||
const roomId = "!room:id";
|
||||
const event = new MatrixEvent({
|
||||
room_id: roomId,
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
body: "@room",
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
cli = MatrixClientPeg.safeGet();
|
||||
// @ts-expect-error
|
||||
cli.pushProcessor = new PushProcessor(cli);
|
||||
room = new Room(roomId, cli, cli.getUserId()!);
|
||||
room.currentState.mayTriggerNotifOfType = jest.fn().mockReturnValue(true);
|
||||
(cli.getRoom as jest.Mock).mockReturnValue(room);
|
||||
cli.pushRules!.global = {
|
||||
override: [
|
||||
{
|
||||
rule_id: ".m.rule.roomnotif",
|
||||
default: true,
|
||||
enabled: true,
|
||||
conditions: [
|
||||
{
|
||||
kind: ConditionKind.EventMatch,
|
||||
key: "content.body",
|
||||
pattern: "@room",
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
PushRuleActionName.Notify,
|
||||
{
|
||||
set_tweak: TweakName.Highlight,
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
rule_id: ".m.rule.is_room_mention",
|
||||
default: true,
|
||||
enabled: true,
|
||||
conditions: [
|
||||
{
|
||||
kind: ConditionKind.EventPropertyIs,
|
||||
key: "content.m\\.mentions.room",
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
kind: ConditionKind.SenderNotificationPermission,
|
||||
key: "room",
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
PushRuleActionName.Notify,
|
||||
{
|
||||
set_tweak: TweakName.Highlight,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
DMRoomMap.makeShared(cli);
|
||||
});
|
||||
|
||||
function renderPills(input: string, mxEvent?: MatrixEvent): RenderResult {
|
||||
return render(
|
||||
<>
|
||||
{parse(input, {
|
||||
replace: combineRenderers(mentionPillRenderer)({
|
||||
mxEvent: mxEvent ?? event,
|
||||
room,
|
||||
isHtml: true,
|
||||
}),
|
||||
})}
|
||||
</>,
|
||||
withClientContextRenderOptions(cli),
|
||||
);
|
||||
}
|
||||
|
||||
it("should do nothing for empty element", () => {
|
||||
const input = "<div></div>";
|
||||
const { asFragment } = renderPills(input);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should pillify @room", () => {
|
||||
const input = "<div>@room</div>";
|
||||
const { container, asFragment } = renderPills(input);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
expect(container.querySelector(".mx_Pill.mx_AtRoomPill")?.textContent).toBe("!@room");
|
||||
});
|
||||
|
||||
it("should pillify @room in an intentional mentions world", () => {
|
||||
mocked(MatrixClientPeg.safeGet().supportsIntentionalMentions).mockReturnValue(true);
|
||||
const { container, asFragment } = renderPills(
|
||||
"<div>@room</div>",
|
||||
new MatrixEvent({
|
||||
room_id: roomId,
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
"body": "@room",
|
||||
"m.mentions": {
|
||||
room: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
expect(container.querySelector(".mx_Pill.mx_AtRoomPill")?.textContent).toBe("!@room");
|
||||
});
|
||||
});
|
||||
|
||||
describe("keyword pills", () => {
|
||||
let cli: MatrixClient;
|
||||
const keywordRegexpPattern = /(test)/i;
|
||||
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
cli = MatrixClientPeg.safeGet();
|
||||
cli.pushRules!.global = {
|
||||
override: [
|
||||
{
|
||||
rule_id: ".m.rule.roomnotif",
|
||||
default: true,
|
||||
enabled: true,
|
||||
conditions: [
|
||||
{
|
||||
kind: ConditionKind.EventMatch,
|
||||
key: "content.body",
|
||||
pattern: "@room",
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
PushRuleActionName.Notify,
|
||||
{
|
||||
set_tweak: TweakName.Highlight,
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
rule_id: ".m.rule.is_room_mention",
|
||||
default: true,
|
||||
enabled: true,
|
||||
conditions: [
|
||||
{
|
||||
kind: ConditionKind.EventPropertyIs,
|
||||
key: "content.m\\.mentions.room",
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
kind: ConditionKind.SenderNotificationPermission,
|
||||
key: "room",
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
PushRuleActionName.Notify,
|
||||
{
|
||||
set_tweak: TweakName.Highlight,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
DMRoomMap.makeShared(cli);
|
||||
});
|
||||
|
||||
function renderPills(input: string): RenderResult {
|
||||
return render(
|
||||
<>
|
||||
{parse(input, {
|
||||
replace: combineRenderers(keywordPillRenderer)({
|
||||
isHtml: true,
|
||||
keywordRegexpPattern,
|
||||
}),
|
||||
})}
|
||||
</>,
|
||||
withClientContextRenderOptions(cli),
|
||||
);
|
||||
}
|
||||
|
||||
it("should do nothing for empty element", () => {
|
||||
const input = "<div></div>";
|
||||
const { asFragment } = renderPills(input);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should pillify", () => {
|
||||
const input = "<div>Foo TeST Bar</div>";
|
||||
const { container, asFragment } = renderPills(input);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
expect(container.querySelector(".mx_Pill.mx_KeywordPill")?.textContent).toBe("TeST");
|
||||
});
|
||||
});
|
||||
@@ -1,142 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { act, render } from "jest-matrix-react";
|
||||
import { MatrixEvent, ConditionKind, EventType, PushRuleActionName, Room, TweakName } from "matrix-js-sdk/src/matrix";
|
||||
import { mocked } from "jest-mock";
|
||||
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
|
||||
|
||||
import { pillifyLinks } from "../../../src/utils/pillify";
|
||||
import { stubClient } from "../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||
import DMRoomMap from "../../../src/utils/DMRoomMap";
|
||||
import { ReactRootManager } from "../../../src/utils/react.tsx";
|
||||
|
||||
describe("pillify", () => {
|
||||
const roomId = "!room:id";
|
||||
const event = new MatrixEvent({
|
||||
room_id: roomId,
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
body: "@room",
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
const room = new Room(roomId, cli, cli.getUserId()!);
|
||||
room.currentState.mayTriggerNotifOfType = jest.fn().mockReturnValue(true);
|
||||
(cli.getRoom as jest.Mock).mockReturnValue(room);
|
||||
cli.pushRules!.global = {
|
||||
override: [
|
||||
{
|
||||
rule_id: ".m.rule.roomnotif",
|
||||
default: true,
|
||||
enabled: true,
|
||||
conditions: [
|
||||
{
|
||||
kind: ConditionKind.EventMatch,
|
||||
key: "content.body",
|
||||
pattern: "@room",
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
PushRuleActionName.Notify,
|
||||
{
|
||||
set_tweak: TweakName.Highlight,
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
rule_id: ".m.rule.is_room_mention",
|
||||
default: true,
|
||||
enabled: true,
|
||||
conditions: [
|
||||
{
|
||||
kind: ConditionKind.EventPropertyIs,
|
||||
key: "content.m\\.mentions.room",
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
kind: ConditionKind.SenderNotificationPermission,
|
||||
key: "room",
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
PushRuleActionName.Notify,
|
||||
{
|
||||
set_tweak: TweakName.Highlight,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
// @ts-expect-error
|
||||
cli.pushProcessor = new PushProcessor(cli);
|
||||
|
||||
DMRoomMap.makeShared(cli);
|
||||
});
|
||||
|
||||
it("should do nothing for empty element", () => {
|
||||
const { container } = render(<div />);
|
||||
const originalHtml = container.outerHTML;
|
||||
const containers = new ReactRootManager();
|
||||
pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
|
||||
expect(containers.elements).toHaveLength(0);
|
||||
expect(container.outerHTML).toEqual(originalHtml);
|
||||
});
|
||||
|
||||
it("should pillify @room", () => {
|
||||
const { container } = render(<div>@room</div>);
|
||||
const containers = new ReactRootManager();
|
||||
act(() => pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers));
|
||||
expect(containers.elements).toHaveLength(1);
|
||||
expect(container.querySelector(".mx_Pill.mx_AtRoomPill")?.textContent).toBe("!@room");
|
||||
});
|
||||
|
||||
it("should pillify @room in an intentional mentions world", () => {
|
||||
mocked(MatrixClientPeg.safeGet().supportsIntentionalMentions).mockReturnValue(true);
|
||||
const { container } = render(<div>@room</div>);
|
||||
const containers = new ReactRootManager();
|
||||
act(() =>
|
||||
pillifyLinks(
|
||||
MatrixClientPeg.safeGet(),
|
||||
[container],
|
||||
new MatrixEvent({
|
||||
room_id: roomId,
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
"body": "@room",
|
||||
"m.mentions": {
|
||||
room: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
containers,
|
||||
),
|
||||
);
|
||||
expect(containers.elements).toHaveLength(1);
|
||||
expect(container.querySelector(".mx_Pill.mx_AtRoomPill")?.textContent).toBe("!@room");
|
||||
});
|
||||
|
||||
it("should not double up pillification on repeated calls", () => {
|
||||
const { container } = render(<div>@room</div>);
|
||||
const containers = new ReactRootManager();
|
||||
act(() => {
|
||||
pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
|
||||
pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
|
||||
pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
|
||||
pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
|
||||
});
|
||||
expect(containers.elements).toHaveLength(1);
|
||||
expect(container.querySelector(".mx_Pill.mx_AtRoomPill")?.textContent).toBe("!@room");
|
||||
});
|
||||
});
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { act, render } from "jest-matrix-react";
|
||||
|
||||
import { tooltipifyLinks } from "../../../src/utils/tooltipify";
|
||||
import PlatformPeg from "../../../src/PlatformPeg";
|
||||
import type BasePlatform from "../../../src/BasePlatform";
|
||||
import { ReactRootManager } from "../../../src/utils/react.tsx";
|
||||
|
||||
describe("tooltipify", () => {
|
||||
jest.spyOn(PlatformPeg, "get").mockReturnValue({ needsUrlTooltips: () => true } as unknown as BasePlatform);
|
||||
|
||||
it("does nothing for empty element", () => {
|
||||
const { container: root } = render(<div />);
|
||||
const originalHtml = root.outerHTML;
|
||||
const containers = new ReactRootManager();
|
||||
tooltipifyLinks([root], [], containers);
|
||||
expect(containers.elements).toHaveLength(0);
|
||||
expect(root.outerHTML).toEqual(originalHtml);
|
||||
});
|
||||
|
||||
it("wraps single anchor", () => {
|
||||
const { container: root } = render(
|
||||
<div>
|
||||
<a href="/foo">click</a>
|
||||
</div>,
|
||||
);
|
||||
const containers = new ReactRootManager();
|
||||
tooltipifyLinks([root], [], containers);
|
||||
expect(containers.elements).toHaveLength(1);
|
||||
const anchor = root.querySelector("a");
|
||||
expect(anchor?.getAttribute("href")).toEqual("/foo");
|
||||
const tooltip = anchor!.querySelector(".mx_TextWithTooltip_target");
|
||||
expect(tooltip).toBeDefined();
|
||||
});
|
||||
|
||||
it("ignores node", () => {
|
||||
const { container: root } = render(
|
||||
<div>
|
||||
<a href="/foo">click</a>
|
||||
</div>,
|
||||
);
|
||||
const originalHtml = root.outerHTML;
|
||||
const containers = new ReactRootManager();
|
||||
tooltipifyLinks([root], [root.children[0]], containers);
|
||||
expect(containers.elements).toHaveLength(0);
|
||||
expect(root.outerHTML).toEqual(originalHtml);
|
||||
});
|
||||
|
||||
it("does not re-wrap if called multiple times", async () => {
|
||||
const { container: root, unmount } = render(
|
||||
<div>
|
||||
<a href="/foo">click</a>
|
||||
</div>,
|
||||
);
|
||||
const containers = new ReactRootManager();
|
||||
tooltipifyLinks([root], [], containers);
|
||||
tooltipifyLinks([root], [], containers);
|
||||
tooltipifyLinks([root], [], containers);
|
||||
tooltipifyLinks([root], [], containers);
|
||||
expect(containers.elements).toHaveLength(1);
|
||||
const anchor = root.querySelector("a");
|
||||
expect(anchor?.getAttribute("href")).toEqual("/foo");
|
||||
const tooltip = anchor!.querySelector(".mx_TextWithTooltip_target");
|
||||
expect(tooltip).toBeDefined();
|
||||
await act(async () => {
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user