Room list: add custom section creation (#33155)
* feat: add creation section dialog * feat: add in skip list a method to change filters * feat: add helper to creation section * feat: add custom sections data to Settings * feat: add custom section to room list store v3 * feat: update header and room list item vms * feat: add toast to room list vm * feat: add new translation * chore: move util functions of room list specs * test: add custom section playwright tests * chore: call loadCustomSections in RoomListStoreV3 ctor
This commit is contained in:
@@ -61,6 +61,7 @@ describe("RoomListHeaderViewModel", () => {
|
||||
if (settingName === "RoomList.preferredSorting") return SortingAlgorithm.Recency;
|
||||
if (settingName === "feature_video_rooms") return true;
|
||||
if (settingName === "feature_element_call_video_rooms") return true;
|
||||
if (settingName === "RoomList.OrderedCustomSections") return [];
|
||||
return false;
|
||||
});
|
||||
});
|
||||
@@ -159,6 +160,23 @@ describe("RoomListHeaderViewModel", () => {
|
||||
vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
|
||||
expect(vm.getSnapshot().isMessagePreviewEnabled).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[true, true, false],
|
||||
[false, false, true],
|
||||
])(
|
||||
"when feature_room_list_sections is %s: canCreateSection=%s, useComposeIcon=%s",
|
||||
(featureEnabled, expectedCanCreateSection, expectedUseComposeIcon) => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string) => {
|
||||
if (settingName === "feature_room_list_sections") return featureEnabled;
|
||||
return false;
|
||||
});
|
||||
|
||||
vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
|
||||
expect(vm.getSnapshot().canCreateSection).toBe(expectedCanCreateSection);
|
||||
expect(vm.getSnapshot().useComposeIcon).toBe(expectedUseComposeIcon);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("event listeners", () => {
|
||||
@@ -296,6 +314,13 @@ describe("RoomListHeaderViewModel", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("should call createSection on RoomListStoreV3 when createSection is called", () => {
|
||||
const createSectionSpy = jest.spyOn(RoomListStoreV3.instance, "createSection").mockResolvedValue();
|
||||
vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
|
||||
vm.createSection();
|
||||
expect(createSectionSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should toggle message preview from enabled to disabled", () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string) => {
|
||||
if (settingName === "RoomList.showMessagePreview") return true;
|
||||
|
||||
@@ -29,6 +29,7 @@ import { Action } from "../../../src/dispatcher/actions";
|
||||
import { CallStore } from "../../../src/stores/CallStore";
|
||||
import { CallEvent, type Call } from "../../../src/models/Call";
|
||||
import { RoomListItemViewModel } from "../../../src/viewmodels/room-list/RoomListItemViewModel";
|
||||
import RoomListStoreV3 from "../../../src/stores/room-list-v3/RoomListStoreV3";
|
||||
|
||||
jest.mock("../../../src/viewmodels/room-list/utils", () => ({
|
||||
hasAccessToOptionsMenu: jest.fn().mockReturnValue(true),
|
||||
@@ -75,6 +76,7 @@ describe("RoomListItemViewModel", () => {
|
||||
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => {
|
||||
if (setting === "RoomList.showMessagePreview") return false;
|
||||
if (setting === "RoomList.OrderedCustomSections") return [];
|
||||
return false;
|
||||
});
|
||||
jest.spyOn(SettingsStore, "watchSetting").mockImplementation(() => "watcher-id");
|
||||
@@ -501,6 +503,20 @@ describe("RoomListItemViewModel", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("canMoveToSection", () => {
|
||||
it.each([
|
||||
[true, true],
|
||||
[false, false],
|
||||
])("should be %s when feature_room_list_sections is %s", (featureEnabled, expected) => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => {
|
||||
if (setting === "feature_room_list_sections") return featureEnabled;
|
||||
return false;
|
||||
});
|
||||
viewModel = new RoomListItemViewModel({ room, client: matrixClient });
|
||||
expect(viewModel.getSnapshot().canMoveToSection).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Actions", () => {
|
||||
it("should dispatch view room action on openRoom", () => {
|
||||
viewModel = new RoomListItemViewModel({ room, client: matrixClient });
|
||||
@@ -572,6 +588,13 @@ describe("RoomListItemViewModel", () => {
|
||||
room_id: "!room:server",
|
||||
});
|
||||
});
|
||||
|
||||
it("should call createSection on RoomListStoreV3 when onCreateSection is called", () => {
|
||||
const createSectionSpy = jest.spyOn(RoomListStoreV3.instance, "createSection").mockResolvedValue();
|
||||
viewModel = new RoomListItemViewModel({ room, client: matrixClient });
|
||||
viewModel.onCreateSection();
|
||||
expect(createSectionSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Cleanup", () => {
|
||||
|
||||
@@ -9,7 +9,7 @@ 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, mkStubRoom, stubClient } from "../../test-utils";
|
||||
import { createTestClient, flushPromises, flushPromisesWithFakeTimers, mkStubRoom, stubClient } from "../../test-utils";
|
||||
import RoomListStoreV3, { CHATS_TAG, 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";
|
||||
@@ -589,6 +589,14 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
describe("Cleanup", () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it("should dispose all room item view models on dispose", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
|
||||
@@ -604,6 +612,51 @@ describe("RoomListViewModel", () => {
|
||||
expect(disposeSpy2).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("Toast", () => {
|
||||
it("should show toast when SectionCreated event fires", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
RoomListStoreV3.instance.emit(RoomListStoreV3Event.SectionCreated);
|
||||
expect(viewModel.getSnapshot().toast).toBe("section_created");
|
||||
});
|
||||
|
||||
it("should clear toast when closeToast is called", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
|
||||
RoomListStoreV3.instance.emit(RoomListStoreV3Event.SectionCreated);
|
||||
expect(viewModel.getSnapshot().toast).toBe("section_created");
|
||||
|
||||
viewModel.closeToast();
|
||||
expect(viewModel.getSnapshot().toast).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should auto-close toast after 15 seconds", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
|
||||
RoomListStoreV3.instance.emit(RoomListStoreV3Event.SectionCreated);
|
||||
expect(viewModel.getSnapshot().toast).toBe("section_created");
|
||||
|
||||
jest.advanceTimersByTime(15 * 1000);
|
||||
expect(viewModel.getSnapshot().toast).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should reset the auto-close timer when a new section is created", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
|
||||
RoomListStoreV3.instance.emit(RoomListStoreV3Event.SectionCreated);
|
||||
jest.advanceTimersByTime(10 * 1000);
|
||||
|
||||
// Second section created — resets the timer
|
||||
RoomListStoreV3.instance.emit(RoomListStoreV3Event.SectionCreated);
|
||||
jest.advanceTimersByTime(10 * 1000);
|
||||
|
||||
// Toast should still be visible (only 10s since last emit)
|
||||
expect(viewModel.getSnapshot().toast).toBe("section_created");
|
||||
|
||||
jest.advanceTimersByTime(5 * 1000);
|
||||
expect(viewModel.getSnapshot().toast).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Sections (feature_room_list_sections)", () => {
|
||||
let favRoom1: Room;
|
||||
let favRoom2: Room;
|
||||
@@ -922,7 +975,7 @@ describe("RoomListViewModel", () => {
|
||||
action: Action.ActiveRoomChanged,
|
||||
newRoomId: "!fav1:server",
|
||||
});
|
||||
await flushPromises();
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
expect(viewModel.getSnapshot().roomListState.activeRoomIndex).toBe(0);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user