Room list: assign room to custom section (#33238)
* feat(sc): add new toast type for room list * feat(sc): add section entries in room list item menu * feat(rls): expose util functions * feat: allows to tag room with custom sections * feat(vm): add new Chat moved toast to room list vm * feat(vm): add section selection to room list item vm * feat(e2e): add tests for adding room in a custom section * test(e2e): update existing screenshots * chore: fix lint after merge * chore: remove outline in test
This commit is contained in:
@@ -11,6 +11,7 @@ import { Room } from "matrix-js-sdk/src/matrix";
|
||||
import RoomListActions from "../../../../src/actions/RoomListActions";
|
||||
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
|
||||
import { DefaultTagID, type TagID } from "../../../../src/stores/room-list-v3/skip-list/tag";
|
||||
import { CUSTOM_SECTION_TAG_PREFIX } from "../../../../src/stores/room-list-v3/section";
|
||||
import { tagRoom } from "../../../../src/utils/room/tagRoom";
|
||||
import { getMockClientWithEventEmitter } from "../../../test-utils";
|
||||
import * as getTagsForRoomUtils from "../../../../src/utils/room/getTagsForRoom";
|
||||
@@ -18,6 +19,7 @@ import * as getTagsForRoomUtils from "../../../../src/utils/room/getTagsForRoom"
|
||||
describe("tagRoom()", () => {
|
||||
const userId = "@alice:server.org";
|
||||
const roomId = "!room:server.org";
|
||||
const customTag = `${CUSTOM_SECTION_TAG_PREFIX}my-section`;
|
||||
|
||||
const makeRoom = (tags: TagID[] = []): Room => {
|
||||
const client = getMockClientWithEventEmitter({
|
||||
@@ -59,7 +61,7 @@ describe("tagRoom()", () => {
|
||||
expect(RoomListActions.tagRoom).toHaveBeenCalledWith(
|
||||
room.client,
|
||||
room,
|
||||
DefaultTagID.LowPriority, // remove
|
||||
null, // remove
|
||||
DefaultTagID.Favourite, // add
|
||||
);
|
||||
});
|
||||
@@ -73,10 +75,24 @@ describe("tagRoom()", () => {
|
||||
expect(RoomListActions.tagRoom).toHaveBeenCalledWith(
|
||||
room.client,
|
||||
room,
|
||||
DefaultTagID.Favourite, // remove
|
||||
null, // remove
|
||||
DefaultTagID.LowPriority, // add
|
||||
);
|
||||
});
|
||||
|
||||
it("should tag a room with a custom section", () => {
|
||||
const room = makeRoom();
|
||||
|
||||
tagRoom(room, customTag);
|
||||
|
||||
expect(defaultDispatcher.dispatch).toHaveBeenCalled();
|
||||
expect(RoomListActions.tagRoom).toHaveBeenCalledWith(
|
||||
room.client,
|
||||
room,
|
||||
null, // remove
|
||||
customTag, // add
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when a room is tagged as favourite", () => {
|
||||
@@ -137,4 +153,26 @@ describe("tagRoom()", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when a room is tagged with a custom section", () => {
|
||||
const otherCustomTag = `${CUSTOM_SECTION_TAG_PREFIX}other-section`;
|
||||
|
||||
it.each([
|
||||
{ label: "untag the custom section", applyTag: customTag, expectedAdd: null },
|
||||
{ label: "replace with favourite", applyTag: DefaultTagID.Favourite, expectedAdd: DefaultTagID.Favourite },
|
||||
{ label: "replace with another custom section", applyTag: otherCustomTag, expectedAdd: otherCustomTag },
|
||||
])("should $label", ({ applyTag, expectedAdd }) => {
|
||||
const room = makeRoom([customTag]);
|
||||
|
||||
tagRoom(room, applyTag);
|
||||
|
||||
expect(defaultDispatcher.dispatch).toHaveBeenCalled();
|
||||
expect(RoomListActions.tagRoom).toHaveBeenCalledWith(
|
||||
room.client,
|
||||
room,
|
||||
customTag, // remove
|
||||
expectedAdd, // add
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ import { RoomNotificationState } from "../../../src/stores/notifications/RoomNot
|
||||
import { RoomNotificationStateStore } from "../../../src/stores/notifications/RoomNotificationStateStore";
|
||||
import { NotificationStateEvents } from "../../../src/stores/notifications/NotificationState";
|
||||
import { type MessagePreview, MessagePreviewStore } from "../../../src/stores/message-preview";
|
||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||
import SettingsStore, { type CallbackFn } from "../../../src/settings/SettingsStore";
|
||||
import DMRoomMap from "../../../src/utils/DMRoomMap";
|
||||
import { DefaultTagID } from "../../../src/stores/room-list-v3/skip-list/tag";
|
||||
import dispatcher from "../../../src/dispatcher/dispatcher";
|
||||
@@ -29,7 +29,8 @@ 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";
|
||||
import RoomListStoreV3, { CHATS_TAG } from "../../../src/stores/room-list-v3/RoomListStoreV3";
|
||||
import * as tagRoomModule from "../../../src/utils/room/tagRoom";
|
||||
|
||||
jest.mock("../../../src/viewmodels/room-list/utils", () => ({
|
||||
hasAccessToOptionsMenu: jest.fn().mockReturnValue(true),
|
||||
@@ -83,6 +84,7 @@ describe("RoomListItemViewModel", () => {
|
||||
|
||||
jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockResolvedValue(null);
|
||||
jest.spyOn(CallStore.instance, "getCall").mockReturnValue(null);
|
||||
jest.spyOn(RoomListStoreV3.instance, "orderedSectionTags", "get").mockReturnValue([]);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -213,8 +215,8 @@ describe("RoomListItemViewModel", () => {
|
||||
let watchCallback: any;
|
||||
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(() => showPreview);
|
||||
jest.spyOn(SettingsStore, "watchSetting").mockImplementation((_setting, _room, callback) => {
|
||||
watchCallback = callback;
|
||||
jest.spyOn(SettingsStore, "watchSetting").mockImplementation((setting, _room, callback) => {
|
||||
if (setting === "RoomList.showMessagePreview") watchCallback = callback;
|
||||
return "watcher-id";
|
||||
});
|
||||
jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockResolvedValue({
|
||||
@@ -595,6 +597,93 @@ describe("RoomListItemViewModel", () => {
|
||||
viewModel.onCreateSection();
|
||||
expect(createSectionSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call tagRoom when onToggleSection is called", () => {
|
||||
const tagRoomSpy = jest.spyOn(tagRoomModule, "tagRoom").mockImplementation(() => {});
|
||||
viewModel = new RoomListItemViewModel({ room, client: matrixClient });
|
||||
|
||||
viewModel.onToggleSection(DefaultTagID.Favourite);
|
||||
|
||||
expect(tagRoomSpy).toHaveBeenCalledWith(room, DefaultTagID.Favourite);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Sections", () => {
|
||||
const customTag = "element.io.section.custom1";
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(RoomListStoreV3.instance, "orderedSectionTags", "get").mockReturnValue([
|
||||
DefaultTagID.Favourite,
|
||||
customTag,
|
||||
CHATS_TAG,
|
||||
DefaultTagID.LowPriority,
|
||||
]);
|
||||
});
|
||||
|
||||
it("should include sections from orderedSectionTags excluding CHATS_TAG", () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => {
|
||||
if (setting === "feature_room_list_sections") return true;
|
||||
return false;
|
||||
});
|
||||
viewModel = new RoomListItemViewModel({ room, client: matrixClient });
|
||||
|
||||
const sections = viewModel.getSnapshot().sections;
|
||||
expect(sections.map((s) => s.tag)).toEqual([DefaultTagID.Favourite, customTag, DefaultTagID.LowPriority]);
|
||||
});
|
||||
|
||||
it("should mark the room current section as selected", () => {
|
||||
room.tags = { [DefaultTagID.Favourite]: { order: 0 } };
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => {
|
||||
if (setting === "feature_room_list_sections") return true;
|
||||
return false;
|
||||
});
|
||||
viewModel = new RoomListItemViewModel({ room, client: matrixClient });
|
||||
|
||||
const sections = viewModel.getSnapshot().sections;
|
||||
expect(sections.find((s) => s.tag === DefaultTagID.Favourite)?.isSelected).toBe(true);
|
||||
expect(sections.find((s) => s.tag === DefaultTagID.LowPriority)?.isSelected).toBe(false);
|
||||
});
|
||||
|
||||
it("should use custom section name from CustomSectionData", () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => {
|
||||
if (setting === "feature_room_list_sections") return true;
|
||||
if (setting === "RoomList.CustomSectionData")
|
||||
return { [customTag]: { name: "My Custom Section", tag: customTag } };
|
||||
return false;
|
||||
});
|
||||
viewModel = new RoomListItemViewModel({ room, client: matrixClient });
|
||||
|
||||
const section = viewModel.getSnapshot().sections.find((s) => s.tag === customTag);
|
||||
expect(section?.name).toBe("My Custom Section");
|
||||
});
|
||||
|
||||
it("should update sections when OrderedCustomSections setting changes", () => {
|
||||
let watchCallback: CallbackFn = () => {};
|
||||
jest.spyOn(SettingsStore, "watchSetting").mockImplementation((setting, _room, callback) => {
|
||||
if (setting === "RoomList.OrderedCustomSections") watchCallback = callback;
|
||||
return "watcher-id";
|
||||
});
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => {
|
||||
if (setting === "feature_room_list_sections") return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
viewModel = new RoomListItemViewModel({ room, client: matrixClient });
|
||||
expect(viewModel.getSnapshot().sections).toHaveLength(3); // Favourite, custom, LowPriority
|
||||
|
||||
// Simulate reordering: custom section removed
|
||||
jest.spyOn(RoomListStoreV3.instance, "orderedSectionTags", "get").mockReturnValue([
|
||||
DefaultTagID.Favourite,
|
||||
CHATS_TAG,
|
||||
DefaultTagID.LowPriority,
|
||||
]);
|
||||
watchCallback("RoomList.OrderedCustomSections", null, null as any, null, null);
|
||||
|
||||
expect(viewModel.getSnapshot().sections.map((s) => s.tag)).toEqual([
|
||||
DefaultTagID.Favourite,
|
||||
DefaultTagID.LowPriority,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Cleanup", () => {
|
||||
|
||||
@@ -619,6 +619,12 @@ describe("RoomListViewModel", () => {
|
||||
expect(viewModel.getSnapshot().toast).toBe("section_created");
|
||||
});
|
||||
|
||||
it("should show toast when RoomTagged event fires", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
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 });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user