Expose SDKContext on the window & avoid using singleton store .instance (#34098)

* Expose SDKContextClass via window for debugging

* Remove stores from window if they are exposed via sdkContext

* Avoid usages of global store instance where React context is accessible

* Fix tests by adding SDKContext.Provider

* Fix tests by adding SDKContext.Provider

* Fix tests by adding SDKContext.Provider

* Fix tests by adding SDKContext.Provider

* Iterate

* Discard changes to apps/web/src/components/views/elements/AppTile.tsx
This commit is contained in:
Michael Telatynski
2026-07-10 09:51:38 +00:00
committed by GitHub
parent 15c85ec50f
commit aeea53dfde
36 changed files with 613 additions and 232 deletions
@@ -24,7 +24,6 @@ import { clientAndSDKContextRenderOptions, stubClient } from "../../../../test-u
import { Action } from "../../../../../src/dispatcher/actions";
import dis from "../../../../../src/dispatcher/dispatcher";
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
import SettingsStore from "../../../../../src/settings/SettingsStore";
import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases";
import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelStore";
@@ -194,13 +193,11 @@ describe("AppTile", () => {
);
renderResult.rerender(
<MatrixClientContext.Provider value={cli}>
<RightPanel
room={r2}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r2, r2.roomId)}
/>
</MatrixClientContext.Provider>,
<RightPanel
room={r2}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r2, r2.roomId)}
/>,
);
expect(renderResult.queryByText("Example 1")).not.toBeInTheDocument();
@@ -278,13 +275,11 @@ describe("AppTile", () => {
}),
);
renderResult.rerender(
<MatrixClientContext.Provider value={cli}>
<RightPanel
room={r2}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r2, r2.roomId)}
/>
</MatrixClientContext.Provider>,
<RightPanel
room={r2}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r2, r2.roomId)}
/>,
);
await waitFor(() => {
@@ -283,6 +283,14 @@ exports[`<ExtensionsCard /> should render widgets 1`] = `
</svg>
</div>
</div>
<a
class="_link_k9ljz_8"
data-kind="primary"
data-size="md"
rel="noreferrer noopener"
>
Set layout for everyone
</a>
</div>
</div>
</DocumentFragment>
@@ -20,6 +20,7 @@ import { CommandPartCreator } from "../../../../../src/editor/parts";
import DocumentOffset from "../../../../../src/editor/offset";
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass";
import SettingsStore from "../../../../../src/settings/SettingsStore";
import { SDKContext } from "../../../../../src/contexts/SDKContext.ts";
describe("BasicMessageComposer", () => {
const renderer = createRenderer();
@@ -35,7 +36,11 @@ describe("BasicMessageComposer", () => {
it("should allow a user to paste a URL without it being mangled", async () => {
const model = new EditorModel([], pc, renderer);
render(<BasicMessageComposer model={model} room={room} />);
render(<BasicMessageComposer model={model} room={room} />, {
wrapper: ({ children }) => (
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
),
});
const testUrl = "https://element.io";
const mockDataTransfer = generateMockDataTransferForString(testUrl);
await userEvent.paste(mockDataTransfer);
@@ -51,7 +56,11 @@ describe("BasicMessageComposer", () => {
});
userEvent.setup();
const model = new EditorModel([], pc, renderer);
render(<BasicMessageComposer model={model} room={room} />);
render(<BasicMessageComposer model={model} room={room} />, {
wrapper: ({ children }) => (
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
),
});
const tranformations = [
{ before: "4:3 video", after: "4:3 video" },
@@ -81,7 +90,11 @@ describe("BasicMessageComposer", () => {
it("should not mangle shift-enter when the autocomplete is open", async () => {
const model = new EditorModel([], pc, renderer);
render(<BasicMessageComposer model={model} room={room} />);
render(<BasicMessageComposer model={model} room={room} />, {
wrapper: ({ children }) => (
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
),
});
const input = screen.getByRole("textbox");
@@ -93,7 +106,11 @@ describe("BasicMessageComposer", () => {
it("should escape single quote in placeholder", async () => {
const model = new EditorModel([], pc, renderer);
const composer = render(<BasicMessageComposer placeholder="Don't" model={model} room={room} />);
const composer = render(<BasicMessageComposer placeholder="Don't" model={model} room={room} />, {
wrapper: ({ children }) => (
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
),
});
const input = composer.queryAllByRole("textbox");
const placeholder = input[0].style.getPropertyValue("--placeholder");
expect(placeholder).toMatch("'Don\\'t'");
@@ -101,7 +118,11 @@ describe("BasicMessageComposer", () => {
it("should escape backslash in placeholder", async () => {
const model = new EditorModel([], pc, renderer);
const composer = render(<BasicMessageComposer placeholder={"w\\e"} model={model} room={room} />);
const composer = render(<BasicMessageComposer placeholder={"w\\e"} model={model} room={room} />, {
wrapper: ({ children }) => (
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
),
});
const input = composer.queryAllByRole("textbox");
const placeholder = input[0].style.getPropertyValue("--placeholder");
expect(placeholder).toMatch("'w\\\\e'");
@@ -116,7 +137,11 @@ describe("BasicMessageComposer", () => {
// spy on typingStore.setSelfTyping
const spy = jest.spyOn(SDKContextClass.instance.typingStore, "setSelfTyping");
render(<BasicMessageComposer model={model} room={room} />);
render(<BasicMessageComposer model={model} room={room} />, {
wrapper: ({ children }) => (
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
),
});
// simulate typing by updating the model - this will call the component's update callback
await model.update(commandPart.text, "insertText", new DocumentOffset(commandPart.text.length, true));
@@ -128,7 +153,11 @@ describe("BasicMessageComposer", () => {
it("should ignore keydown events during IME composition", () => {
const model = new EditorModel([], pc, renderer);
render(<BasicMessageComposer model={model} room={room} />);
render(<BasicMessageComposer model={model} room={room} />, {
wrapper: ({ children }) => (
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
),
});
const input = screen.getByRole("textbox");
// Start IME composition
@@ -167,7 +196,11 @@ describe("BasicMessageComposer", () => {
it("should handle keydown events normally when not composing", () => {
const model = new EditorModel([], pc, renderer);
render(<BasicMessageComposer model={model} room={room} />);
render(<BasicMessageComposer model={model} room={room} />, {
wrapper: ({ children }) => (
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
),
});
const input = screen.getByRole("textbox");
// Simulate Tab key when NOT composing
@@ -33,6 +33,8 @@ import NotifProvider from "../../../../../src/autocomplete/NotifProvider";
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
import type { RoomContextType } from "../../../../../src/contexts/RoomContext.ts";
import { SDKContext } from "../../../../../src/contexts/SDKContext.ts";
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass.ts";
describe("<EditMessageComposer/>", () => {
const userId = "@alice:server.org";
@@ -78,9 +80,11 @@ describe("<EditMessageComposer/>", () => {
const getComponent = (editState: EditorStateTransfer, roomContext: RoomContextType = defaultRoomContext) =>
render(<EditMessageComposerWithMatrixClient editState={editState} />, {
wrapper: ({ children }) => (
<MatrixClientContext.Provider value={mockClient}>
<ScopedRoomContextProvider {...roomContext}>{children}</ScopedRoomContextProvider>
</MatrixClientContext.Provider>
<SDKContext.Provider value={SDKContextClass.instance}>
<MatrixClientContext.Provider value={mockClient}>
<ScopedRoomContextProvider {...roomContext}>{children}</ScopedRoomContextProvider>
</MatrixClientContext.Provider>
</SDKContext.Provider>
),
});
@@ -51,6 +51,8 @@ import SettingsStore from "../../../../../src/settings/SettingsStore";
import EditorStateTransfer from "../../../../../src/utils/EditorStateTransfer";
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
import PlatformPeg from "../../../../../src/PlatformPeg";
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass.ts";
import { SDKContext } from "../../../../../src/contexts/SDKContext.ts";
function getTile(container: HTMLElement): HTMLElement {
const tile = container.querySelector(".mx_EventTile");
@@ -1652,7 +1654,11 @@ describe("EventTile", () => {
return Element.prototype.matches.call(this, selector);
});
const { container, rerender } = render(<WrappedEventTiles events={events} editEvent={firstEvent} />);
const { container, rerender } = render(<WrappedEventTiles events={events} editEvent={firstEvent} />, {
wrapper: ({ children }) => (
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
),
});
const editingTile = container.querySelector(".mx_EventTile_isEditing");
expect(editingTile).not.toBeNull();
@@ -37,6 +37,8 @@ import { Action } from "../../../../../src/dispatcher/actions";
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
import { TimelineRenderingType, type RoomContextType } from "../../../../../src/contexts/RoomContext.ts";
import { RoomUploadContextProvider } from "../../../../../src/viewmodels/room/RoomUploadViewModel.tsx";
import { SDKContext } from "../../../../../src/contexts/SDKContext.ts";
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass.ts";
const openStickerPicker = async (): Promise<void> => {
await userEvent.click(screen.getByLabelText("More options"));
@@ -469,13 +471,15 @@ function wrapAndRender(
};
const getRawComponent = (props = {}, context = roomContext, client = mockClient) => (
<MatrixClientContext.Provider value={client}>
<ScopedRoomContextProvider {...context}>
<RoomUploadContextProvider>
<MessageComposer {...defaultProps} {...props} />
</RoomUploadContextProvider>
</ScopedRoomContextProvider>
</MatrixClientContext.Provider>
<SDKContext.Provider value={SDKContextClass.instance}>
<MatrixClientContext.Provider value={client}>
<ScopedRoomContextProvider {...context}>
<RoomUploadContextProvider>
<MessageComposer {...defaultProps} {...props} />
</RoomUploadContextProvider>
</ScopedRoomContextProvider>
</MatrixClientContext.Provider>
</SDKContext.Provider>
);
return {
rawComponent: getRawComponent(props, roomContext, mockClient),
@@ -59,6 +59,7 @@ import WidgetStore, { type IApp } from "../../../../../../src/stores/WidgetStore
import { UIFeature } from "../../../../../../src/settings/UIFeature";
import { SettingLevel } from "../../../../../../src/settings/SettingLevel";
import { ElementCallMemberEventType } from "../../../../../../src/call-types";
import { SDKContext } from "../../../../../../src/contexts/SDKContext";
import { SDKContextClass } from "../../../../../../src/contexts/SDKContextClass.ts";
jest.mock("../../../../../../src/utils/ShieldUtils");
@@ -93,9 +94,11 @@ describe("RoomHeader", () => {
function getWrapper(): RenderOptions {
return {
wrapper: ({ children }) => (
<MatrixClientContext.Provider value={client}>
<ScopedRoomContextProvider {...roomContext}>{children}</ScopedRoomContextProvider>
</MatrixClientContext.Provider>
<SDKContext.Provider value={SDKContextClass.instance}>
<MatrixClientContext.Provider value={client}>
<ScopedRoomContextProvider {...roomContext}>{children}</ScopedRoomContextProvider>
</MatrixClientContext.Provider>
</SDKContext.Provider>
),
};
}
@@ -10,9 +10,10 @@ import React from "react";
import { MatrixEvent, Room, EventType } from "matrix-js-sdk/src/matrix";
import { render, type RenderResult } from "jest-matrix-react";
import { stubClient, withClientContextRenderOptions } from "../../../../test-utils";
import { clientAndSDKContextRenderOptions, stubClient } from "../../../../test-utils";
import SearchResultTile from "../../../../../src/components/views/rooms/SearchResultTile";
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass.ts";
const ROOM_ID = "!qPewotXpIctQySfjSy:localhost";
@@ -30,7 +31,7 @@ describe("SearchResultTile", () => {
function renderComponent(props: Partial<Props>): RenderResult {
return render(
<SearchResultTile timeline={[]} ourEventsIndexes={[1]} {...props} />,
withClientContextRenderOptions(MatrixClientPeg.safeGet()),
clientAndSDKContextRenderOptions(MatrixClientPeg.safeGet(), SDKContextClass.instance),
);
}
@@ -35,6 +35,7 @@ import { addTextToComposer } from "../../../../test-utils/composer";
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass";
import { RoomUploadContextProvider } from "../../../../../src/viewmodels/room/RoomUploadViewModel.tsx";
import { SDKContext } from "../../../../../src/contexts/SDKContext.ts";
jest.mock("../../../../../src/utils/local-room", () => ({
doMaybeLocalRoomAction: jest.fn(),
@@ -199,7 +200,11 @@ describe("<SendMessageComposer/>", () => {
</MatrixClientContext.Provider>
);
const getComponent = (props = {}, roomContext = defaultRoomContext, client = mockClient) => {
return render(getRawComponent(props, roomContext, client));
return render(getRawComponent(props, roomContext, client), {
wrapper: ({ children }) => (
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
),
});
};
it("renders text and placeholder correctly", () => {
@@ -447,6 +452,11 @@ describe("<SendMessageComposer/>", () => {
</RoomUploadContextProvider>
</ScopedRoomContextProvider>
</MatrixClientContext.Provider>,
{
wrapper: ({ children }) => (
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
),
},
);
const composer = container.querySelector<HTMLDivElement>(".mx_BasicMessageComposer_input")!;
@@ -14,12 +14,17 @@ import { mocked } from "jest-mock";
import QuickSettingsButton from "../../../../../src/components/views/spaces/QuickSettingsButton";
import SettingsStore from "../../../../../src/settings/SettingsStore";
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass";
import { SDKContext } from "../../../../../src/contexts/SDKContext.ts";
describe("QuickSettingsButton", () => {
const roomId = "!room:example.com";
const renderQuickSettingsButton = () => {
render(<QuickSettingsButton isPanelCollapsed={true} />);
render(<QuickSettingsButton isPanelCollapsed={true} />, {
wrapper: ({ children }) => (
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
),
});
};
const getQuickSettingsButton = () => {
@@ -37,7 +42,11 @@ describe("QuickSettingsButton", () => {
});
it("should render the quick settings button in expanded mode", () => {
const { asFragment } = render(<QuickSettingsButton isPanelCollapsed={false} />);
const { asFragment } = render(<QuickSettingsButton isPanelCollapsed={false} />, {
wrapper: ({ children }) => (
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
),
});
expect(asFragment()).toMatchSnapshot();
});