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
@@ -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(() => {