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:
Florian Duros
2026-07-29 13:56:40 +00:00
committed by GitHub
parent 1683525c5b
commit 7f8c4cef80
8 changed files with 267 additions and 29 deletions
@@ -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],