Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -204,36 +204,45 @@ test.describe("Room list filters and sort", () => {
await expect(primaryFilters.locator("role=option").first()).toHaveText("Invites");
});

test(
"unread filter should only match unread rooms that have a count",
{ tag: "@screenshot" },
async ({ page, app, bot }) => {
const roomListView = getRoomList(page);
const primaryFilters = getPrimaryFilters(page);

// Let's configure unread dm room so that we only get notification for mentions and keywords
await app.viewRoomById(unReadDmId);
await app.settings.openRoomSettings("Notifications");
await page.getByText("@mentions & keywords").click();
await app.settings.closeDialog();

// Let's open a room other than unread room or unread dm
await roomListView.getByRole("button", { name: "Open room favourite room" }).click();

// Let's make the bot send a new message in both rooms
await bot.sendMessage(unReadDmId, "Hello!");
await bot.sendMessage(unReadRoomId, "Hello!");

// Let's activate the unread filter now
await primaryFilters.getByRole("option", { name: "Unread" }).click();

// Unread filter should only show unread room and not unread dm!
const unreadDm = roomListView.getByRole("option", { name: "Open room unread room" });
await expect(unreadDm).toBeVisible();
await expect(unreadDm).toMatchScreenshot("unread-dm.png");
await expect(roomListView.getByRole("option", { name: "Open room unread dm" })).not.toBeVisible();
},
);
test("unread filter should obey the showbold setting", async ({ page, app, bot }) => {
const roomListView = getRoomList(page);
const primaryFilters = getPrimaryFilters(page);

// Set the "showbold" setting to off
await app.settings.setValue("Notifications.showbold", null, SettingLevel.DEVICE, false);

// The unread DM room only notifies for mentions and keywords
await app.viewRoomById(unReadDmId);
await app.settings.openRoomSettings("Notifications");
await page.getByText("@mentions & keywords").click();
await app.settings.closeDialog();

// (The unread non-DM room still notifies for all activity)

// Open some other room
await roomListView.getByRole("button", { name: "Open room favourite room" }).click();

// Send a message in the unread DM room and the unread non-DM room
await bot.sendMessage(unReadDmId, "Hello!");
await bot.sendMessage(unReadRoomId, "Hello!");

// Turn the "Unreads" filter on
await primaryFilters.getByRole("option", { name: "Unreads" }).click();

const unreadRoom = roomListView.getByRole("option", { name: "Open room unread room" });
const unreadDm = roomListView.getByRole("option", { name: "Open room unread dm" });

// Only the unread room is visible. The DM room is hidden.
await expect(unreadRoom).toBeVisible();
await expect(unreadDm).not.toBeVisible();

// Now set "showbold" to on
await app.settings.setValue("Notifications.showbold", null, SettingLevel.DEVICE, true);

// Now both unread rooms are visible
await expect(unreadRoom).toBeVisible();
await expect(unreadDm).toBeVisible();
});

