RoomList: add back Favourites and Low Priority filters when sections are disabled (#34162)
* Add back Favourites and Low Priority filter into shared components * Wireup RoomList.showSections and filters in room list vm * Add e2e test for filters and showSection setting
This commit is contained in:
@@ -129,6 +129,46 @@ test.describe("Room list sections", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Filters when sections are disabled", () => {
|
||||
test.beforeEach(async ({ app }) => {
|
||||
await app.settings.setValue("RoomList.showSections", null, SettingLevel.ACCOUNT, false);
|
||||
|
||||
// A favourite room, a low priority room, and a regular room.
|
||||
const favouriteId = await app.client.createRoom({ name: "favourite room" });
|
||||
await app.client.evaluate(async (client, roomId) => {
|
||||
await client.setRoomTag(roomId, "m.favourite");
|
||||
}, favouriteId);
|
||||
const lowPrioId = await app.client.createRoom({ name: "low prio room" });
|
||||
await app.client.evaluate(async (client, roomId) => {
|
||||
await client.setRoomTag(roomId, "m.lowpriority");
|
||||
}, lowPrioId);
|
||||
await app.client.createRoom({ name: "regular room" });
|
||||
});
|
||||
|
||||
test("shows the Favourites and Low Priority filters and filters the flat list", async ({ page }) => {
|
||||
const roomList = getRoomList(page);
|
||||
const primaryFilters = getPrimaryFilters(page);
|
||||
|
||||
// Expand the filter list to reveal all filters
|
||||
await primaryFilters.getByRole("button", { name: "Expand filter list" }).click();
|
||||
|
||||
// The Favourites and Low Priority filters are available again when sections are disabled
|
||||
await expect(primaryFilters.getByRole("option", { name: "Favourites" })).toBeVisible();
|
||||
await expect(primaryFilters.getByRole("option", { name: "Low priority" })).toBeVisible();
|
||||
|
||||
// Filtering by Favourites shows only the favourite room
|
||||
await primaryFilters.getByRole("option", { name: "Favourites" }).click();
|
||||
await expect(roomList.getByRole("option", { name: "Open room favourite room" })).toBeVisible();
|
||||
await expect(roomList.getByRole("option", { name: "Open room regular room" })).not.toBeVisible();
|
||||
await expect(roomList.getByRole("option", { name: "Open room low prio room" })).not.toBeVisible();
|
||||
|
||||
// Switching to the Low Priority filter shows only the low priority room
|
||||
await primaryFilters.getByRole("option", { name: "Low priority" }).click();
|
||||
await expect(roomList.getByRole("option", { name: "Open room low prio room" })).toBeVisible();
|
||||
await expect(roomList.getByRole("option", { name: "Open room favourite room" })).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Section collapse and expand", () => {
|
||||
[
|
||||
{ section: "Favourites", roomName: "favourite room", tag: "m.favourite" },
|
||||
|
||||
@@ -64,10 +64,30 @@ const filterKeyToIdMap: Map<FilterEnum, FilterId> = new Map([
|
||||
[FilterEnum.UnreadFilter, "unread"],
|
||||
[FilterEnum.PeopleFilter, "people"],
|
||||
[FilterEnum.RoomsFilter, "rooms"],
|
||||
[FilterEnum.FavouriteFilter, "favourite"],
|
||||
[FilterEnum.MentionsFilter, "mentions"],
|
||||
[FilterEnum.InvitesFilter, "invites"],
|
||||
[FilterEnum.LowPriorityFilter, "low_priority"],
|
||||
]);
|
||||
|
||||
/**
|
||||
* Filters that are redundant when sections are enabled: Favourites and Low Priority rooms
|
||||
* already have their own sections, so these filters are only shown as chips when sectioning
|
||||
* is disabled (see {@link getVisibleFilterIds}).
|
||||
*/
|
||||
const SECTION_ONLY_FILTER_IDS: ReadonlySet<FilterId> = new Set<FilterId>(["favourite", "low_priority"]);
|
||||
|
||||
/**
|
||||
* Compute the filter ids to display as primary filter chips.
|
||||
* When sections are enabled, the Favourites and Low Priority filters are hidden because those
|
||||
* rooms are surfaced as dedicated sections instead.
|
||||
*/
|
||||
function getVisibleFilterIds(): FilterId[] {
|
||||
const areSectionsEnabled = SettingsStore.getValue("RoomList.showSections");
|
||||
const filterIds = [...filterKeyToIdMap.values()];
|
||||
return areSectionsEnabled ? filterIds.filter((id) => !SECTION_ONLY_FILTER_IDS.has(id)) : filterIds;
|
||||
}
|
||||
|
||||
const TAG_TO_TITLE_MAP: Record<string, string> = {
|
||||
[DefaultTagID.Favourite]: _t("room_list|section|favourites"),
|
||||
[CHATS_TAG]: _t("room_list|section|chats"),
|
||||
@@ -144,7 +164,7 @@ export class RoomListViewModel
|
||||
const roomsResult = RoomListStoreV3.instance.getSortedRoomsInActiveSpace(undefined);
|
||||
const canCreateRoom = hasCreateRoomRights(props.client, activeSpace);
|
||||
|
||||
const filterIds = [...filterKeyToIdMap.values()];
|
||||
const filterIds = getVisibleFilterIds();
|
||||
|
||||
// By default, all sections are expanded
|
||||
const { sections, isFlatList } = computeSections(roomsResult, (tag) => true);
|
||||
@@ -214,6 +234,10 @@ export class RoomListViewModel
|
||||
dispatcher.unregister(dispatcherRef);
|
||||
});
|
||||
|
||||
// Recompute the lis when setting changes
|
||||
const showSectionsRef = SettingsStore.watchSetting("RoomList.showSections", null, this.onShowSectionsChange);
|
||||
this.disposables.track(() => SettingsStore.unwatchSetting(showSectionsRef));
|
||||
|
||||
// Track cleanup of all child view models
|
||||
this.disposables.track(() => {
|
||||
for (const viewModel of this.roomItemViewModels.values()) {
|
||||
@@ -249,6 +273,20 @@ export class RoomListViewModel
|
||||
this.updateRoomListData();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle changes to the {@link RoomList.showSections} setting.
|
||||
* Toggling sections is a rare action, so we simply reset the filters and rebuild
|
||||
* the list from scratch rather than trying to reconcile the previous state.
|
||||
*/
|
||||
private readonly onShowSectionsChange = (): void => {
|
||||
this.activeFilter = undefined;
|
||||
this.clearViewModels();
|
||||
this.roomsResult = RoomListStoreV3.instance.getSortedRoomsInActiveSpace();
|
||||
this.updateRoomsMap(this.roomsResult);
|
||||
this.snapshot.merge({ filterIds: getVisibleFilterIds() });
|
||||
this.updateRoomListData();
|
||||
};
|
||||
|
||||
/**
|
||||
* Add rooms from the RoomsResult to the roomsMap for quick lookup.
|
||||
* This does not clear the roomsMap.
|
||||
|
||||
@@ -350,6 +350,75 @@ describe("RoomListViewModel", () => {
|
||||
"!room3:server",
|
||||
]);
|
||||
});
|
||||
|
||||
describe("Favourites and Low Priority filters (RoomList.showSections)", () => {
|
||||
function mockShowSections(showSections: boolean): void {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => {
|
||||
if (setting === "RoomList.showSections") return showSections;
|
||||
if (setting === "RoomList.CustomSectionData") return {};
|
||||
if (setting === "RoomList.OrderedCustomSections") return [];
|
||||
return undefined as any;
|
||||
});
|
||||
}
|
||||
|
||||
it("hides the Favourites and Low Priority filters when sections are enabled", () => {
|
||||
mockShowSections(true);
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
|
||||
const { filterIds } = viewModel.getSnapshot();
|
||||
expect(filterIds).not.toContain("favourite");
|
||||
expect(filterIds).not.toContain("low_priority");
|
||||
});
|
||||
|
||||
it("shows the Favourites and Low Priority filters when sections are disabled", () => {
|
||||
mockShowSections(false);
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
|
||||
const { filterIds } = viewModel.getSnapshot();
|
||||
expect(filterIds).toContain("favourite");
|
||||
expect(filterIds).toContain("low_priority");
|
||||
});
|
||||
|
||||
it("recomputes the filters and clears the active filter when the setting changes", () => {
|
||||
let showSections = false;
|
||||
let watchCallback: () => void = () => {};
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => {
|
||||
if (setting === "RoomList.showSections") return showSections;
|
||||
if (setting === "RoomList.CustomSectionData") return {};
|
||||
if (setting === "RoomList.OrderedCustomSections") return [];
|
||||
return undefined as any;
|
||||
});
|
||||
jest.spyOn(SettingsStore, "watchSetting").mockImplementation((setting, _room, callback) => {
|
||||
if (setting === "RoomList.showSections") watchCallback = callback as () => void;
|
||||
return "watcher-id";
|
||||
});
|
||||
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
expect(viewModel.getSnapshot().filterIds).toContain("favourite");
|
||||
|
||||
// Activate the Favourites filter
|
||||
jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({
|
||||
spaceId: "home",
|
||||
sections: [{ tag: CHATS_TAG, rooms: [room1] }],
|
||||
filterKeys: [FilterEnum.FavouriteFilter],
|
||||
});
|
||||
viewModel.onToggleFilter("favourite");
|
||||
expect(viewModel.getSnapshot().activeFilterId).toBe("favourite");
|
||||
|
||||
// Enabling sections hides the Favourites filter and resets the active filter
|
||||
showSections = true;
|
||||
jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({
|
||||
spaceId: "home",
|
||||
sections: [{ tag: CHATS_TAG, rooms: [room1, room2, room3] }],
|
||||
});
|
||||
watchCallback();
|
||||
|
||||
const snapshot = viewModel.getSnapshot();
|
||||
expect(snapshot.filterIds).not.toContain("favourite");
|
||||
expect(snapshot.filterIds).not.toContain("low_priority");
|
||||
expect(snapshot.activeFilterId).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Room item view models", () => {
|
||||
|
||||
@@ -148,7 +148,9 @@
|
||||
"expand_all_sections": "Expand all sections",
|
||||
"expand_filters": "Expand filter list",
|
||||
"filters": {
|
||||
"favourite": "Favourites",
|
||||
"invites": "Invites",
|
||||
"low_priority": "Low priority",
|
||||
"mentions": "Mentions",
|
||||
"people": "People",
|
||||
"rooms": "Rooms",
|
||||
|
||||
+4
@@ -26,10 +26,14 @@ const filterIdToLabel = (filterId: FilterId): string => {
|
||||
return _t("room_list|filters|people");
|
||||
case "rooms":
|
||||
return _t("room_list|filters|rooms");
|
||||
case "favourite":
|
||||
return _t("room_list|filters|favourite");
|
||||
case "mentions":
|
||||
return _t("room_list|filters|mentions");
|
||||
case "invites":
|
||||
return _t("room_list|filters|invites");
|
||||
case "low_priority":
|
||||
return _t("room_list|filters|low_priority");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import { useEffect, useState } from "react";
|
||||
* Standard filter identifiers that can be used across implementations.
|
||||
* These are stable keys - the view layer maps them to translated labels.
|
||||
*/
|
||||
export type FilterId = "unread" | "people" | "rooms" | "mentions" | "invites";
|
||||
export type FilterId = "unread" | "people" | "rooms" | "favourite" | "mentions" | "invites" | "low_priority";
|
||||
|
||||
/**
|
||||
* A hook to sort the filter IDs by active state.
|
||||
|
||||
Reference in New Issue
Block a user