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
+1
View File
@@ -188,6 +188,7 @@ export const mockClientMethodsCrypto = (): Partial<
});
export const mockClientMethodsRooms = (rooms: Room[] = []): Partial<Record<MethodLikeKeys<MatrixClient>, unknown>> => ({
getVisibleRooms: vi.fn().mockReturnValue(rooms),
getRooms: vi.fn().mockReturnValue(rooms),
getRoom: vi.fn((roomId) => rooms.find((r) => r.roomId === roomId) ?? null),
isRoomEncrypted: vi.fn(),
@@ -37,6 +37,7 @@ import { ScopedRoomContextProvider } from "../../../../src/contexts/ScopedRoomCo
import { untilDispatch } from "../../../test-utils/utilities.ts";
import { TimelineRenderingType } from "../../../../src/contexts/RoomContext.ts";
import { type ComposerInsertPayload, ComposerType } from "../../../../src/dispatcher/payloads/ComposerInsertPayload.ts";
import { SDKContext } from "../../../../src/contexts/SDKContext.ts";
describe("ThreadView", () => {
const ROOM_ID = "!roomId:example.org";
@@ -73,7 +74,11 @@ describe("ThreadView", () => {
}
async function getComponent(initialEvent?: MatrixEvent): Promise<RenderResult> {
const renderResult = render(<TestThreadView initialEvent={initialEvent} />);
const renderResult = render(<TestThreadView initialEvent={initialEvent} />, {
wrapper: ({ children }) => (
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
),
});
await waitFor(() => {
expect(() => getByTestId(renderResult.container, "spinner")).toThrow();
@@ -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();
});
@@ -9,7 +9,14 @@ import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
import { mocked } from "jest-mock";
import { waitFor } from "jest-matrix-react";
import { createTestClient, flushPromises, flushPromisesWithFakeTimers, mkStubRoom, stubClient } from "../../test-utils";
import {
createTestClient,
flushPromises,
flushPromisesWithFakeTimers,
mkStubRoom,
stubClient,
TestSDKContext,
} from "../../test-utils";
import RoomListStoreV3, { RoomListStoreV3Event } from "../../../src/stores/room-list-v3/RoomListStoreV3";
import SpaceStore from "../../../src/stores/spaces/SpaceStore";
import { FilterEnum } from "../../../src/stores/room-list-v3/skip-list/filters";
@@ -44,6 +51,7 @@ jest.mock("../../../src/viewmodels/room-list/utils", () => ({
describe("RoomListViewModel", () => {
let matrixClient: MatrixClient;
let sdkContext: TestSDKContext;
let room1: Room;
let room2: Room;
let room3: Room;
@@ -51,6 +59,8 @@ describe("RoomListViewModel", () => {
beforeEach(() => {
matrixClient = createTestClient();
sdkContext = new TestSDKContext();
sdkContext._client = matrixClient;
room1 = mkStubRoom("!room1:server", "Room 1", matrixClient);
room2 = mkStubRoom("!room2:server", "Room 2", matrixClient);
room3 = mkStubRoom("!room3:server", "Room 3", matrixClient);
@@ -80,7 +90,11 @@ describe("RoomListViewModel", () => {
describe("Initialization", () => {
it("should initialize with correct snapshot", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const snapshot = viewModel.getSnapshot();
expect(snapshot.sections[0].roomIds).toEqual(["!room1:server", "!room2:server", "!room3:server"]);
@@ -97,7 +111,11 @@ describe("RoomListViewModel", () => {
sections: [{ tag: CHATS_TAG, rooms: [] }],
});
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
expect(viewModel.getSnapshot().sections).toEqual([]);
expect(viewModel.getSnapshot().isRoomListEmpty).toBe(true);
@@ -106,7 +124,11 @@ describe("RoomListViewModel", () => {
it("should set canCreateRoom based on user rights", () => {
mocked(hasCreateRoomRights).mockReturnValue(true);
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
expect(viewModel.getSnapshot().canCreateRoom).toBe(true);
});
@@ -114,7 +136,11 @@ describe("RoomListViewModel", () => {
describe("Room list updates", () => {
it("should update room list when ListsUpdate event fires", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const newRoom = mkStubRoom("!room4:server", "Room 4", matrixClient);
jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({
@@ -134,7 +160,11 @@ describe("RoomListViewModel", () => {
it("should update loading state when ListsLoaded event fires", () => {
jest.spyOn(RoomListStoreV3.instance, "isLoadingRooms", "get").mockReturnValue(true);
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
expect(viewModel.getSnapshot().isLoadingRooms).toBe(true);
@@ -145,7 +175,11 @@ describe("RoomListViewModel", () => {
// This test ensures that the room list item vms are preserved when the room list is changing
it("should keep existing view model when ListsUpdate event fires", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
// Create view model for room1
const room1VM = viewModel.getRoomItemViewModel("!room1:server");
@@ -160,7 +194,11 @@ describe("RoomListViewModel", () => {
describe("Space switching", () => {
it("should update room list when space changes", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const spaceRoomList = [room1, room2];
@@ -178,7 +216,11 @@ describe("RoomListViewModel", () => {
});
it("should clear view models when space changes", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
// Get view models for visible rooms
const vm1 = viewModel.getRoomItemViewModel("!room1:server")!;
@@ -200,7 +242,11 @@ describe("RoomListViewModel", () => {
});
it("should clear roomsMap when space changes and repopulate with new rooms", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const newSpaceRoom = mkStubRoom("!spaceroom:server", "Space Room", matrixClient);
@@ -221,7 +267,11 @@ describe("RoomListViewModel", () => {
describe("Active room tracking", () => {
it("should update active room index when room is selected", async () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
jest.spyOn(SDKContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server");
@@ -237,7 +287,11 @@ describe("RoomListViewModel", () => {
});
it("should return undefined active room index when no room is selected", async () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
jest.spyOn(SDKContextClass.instance.roomViewStore, "getRoomId").mockReturnValue(null);
@@ -255,7 +309,11 @@ describe("RoomListViewModel", () => {
describe("Sticky room behavior", () => {
it("should keep selected room at same index when room list updates", async () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
// Select room at index 1
jest.spyOn(SDKContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server");
@@ -281,7 +339,11 @@ describe("RoomListViewModel", () => {
});
it("should not apply sticky behavior when user changes rooms", async () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
// Select room at index 1
jest.spyOn(SDKContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server");
@@ -307,7 +369,11 @@ describe("RoomListViewModel", () => {
describe("Filters", () => {
it("should toggle filter on", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
expect(viewModel.getSnapshot().activeFilterId).toBeUndefined();
@@ -324,7 +390,11 @@ describe("RoomListViewModel", () => {
});
it("should toggle filter off", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
// Turn filter on
jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({
@@ -363,7 +433,11 @@ describe("RoomListViewModel", () => {
it("hides the Favourites and Low Priority filters when sections are enabled", () => {
mockShowSections(true);
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
roomViewStore: sdkContext.roomViewStore,
spaceStore: sdkContext.spaceStore,
});
const { filterIds } = viewModel.getSnapshot();
expect(filterIds).not.toContain("favourite");
@@ -372,7 +446,11 @@ describe("RoomListViewModel", () => {
it("shows the Favourites and Low Priority filters when sections are disabled", () => {
mockShowSections(false);
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
roomViewStore: sdkContext.roomViewStore,
spaceStore: sdkContext.spaceStore,
});
const { filterIds } = viewModel.getSnapshot();
expect(filterIds).toContain("favourite");
@@ -393,7 +471,11 @@ describe("RoomListViewModel", () => {
return "watcher-id";
});
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
roomViewStore: sdkContext.roomViewStore,
spaceStore: sdkContext.spaceStore,
});
expect(viewModel.getSnapshot().filterIds).toContain("favourite");
// Activate the Favourites filter
@@ -423,7 +505,11 @@ describe("RoomListViewModel", () => {
describe("Room item view models", () => {
it("should create room item view model on demand", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const itemViewModel = viewModel.getRoomItemViewModel("!room1:server");
@@ -432,7 +518,11 @@ describe("RoomListViewModel", () => {
});
it("should reuse existing room item view model", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const itemViewModel1 = viewModel.getRoomItemViewModel("!room1:server");
const itemViewModel2 = viewModel.getRoomItemViewModel("!room1:server");
@@ -441,13 +531,21 @@ describe("RoomListViewModel", () => {
});
it("should return undefined for non-existent room", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
expect(viewModel.getRoomItemViewModel("!nonexistent:server")).toBeUndefined();
});
it("should not throw when requesting view model for a room removed from the list but still in roomsMap", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
// Normal list update removes room2 from the list
jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({
@@ -461,7 +559,11 @@ describe("RoomListViewModel", () => {
});
it("should return undefined for a room from old space after space change", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const spaceRoom = mkStubRoom("!newroom:server", "New Room", matrixClient);
@@ -478,7 +580,11 @@ describe("RoomListViewModel", () => {
});
it("should recover when roomsMap is stale but roomsResult has the room", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
// Manually clear roomsMap to simulate stale cache, but keep roomsResult intact
(viewModel as any).roomsMap.clear();
@@ -488,7 +594,11 @@ describe("RoomListViewModel", () => {
});
it("should dispose view models for rooms no longer visible", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const vm1 = viewModel.getRoomItemViewModel("!room1:server")!;
const vm2 = viewModel.getRoomItemViewModel("!room2:server")!;
@@ -511,7 +621,11 @@ describe("RoomListViewModel", () => {
describe("Room creation", () => {
it("should dispatch CreateChat action when createChatRoom is called", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const dispatchSpy = jest.spyOn(dispatcher, "fire");
@@ -521,7 +635,11 @@ describe("RoomListViewModel", () => {
});
it("should dispatch CreateRoom action without parent space", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
@@ -536,7 +654,11 @@ describe("RoomListViewModel", () => {
const spaceRoom = mkStubRoom("!space:server", "Space", matrixClient);
jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(spaceRoom);
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
@@ -556,7 +678,11 @@ describe("RoomListViewModel", () => {
});
it("should navigate to next room when delta is 1", async () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
jest.spyOn(SDKContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room1:server");
@@ -579,7 +705,11 @@ describe("RoomListViewModel", () => {
});
it("should navigate to previous room when delta is -1", async () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
jest.spyOn(SDKContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server");
@@ -602,7 +732,11 @@ describe("RoomListViewModel", () => {
});
it("should wrap around to last room when navigating backwards from first room", async () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
jest.spyOn(SDKContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room1:server");
@@ -625,7 +759,11 @@ describe("RoomListViewModel", () => {
});
it("should not navigate when current room is not found", async () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
jest.spyOn(SDKContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!unknown:server");
@@ -649,7 +787,11 @@ describe("RoomListViewModel", () => {
});
it("should not navigate when no room is selected", async () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
jest.spyOn(SDKContextClass.instance.roomViewStore, "getRoomId").mockReturnValue(null);
@@ -682,7 +824,11 @@ describe("RoomListViewModel", () => {
});
it("should dispose all room item view models on dispose", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const vm1 = viewModel.getRoomItemViewModel("!room1:server")!;
const vm2 = viewModel.getRoomItemViewModel("!room2:server")!;
@@ -698,19 +844,31 @@ describe("RoomListViewModel", () => {
describe("Toast", () => {
it("should show toast when SectionCreated event fires", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
RoomListStoreV3.instance.emit(RoomListStoreV3Event.SectionCreated);
expect(viewModel.getSnapshot().toast).toBe("section_created");
});
it("should show toast when RoomTagged event fires", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
RoomListStoreV3.instance.emit(RoomListStoreV3Event.RoomTagged);
expect(viewModel.getSnapshot().toast).toBe("chat_moved");
});
it("should clear toast when closeToast is called", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
RoomListStoreV3.instance.emit(RoomListStoreV3Event.SectionCreated);
expect(viewModel.getSnapshot().toast).toBe("section_created");
@@ -720,7 +878,11 @@ describe("RoomListViewModel", () => {
});
it("should auto-close toast after 15 seconds", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
RoomListStoreV3.instance.emit(RoomListStoreV3Event.SectionCreated);
expect(viewModel.getSnapshot().toast).toBe("section_created");
@@ -730,7 +892,11 @@ describe("RoomListViewModel", () => {
});
it("should reset the auto-close timer when a new section is created", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
RoomListStoreV3.instance.emit(RoomListStoreV3Event.SectionCreated);
jest.advanceTimersByTime(10 * 1000);
@@ -755,7 +921,11 @@ describe("RoomListViewModel", () => {
it("should show the unread-activity toast when an unread room is below the fold", () => {
mockRoom3Unread();
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
// room1/room2 visible, room3 (unread) scrolled below the fold.
viewModel.updateVisibleFold(1);
@@ -765,7 +935,11 @@ describe("RoomListViewModel", () => {
it("should prefer the event toast over the unread-activity toast, restoring it on auto-close", () => {
mockRoom3Unread();
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
viewModel.updateVisibleFold(1);
expect(viewModel.getSnapshot().toast).toBe("unread_activity");
@@ -804,7 +978,11 @@ describe("RoomListViewModel", () => {
});
it("should initialize with multiple sections", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const snapshot = viewModel.getSnapshot();
expect(snapshot.sections).toHaveLength(3);
@@ -817,7 +995,11 @@ describe("RoomListViewModel", () => {
});
it("should not be a flat list when multiple sections exist", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
expect(viewModel.getSnapshot().isFlatList).toBe(false);
});
@@ -832,7 +1014,11 @@ describe("RoomListViewModel", () => {
],
});
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
expect(viewModel.getSnapshot().isFlatList).toBe(true);
expect(viewModel.getSnapshot().sections).toHaveLength(1);
@@ -849,14 +1035,22 @@ describe("RoomListViewModel", () => {
],
});
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
expect(viewModel.getSnapshot().isFlatList).toBe(true);
expect(viewModel.getSnapshot().sections).toHaveLength(0);
});
it("should exclude favourite and low_priority from filter list", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const snapshot = viewModel.getSnapshot();
expect(snapshot.filterIds).not.toContain("favourite");
@@ -876,7 +1070,11 @@ describe("RoomListViewModel", () => {
],
});
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const snapshot = viewModel.getSnapshot();
expect(snapshot.sections).toHaveLength(1);
@@ -884,7 +1082,11 @@ describe("RoomListViewModel", () => {
});
it("should create section header view models on demand", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const headerVM = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
expect(headerVM).toBeDefined();
@@ -893,7 +1095,11 @@ describe("RoomListViewModel", () => {
});
it("should reuse section header view models", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const headerVM1 = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
const headerVM2 = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
@@ -901,7 +1107,11 @@ describe("RoomListViewModel", () => {
});
it("should hide room IDs when a section is collapsed", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
// Collapse the favourite section
const favHeader = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
@@ -920,7 +1130,11 @@ describe("RoomListViewModel", () => {
});
it("should compute activeRoomIndex relative to visible rooms when a section is collapsed", async () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
// Collapse the favourite section (which has 2 rooms: fav1, fav2)
const favHeader = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
@@ -943,7 +1157,11 @@ describe("RoomListViewModel", () => {
});
it("should restore room IDs when a section is re-expanded", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const favHeader = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
@@ -958,7 +1176,11 @@ describe("RoomListViewModel", () => {
});
it("should update sections when room list changes", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const newFav = mkStubRoom("!fav3:server", "Fav 3", matrixClient);
@@ -978,7 +1200,11 @@ describe("RoomListViewModel", () => {
});
it("should preserve section collapse state across list updates", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
// Collapse favourites
const favHeader = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
@@ -993,7 +1219,11 @@ describe("RoomListViewModel", () => {
});
it("should track section collapse state per space", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
// Collapse favourites in the home space
const favHeader = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
@@ -1048,7 +1278,11 @@ describe("RoomListViewModel", () => {
});
it("should apply filters across all sections", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
// Only favRoom1 is unread
jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({
@@ -1102,7 +1336,11 @@ describe("RoomListViewModel", () => {
],
});
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
expect(viewModel.getSnapshot().sections.some((s) => s.id === customTag)).toBe(true);
});
@@ -1116,7 +1354,11 @@ describe("RoomListViewModel", () => {
],
});
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
expect(viewModel.getSnapshot().sections.some((s) => s.id === customTag)).toBe(false);
});
@@ -1130,7 +1372,11 @@ describe("RoomListViewModel", () => {
],
});
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
expect(viewModel.getSnapshot().sections.some((s) => s.id === customTag)).toBe(true);
});
@@ -1138,7 +1384,11 @@ describe("RoomListViewModel", () => {
describe("Collapse/expand all sections", () => {
it("should collapse all sections when Action.RoomListCollapseAllSections is dispatched", async () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const favHeader = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
const chatsHeader = viewModel.getSectionHeaderViewModel(CHATS_TAG);
@@ -1156,7 +1406,11 @@ describe("RoomListViewModel", () => {
});
it("should expand all sections when Action.RoomListExpandAllSections is dispatched", async () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
// Collapse first
const favHeader = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
@@ -1177,7 +1431,11 @@ describe("RoomListViewModel", () => {
describe("notifyCollapseState", () => {
it("should dispatch collapseSections=expand when all sections are expanded (default)", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
RoomListStoreV3.instance.emit(RoomListStoreV3Event.ListsUpdate);
@@ -1189,7 +1447,11 @@ describe("RoomListViewModel", () => {
});
it("should dispatch collapseSection=collapse when all sections are collapsed", () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
// Collapse all sections
viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite).isExpanded = false;
@@ -1217,7 +1479,11 @@ describe("RoomListViewModel", () => {
{ tag: DefaultTagID.LowPriority, rooms: [] },
],
});
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
RoomListStoreV3.instance.emit(RoomListStoreV3Event.ListsUpdate);
@@ -1231,7 +1497,11 @@ describe("RoomListViewModel", () => {
it("should apply sticky room within the correct section", async () => {
stubClient();
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
// Select favRoom1 (index 0 globally, index 0 in favourites section)
jest.spyOn(SDKContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!fav1:server");
@@ -1263,7 +1533,11 @@ describe("RoomListViewModel", () => {
describe("Drag and drop", () => {
beforeEach(() => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
// Ensure section header VMs are created before tests that interact with them
viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
viewModel.getSectionHeaderViewModel(CHATS_TAG);
@@ -1367,7 +1641,11 @@ describe("RoomListViewModel", () => {
describe("changeRoomSection", () => {
beforeEach(() => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
spaceStore: SpaceStore.instance,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
mocked(tagRoom).mockClear();
});
@@ -1406,7 +1684,11 @@ describe("RoomListViewModel", () => {
});
it("should scroll a room into view in a flat list", async () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
roomViewStore: sdkContext.roomViewStore,
spaceStore: sdkContext.spaceStore,
});
const scrollSpy = jest.fn();
viewModel.setScrollToIndex(scrollSpy);
@@ -1432,7 +1714,11 @@ describe("RoomListViewModel", () => {
{ tag: CHATS_TAG, rooms: [regularRoom1] },
],
});
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
roomViewStore: sdkContext.roomViewStore,
spaceStore: sdkContext.spaceStore,
});
const scrollSpy = jest.fn();
viewModel.setScrollToIndex(scrollSpy);
@@ -1448,7 +1734,11 @@ describe("RoomListViewModel", () => {
});
it("should not scroll when the room is not in the current list", async () => {
viewModel = new RoomListViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({
client: matrixClient,
roomViewStore: sdkContext.roomViewStore,
spaceStore: sdkContext.spaceStore,
});
const scrollSpy = jest.fn();
viewModel.setScrollToIndex(scrollSpy);
@@ -70,7 +70,11 @@ describe("DateSeparatorViewModel", () => {
const createViewModel = (
props: Partial<typeof defaultProps> & { forExport?: boolean } = {},
): DateSeparatorViewModel => {
return new DateSeparatorViewModel({ ...defaultProps, ...props });
return new DateSeparatorViewModel({
...defaultProps,
...props,
roomViewStore: SDKContextClass.instance.roomViewStore,
});
};
beforeEach(() => {