test("should sort the room list alphabetically", async ({ page }) => {
const roomListView = getRoomList(page);
Expand Down
Binary file not shown.
11 changes: 11 additions & 0 deletions apps/web/src/stores/room-list-v3/RoomListStoreV3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export class RoomListStoreV3Class extends AsyncStoreWithClient<EmptyObject> {
});
SpaceStore.instance.on(UPDATE_HOME_BEHAVIOUR, () => this.onActiveSpaceChanged());
SettingsStore.watchSetting("RoomList.OrderedCustomSections", null, () => this.onOrderedCustomSectionsChange());
SettingsStore.watchSetting("Notifications.showbold", null, () => this.onShowBoldChange());
this.loadCustomSections();

SettingsStore.watchSetting("RoomList.showSections", null, () => this.scheduleEmit());
Expand Down Expand Up @@ -517,6 +518,16 @@ export class RoomListStoreV3Class extends AsyncStoreWithClient<EmptyObject> {
this.scheduleEmit();
}

/**
* Handle changes to the showbold setting.
* Updates the skip list filters to reflect the new setting and emits an update.
* Emit {@link LISTS_UPDATE_EVENT}.
*/
private onShowBoldChange(): void {
this.roomSkipList?.useNewFilters(this.getSkipListFilters());
this.scheduleEmit();
}

/**
* Create a new section.
* Emits {@link SECTION_CREATED_EVENT} if the section was successfully created.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,18 @@ import type { Room } from "matrix-js-sdk/src/matrix";
import { type Filter, FilterEnum } from ".";
import { RoomNotificationStateStore } from "../../../notifications/RoomNotificationStateStore";
import { getMarkedUnreadState } from "../../../../utils/notifications";
import SettingsStore from "../../../../settings/SettingsStore";

export class UnreadFilter implements Filter {
public matches(room: Room): boolean {
return RoomNotificationStateStore.instance.getRoomState(room).hasUnreadCount || !!getMarkedUnreadState(room);
// If the user marked this room as unread, it's unread
if (getMarkedUnreadState(room)) {
return true;
}

const showBold = SettingsStore.getValue("Notifications.showbold");
const notifState = RoomNotificationStateStore.instance.getRoomState(room);
return showBold ? notifState.hasAnyNotificationOrActivity : notifState.hasUnreadCount;
}

public get key(): FilterEnum.UnreadFilter {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -593,15 +593,58 @@ describe("RoomListStoreV3", () => {
}
});

it("supports filtering unread rooms", async () => {
it("supports filtering unread rooms when showbold is off", async () => {
// Ensure that Notifications.showbold is off
jest.spyOn(SettingsStore, "getValue").mockImplementation(() => false);

const { client, rooms } = getClientAndRooms();
// Let's choose 5 rooms to put in space
const { spaceRoom, roomIds } = createSpace(rooms, [6, 8, 13, 27, 75], client);

// Let's say 8, 27 are unread
jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockImplementation((room) => {
const state = {
// This should be used, because showbold is off
hasUnreadCount: [rooms[8], rooms[27]].includes(room),

// This should not be used
hasAnyNotificationOrActivity: false,
} as unknown as RoomNotificationState;
return state;
});

setupMocks(spaceRoom, roomIds);
const store = new RoomListStoreV3Class(dispatcher);
await store.start();

// Should only give us rooms at index 8 and 27
const result = store
.getSortedRoomsInActiveSpace([FilterEnum.UnreadFilter])
.sections.flatMap((s) => s.rooms);
expect(result).toHaveLength(2);
for (const i of [8, 27]) {
expect(result).toContain(rooms[i]);
}
});

it("supports filtering unread rooms when showbold is on", async () => {
// Ensure that Notifications.showbold is on
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting: string) => {
return setting === "Notifications.showbold";
});

const { client, rooms } = getClientAndRooms();
// Let's choose 5 rooms to put in space
const { spaceRoom, roomIds } = createSpace(rooms, [6, 8, 13, 27, 75], client);

// Let's say 8, 27 are unread
jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockImplementation((room) => {
const state = {
// This should be used, because showbold is off
hasUnreadCount: false,

// This should not be used
hasAnyNotificationOrActivity: [rooms[8], rooms[27]].includes(room),
} as unknown as RoomNotificationState;
return state;
});
Expand Down Expand Up @@ -777,6 +820,7 @@ describe("RoomListStoreV3", () => {
jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockImplementation((room) => {
const state = {
hasUnreadCount: [rooms[8], rooms[27]].includes(room),
hasAnyNotificationOrActivity: [rooms[8], rooms[27]].includes(room),
} as unknown as RoomNotificationState;
return state;
});
Expand Down Expand Up @@ -825,6 +869,40 @@ describe("RoomListStoreV3", () => {
store.getSortedRoomsInActiveSpace([FilterEnum.InvitesFilter]).sections.flatMap((s) => s.rooms),
).toContain(room);
});

it("updates filters when showbold setting changes", async () => {
// Given one room is "bold" (unread) and one has a notification
const { store, rooms } = await getRoomListStore();
jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockImplementation((room) => {
const state = {
// Only 27 has notifications
hasUnreadCount: [rooms[27]].includes(room),
// But both 8 and 27 have unread messages (bold)
hasAnyNotificationOrActivity: [rooms[8], rooms[27]].includes(room),
} as unknown as RoomNotificationState;
return state;
});

// When showbold is set to true
await SettingsStore.setValue("Notifications.showbold", null, SettingLevel.DEVICE, true);

// Then both rooms are in the room list
const showboldRooms = store
.getSortedRoomsInActiveSpace([FilterEnum.UnreadFilter])
.sections.flatMap((s) => s.rooms);
expect(showboldRooms).toContain(rooms[27]);
expect(showboldRooms).toContain(rooms[8]);

// But when showbold is set to false
await SettingsStore.setValue("Notifications.showbold", null, SettingLevel.DEVICE, false);

// Then only the room with a notification is in the room list
const noShowboldRooms = store
.getSortedRoomsInActiveSpace([FilterEnum.UnreadFilter])
.sections.flatMap((s) => s.rooms);
expect(noShowboldRooms).toContain(rooms[27]);
expect(noShowboldRooms).not.toContain(rooms[8]);
});
});

describe("getServerNoticeRooms", () => {
Expand Down
Loading