Remove more usages of singleton store getter in favour of contexts (#34099)

* 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

* Remove more usages of singleton store getter in favour of contexts

* Remove more usages of singleton store getter in favour of contexts

* Fix tests by adding SDKContext.Provider

* Fix tests by adding SDKContext.Provider

* Fix tests by adding SDKContext.Provider

* Fix tests by adding SDKContext.Provider

* Fix tests

* Fix tests

* Fix tests

* Iterate

* Fix bad merge

* Iterate

* Fix tests

* Iterate

* Iterate

* Iterate

* Iterate

* Iterate

* Improve coverage

* Improve coverage
This commit is contained in:
Michael Telatynski
2026-07-14 12:58:34 +00:00
committed by GitHub
parent 9575b236c0
commit b5fd249e38
52 changed files with 666 additions and 331 deletions
@@ -12,8 +12,9 @@ import { screen, render, waitFor } from "jest-matrix-react";
import { mocked } from "jest-mock";
import FilePanel from "../../../../src/components/structures/FilePanel";
import { mkEvent, stubClient } from "../../../test-utils";
import { clientAndSDKContextRenderOptions, mkEvent, stubClient } from "../../../test-utils";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import { SDKContextClass } from "../../../../src/contexts/SDKContextClass.ts";
jest.mock("matrix-js-sdk/src/matrix", () => ({
...jest.requireActual("matrix-js-sdk/src/matrix"),
@@ -38,7 +39,10 @@ describe("FilePanel", () => {
room.getOrCreateFilteredTimelineSet = jest.fn().mockReturnValue(timelineSet);
mocked(cli.getRoom).mockReturnValue(room);
const { asFragment } = render(<FilePanel roomId={room.roomId} onClose={jest.fn()} />);
const { asFragment } = render(
<FilePanel roomId={room.roomId} onClose={jest.fn()} />,
clientAndSDKContextRenderOptions(cli, SDKContextClass.instance),
);
await waitFor(() => {
expect(screen.getByText("No files visible in this room")).toBeInTheDocument();
});
@@ -65,6 +69,7 @@ describe("FilePanel", () => {
filePanel = ref;
}}
/>,
clientAndSDKContextRenderOptions(cli, SDKContextClass.instance),
);
await screen.findByText("No files visible in this room");
@@ -46,6 +46,7 @@ import Modal from "../../../../src/Modal";
import { SETTINGS } from "../../../../src/settings/Settings";
import ToastStore from "../../../../src/stores/ToastStore";
import { ModuleApi } from "../../../../src/modules/Api";
import { fireEvent } from "@testing-library/dom";
describe("<LoggedInView />", () => {
const userId = "@alice:domain.org";
@@ -67,6 +68,8 @@ describe("<LoggedInView />", () => {
on: jest.fn(),
},
getAuthMetadata: jest.fn().mockRejectedValue(new Error("Legacy auth")),
hasLazyLoadMembersEnabled: jest.fn(),
isInitialSyncComplete: jest.fn(),
});
const mediaHandler = new MediaHandler(mockClient);
const mockSdkContext = new TestSDKContext();
@@ -555,4 +558,11 @@ describe("<LoggedInView />", () => {
expect(container.querySelector(".mx_SpacePanel")).toBeInTheDocument();
});
});
it("should handle KeyBindingAction.ToggleRoomSidePanel", async () => {
getComponent({ page_type: "room_view" });
jest.spyOn(mockSdkContext.rightPanelStore, "togglePanel");
fireEvent.keyDown(document.body, { key: ".", code: "Period", ctrlKey: true, keyCode: 190 });
expect(mockSdkContext.rightPanelStore.togglePanel).toHaveBeenCalledWith(null);
});
});
@@ -21,6 +21,7 @@ import {
RoomMember,
RoomStateEvent,
SearchResult,
User,
} from "matrix-js-sdk/src/matrix";
import { type CryptoApi, CryptoEvent, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
import { KnownMembership } from "matrix-js-sdk/src/types";
@@ -1132,6 +1133,54 @@ describe("RoomView", () => {
});
});
it("should handle Action.ViewUser", async () => {
await mountRoomView();
jest.spyOn(stores.rightPanelStore, "setCards");
const member = new User("@user:server");
defaultDispatcher.dispatch(
{
action: Action.ViewUser,
member,
},
true,
);
expect(stores.rightPanelStore.setCards).toHaveBeenCalledWith([
{ phase: RightPanelPhases.RoomSummary },
{ phase: RightPanelPhases.MemberList },
{ phase: RightPanelPhases.MemberInfo, state: { member } },
]);
});
it("should handle Action.ViewUser with push", async () => {
await mountRoomView();
jest.spyOn(stores.rightPanelStore, "pushCard");
const member = new User("@user:server");
defaultDispatcher.dispatch(
{
action: Action.ViewUser,
member,
push: true,
},
true,
);
expect(stores.rightPanelStore.pushCard).toHaveBeenCalledWith({
phase: RightPanelPhases.MemberInfo,
state: { member },
});
});
it("should handle Action.View3pidInvite", async () => {
await mountRoomView();
jest.spyOn(stores.rightPanelStore, "showOrHidePhase");
defaultDispatcher.dispatch(
{
action: Action.View3pidInvite,
},
true,
);
expect(stores.rightPanelStore.showOrHidePhase).toHaveBeenCalledWith("MemberList");
});
describe("when there is a RoomView", () => {
const widget1Id = "widget1";
const widget2Id = "widget2";
@@ -10,7 +10,7 @@ import { mocked, type MockedObject } from "jest-mock";
import { type MatrixClient, MatrixEvent, Preset, Room } from "matrix-js-sdk/src/matrix";
import { render, cleanup, screen, fireEvent, waitFor, act } from "jest-matrix-react";
import { stubClient, mockPlatformPeg, unmockPlatformPeg, withClientContextRenderOptions } from "../../../test-utils";
import { stubClient, mockPlatformPeg, unmockPlatformPeg, clientAndSDKContextRenderOptions } from "../../../test-utils";
import { RightPanelPhases } from "../../../../src/stores/right-panel/RightPanelStorePhases";
import SpaceRoomView from "../../../../src/components/structures/SpaceRoomView.tsx";
import ResizeNotifier from "../../../../src/utils/ResizeNotifier.ts";
@@ -101,7 +101,7 @@ describe("SpaceRoomView", () => {
onRejectButtonClicked={jest.fn()}
justCreatedOpts={justCreatedOpts}
/>,
withClientContextRenderOptions(cli),
clientAndSDKContextRenderOptions(cli, SDKContextClass.instance),
);
return spaceRoomView;
};
@@ -24,10 +24,18 @@ import { _t } from "../../../../src/languageHandler";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
import ResizeNotifier from "../../../../src/utils/ResizeNotifier";
import { createTestClient, getRoomContext, mkRoom, mockPlatformPeg, stubClient } from "../../../test-utils";
import {
clientAndSDKContextRenderOptions,
createTestClient,
getRoomContext,
mkRoom,
mockPlatformPeg,
stubClient,
} from "../../../test-utils";
import { mkThread } from "../../../test-utils/threads";
import { ScopedRoomContextProvider } from "../../../../src/contexts/ScopedRoomContext.tsx";
import type { RoomContextType } from "../../../../src/contexts/RoomContext.ts";
import { SDKContextClass } from "../../../../src/contexts/SDKContextClass.ts";
jest.mock("../../../../src/utils/Feedback");
@@ -215,7 +223,10 @@ describe("ThreadPanel", () => {
myThreads!.addLiveEvent(mixedThread.rootEvent, { addToState: true });
myThreads!.addLiveEvent(ownThread.rootEvent, { addToState: true });
const renderResult = render(<TestThreadPanel />);
const renderResult = render(
<TestThreadPanel />,
clientAndSDKContextRenderOptions(createTestClient(), SDKContextClass.instance),
);
await waitFor(() => expect(renderResult.container.querySelector(".mx_AutoHideScrollbar")).toBeFalsy());
await waitFor(() => {
const events = findEvents(renderResult.container);
@@ -260,7 +271,10 @@ describe("ThreadPanel", () => {
const [allThreads] = room.threadsTimelineSets;
allThreads!.addLiveEvent(otherThread.rootEvent, { addToState: true });
const renderResult = render(<TestThreadPanel />);
const renderResult = render(
<TestThreadPanel />,
clientAndSDKContextRenderOptions(createTestClient(), SDKContextClass.instance),
);
await waitFor(() => expect(renderResult.container.querySelector(".mx_AutoHideScrollbar")).toBeFalsy());
await waitFor(() => {
const events = findEvents(renderResult.container);
@@ -10,9 +10,10 @@ import { type Mocked } from "jest-mock";
import { UserVerificationStatus, type CryptoApi } from "matrix-js-sdk/src/crypto-api";
import { renderHook, waitFor } from "jest-matrix-react";
import { createTestClient, withClientContextRenderOptions } from "../../../../../test-utils";
import { clientAndSDKContextRenderOptions, createTestClient } from "../../../../../test-utils";
import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
import { useUserInfoVerificationViewModel } from "../../../../../../src/components/viewmodels/right_panel/user_info/UserInfoHeaderVerificationViewModel";
import { TestSDKContext } from "../../../../TestSDKContext.ts";
describe("useUserInfoVerificationHeaderViewModel", () => {
const defaultRoomId = "!fkfk";
@@ -26,6 +27,7 @@ describe("useUserInfoVerificationHeaderViewModel", () => {
};
let mockClient: MatrixClient;
let mockCrypto: Mocked<CryptoApi>;
let sdkContext: TestSDKContext;
beforeEach(() => {
mockCrypto = {
@@ -43,6 +45,9 @@ describe("useUserInfoVerificationHeaderViewModel", () => {
} as unknown as Mocked<CryptoApi>;
mockClient = createTestClient();
sdkContext = new TestSDKContext();
sdkContext._client = mockClient;
jest.spyOn(mockClient, "doesServerSupportUnstableFeature").mockResolvedValue(true);
jest.spyOn(mockClient.secretStorage, "hasKey").mockResolvedValue(true);
jest.spyOn(mockClient, "getCrypto").mockReturnValue(mockCrypto);
@@ -57,7 +62,7 @@ describe("useUserInfoVerificationHeaderViewModel", () => {
const renderUserInfoHeaderVerificationHook = (props = defaultProps) => {
return renderHook(
() => useUserInfoVerificationViewModel(props.member, props.devices),
withClientContextRenderOptions(mockClient),
clientAndSDKContextRenderOptions(mockClient, sdkContext),
);
};
@@ -17,8 +17,9 @@ import ThreadListContextMenu, {
} from "../../../../../src/components/views/context_menus/ThreadListContextMenu";
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
import { stubClient } from "../../../../test-utils/test-utils";
import { stubClient, clientAndSDKContextRenderOptions } from "../../../../test-utils";
import { mkThread } from "../../../../test-utils/threads";
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass.ts";
describe("ThreadListContextMenu", () => {
const ROOM_ID = "!123:matrix.org";
@@ -28,7 +29,10 @@ describe("ThreadListContextMenu", () => {
let event: MatrixEvent;
function getComponent(props: Partial<ThreadListContextMenuProps>) {
return render(<ThreadListContextMenu mxEvent={event} {...props} />);
return render(
<ThreadListContextMenu mxEvent={event} {...props} />,
clientAndSDKContextRenderOptions(mockClient, SDKContextClass.instance),
);
}
beforeEach(() => {
@@ -19,8 +19,9 @@ import {
} from "matrix-js-sdk/src/crypto-api";
import { VerificationMethod } from "matrix-js-sdk/src/types";
import { stubClient } from "../../../../test-utils";
import { stubClient, withContexts } from "../../../../test-utils";
import VerificationRequestDialog from "../../../../../src/components/views/dialogs/VerificationRequestDialog";
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass";
describe("VerificationRequestDialog", () => {
function renderComponent(phase: VerificationPhase, method?: "emoji" | "qr"): ReturnType<typeof render> {
@@ -29,6 +30,7 @@ describe("VerificationRequestDialog", () => {
return render(
<VerificationRequestDialog onFinished={jest.fn()} member={member} verificationRequest={request} />,
withContexts({ sdkContext: SDKContextClass.instance }),
);
}
@@ -124,6 +126,7 @@ describe("VerificationRequestDialog", () => {
member={member}
verificationRequestPromise={requestPromise}
/>,
withContexts({ sdkContext: SDKContextClass.instance }),
);
// And wait for the component to mount, the promise to resolve and the component state to update
@@ -153,6 +156,7 @@ describe("VerificationRequestDialog", () => {
verificationRequest={request}
verificationRequestPromise={requestPromise}
/>,
withContexts({ sdkContext: SDKContextClass.instance }),
);
// And wait for the component to mount, the promise to resolve and the component state to update
@@ -173,7 +177,10 @@ describe("VerificationRequestDialog", () => {
const member = User.createUser("@alice:example.org", stubClient());
const request = createRequest(VerificationPhase.Unsent);
render(<VerificationRequestDialog onFinished={jest.fn()} member={member} verificationRequest={request} />);
render(
<VerificationRequestDialog onFinished={jest.fn()} member={member} verificationRequest={request} />,
withContexts({ sdkContext: SDKContextClass.instance }),
);
// When I cancel the request (which changes phase and emits a Changed event)
await act(async () => await request.cancel());
@@ -10,6 +10,8 @@ import { fireEvent, render, screen } from "jest-matrix-react";
import BaseCard from "../../../../../src/components/views/right_panel/BaseCard.tsx";
import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelStore.ts";
import { clientAndSDKContextRenderOptions } from "../../../../test-utils";
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass.ts";
jest.mock("../../../../../src/stores/right-panel/RightPanelStore", () => ({
instance: {
@@ -24,6 +26,7 @@ describe("<BaseCard />", () => {
<BaseCard header="Heading text">
<div>Content</div>
</BaseCard>,
clientAndSDKContextRenderOptions(SDKContextClass.instance.client!, SDKContextClass.instance),
);
expect(screen.getByRole("heading")).toHaveTextContent("Heading text");
@@ -14,27 +14,34 @@ import { MatrixWidgetType } from "matrix-widget-api";
import userEvent from "@testing-library/user-event";
import ExtensionsCard from "../../../../../src/components/views/right_panel/ExtensionsCard";
import { stubClient } from "../../../../test-utils";
import { clientAndSDKContextRenderOptions, stubClient } from "../../../../test-utils";
import { type IApp } from "../../../../../src/stores/WidgetStore";
import WidgetUtils, { useWidgets } from "../../../../../src/utils/WidgetUtils";
import { WidgetLayoutStore } from "../../../../../src/stores/widgets/WidgetLayoutStore";
import { IntegrationManagers } from "../../../../../src/integrations/IntegrationManagers";
import { TestSDKContext } from "../../../TestSDKContext.ts";
jest.mock("../../../../../src/utils/WidgetUtils");
describe("<ExtensionsCard />", () => {
let client: Mocked<MatrixClient>;
let room: Room;
let sdkContext: TestSDKContext;
beforeEach(() => {
client = mocked(stubClient());
sdkContext = new TestSDKContext();
sdkContext._client = client;
room = new Room("!room:server", client, client.getSafeUserId());
mocked(WidgetUtils.getWidgetName).mockImplementation((app) => app?.name ?? "No Name");
});
it("should render empty state", () => {
mocked(useWidgets).mockReturnValue([]);
const { asFragment } = render(<ExtensionsCard room={room} onClose={jest.fn()} />);
const { asFragment } = render(
<ExtensionsCard room={room} onClose={jest.fn()} />,
clientAndSDKContextRenderOptions(client, sdkContext),
);
expect(screen.getByText("Boost productivity with more tools, widgets and bots")).toBeInTheDocument();
expect(asFragment()).toMatchSnapshot();
});
@@ -61,7 +68,10 @@ describe("<ExtensionsCard />", () => {
},
] satisfies IApp[]);
const { asFragment } = render(<ExtensionsCard room={room} onClose={jest.fn()} />);
const { asFragment } = render(
<ExtensionsCard room={room} onClose={jest.fn()} />,
clientAndSDKContextRenderOptions(client, sdkContext),
);
expect(screen.getByText("Custom Widget")).toBeInTheDocument();
expect(screen.getByText("Jitsi")).toBeInTheDocument();
expect(asFragment()).toMatchSnapshot();
@@ -81,7 +91,10 @@ describe("<ExtensionsCard />", () => {
},
] satisfies IApp[]);
const { container } = render(<ExtensionsCard room={room} onClose={jest.fn()} />);
const { container } = render(
<ExtensionsCard room={room} onClose={jest.fn()} />,
clientAndSDKContextRenderOptions(client, sdkContext),
);
await userEvent.click(container.querySelector(".mx_ExtensionsCard_app_options")!);
expect(document.querySelector(".mx_IconizedContextMenu")).toMatchSnapshot();
});
@@ -100,7 +113,10 @@ describe("<ExtensionsCard />", () => {
},
] satisfies IApp[]);
render(<ExtensionsCard room={room} onClose={jest.fn()} />);
render(
<ExtensionsCard room={room} onClose={jest.fn()} />,
clientAndSDKContextRenderOptions(client, sdkContext),
);
expect(screen.getByText("Set layout for everyone")).toBeInTheDocument();
});
@@ -118,7 +134,10 @@ describe("<ExtensionsCard />", () => {
},
] satisfies IApp[]);
render(<ExtensionsCard room={room} onClose={jest.fn()} />);
render(
<ExtensionsCard room={room} onClose={jest.fn()} />,
clientAndSDKContextRenderOptions(client, sdkContext),
);
expect(screen.getByText("Custom Widget").closest(".mx_ExtensionsCard_Button_pinned")).toBeInTheDocument();
});
@@ -137,15 +156,52 @@ describe("<ExtensionsCard />", () => {
},
] satisfies IApp[]);
render(<ExtensionsCard room={room} onClose={jest.fn()} />);
render(
<ExtensionsCard room={room} onClose={jest.fn()} />,
clientAndSDKContextRenderOptions(client, sdkContext),
);
expect(screen.getByLabelText("You can only pin up to 3 widgets")).toBeInTheDocument();
});
it("should should open integration manager on click", async () => {
jest.spyOn(IntegrationManagers.sharedInstance(), "hasManager").mockReturnValue(false);
const spy = jest.spyOn(IntegrationManagers.sharedInstance(), "openNoManagerDialog");
render(<ExtensionsCard room={room} onClose={jest.fn()} />);
render(
<ExtensionsCard room={room} onClose={jest.fn()} />,
clientAndSDKContextRenderOptions(client, sdkContext),
);
await userEvent.click(screen.getByText("Add extensions"));
expect(spy).toHaveBeenCalled();
});
it("should set room layout on click", async () => {
mocked(useWidgets).mockReturnValue([
{
id: "id",
roomId: room.roomId,
eventId: "$event1",
creatorUserId: client.getSafeUserId(),
type: MatrixWidgetType.Custom,
name: "Custom Widget",
url: "http://url1",
},
{
id: "jitsi",
roomId: room.roomId,
eventId: "$event2",
creatorUserId: client.getSafeUserId(),
type: MatrixWidgetType.JitsiMeet,
name: "Jitsi",
url: "http://jitsi",
},
] satisfies IApp[]);
jest.spyOn(sdkContext.widgetLayoutStore, "copyLayoutToRoom");
render(
<ExtensionsCard room={room} onClose={jest.fn()} />,
clientAndSDKContextRenderOptions(client, sdkContext),
);
await userEvent.click(screen.getByText("Set layout for everyone"));
expect(sdkContext.widgetLayoutStore.copyLayoutToRoom).toHaveBeenCalledWith(room);
});
});
@@ -27,13 +27,19 @@ import { PollEndEvent } from "matrix-js-sdk/src/extensible_events_v1/PollEndEven
import { sleep } from "matrix-js-sdk/src/utils";
import userEvent from "@testing-library/user-event";
import { stubClient, mkEvent, mkMessage, flushPromises } from "../../../../test-utils";
import {
stubClient,
mkEvent,
mkMessage,
flushPromises,
clientAndSDKContextRenderOptions,
} from "../../../../test-utils";
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
import { PinnedMessagesCard } from "../../../../../src/components/views/right_panel/PinnedMessagesCard";
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
import Modal from "../../../../../src/Modal";
import { UnpinAllDialog } from "../../../../../src/components/views/dialogs/UnpinAllDialog";
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass.ts";
describe("<PinnedMessagesCard />", () => {
let cli: MockedObject<MatrixClient>;
@@ -86,13 +92,12 @@ describe("<PinnedMessagesCard />", () => {
async function renderMessagePinList(room: Room): Promise<RenderResult> {
const renderResult = render(
<MatrixClientContext.Provider value={cli}>
<PinnedMessagesCard
room={room}
onClose={jest.fn()}
permalinkCreator={new RoomPermalinkCreator(room, room.roomId)}
/>
</MatrixClientContext.Provider>,
<PinnedMessagesCard
room={room}
onClose={jest.fn()}
permalinkCreator={new RoomPermalinkCreator(room, room.roomId)}
/>,
clientAndSDKContextRenderOptions(cli, SDKContextClass.instance),
);
// Wait a tick for state updates
await act(() => sleep(0));
@@ -170,13 +175,12 @@ describe("<PinnedMessagesCard />", () => {
it("should show spinner whilst loading", async () => {
const room = mkRoom([], [pin1]);
render(
<MatrixClientContext.Provider value={cli}>
<PinnedMessagesCard
room={room}
onClose={jest.fn()}
permalinkCreator={new RoomPermalinkCreator(room, room.roomId)}
/>
</MatrixClientContext.Provider>,
<PinnedMessagesCard
room={room}
onClose={jest.fn()}
permalinkCreator={new RoomPermalinkCreator(room, room.roomId)}
/>,
clientAndSDKContextRenderOptions(cli, SDKContextClass.instance),
);
await waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar"));
@@ -322,13 +326,12 @@ describe("<PinnedMessagesCard />", () => {
).mockReturnValue(false);
const { asFragment } = render(
<MatrixClientContext.Provider value={cli}>
<PinnedMessagesCard
room={room}
onClose={jest.fn()}
permalinkCreator={new RoomPermalinkCreator(room, room.roomId)}
/>
</MatrixClientContext.Provider>,
<PinnedMessagesCard
room={room}
onClose={jest.fn()}
permalinkCreator={new RoomPermalinkCreator(room, room.roomId)}
/>,
clientAndSDKContextRenderOptions(cli, SDKContextClass.instance),
);
// Wait a tick for state updates
@@ -23,6 +23,8 @@ import {
useRoomSummaryCardViewModel,
} from "../../../../../src/components/viewmodels/right_panel/RoomSummaryCardViewModel";
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
import { SDKContext } from "../../../../../src/contexts/SDKContext.ts";
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass.ts";
// Mock the viewmodel hooks
jest.mock("../../../../../src/components/viewmodels/right_panel/RoomSummaryCardViewModel", () => ({
@@ -45,9 +47,11 @@ describe("<RoomSummaryCard />", () => {
return render(<RoomSummaryCardView {...defaultProps} {...props} />, {
wrapper: ({ children }) => (
<MatrixClientContext.Provider value={mockClient}>
<LinkedTextContext.Provider value={{}}>{children}</LinkedTextContext.Provider>
</MatrixClientContext.Provider>
<SDKContext.Provider value={SDKContextClass.instance}>
<MatrixClientContext.Provider value={mockClient}>
<LinkedTextContext.Provider value={{}}>{children}</LinkedTextContext.Provider>
</MatrixClientContext.Provider>
</SDKContext.Provider>
),
});
};
@@ -32,12 +32,12 @@ import UserInfo, { disambiguateDevices } from "../../../../../src/components/vie
import { getPowerLevels } from "../../../../../src/components/viewmodels/right_panel/user_info/UserInfoBasicViewModel";
import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases";
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
import Modal from "../../../../../src/Modal";
import { clearAllModals, flushPromises } from "../../../../test-utils";
import { clearAllModals, clientAndSDKContextRenderOptions, flushPromises } from "../../../../test-utils";
import ErrorDialog from "../../../../../src/components/views/dialogs/ErrorDialog";
import { shouldShowComponent } from "../../../../../src/customisations/helpers/UIComponents";
import { UIComponent } from "../../../../../src/settings/UIFeature";
import { TestSDKContext } from "../../../TestSDKContext.ts";
jest.mock("../../../../../src/utils/direct-messages", () => ({
...jest.requireActual("../../../../../src/utils/direct-messages"),
@@ -78,6 +78,7 @@ const defaultUser = new User(defaultUserId);
let mockRoom: Mocked<Room>;
let mockClient: Mocked<MatrixClient>;
let mockCrypto: Mocked<CryptoApi>;
let sdkContext: TestSDKContext;
const origDate = global.Date.prototype.toLocaleString;
beforeEach(() => {
@@ -131,6 +132,8 @@ beforeEach(() => {
setPowerLevel: jest.fn(),
getCrypto: jest.fn().mockReturnValue(mockCrypto),
} as unknown as MatrixClient);
sdkContext = new TestSDKContext();
sdkContext._client = mockClient;
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
jest.spyOn(MatrixClientPeg, "safeGet").mockReturnValue(mockClient);
@@ -162,13 +165,10 @@ describe("<UserInfo />", () => {
};
const renderComponent = (props = {}) => {
const Wrapper = (wrapperProps = {}) => {
return <MatrixClientContext.Provider value={mockClient} {...wrapperProps} />;
};
return render(<UserInfo {...defaultProps} {...props} />, {
wrapper: Wrapper,
});
return render(
<UserInfo {...defaultProps} {...props} />,
clientAndSDKContextRenderOptions(mockClient, sdkContext),
);
};
beforeEach(() => {
@@ -307,11 +307,7 @@ describe("<UserInfo />", () => {
});
it("renders the message button", () => {
render(
<MatrixClientContext.Provider value={mockClient}>
<UserInfo {...defaultProps} />
</MatrixClientContext.Provider>,
);
render(<UserInfo {...defaultProps} />, clientAndSDKContextRenderOptions(mockClient, sdkContext));
screen.getByRole("button", { name: "Send message" });
});
@@ -322,11 +318,7 @@ describe("<UserInfo />", () => {
return component !== UIComponent.CreateRooms;
},
() => {
render(
<MatrixClientContext.Provider value={mockClient}>
<UserInfo {...defaultProps} />
</MatrixClientContext.Provider>,
);
render(<UserInfo {...defaultProps} />, clientAndSDKContextRenderOptions(mockClient, sdkContext));
expect(screen.queryByRole("button", { name: "Message" })).toBeNull();
},
@@ -13,9 +13,9 @@ import { render, waitFor, screen } from "jest-matrix-react";
import React from "react";
import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
import { UserInfoHeaderVerificationView } from "../../../../../../src/components/views/right_panel/user_info/UserInfoHeaderVerificationView";
import { createTestClient } from "../../../../../test-utils";
import { clientAndSDKContextRenderOptions, createTestClient } from "../../../../../test-utils";
import { TestSDKContext } from "../../../../TestSDKContext.ts";
describe("<UserInfoHeaderVerificationView />", () => {
const defaultRoomId = "!fkfk";
@@ -25,6 +25,7 @@ describe("<UserInfoHeaderVerificationView />", () => {
let mockClient: MatrixClient;
let mockCrypto: Mocked<CryptoApi>;
let sdkContext: TestSDKContext;
beforeEach(() => {
mockCrypto = mocked({
@@ -42,6 +43,8 @@ describe("<UserInfoHeaderVerificationView />", () => {
} as unknown as CryptoApi);
mockClient = createTestClient();
sdkContext = new TestSDKContext();
sdkContext._client = mockClient;
jest.spyOn(mockClient, "doesServerSupportUnstableFeature").mockResolvedValue(true);
jest.spyOn(mockClient.secretStorage, "hasKey").mockResolvedValue(true);
jest.spyOn(mockClient, "getCrypto").mockReturnValue(mockCrypto);
@@ -62,13 +65,11 @@ describe("<UserInfoHeaderVerificationView />", () => {
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
jest.spyOn(mockClient, "doesServerSupportUnstableFeature").mockResolvedValue(true);
const Wrapper = (wrapperProps = {}) => {
return <MatrixClientContext.Provider value={mockClient} {...wrapperProps} />;
};
return render(<UserInfoHeaderVerificationView member={defaultMember} devices={[device1]} />, {
wrapper: Wrapper,
});
return render(
<UserInfoHeaderVerificationView member={defaultMember} devices={[device1]} />,
clientAndSDKContextRenderOptions(mockClient, sdkContext),
);
};
it("renders verified badge when user is verified", async () => {
@@ -13,10 +13,10 @@ import { fireEvent, render, screen } from "jest-matrix-react";
import React from "react";
import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
import { UserInfoHeaderView } from "../../../../../../src/components/views/right_panel/user_info/UserInfoHeaderView";
import { createTestClient } from "../../../../../test-utils";
import { clientAndSDKContextRenderOptions, createTestClient } from "../../../../../test-utils";
import { useUserfoHeaderViewModel } from "../../../../../../src/components/viewmodels/right_panel/user_info/UserInfoHeaderViewModel";
import { TestSDKContext } from "../../../../TestSDKContext.ts";
// Mock the viewmodel hooks
jest.mock("../../../../../../src/components/viewmodels/right_panel/user_info/UserInfoHeaderViewModel", () => ({
@@ -45,6 +45,7 @@ describe("<UserInfoHeaderView />", () => {
let mockClient: MatrixClient;
let mockCrypto: Mocked<CryptoApi>;
let sdkContext: TestSDKContext;
beforeEach(() => {
mockCrypto = mocked({
@@ -63,6 +64,8 @@ describe("<UserInfoHeaderView />", () => {
mockClient = createTestClient();
mockClient.doesServerSupportExtendedProfiles = () => Promise.resolve(false);
sdkContext = new TestSDKContext();
sdkContext._client = mockClient;
jest.spyOn(mockClient, "doesServerSupportUnstableFeature").mockResolvedValue(true);
jest.spyOn(mockClient.secretStorage, "hasKey").mockResolvedValue(true);
@@ -90,10 +93,6 @@ describe("<UserInfoHeaderView />", () => {
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
const Wrapper = (wrapperProps = {}) => {
return <MatrixClientContext.Provider value={mockClient} {...wrapperProps} />;
};
return render(
<UserInfoHeaderView
{...defaultProps}
@@ -101,9 +100,7 @@ describe("<UserInfoHeaderView />", () => {
devices={[device1]}
hideVerificationSection={props.hideVerificationSection}
/>,
{
wrapper: Wrapper,
},
clientAndSDKContextRenderOptions(mockClient, sdkContext),
);
};
@@ -10,7 +10,9 @@ import React from "react";
import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
import RoomInfoLine from "../../../../../src/components/views/rooms/RoomInfoLine.tsx";
import { stubClient } from "../../../../test-utils";
import { stubClient, TestSDKContext, withContexts } from "../../../../test-utils";
import { fireEvent } from "@testing-library/dom";
import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases.ts";
describe("RoomInfoLine", () => {
it("renders for public room", () => {
@@ -33,4 +35,29 @@ describe("RoomInfoLine", () => {
expect(getByText("Public room")).toBeVisible();
expect(asFragment()).toMatchSnapshot();
});
it("should render members button which opens right panel", async () => {
const sdkContext = new TestSDKContext();
sdkContext._client = stubClient();
const room = new Room("!roomId", sdkContext.client!, sdkContext.client!.getUserId()!);
room.currentState.setStateEvents([
new MatrixEvent({
sender: sdkContext.client!.getUserId()!,
room_id: room.roomId,
state_key: "",
event_id: "$eventId",
type: "m.room.join_rules",
content: {
join_rule: "public",
},
}),
]);
jest.spyOn(room, "getJoinedMemberCount").mockReturnValue(50);
jest.spyOn(sdkContext.rightPanelStore, "setCard");
const { findByText } = render(<RoomInfoLine room={room} />, withContexts({ sdkContext }));
fireEvent.click(await findByText("50 members"));
expect(sdkContext.rightPanelStore.setCard).toHaveBeenCalledWith({ phase: RightPanelPhases.MemberList });
});
});
@@ -11,7 +11,12 @@ import { render, screen } from "jest-matrix-react";
import { EventType, type IEvent, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
import ThirdPartyMemberInfo from "../../../../../src/components/views/rooms/ThirdPartyMemberInfo";
import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../../test-utils";
import {
clientAndSDKContextRenderOptions,
getMockClientWithEventEmitter,
mockClientMethodsUser,
} from "../../../../test-utils";
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass.ts";
describe("<ThirdPartyMemberInfo />", () => {
const userId = "@alice:server.org";
@@ -37,7 +42,11 @@ describe("<ThirdPartyMemberInfo />", () => {
});
const defaultEvent = makeInviteEvent();
const getComponent = (event: MatrixEvent = defaultEvent) => render(<ThirdPartyMemberInfo event={event} />);
const getComponent = (event: MatrixEvent = defaultEvent) =>
render(
<ThirdPartyMemberInfo event={event} />,
clientAndSDKContextRenderOptions(mockClient, SDKContextClass.instance),
);
const room = new Room(roomId, mockClient, userId);
const aliceMember = new RoomMember(roomId, userId);
aliceMember.name = "Alice DisplayName";
@@ -14,26 +14,32 @@ import { type VerificationRequest, VerificationRequestEvent } from "matrix-js-sd
import VerificationRequestToast from "../../../../../src/components/views/toasts/VerificationRequestToast";
import {
clientAndSDKContextRenderOptions,
flushPromises,
getMockClientWithEventEmitter,
mockClientMethodsCrypto,
mockClientMethodsUser,
} from "../../../../test-utils";
import ToastStore from "../../../../../src/stores/ToastStore";
function renderComponent(
props: Partial<ComponentProps<typeof VerificationRequestToast>> & { request: VerificationRequest },
): RenderResult {
const propsWithDefaults = {
toastKey: "test",
...props,
};
return render(<VerificationRequestToast {...propsWithDefaults} />);
}
import { TestSDKContext } from "../../../TestSDKContext.ts";
describe("VerificationRequestToast", () => {
let client: Mocked<MatrixClient>;
let sdkContext: TestSDKContext;
function renderComponent(
props: Partial<ComponentProps<typeof VerificationRequestToast>> & { request: VerificationRequest },
): RenderResult {
const propsWithDefaults = {
toastKey: "test",
...props,
};
return render(
<VerificationRequestToast {...propsWithDefaults} />,
clientAndSDKContextRenderOptions(client, sdkContext),
);
}
beforeEach(() => {
client = getMockClientWithEventEmitter({
@@ -41,6 +47,8 @@ describe("VerificationRequestToast", () => {
...mockClientMethodsCrypto(),
getDevice: jest.fn(),
});
sdkContext = new TestSDKContext();
sdkContext._client = client;
});
it("should render a self-verification", async () => {
@@ -27,8 +27,6 @@ describe("LegacyCallView", () => {
document.fullscreenElement = element;
document.exitFullscreen = jest.fn();
stubClient();
const call = {
on: jest.fn(),
removeListener: jest.fn(),
@@ -50,8 +48,7 @@ describe("LegacyCallView", () => {
});
it("should show/hide the sidebar based on the sidebarShown prop", async () => {
stubClient();
const cli = stubClient();
const call = {
roomId: "test-room",
on: jest.fn(),
@@ -93,8 +90,7 @@ describe("LegacyCallView", () => {
});
it("should not show the sidebar button in picture-in-picture mode", async () => {
stubClient();
const cli = stubClient();
const call = {
on: jest.fn(),
removeListener: jest.fn(),
@@ -16,7 +16,6 @@ import { clientAndSDKContextRenderOptions, mkStubRoom, stubClient } from "../../
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
import LegacyCallHandler from "../../../../../src/LegacyCallHandler";
import { SDKContext } from "../../../../../src/contexts/SDKContext";
import { TestSDKContext } from "../../../TestSDKContext.ts";
jest.mock("../../../../../src/components/views/voip/LegacyCallView", () => jest.fn(() => "LegacyCallView"));
@@ -93,9 +92,10 @@ describe("LegacyCallViewForRoom", () => {
jest.spyOn(sdkContext.resizeNotifier, "stopResizing");
jest.spyOn(sdkContext.resizeNotifier, "notifyTimelineHeightChanged");
const { container } = render(<LegacyCallViewForRoom roomId={call.roomId} />, {
wrapper: ({ children }) => <SDKContext.Provider value={sdkContext}>{children}</SDKContext.Provider>,
});
const { container } = render(
<LegacyCallViewForRoom roomId={call.roomId} />,
clientAndSDKContextRenderOptions(sdkContext.client!, sdkContext),
);
const resizer = container.querySelector(".mx_LegacyCallViewForRoom_ResizeHandle");
await waitFor(() => {