Room list: persist section state (expanded/collapsed) (#34351)
* Add new setting to store section state * Store section state in device settings * Wire up section header view model and new section functions * Update room list vm tests * Add e2e test on section state persistence * Use more intuitive syntax for SectionExpansionState * Formatting * Fix chats section staying expanded * Add CHAT section case in e2e test
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { rejectToast } from "@element-hq/element-web-playwright-common";
|
||||
import { rejectToast, rejectToastIfExists } from "@element-hq/element-web-playwright-common";
|
||||
|
||||
import { expect, test } from "../../../element-web-test";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
@@ -229,6 +229,63 @@ test.describe("Room list sections", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Section collapse state persistence", () => {
|
||||
test.beforeEach(async ({ app }) => {
|
||||
// A favourite room (so we get a Favourites section) and a regular room in Chats,
|
||||
// giving us two independent sections whose expansion state we can assert.
|
||||
const favouriteId = await app.client.createRoom({ name: "favourite room" });
|
||||
await app.client.evaluate(async (client, roomId) => {
|
||||
await client.setRoomTag(roomId, "m.favourite");
|
||||
}, favouriteId);
|
||||
await app.client.createRoom({ name: "regular room" });
|
||||
});
|
||||
|
||||
test("persists the collapsed/expanded state across reloads", async ({ page }) => {
|
||||
const roomList = getRoomList(page);
|
||||
const favouritesHeader = getSectionHeader(page, "Favourites");
|
||||
const chatsHeader = getSectionHeader(page, "Chats");
|
||||
const favRoom = roomList.getByRole("row", { name: "Open room favourite room" });
|
||||
const regularRoom = roomList.getByRole("row", { name: "Open room regular room" });
|
||||
|
||||
// Collapse both the Favourites and Chats sections
|
||||
await expect(favouritesHeader).toHaveAttribute("aria-expanded", "true");
|
||||
await favouritesHeader.click();
|
||||
await expect(favouritesHeader).toHaveAttribute("aria-expanded", "false");
|
||||
await expect(favRoom).not.toBeVisible();
|
||||
|
||||
await expect(chatsHeader).toHaveAttribute("aria-expanded", "true");
|
||||
await chatsHeader.click();
|
||||
await expect(chatsHeader).toHaveAttribute("aria-expanded", "false");
|
||||
await expect(regularRoom).not.toBeVisible();
|
||||
|
||||
// Reload the page: the collapsed state is persisted at the device level and should survive
|
||||
await page.reload();
|
||||
await rejectToastIfExists(page, "Verify this device");
|
||||
await rejectToastIfExists(page, "Notifications");
|
||||
|
||||
// Both sections are still collapsed and their rooms stay hidden
|
||||
await expect(getSectionHeader(page, "Favourites")).toHaveAttribute("aria-expanded", "false");
|
||||
await expect(getRoomList(page).getByRole("row", { name: "Open room favourite room" })).not.toBeVisible();
|
||||
await expect(getSectionHeader(page, "Chats")).toHaveAttribute("aria-expanded", "false");
|
||||
await expect(getRoomList(page).getByRole("row", { name: "Open room regular room" })).not.toBeVisible();
|
||||
|
||||
// Expand them again and reload: the expanded state is likewise persisted
|
||||
await getSectionHeader(page, "Favourites").click();
|
||||
await expect(getSectionHeader(page, "Favourites")).toHaveAttribute("aria-expanded", "true");
|
||||
await getSectionHeader(page, "Chats").click();
|
||||
await expect(getSectionHeader(page, "Chats")).toHaveAttribute("aria-expanded", "true");
|
||||
|
||||
await page.reload();
|
||||
await rejectToastIfExists(page, "Verify this device");
|
||||
await rejectToastIfExists(page, "Notifications");
|
||||
|
||||
await expect(getSectionHeader(page, "Favourites")).toHaveAttribute("aria-expanded", "true");
|
||||
await expect(getRoomList(page).getByRole("row", { name: "Open room favourite room" })).toBeVisible();
|
||||
await expect(getSectionHeader(page, "Chats")).toHaveAttribute("aria-expanded", "true");
|
||||
await expect(getRoomList(page).getByRole("row", { name: "Open room regular room" })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Rooms placement in sections", () => {
|
||||
test("should move a room between sections when tags change", async ({ page, app }) => {
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
|
||||
@@ -53,7 +53,11 @@ import InviteRulesConfigController from "./controllers/InviteRulesConfigControll
|
||||
import { type ComputedInviteConfig } from "../@types/invite-rules.ts";
|
||||
import BlockInvitesConfigController from "./controllers/BlockInvitesConfigController.ts";
|
||||
import RequiresSettingsController from "./controllers/RequiresSettingsController.ts";
|
||||
import { type ReorderableSection, type CustomSectionsData } from "../stores/room-list-v3/section.ts";
|
||||
import {
|
||||
type ReorderableSection,
|
||||
type CustomSectionsData,
|
||||
type SectionExpansionState,
|
||||
} from "../stores/room-list-v3/section.ts";
|
||||
import { type NotificationSound } from "../Notifier.ts";
|
||||
import VideoRoomsBetaImage from "../../res/img/betas/video_rooms.png";
|
||||
|
||||
@@ -364,6 +368,7 @@ export interface Settings {
|
||||
"Developer.elementCallUrl": IBaseSetting<string>;
|
||||
"RoomList.CustomSectionData": IBaseSetting<CustomSectionsData>;
|
||||
"RoomList.OrderedCustomSections": IBaseSetting<ReorderableSection[]>;
|
||||
"RoomList.SectionExpansionState": IBaseSetting<SectionExpansionState>;
|
||||
"RoomList.showSections": IBaseSetting<boolean>;
|
||||
}
|
||||
|
||||
@@ -1349,6 +1354,14 @@ export const SETTINGS: Settings = {
|
||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||
default: [],
|
||||
},
|
||||
/**
|
||||
* Managed by the {@link RoomListSectionHeaderViewModel}
|
||||
* Store the expanded/collapsed state of the room list sections, per space and per section tag
|
||||
*/
|
||||
"RoomList.SectionExpansionState": {
|
||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
|
||||
default: {},
|
||||
},
|
||||
[UIFeature.RoomHistorySettings]: {
|
||||
supportedLevels: LEVELS_UI_FEATURE,
|
||||
default: true,
|
||||
|
||||
@@ -132,6 +132,36 @@ export function getCustomSectionData(): CustomSectionsData {
|
||||
) satisfies CustomSectionsData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persisted expanded/collapsed state of the room list sections, stored per space then per section tag.
|
||||
*/
|
||||
export type SectionExpansionState = { [spaceId: string]: { [sectionTag: string]: boolean } };
|
||||
|
||||
/**
|
||||
* Returns whether the section with the given tag is expanded in the given space.
|
||||
* Defaults to expanded when no state has been persisted.
|
||||
* @param spaceId - The id of the space.
|
||||
* @param tag - The tag of the section.
|
||||
*/
|
||||
export function isSectionExpanded(spaceId: string, tag: string): boolean {
|
||||
return SettingsStore.getValue("RoomList.SectionExpansionState")[spaceId]?.[tag] ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists the expanded/collapsed state of a section for a given space at the device level.
|
||||
* @param spaceId - The id of the space.
|
||||
* @param tag - The tag of the section.
|
||||
* @param expanded - Whether the section is expanded.
|
||||
*/
|
||||
export async function setSectionExpanded(spaceId: string, tag: string, expanded: boolean): Promise<void> {
|
||||
const state = SettingsStore.getValue("RoomList.SectionExpansionState");
|
||||
const newState: SectionExpansionState = {
|
||||
...state,
|
||||
[spaceId]: { ...state[spaceId], [tag]: expanded },
|
||||
};
|
||||
await SettingsStore.setValue("RoomList.SectionExpansionState", null, SettingLevel.DEVICE, newState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the ordered list of custom section tags from the settings.
|
||||
* If the settings contain tags that are not present in the custom section data, they will be filtered out and the settings will be updated to remove the unknown tags.
|
||||
|
||||
@@ -24,6 +24,8 @@ import {
|
||||
getCustomSectionData,
|
||||
isCustomSectionTag,
|
||||
isDefaultSectionTag,
|
||||
isSectionExpanded,
|
||||
setSectionExpanded,
|
||||
} from "../../stores/room-list-v3/section";
|
||||
import PosthogTrackers from "../../PosthogTrackers";
|
||||
import { CallStore, CallStoreEvent } from "../../stores/CallStore";
|
||||
@@ -49,12 +51,6 @@ export class RoomListSectionHeaderViewModel
|
||||
*/
|
||||
private roomNotificationStates = new Set<RoomNotificationState>();
|
||||
|
||||
/**
|
||||
* Tracks the expanded/collapsed state per space.
|
||||
* Key is spaceId. Defaults to expanded if not set.
|
||||
*/
|
||||
private readonly expandedBySpace = new Map<string, boolean>();
|
||||
|
||||
/**
|
||||
* The calls of the rooms currently in this section that we are listening to, used to aggregate the call decoration.
|
||||
*/
|
||||
@@ -65,7 +61,7 @@ export class RoomListSectionHeaderViewModel
|
||||
super(props, {
|
||||
id: props.tag,
|
||||
title: props.title,
|
||||
isExpanded: true,
|
||||
isExpanded: isSectionExpanded(props.spaceId, props.tag),
|
||||
isUnread: false,
|
||||
displaySectionMenu: !isDefaultSection,
|
||||
canBeReordered: !isDefaultSection || props.tag === CHATS_TAG,
|
||||
@@ -79,9 +75,10 @@ export class RoomListSectionHeaderViewModel
|
||||
this.disposables.trackListener(CallStore.instance, CallStoreEvent.Call, this.onCallChanged);
|
||||
}
|
||||
|
||||
public onClick = (): void => {
|
||||
public onClick = async (): Promise<void> => {
|
||||
const isExpanded = !this.snapshot.current.isExpanded;
|
||||
this.expandedBySpace.set(this.props.spaceId, isExpanded);
|
||||
// We don't wait to persist the expanded state to storage, as it is not critical and we want the UI to update immediately
|
||||
void setSectionExpanded(this.props.spaceId, this.props.tag, isExpanded);
|
||||
this.snapshot.merge({ isExpanded });
|
||||
this.props.onToggleExpanded(isExpanded);
|
||||
};
|
||||
@@ -98,7 +95,8 @@ export class RoomListSectionHeaderViewModel
|
||||
* This will not trigger the onToggleExpanded callback.
|
||||
*/
|
||||
public set isExpanded(value: boolean) {
|
||||
this.expandedBySpace.set(this.props.spaceId, value);
|
||||
// We don't wait to persist the expanded state to storage, as it is not critical and we want the UI to update immediately
|
||||
void setSectionExpanded(this.props.spaceId, this.props.tag, value);
|
||||
this.snapshot.merge({ isExpanded: value });
|
||||
|
||||
const kind = value ? "Expand" : "Collapse";
|
||||
@@ -111,7 +109,7 @@ export class RoomListSectionHeaderViewModel
|
||||
*/
|
||||
public setSpace(spaceId: string): void {
|
||||
this.props.spaceId = spaceId;
|
||||
const isExpanded = this.expandedBySpace.get(this.props.spaceId) ?? true;
|
||||
const isExpanded = isSectionExpanded(this.props.spaceId, this.props.tag);
|
||||
this.snapshot.merge({ isExpanded });
|
||||
}
|
||||
|
||||
|
||||
@@ -741,6 +741,9 @@ export class RoomListViewModel
|
||||
roomIdOverride: string | null = null,
|
||||
scrollToSectionTag: string | undefined = undefined,
|
||||
): Promise<void> {
|
||||
// Store is still loading rooms - don't update the list yet, we'll get another update when loading finishes
|
||||
if (RoomListStoreV3.instance.isLoadingRooms) return;
|
||||
|
||||
// Determine the room ID to use for calculations
|
||||
// Use override if provided (e.g., during space changes), otherwise fall back to RoomViewStore
|
||||
const roomId = roomIdOverride ?? this.props.roomViewStore.getRoomId();
|
||||
@@ -770,12 +773,6 @@ export class RoomListViewModel
|
||||
this.roomsResult,
|
||||
(tag) => this.roomSectionHeaderViewModels.get(tag)?.isExpanded ?? true,
|
||||
);
|
||||
// If it's a flat list, we need to make sure the single section is expanded and has all rooms, otherwise the room list will be empty
|
||||
if (isFlatList) {
|
||||
const chatSections = this.roomSectionHeaderViewModels.get(CHATS_TAG);
|
||||
if (chatSections) chatSections.isExpanded = true;
|
||||
chatSections?.setRooms(this.roomsResult.sections.flatMap((section) => section.rooms));
|
||||
}
|
||||
this.sections = sections;
|
||||
|
||||
// Calculate the active room index from the computed sections (which exclude collapsed sections' rooms)
|
||||
@@ -958,19 +955,21 @@ function computeSections(
|
||||
): { sections: Section[]; isFlatList: boolean } {
|
||||
const customSections = getCustomSectionData();
|
||||
|
||||
const sections = roomsResult.sections
|
||||
const filtered = roomsResult.sections
|
||||
// Only include sections that have rooms, or custom sections that were created in the current space.
|
||||
.filter(
|
||||
(section) =>
|
||||
section.rooms.length > 0 ||
|
||||
(isCustomSectionTag(section.tag) && customSections[section.tag]?.spaceId === roomsResult.spaceId),
|
||||
)
|
||||
// Remove roomIds for sections that are currently collapsed according to their section header view model
|
||||
.map((section) => ({
|
||||
...section,
|
||||
rooms: isSectionExpanded(section.tag) ? section.rooms : [],
|
||||
}));
|
||||
const isFlatList = sections.length === 0 || (sections.length === 1 && sections[0].tag === CHATS_TAG);
|
||||
);
|
||||
const isFlatList = filtered.length === 0 || (filtered.length === 1 && filtered[0].tag === CHATS_TAG);
|
||||
|
||||
const sections = filtered.map((section) => ({
|
||||
...section,
|
||||
// A flat list has no section header to toggle, so always render its rooms.
|
||||
// Otherwise, remove roomIds for sections that are currently collapsed.
|
||||
rooms: isFlatList || isSectionExpanded(section.tag) ? section.rooms : [],
|
||||
}));
|
||||
|
||||
return { sections, isFlatList };
|
||||
}
|
||||
|
||||
@@ -16,11 +16,14 @@ import {
|
||||
getCustomSectionData,
|
||||
getOrderedCustomSections,
|
||||
isDefaultSectionTag,
|
||||
isSectionExpanded,
|
||||
setSectionExpanded,
|
||||
CHATS_TAG,
|
||||
CUSTOM_SECTION_TAG_PREFIX,
|
||||
isSectionTag,
|
||||
reorderSection,
|
||||
} from "../../../../src/stores/room-list-v3/section";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
import { CreateSectionDialog } from "../../../../src/components/views/dialogs/CreateSectionDialog";
|
||||
import { RemoveSectionDialog } from "../../../../src/components/views/dialogs/RemoveSectionDialog";
|
||||
import { DefaultTagID } from "../../../../src/stores/room-list-v3/skip-list/tag";
|
||||
@@ -131,6 +134,63 @@ describe("section", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("isSectionExpanded", () => {
|
||||
const spaceId = "!space:server";
|
||||
const tag = "element.io.section.abc";
|
||||
|
||||
it.each([
|
||||
{ value: {}, result: true },
|
||||
{ value: { "!other:server": { [tag]: false } }, result: true },
|
||||
{ value: { [spaceId]: { "other.tag": false } }, result: true },
|
||||
{ value: { [spaceId]: { [tag]: false } }, result: false },
|
||||
])("returns the persisted state=$result when value=$value", ({ value, result }) => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(value);
|
||||
expect(isSectionExpanded(spaceId, tag)).toBe(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setSectionExpanded", () => {
|
||||
const spaceId = "!space:server";
|
||||
const tag = "element.io.section.abc";
|
||||
|
||||
it("persists the state at the device level", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue({});
|
||||
const setValueSpy = jest.spyOn(SettingsStore, "setValue").mockResolvedValue(undefined);
|
||||
|
||||
await setSectionExpanded(spaceId, tag, false);
|
||||
|
||||
expect(setValueSpy).toHaveBeenCalledWith("RoomList.SectionExpansionState", null, SettingLevel.DEVICE, {
|
||||
[spaceId]: { [tag]: false },
|
||||
});
|
||||
});
|
||||
|
||||
it("merges with existing state for other spaces and tags", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue({
|
||||
"!other:server": { "other.tag": false },
|
||||
[spaceId]: { "existing.tag": true },
|
||||
});
|
||||
const setValueSpy = jest.spyOn(SettingsStore, "setValue").mockResolvedValue(undefined);
|
||||
|
||||
await setSectionExpanded(spaceId, tag, false);
|
||||
|
||||
expect(setValueSpy).toHaveBeenCalledWith("RoomList.SectionExpansionState", null, SettingLevel.DEVICE, {
|
||||
"!other:server": { "other.tag": false },
|
||||
[spaceId]: { "existing.tag": true, [tag]: false },
|
||||
});
|
||||
});
|
||||
|
||||
it("overwrites the previous state for the same space and tag", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue({ [spaceId]: { [tag]: false } });
|
||||
const setValueSpy = jest.spyOn(SettingsStore, "setValue").mockResolvedValue(undefined);
|
||||
|
||||
await setSectionExpanded(spaceId, tag, true);
|
||||
|
||||
expect(setValueSpy).toHaveBeenCalledWith("RoomList.SectionExpansionState", null, SettingLevel.DEVICE, {
|
||||
[spaceId]: { [tag]: true },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("createSection", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(null);
|
||||
|
||||
@@ -16,23 +16,34 @@ import { CallStore } from "../../../src/stores/CallStore";
|
||||
import { type Call } from "../../../src/models/Call";
|
||||
import { createTestClient, mkRoom } from "../../test-utils";
|
||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||
import RoomListStoreV3 from "../../../src/stores/room-list-v3/RoomListStoreV3";
|
||||
import { DefaultTagID } from "../../../src/stores/room-list-v3/skip-list/tag";
|
||||
import { CHATS_TAG } from "../../../src/stores/room-list-v3/section";
|
||||
import { CHATS_TAG, type SectionExpansionState } from "../../../src/stores/room-list-v3/section";
|
||||
|
||||
describe("RoomListSectionHeaderViewModel", () => {
|
||||
let onToggleExpanded: jest.Mock;
|
||||
let matrixClient: MatrixClient;
|
||||
// In-memory backing store shared between the getValue/setValue mocks so that
|
||||
// persisted expansion state round-trips within a test.
|
||||
let sectionExpansionState: SectionExpansionState;
|
||||
|
||||
beforeEach(() => {
|
||||
onToggleExpanded = jest.fn();
|
||||
matrixClient = createTestClient();
|
||||
sectionExpansionState = {};
|
||||
jest.spyOn(SettingsStore, "watchSetting").mockReturnValue("watcher-id");
|
||||
jest.spyOn(SettingsStore, "unwatchSetting").mockReturnValue(undefined);
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => {
|
||||
if (setting === "RoomList.OrderedCustomSections") return [];
|
||||
if (setting === "RoomList.SectionExpansionState") return sectionExpansionState;
|
||||
return null;
|
||||
});
|
||||
jest.spyOn(SettingsStore, "setValue").mockImplementation(async (setting, _roomId, _level, value) => {
|
||||
if (setting === "RoomList.SectionExpansionState") {
|
||||
sectionExpansionState = value as SectionExpansionState;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -100,6 +111,52 @@ describe("RoomListSectionHeaderViewModel", () => {
|
||||
expect(vm.isExpanded).toBe(false);
|
||||
});
|
||||
|
||||
it("should initialize expanded state from the persisted setting", () => {
|
||||
sectionExpansionState = { "!space:server": { "m.favourite": false } };
|
||||
|
||||
const vm = new RoomListSectionHeaderViewModel({
|
||||
tag: "m.favourite",
|
||||
title: "Favourites",
|
||||
spaceId: "!space:server",
|
||||
onToggleExpanded,
|
||||
});
|
||||
|
||||
expect(vm.getSnapshot().isExpanded).toBe(false);
|
||||
});
|
||||
|
||||
it("should persist the expanded state at the device level on click", () => {
|
||||
const setValue = jest.spyOn(SettingsStore, "setValue");
|
||||
const vm = new RoomListSectionHeaderViewModel({
|
||||
tag: "m.favourite",
|
||||
title: "Favourites",
|
||||
spaceId: "!space:server",
|
||||
onToggleExpanded,
|
||||
});
|
||||
|
||||
vm.onClick();
|
||||
|
||||
expect(setValue).toHaveBeenCalledWith("RoomList.SectionExpansionState", null, SettingLevel.DEVICE, {
|
||||
"!space:server": { "m.favourite": false },
|
||||
});
|
||||
expect(sectionExpansionState).toEqual({ "!space:server": { "m.favourite": false } });
|
||||
});
|
||||
|
||||
it("should persist the expanded state at the device level when set via the setter", () => {
|
||||
const setValue = jest.spyOn(SettingsStore, "setValue");
|
||||
const vm = new RoomListSectionHeaderViewModel({
|
||||
tag: "m.favourite",
|
||||
title: "Favourites",
|
||||
spaceId: "!space:server",
|
||||
onToggleExpanded,
|
||||
});
|
||||
|
||||
vm.isExpanded = false;
|
||||
|
||||
expect(setValue).toHaveBeenCalledWith("RoomList.SectionExpansionState", null, SettingLevel.DEVICE, {
|
||||
"!space:server": { "m.favourite": false },
|
||||
});
|
||||
});
|
||||
|
||||
describe("displaySectionMenu", () => {
|
||||
it.each([
|
||||
[DefaultTagID.Favourite, false],
|
||||
|
||||
@@ -29,7 +29,11 @@ import { DefaultTagID } from "../../../src/stores/room-list-v3/skip-list/tag";
|
||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||
import { tagRoom } from "../../../src/utils/room/tagRoom";
|
||||
import { getSectionTagForRoom } from "../../../src/utils/room/getSectionTagForRoom";
|
||||
import { CHATS_TAG, CUSTOM_SECTION_TAG_PREFIX } from "../../../src/stores/room-list-v3/section";
|
||||
import {
|
||||
CHATS_TAG,
|
||||
CUSTOM_SECTION_TAG_PREFIX,
|
||||
type SectionExpansionState,
|
||||
} from "../../../src/stores/room-list-v3/section";
|
||||
import { MetaSpace } from "../../../src/stores/spaces";
|
||||
import { RoomNotificationStateStore } from "../../../src/stores/notifications/RoomNotificationStateStore";
|
||||
import { type RoomNotificationState } from "../../../src/stores/notifications/RoomNotificationState";
|
||||
@@ -55,9 +59,27 @@ describe("RoomListViewModel", () => {
|
||||
let room2: Room;
|
||||
let room3: Room;
|
||||
let viewModel: RoomListViewModel;
|
||||
// In-memory backing store for the persisted section expansion setting, reset each test so
|
||||
// collapse state does not leak between tests and writes round-trip synchronously.
|
||||
let sectionExpansionState: SectionExpansionState;
|
||||
|
||||
beforeEach(() => {
|
||||
matrixClient = createTestClient();
|
||||
|
||||
sectionExpansionState = {};
|
||||
const realGetValue = SettingsStore.getValue.bind(SettingsStore);
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting, roomId, excludeDefault) => {
|
||||
if (setting === "RoomList.SectionExpansionState") return sectionExpansionState;
|
||||
return realGetValue(setting, roomId, excludeDefault);
|
||||
});
|
||||
const realSetValue = SettingsStore.setValue.bind(SettingsStore);
|
||||
jest.spyOn(SettingsStore, "setValue").mockImplementation(async (setting, roomId, level, value) => {
|
||||
if (setting === "RoomList.SectionExpansionState") {
|
||||
sectionExpansionState = value as SectionExpansionState;
|
||||
return;
|
||||
}
|
||||
return realSetValue(setting, roomId, level, value);
|
||||
});
|
||||
sdkContext = new TestSDKContext();
|
||||
sdkContext._client = matrixClient;
|
||||
room1 = mkStubRoom("!room1:server", "Room 1", matrixClient);
|
||||
@@ -428,6 +450,7 @@ describe("RoomListViewModel", () => {
|
||||
if (setting === "RoomList.showSections") return showSections;
|
||||
if (setting === "RoomList.CustomSectionData") return {};
|
||||
if (setting === "RoomList.OrderedCustomSections") return [];
|
||||
if (setting === "RoomList.SectionExpansionState") return {};
|
||||
return undefined as any;
|
||||
});
|
||||
}
|
||||
@@ -465,6 +488,7 @@ describe("RoomListViewModel", () => {
|
||||
if (setting === "RoomList.showSections") return showSections;
|
||||
if (setting === "RoomList.CustomSectionData") return {};
|
||||
if (setting === "RoomList.OrderedCustomSections") return [];
|
||||
if (setting === "RoomList.SectionExpansionState") return {};
|
||||
return undefined as any;
|
||||
});
|
||||
jest.spyOn(SettingsStore, "watchSetting").mockImplementation((setting, _room, callback) => {
|
||||
|
||||
Reference in New Issue
Block a user