diff --git a/apps/web/src/Modal.test.tsx b/apps/web/src/Modal.test.tsx new file mode 100644 index 0000000000..ccc414276e --- /dev/null +++ b/apps/web/src/Modal.test.tsx @@ -0,0 +1,44 @@ +/* +Copyright 2026 Element Creations Ltd. + +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. +*/ + +// @vitest-environment happy-dom + +import React from "react"; +import { describe, it, expect, beforeEach, afterEach } from "vitest"; +import { LinkedText } from "@element-hq/web-shared-components"; +import { screen } from "test-utils-rtl"; +import { flushPromises, stubClient } from "test-utils"; +import type { I18nApi } from "@element-hq/web-shared-components"; + +import Modal from "./Modal"; +import { type ModuleApi } from "./modules/Api"; + +function LinkedTextDialog({ onFinished }: { onFinished(): void }): React.JSX.Element { + return Check out https://matrix.org for more info; +} + +describe("Modal", () => { + let originalMxModuleApi: ModuleApi; + + beforeEach(() => { + stubClient(); + // Required for the i18n context around the dialog to work. + originalMxModuleApi = window.mxModuleApi; + window.mxModuleApi = { i18n: {} as I18nApi } as ModuleApi; + }); + + afterEach(() => { + Modal.forceCloseAllModals(); + window.mxModuleApi = originalMxModuleApi; + }); + + it("provides LinkedTextContext to dialogs rendered in the separate dialog root", async () => { + Modal.createDialog(LinkedTextDialog); + await flushPromises(); + expect(screen.getByRole("link")).toBeInTheDocument(); + }); +}); diff --git a/apps/web/src/Modal.tsx b/apps/web/src/Modal.tsx index 8ba4313576..79cf22859d 100644 --- a/apps/web/src/Modal.tsx +++ b/apps/web/src/Modal.tsx @@ -12,7 +12,7 @@ import { createRoot, type Root } from "react-dom/client"; import classNames from "classnames"; import { TypedEventEmitter } from "matrix-js-sdk/src/matrix"; import { Glass, TooltipProvider } from "@vector-im/compound-web"; -import { I18nContext } from "@element-hq/web-shared-components"; +import { I18nContext, LinkedTextContext } from "@element-hq/web-shared-components"; import defaultDispatcher from "./dispatcher/dispatcher"; import AsyncWrapper from "./AsyncWrapper"; @@ -20,6 +20,7 @@ import { type Defaultize } from "./@types/common"; import { type ActionPayload } from "./dispatcher/payloads"; import { filterBoolean } from "./utils/arrays.ts"; import { SDKContext } from "./contexts/SDKContext.ts"; +import { LinkedTextConfiguration } from "./Linkify"; const DIALOG_CONTAINER_ID = "mx_Dialog_Container"; const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer"; @@ -439,22 +440,24 @@ export class ModalManager extends TypedEventEmitter - {/* Provide I18nContext for shared-components used inside dialogs rendered in a separate root. */} + {/* Provide I18nContext and LinkedTextContext for shared-components used inside dialogs rendered in a separate root. */} - -
- -
{this.staticModal.elem}
-
- {/* We break the rule here as this is a mouse-only interaction */} - {/* oxlint-disable-next-line jsx-a11y/click-events-have-key-events */} -
-
- + + +
+ +
{this.staticModal.elem}
+
+ {/* We break the rule here as this is a mouse-only interaction */} + {/* oxlint-disable-next-line jsx-a11y/click-events-have-key-events */} +
+
+ + @@ -475,22 +478,24 @@ export class ModalManager extends TypedEventEmitter - {/* Provide I18nContext for shared-components used inside dialogs rendered in a separate root. */} + {/* Provide I18nContext and LinkedTextContext for shared-components used inside dialogs rendered in a separate root. */} - -
- -
{modal.elem}
-
- {/* We break the rule here as this is a mouse-only interaction */} - {/* oxlint-disable-next-line jsx-a11y/click-events-have-key-events */} -
-
- + + +
+ +
{modal.elem}
+
+ {/* We break the rule here as this is a mouse-only interaction */} + {/* oxlint-disable-next-line jsx-a11y/click-events-have-key-events */} +
+
+ +