Compare commits

..

1 Commits

Author SHA1 Message Date
renovate[bot] 81a1f394f3 Update css 2024-10-21 14:09:48 +00:00
17 changed files with 143 additions and 154 deletions
+2 -2
View File
@@ -263,14 +263,14 @@
"mailhog": "^4.16.0",
"matrix-mock-request": "^2.5.0",
"matrix-web-i18n": "^3.2.1",
"mini-css-extract-plugin": "2.9.0",
"mini-css-extract-plugin": "2.9.1",
"minimist": "^1.2.6",
"mkdirp": "^3.0.0",
"mocha-junit-reporter": "^2.2.0",
"modernizr": "^3.12.0",
"node-fetch": "^2.6.7",
"playwright-core": "^1.45.1",
"postcss": "8.4.38",
"postcss": "8.4.47",
"postcss-easings": "^4.0.0",
"postcss-hexrgba": "2.1.0",
"postcss-import": "16.1.0",
+2 -2
View File
@@ -10,6 +10,7 @@ Please see LICENSE files in the repository root for full details.
import "matrix-js-sdk/src/@types/global"; // load matrix-js-sdk's type extensions first
import "@types/modernizr";
import type { Renderer } from "react-dom";
import type { logger } from "matrix-js-sdk/src/logger";
import ContentMessages from "../ContentMessages";
import { IMatrixClientPeg } from "../MatrixClientPeg";
@@ -43,7 +44,6 @@ import AutoRageshakeStore from "../stores/AutoRageshakeStore";
import { IConfigOptions } from "../IConfigOptions";
import { MatrixDispatcher } from "../dispatcher/dispatcher";
import { DeepReadonly } from "./common";
import MatrixChat from "../components/structures/MatrixChat";
/* eslint-disable @typescript-eslint/naming-convention */
@@ -71,7 +71,7 @@ declare global {
interface Window {
mxSendRageshake: (text: string, withLogs?: boolean) => void;
matrixLogger: typeof logger;
matrixChat: MatrixChat;
matrixChat: ReturnType<Renderer>;
mxSendSentryReport: (userText: string, issueUrl: string, error: Error) => Promise<void>;
mxLoginWithAccessToken: (hsUrl: string, accessToken: string) => Promise<void>;
mxAutoRageshakeStore?: AutoRageshakeStore;
+21 -25
View File
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { createRoot, Root } from "react-dom/client";
import ReactDOM from "react-dom";
import classNames from "classnames";
import { IDeferred, defer, sleep } from "matrix-js-sdk/src/utils";
import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
@@ -83,26 +83,28 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
// Neither the static nor priority modal will be in this list.
private modals: IModal<any>[] = [];
private static root?: Root;
private static getOrCreateRoot(): Root {
if (!ModalManager.root) {
const container = document.createElement("div");
private static getOrCreateContainer(): HTMLElement {
let container = document.getElementById(DIALOG_CONTAINER_ID);
if (!container) {
container = document.createElement("div");
container.id = DIALOG_CONTAINER_ID;
document.body.appendChild(container);
ModalManager.root = createRoot(container);
}
return ModalManager.root;
return container;
}
private static staticRoot?: Root;
private static getOrCreateStaticRoot(): Root {
if (!ModalManager.staticRoot) {
const container = document.createElement("div");
private static getOrCreateStaticContainer(): HTMLElement {
let container = document.getElementById(STATIC_DIALOG_CONTAINER_ID);
if (!container) {
container = document.createElement("div");
container.id = STATIC_DIALOG_CONTAINER_ID;
document.body.appendChild(container);
ModalManager.staticRoot = createRoot(container);
}
return ModalManager.staticRoot;
return container;
}
public constructor() {
@@ -398,10 +400,8 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
dis.dispatch({
action: "aria_unhide_main_app",
});
ModalManager.getOrCreateRoot().unmount();
ModalManager.root = undefined;
ModalManager.getOrCreateStaticRoot().unmount();
ModalManager.staticRoot = undefined;
ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateContainer());
ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateStaticContainer());
return;
}
@@ -430,11 +430,10 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
</TooltipProvider>
);
ModalManager.getOrCreateStaticRoot().render(staticDialog);
ReactDOM.render(staticDialog, ModalManager.getOrCreateStaticContainer());
} else {
// This is safe to call repeatedly if we happen to do that
ModalManager.getOrCreateStaticRoot().unmount();
ModalManager.staticRoot = undefined;
ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateStaticContainer());
}
const modal = this.getCurrentModal();
@@ -458,13 +457,10 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
</TooltipProvider>
);
setTimeout(() => {
ModalManager.getOrCreateRoot().render(dialog);
}, 0);
setTimeout(() => ReactDOM.render(dialog, ModalManager.getOrCreateContainer()), 0);
} else {
// This is safe to call repeatedly if we happen to do that
ModalManager.getOrCreateRoot().unmount();
ModalManager.root = undefined;
ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateContainer());
}
}
}
@@ -6,7 +6,7 @@ Please see LICENSE files in the repository root for full details.
*/
import React, { MutableRefObject, ReactNode } from "react";
import { createRoot } from "react-dom/client";
import ReactDOM from "react-dom";
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
import { TooltipProvider } from "@vector-im/compound-web";
@@ -176,8 +176,7 @@ export default class PersistedElement extends React.Component<IProps> {
</MatrixClientContext.Provider>
);
const root = createRoot(getOrCreateContainer("mx_persistedElement_" + this.props.persistKey));
root.render(content);
ReactDOM.render(content, getOrCreateContainer("mx_persistedElement_" + this.props.persistKey));
}
private updateChildVisibility(child?: HTMLDivElement, visible = false): void {
@@ -13,8 +13,8 @@ import classNames from "classnames";
import * as HtmlUtils from "../../../HtmlUtils";
import { editBodyDiffToHtml } from "../../../utils/MessageDiffUtils";
import { formatTime } from "../../../DateUtils";
import { pillifyLinks } from "../../../utils/pillify";
import { tooltipifyLinks } from "../../../utils/tooltipify";
import { pillifyLinks, unmountPills } from "../../../utils/pillify";
import { tooltipifyLinks, unmountTooltips } from "../../../utils/tooltipify";
import { _t } from "../../../languageHandler";
import Modal from "../../../Modal";
import RedactedBody from "./RedactedBody";
@@ -23,7 +23,6 @@ import ConfirmAndWaitRedactDialog from "../dialogs/ConfirmAndWaitRedactDialog";
import ViewSource from "../../structures/ViewSource";
import SettingsStore from "../../../settings/SettingsStore";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { ReactRootManager } from "../../../utils/react";
function getReplacedContent(event: MatrixEvent): IContent {
const originalContent = event.getOriginalContent();
@@ -48,8 +47,8 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
public declare context: React.ContextType<typeof MatrixClientContext>;
private content = createRef<HTMLDivElement>();
private pills = new ReactRootManager();
private tooltips = new ReactRootManager();
private pills: Element[] = [];
private tooltips: Element[] = [];
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
super(props, context);
@@ -104,7 +103,7 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
private tooltipifyLinks(): void {
// not present for redacted events
if (this.content.current) {
tooltipifyLinks(this.content.current.children, this.pills.elements, this.tooltips);
tooltipifyLinks(this.content.current.children, this.pills, this.tooltips);
}
}
@@ -114,8 +113,8 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
}
public componentWillUnmount(): void {
this.pills.unmount();
this.tooltips.unmount();
unmountPills(this.pills);
unmountTooltips(this.tooltips);
const event = this.props.mxEvent;
event.localRedactionEvent()?.off(MatrixEventEvent.Status, this.onAssociatedStatusChanged);
}
+19 -13
View File
@@ -17,8 +17,8 @@ import Modal from "../../../Modal";
import dis from "../../../dispatcher/dispatcher";
import { _t } from "../../../languageHandler";
import SettingsStore from "../../../settings/SettingsStore";
import { pillifyLinks } from "../../../utils/pillify";
import { tooltipifyLinks } from "../../../utils/tooltipify";
import { pillifyLinks, unmountPills } from "../../../utils/pillify";
import { tooltipifyLinks, unmountTooltips } from "../../../utils/tooltipify";
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
import { isPermalinkHost, tryTransformPermalinkToLocalHref } from "../../../utils/permalinks/Permalinks";
import { Action } from "../../../dispatcher/actions";
@@ -36,7 +36,6 @@ import { EditWysiwygComposer } from "../rooms/wysiwyg_composer";
import { IEventTileOps } from "../rooms/EventTile";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import CodeBlock from "./CodeBlock";
import { ReactRootManager } from "../../../utils/react";
interface IState {
// the URLs (if any) to be previewed with a LinkPreviewWidget inside this TextualBody.
@@ -49,9 +48,9 @@ interface IState {
export default class TextualBody extends React.Component<IBodyProps, IState> {
private readonly contentRef = createRef<HTMLDivElement>();
private pills = new ReactRootManager();
private tooltips = new ReactRootManager();
private reactRoots = new ReactRootManager();
private pills: Element[] = [];
private tooltips: Element[] = [];
private reactRoots: Element[] = [];
public static contextType = RoomContext;
public declare context: React.ContextType<typeof RoomContext>;
@@ -81,7 +80,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
// tooltipifyLinks AFTER calculateUrlPreview because the DOM inside the tooltip
// container is empty before the internal component has mounted so calculateUrlPreview
// won't find any anchors
tooltipifyLinks([content], [...this.pills.elements, ...this.reactRoots.elements], this.tooltips);
tooltipifyLinks([content], this.pills, this.tooltips);
if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") {
// Handle expansion and add buttons
@@ -112,11 +111,12 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
private wrapPreInReact(pre: HTMLPreElement): void {
const root = document.createElement("div");
root.className = "mx_EventTile_pre_container";
this.reactRoots.push(root);
// Insert containing div in place of <pre> block
pre.parentNode?.replaceChild(root, pre);
this.reactRoots.render(<CodeBlock onHeightChanged={this.props.onHeightChanged}>{pre}</CodeBlock>, root);
ReactDOM.render(<CodeBlock onHeightChanged={this.props.onHeightChanged}>{pre}</CodeBlock>, root);
}
public componentDidUpdate(prevProps: Readonly<IBodyProps>): void {
@@ -130,9 +130,16 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
}
public componentWillUnmount(): void {
this.pills.unmount();
this.tooltips.unmount();
this.reactRoots.unmount();
unmountPills(this.pills);
unmountTooltips(this.tooltips);
for (const root of this.reactRoots) {
ReactDOM.unmountComponentAtNode(root);
}
this.pills = [];
this.tooltips = [];
this.reactRoots = [];
}
public shouldComponentUpdate(nextProps: Readonly<IBodyProps>, nextState: Readonly<IState>): boolean {
@@ -188,8 +195,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
</TooltipProvider>
);
this.reactRoots.render(spoiler, spoilerContainer);
ReactDOM.render(spoiler, spoilerContainer);
node.parentNode?.replaceChild(spoilerContainer, node);
node = spoilerContainer;
+4 -6
View File
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { createRoot } from "react-dom/client";
import ReactDOM from "react-dom";
import { Room, MatrixEvent, EventType, MsgType } from "matrix-js-sdk/src/matrix";
import { renderToStaticMarkup } from "react-dom/server";
import { logger } from "matrix-js-sdk/src/logger";
@@ -313,11 +313,9 @@ export default class HTMLExporter extends Exporter {
) {
// to linkify textual events, we'll need lifecycle methods which won't be invoked in renderToString
// So, we'll have to render the component into a temporary root element
const tempElement = document.createElement("div");
const tempRoot = createRoot(tempElement);
tempRoot.render(EventTile);
eventTileMarkup = tempElement.innerHTML;
tempRoot.unmount();
const tempRoot = document.createElement("div");
ReactDOM.render(EventTile, tempRoot);
eventTileMarkup = tempRoot.innerHTML;
} else {
eventTileMarkup = renderToStaticMarkup(EventTile);
}
+3 -1
View File
@@ -6,6 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import type MatrixChat from "../components/structures/MatrixChat";
import Views from "../Views";
export function isLoggedIn(): boolean {
@@ -13,5 +14,6 @@ export function isLoggedIn(): boolean {
// `element-web` and into this file? Better yet, we should probably create a
// store to hold this state.
// See also https://github.com/vector-im/element-web/issues/15034.
return window.matrixChat?.state.view === Views.LOGGED_IN;
const app = window.matrixChat;
return (app as MatrixChat)?.state.view === Views.LOGGED_IN;
}
+25 -7
View File
@@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import ReactDOM from "react-dom";
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
import { MatrixClient, MatrixEvent, RuleId } from "matrix-js-sdk/src/matrix";
import { TooltipProvider } from "@vector-im/compound-web";
@@ -15,7 +16,6 @@ import SettingsStore from "../settings/SettingsStore";
import { Pill, pillRoomNotifLen, pillRoomNotifPos, PillType } from "../components/views/elements/Pill";
import { parsePermalink } from "./permalinks/Permalinks";
import { PermalinkParts } from "./permalinks/PermalinkConstructor";
import { ReactRootManager } from "./react";
/**
* A node here is an A element with a href attribute tag.
@@ -48,7 +48,7 @@ const shouldBePillified = (node: Element, href: string, parts: PermalinkParts |
* to turn into pills.
* @param {MatrixEvent} mxEvent - the matrix event which the DOM nodes are
* part of representing.
* @param {ReactRootManager} pills - an accumulator of the DOM nodes which contain
* @param {Element[]} pills: an accumulator of the DOM nodes which contain
* React components which have been mounted as part of this.
* The initial caller should pass in an empty array to seed the accumulator.
*/
@@ -56,7 +56,7 @@ export function pillifyLinks(
matrixClient: MatrixClient,
nodes: ArrayLike<Element>,
mxEvent: MatrixEvent,
pills: ReactRootManager,
pills: Element[],
): void {
const room = matrixClient.getRoom(mxEvent.getRoomId()) ?? undefined;
const shouldShowPillAvatar = SettingsStore.getValue("Pill.shouldShowPillAvatar");
@@ -64,7 +64,7 @@ export function pillifyLinks(
while (node) {
let pillified = false;
if (node.tagName === "PRE" || node.tagName === "CODE" || pills.elements.includes(node)) {
if (node.tagName === "PRE" || node.tagName === "CODE" || pills.includes(node)) {
// Skip code blocks and existing pills
node = node.nextSibling as Element;
continue;
@@ -81,9 +81,9 @@ export function pillifyLinks(
</TooltipProvider>
);
pills.render(pill, pillContainer);
ReactDOM.render(pill, pillContainer);
node.parentNode?.replaceChild(pillContainer, node);
pills.push(pillContainer);
// Pills within pills aren't going to go well, so move on
pillified = true;
@@ -143,8 +143,9 @@ export function pillifyLinks(
</TooltipProvider>
);
pills.render(pill, pillContainer);
ReactDOM.render(pill, pillContainer);
roomNotifTextNode.parentNode?.replaceChild(pillContainer, roomNotifTextNode);
pills.push(pillContainer);
}
// Nothing else to do for a text node (and we don't need to advance
// the loop pointer because we did it above)
@@ -160,3 +161,20 @@ export function pillifyLinks(
node = node.nextSibling as Element;
}
}
/**
* Unmount all the pill containers from React created by pillifyLinks.
*
* It's critical to call this after pillifyLinks, otherwise
* Pills will leak, leaking entire DOM trees via the event
* emitter on BaseAvatar as per
* https://github.com/vector-im/element-web/issues/12417
*
* @param {Element[]} pills - array of pill containers whose React
* components should be unmounted.
*/
export function unmountPills(pills: Element[]): void {
for (const pillContainer of pills) {
ReactDOM.unmountComponentAtNode(pillContainer);
}
}
-33
View File
@@ -1,33 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import { ReactNode } from "react";
import { createRoot, Root } from "react-dom/client";
export class ReactRootManager {
private roots: Root[] = [];
private rootElements: Element[] = [];
public get elements(): Element[] {
return this.rootElements;
}
public render(children: ReactNode, element: Element): void {
const root = createRoot(element);
this.roots.push(root);
this.rootElements.push(element);
root.render(children);
}
public unmount(): void {
while (this.roots.length) {
const root = this.roots.pop()!;
this.rootElements.pop();
root.unmount();
}
}
}
+22 -11
View File
@@ -7,11 +7,11 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import ReactDOM from "react-dom";
import { TooltipProvider } from "@vector-im/compound-web";
import PlatformPeg from "../PlatformPeg";
import LinkWithTooltip from "../components/views/elements/LinkWithTooltip";
import { ReactRootManager } from "./react";
/**
* If the platform enabled needsUrlTooltips, recurses depth-first through a DOM tree, adding tooltip previews
@@ -19,16 +19,12 @@ import { ReactRootManager } from "./react";
*
* @param {Element[]} rootNodes - a list of sibling DOM nodes to traverse to try
* to add tooltips.
* @param {Element[]} ignoredNodes - a list of nodes to not recurse into.
* @param {ReactRootManager} tooltips - an accumulator of the DOM nodes which contain
* @param {Element[]} ignoredNodes: a list of nodes to not recurse into.
* @param {Element[]} containers: an accumulator of the DOM nodes which contain
* React components that have been mounted by this function. The initial caller
* should pass in an empty array to seed the accumulator.
*/
export function tooltipifyLinks(
rootNodes: ArrayLike<Element>,
ignoredNodes: Element[],
tooltips: ReactRootManager,
): void {
export function tooltipifyLinks(rootNodes: ArrayLike<Element>, ignoredNodes: Element[], containers: Element[]): void {
if (!PlatformPeg.get()?.needsUrlTooltips()) {
return;
}
@@ -36,7 +32,7 @@ export function tooltipifyLinks(
let node = rootNodes[0];
while (node) {
if (ignoredNodes.includes(node) || tooltips.elements.includes(node)) {
if (ignoredNodes.includes(node) || containers.includes(node)) {
node = node.nextSibling as Element;
continue;
}
@@ -64,11 +60,26 @@ export function tooltipifyLinks(
</TooltipProvider>
);
tooltips.render(tooltip, node);
ReactDOM.render(tooltip, node);
containers.push(node);
} else if (node.childNodes?.length) {
tooltipifyLinks(node.childNodes as NodeListOf<Element>, ignoredNodes, tooltips);
tooltipifyLinks(node.childNodes as NodeListOf<Element>, ignoredNodes, containers);
}
node = node.nextSibling as Element;
}
}
/**
* Unmount tooltip containers created by tooltipifyLinks.
*
* It's critical to call this after tooltipifyLinks, otherwise
* tooltips will leak.
*
* @param {Element[]} containers - array of tooltip containers to unmount
*/
export function unmountTooltips(containers: Element[]): void {
for (const container of containers) {
ReactDOM.unmountComponentAtNode(container);
}
}
+11 -9
View File
@@ -8,8 +8,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import { createRoot } from "react-dom/client";
import React from "react";
import * as ReactDOM from "react-dom";
import * as React from "react";
import { logger } from "matrix-js-sdk/src/logger";
import * as languageHandler from "../languageHandler";
@@ -96,9 +96,7 @@ export async function loadApp(fragParams: {}): Promise<void> {
function setWindowMatrixChat(matrixChat: MatrixChat): void {
window.matrixChat = matrixChat;
}
const app = await module.loadApp(fragParams, setWindowMatrixChat);
const root = createRoot(document.getElementById("matrixchat")!);
root.render(app);
ReactDOM.render(await module.loadApp(fragParams, setWindowMatrixChat), document.getElementById("matrixchat"));
}
export async function showError(title: string, messages?: string[]): Promise<void> {
@@ -106,8 +104,10 @@ export async function showError(title: string, messages?: string[]): Promise<voi
/* webpackChunkName: "error-view" */
"../async-components/structures/ErrorView"
);
const root = createRoot(document.getElementById("matrixchat")!);
root.render(<ErrorView title={title} messages={messages} />);
window.matrixChat = ReactDOM.render(
<ErrorView title={title} messages={messages} />,
document.getElementById("matrixchat"),
);
}
export async function showIncompatibleBrowser(onAccept: () => void): Promise<void> {
@@ -115,8 +115,10 @@ export async function showIncompatibleBrowser(onAccept: () => void): Promise<voi
/* webpackChunkName: "error-view" */
"../async-components/structures/ErrorView"
);
const root = createRoot(document.getElementById("matrixchat")!);
root.render(<UnsupportedBrowserView onAccept={onAccept} />);
window.matrixChat = ReactDOM.render(
<UnsupportedBrowserView onAccept={onAccept} />,
document.getElementById("matrixchat"),
);
}
export async function loadModules(): Promise<void> {
+2 -1
View File
@@ -11,6 +11,7 @@ Please see LICENSE files in the repository root for full details.
import { logger } from "matrix-js-sdk/src/logger";
import { QueryDict } from "matrix-js-sdk/src/utils";
import MatrixChatType from "../components/structures/MatrixChat";
import { parseQsFromFragment } from "./url_utils";
let lastLocationHashSet: string | null = null;
@@ -30,7 +31,7 @@ function routeUrl(location: Location): void {
logger.log("Routing URL ", location.href);
const s = getScreenFromLocation(location);
window.matrixChat.showScreen(s.screen, s.params);
(window.matrixChat as MatrixChatType).showScreen(s.screen, s.params);
}
function onHashChange(): void {
+1
View File
@@ -27,6 +27,7 @@ const wrapWithTooltipProvider = (Wrapper: RenderOptions["wrapper"]) => {
const customRender = (ui: ReactElement, options: RenderOptions = {}) => {
return render(ui, {
legacyRoot: true,
...options,
wrapper: wrapWithTooltipProvider(options?.wrapper) as RenderOptions["wrapper"],
}) as ReturnType<typeof render>;
+8 -9
View File
@@ -15,7 +15,6 @@ 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";
@@ -85,24 +84,24 @@ describe("pillify", () => {
it("should do nothing for empty element", () => {
const { container } = render(<div />);
const originalHtml = container.outerHTML;
const containers = new ReactRootManager();
const containers: Element[] = [];
pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
expect(containers.elements).toHaveLength(0);
expect(containers).toHaveLength(0);
expect(container.outerHTML).toEqual(originalHtml);
});
it("should pillify @room", () => {
const { container } = render(<div>@room</div>);
const containers = new ReactRootManager();
const containers: Element[] = [];
pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
expect(containers.elements).toHaveLength(1);
expect(containers).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();
const containers: Element[] = [];
pillifyLinks(
MatrixClientPeg.safeGet(),
[container],
@@ -118,18 +117,18 @@ describe("pillify", () => {
}),
containers,
);
expect(containers.elements).toHaveLength(1);
expect(containers).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();
const containers: Element[] = [];
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(containers).toHaveLength(1);
expect(container.querySelector(".mx_Pill.mx_AtRoomPill")?.textContent).toBe("!@room");
});
});
+8 -9
View File
@@ -12,7 +12,6 @@ import { act, render } from "jest-matrix-react";
import { tooltipifyLinks } from "../../../src/utils/tooltipify";
import PlatformPeg from "../../../src/PlatformPeg";
import BasePlatform from "../../../src/BasePlatform";
import { ReactRootManager } from "../../../src/utils/react.tsx";
describe("tooltipify", () => {
jest.spyOn(PlatformPeg, "get").mockReturnValue({ needsUrlTooltips: () => true } as unknown as BasePlatform);
@@ -20,9 +19,9 @@ describe("tooltipify", () => {
it("does nothing for empty element", () => {
const { container: root } = render(<div />);
const originalHtml = root.outerHTML;
const containers = new ReactRootManager();
const containers: Element[] = [];
tooltipifyLinks([root], [], containers);
expect(containers.elements).toHaveLength(0);
expect(containers).toHaveLength(0);
expect(root.outerHTML).toEqual(originalHtml);
});
@@ -32,9 +31,9 @@ describe("tooltipify", () => {
<a href="/foo">click</a>
</div>,
);
const containers = new ReactRootManager();
const containers: Element[] = [];
tooltipifyLinks([root], [], containers);
expect(containers.elements).toHaveLength(1);
expect(containers).toHaveLength(1);
const anchor = root.querySelector("a");
expect(anchor?.getAttribute("href")).toEqual("/foo");
const tooltip = anchor!.querySelector(".mx_TextWithTooltip_target");
@@ -48,9 +47,9 @@ describe("tooltipify", () => {
</div>,
);
const originalHtml = root.outerHTML;
const containers = new ReactRootManager();
const containers: Element[] = [];
tooltipifyLinks([root], [root.children[0]], containers);
expect(containers.elements).toHaveLength(0);
expect(containers).toHaveLength(0);
expect(root.outerHTML).toEqual(originalHtml);
});
@@ -60,12 +59,12 @@ describe("tooltipify", () => {
<a href="/foo">click</a>
</div>,
);
const containers = new ReactRootManager();
const containers: Element[] = [];
tooltipifyLinks([root], [], containers);
tooltipifyLinks([root], [], containers);
tooltipifyLinks([root], [], containers);
tooltipifyLinks([root], [], containers);
expect(containers.elements).toHaveLength(1);
expect(containers).toHaveLength(1);
const anchor = root.querySelector("a");
expect(anchor?.getAttribute("href")).toEqual("/foo");
const tooltip = anchor!.querySelector(".mx_TextWithTooltip_target");
+6 -15
View File
@@ -8430,10 +8430,10 @@ min-indent@^1.0.0:
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
mini-css-extract-plugin@2.9.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz#c73a1327ccf466f69026ac22a8e8fd707b78a235"
integrity sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==
mini-css-extract-plugin@2.9.1:
version "2.9.1"
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.1.tgz#4d184f12ce90582e983ccef0f6f9db637b4be758"
integrity sha512-+Vyi+GCCOHnrJ2VPS+6aPoXN2k2jgUzDRhTFLjjTBn23qyXJXkjUWQgTL+mXpF5/A8ixLdCc6kWsoeOjKGejKQ==
dependencies:
schema-utils "^4.0.0"
tapable "^2.2.1"
@@ -9707,16 +9707,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@8.4.38:
version "8.4.38"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e"
integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==
dependencies:
nanoid "^3.3.7"
picocolors "^1.0.0"
source-map-js "^1.2.0"
postcss@^8.3.11, postcss@^8.4.33, postcss@^8.4.38, postcss@^8.4.47:
postcss@8.4.47, postcss@^8.3.11, postcss@^8.4.33, postcss@^8.4.38, postcss@^8.4.47:
version "8.4.47"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365"
integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==
@@ -10710,7 +10701,7 @@ sockjs@^0.3.24:
uuid "^8.3.2"
websocket-driver "^0.7.4"
source-map-js@^1.0.1, source-map-js@^1.2.0, source-map-js@^1.2.1:
source-map-js@^1.0.1, source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==