From e247d3180806e42b941818166334429087cbbd10 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Fri, 15 Mar 2024 10:11:52 +0100 Subject: [PATCH] TAC: Order rooms by most recent after notification level (#12329) * Order room by thread timestamp * Fix key errors in test * Update jest snapshots * Update snapshots * Rename alpha/beta to numbers * Add playwright test --- .../spaces/threads-activity-centre/index.ts | 44 +++-- .../threadsActivityCentre.spec.ts | 29 +-- .../tac-panel-mix-unread-linux.png | Bin 8147 -> 6969 bytes .../tac-panel-notification-unread-linux.png | Bin 8158 -> 6972 bytes .../useUnreadThreadRooms.ts | 29 ++- .../spaces/ThreadsActivityCentre-test.tsx | 78 ++++++-- .../ThreadsActivityCentre-test.tsx.snap | 177 +++++++++++++++++- 7 files changed, 297 insertions(+), 60 deletions(-) diff --git a/playwright/e2e/spaces/threads-activity-centre/index.ts b/playwright/e2e/spaces/threads-activity-centre/index.ts index 224028d06f..4360ddb981 100644 --- a/playwright/e2e/spaces/threads-activity-centre/index.ts +++ b/playwright/e2e/spaces/threads-activity-centre/index.ts @@ -30,30 +30,30 @@ import { ElementAppPage } from "../../../pages/ElementAppPage"; * - Invite the bot to both rooms and ensure that it has joined */ export const test = base.extend<{ - roomAlphaName?: string; - roomAlpha: { name: string; roomId: string }; - roomBetaName?: string; - roomBeta: { name: string; roomId: string }; + room1Name?: string; + room1: { name: string; roomId: string }; + room2Name?: string; + room2: { name: string; roomId: string }; msg: MessageBuilder; util: Helpers; }>({ displayName: "Mae", botCreateOpts: { displayName: "Other User" }, - roomAlphaName: "Room Alpha", - roomAlpha: async ({ roomAlphaName: name, app, user, bot }, use) => { + room1Name: "Room 1", + room1: async ({ room1Name: name, app, user, bot }, use) => { const roomId = await app.client.createRoom({ name, invite: [bot.credentials.userId] }); await use({ name, roomId }); }, - roomBetaName: "Room Beta", - roomBeta: async ({ roomBetaName: name, app, user, bot }, use) => { + room2Name: "Room 2", + room2: async ({ room2Name: name, app, user, bot }, use) => { const roomId = await app.client.createRoom({ name, invite: [bot.credentials.userId] }); await use({ name, roomId }); }, msg: async ({ page, app, util }, use) => { await use(new MessageBuilder(page, app, util)); }, - util: async ({ roomAlpha, roomBeta, page, app, bot }, use) => { + util: async ({ room1, room2, page, app, bot }, use) => { await use(new Helpers(page, app, bot)); }, }); @@ -337,23 +337,27 @@ export class Helpers { * @param room1 * @param room2 * @param msg - MessageBuilder + * @param hasMention - whether to include a mention in the first message */ async populateThreads( room1: { name: string; roomId: string }, room2: { name: string; roomId: string }, msg: MessageBuilder, + hasMention = true, ) { - await this.receiveMessages(room2, [ - "Msg1", - msg.threadedOff("Msg1", { - "body": "User", - "format": "org.matrix.custom.html", - "formatted_body": "User", - "m.mentions": { - user_ids: ["@user:localhost"], - }, - }), - ]); + if (hasMention) { + await this.receiveMessages(room2, [ + "Msg1", + msg.threadedOff("Msg1", { + "body": "User", + "format": "org.matrix.custom.html", + "formatted_body": "User", + "m.mentions": { + user_ids: ["@user:localhost"], + }, + }), + ]); + } await this.receiveMessages(room2, ["Msg2", msg.threadedOff("Msg2", "Resp2")]); await this.receiveMessages(room1, ["Msg3", msg.threadedOff("Msg3", "Resp3")]); } diff --git a/playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts b/playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts index 065d7d82ec..93094073b3 100644 --- a/playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts +++ b/playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts @@ -35,7 +35,7 @@ test.describe("Threads Activity Centre", () => { await expect(util.getSpacePanel()).toMatchScreenshot("tac-button-expanded.png"); }); - test("should not show indicator when there is no thread", async ({ roomAlpha: room1, util }) => { + test("should not show indicator when there is no thread", async ({ room1, util }) => { // No indicator should be shown await util.assertNoTacIndicator(); @@ -46,11 +46,7 @@ test.describe("Threads Activity Centre", () => { await util.assertNoTacIndicator(); }); - test("should show a notification indicator when there is a message in a thread", async ({ - roomAlpha: room1, - util, - msg, - }) => { + test("should show a notification indicator when there is a message in a thread", async ({ room1, util, msg }) => { await util.goTo(room1); await util.receiveMessages(room1, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); @@ -58,11 +54,7 @@ test.describe("Threads Activity Centre", () => { await util.assertNotificationTac(); }); - test("should show a highlight indicator when there is a mention in a thread", async ({ - roomAlpha: room1, - util, - msg, - }) => { + test("should show a highlight indicator when there is a mention in a thread", async ({ room1, util, msg }) => { await util.goTo(room1); await util.receiveMessages(room1, [ "Msg1", @@ -80,7 +72,7 @@ test.describe("Threads Activity Centre", () => { await util.assertHighlightIndicator(); }); - test("should show the rooms with unread threads", async ({ roomAlpha: room1, roomBeta: room2, util, msg }) => { + test("should show the rooms with unread threads", async ({ room1, room2, util, msg }) => { await util.goTo(room2); await util.populateThreads(room1, room2, msg); // The indicator should be shown @@ -97,7 +89,7 @@ test.describe("Threads Activity Centre", () => { await expect(util.getTacPanel()).toMatchScreenshot("tac-panel-mix-unread.png"); }); - test("should update with a thread is read", async ({ roomAlpha: room1, roomBeta: room2, util, msg }) => { + test("should update with a thread is read", async ({ room1, room2, util, msg }) => { await util.goTo(room2); await util.populateThreads(room1, room2, msg); @@ -120,6 +112,17 @@ test.describe("Threads Activity Centre", () => { await expect(util.getTacPanel()).toMatchScreenshot("tac-panel-notification-unread.png"); }); + test("should order by recency after notification level", async ({ room1, room2, util, msg }) => { + await util.goTo(room2); + await util.populateThreads(room1, room2, msg, false); + + await util.openTac(); + await util.assertRoomsInTac([ + { room: room1.name, notificationLevel: "notification" }, + { room: room2.name, notificationLevel: "notification" }, + ]); + }); + test("should block the Spotlight to open when the TAC is opened", async ({ util, page }) => { const toggleSpotlight = () => page.keyboard.press(`${CommandOrControl}+k`); diff --git a/playwright/snapshots/spaces/threads-activity-centre/threadsActivityCentre.spec.ts/tac-panel-mix-unread-linux.png b/playwright/snapshots/spaces/threads-activity-centre/threadsActivityCentre.spec.ts/tac-panel-mix-unread-linux.png index f02a943d6bd1ed88d52c6488e1fdb1949c112cf3..ec5a8193d25ff8e68a1ce6f4e69e504c8bdc6ff1 100644 GIT binary patch literal 6969 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 P76y1PqbywlHuC#_+vd}2 literal 8147 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 diff --git a/src/components/views/spaces/threads-activity-centre/useUnreadThreadRooms.ts b/src/components/views/spaces/threads-activity-centre/useUnreadThreadRooms.ts index 115b208560..72b5380fbd 100644 --- a/src/components/views/spaces/threads-activity-centre/useUnreadThreadRooms.ts +++ b/src/components/views/spaces/threads-activity-centre/useUnreadThreadRooms.ts @@ -89,12 +89,12 @@ function computeUnreadThreadRooms(mxClient: MatrixClient, msc3946ProcessDynamicP const visibleRooms = mxClient.getVisibleRooms(msc3946ProcessDynamicPredecessor); let greatestNotificationLevel = NotificationLevel.None; - const rooms = []; + const rooms: Result["rooms"] = []; for (const room of visibleRooms) { // We only care about rooms with unread threads if (VisibilityProvider.instance.isRoomVisible(room) && doesRoomHaveUnreadThreads(room)) { - // Get the greatest notification level of all rooms + // Get the greatest notification level of all threads const notificationLevel = getThreadNotificationLevel(room); // If the room has an activity notification or less, we ignore it @@ -110,20 +110,35 @@ function computeUnreadThreadRooms(mxClient: MatrixClient, msc3946ProcessDynamicP } } - const sortedRooms = rooms.sort((a, b) => sortRoom(a.notificationLevel, b.notificationLevel)); + const sortedRooms = rooms.sort((a, b) => sortRoom(a, b)); return { greatestNotificationLevel, rooms: sortedRooms }; } +/** + * Store the room and its thread notification level + */ +type RoomData = Result["rooms"][0]; + /** * Sort notification level by the most important notification level to the least important * Highlight > Notification > Activity - * @param notificationLevelA - notification level of room A - * @param notificationLevelB - notification level of room B + * If the notification level is the same, we sort by the most recent thread + * @param roomDataA - room and notification level of room A + * @param roomDataB - room and notification level of room B * @returns {number} */ -function sortRoom(notificationLevelA: NotificationLevel, notificationLevelB: NotificationLevel): number { +function sortRoom(roomDataA: RoomData, roomDataB: RoomData): number { + const { notificationLevel: notificationLevelA, room: roomA } = roomDataA; + const { notificationLevel: notificationLevelB, room: roomB } = roomDataB; + + const timestampA = roomA.getLastThread()?.events.at(-1)?.getTs(); + const timestampB = roomB.getLastThread()?.events.at(-1)?.getTs(); + // NotificationLevel is a numeric enum, so we can compare them directly if (notificationLevelA > notificationLevelB) return -1; else if (notificationLevelB > notificationLevelA) return 1; - else return 0; + // Display most recent first + else if (!timestampA) return 1; + else if (!timestampB) return -1; + else return timestampB - timestampA; } diff --git a/test/components/views/spaces/ThreadsActivityCentre-test.tsx b/test/components/views/spaces/ThreadsActivityCentre-test.tsx index f5f183e7c6..8deb27ec7e 100644 --- a/test/components/views/spaces/ThreadsActivityCentre-test.tsx +++ b/test/components/views/spaces/ThreadsActivityCentre-test.tsx @@ -59,16 +59,23 @@ describe("ThreadsActivityCentre", () => { }); roomWithActivity.name = "Just activity"; - const roomWithNotif = new Room("!room:server", cli, cli.getSafeUserId(), { + const roomWithNotif = new Room("!room2:server", cli, cli.getSafeUserId(), { pendingEventOrdering: PendingEventOrdering.Detached, }); roomWithNotif.name = "A notification"; - const roomWithHighlight = new Room("!room:server", cli, cli.getSafeUserId(), { + const roomWithHighlight = new Room("!room3:server", cli, cli.getSafeUserId(), { pendingEventOrdering: PendingEventOrdering.Detached, }); roomWithHighlight.name = "This is a real highlight"; + const getDefaultThreadArgs = (room: Room) => ({ + room: room, + client: cli, + authorId: "@foo:bar", + participantUserIds: ["@fee:bar"], + }); + beforeAll(async () => { jest.spyOn(MatrixClientPeg, "get").mockReturnValue(cli); jest.spyOn(MatrixClientPeg, "safeGet").mockReturnValue(cli); @@ -77,26 +84,15 @@ describe("ThreadsActivityCentre", () => { jest.spyOn(dmRoomMap, "getUserIdForRoomId"); jest.spyOn(DMRoomMap, "shared").mockReturnValue(dmRoomMap); - await populateThread({ - room: roomWithActivity, - client: cli, - authorId: "@foo:bar", - participantUserIds: ["@fee:bar"], - }); + await populateThread(getDefaultThreadArgs(roomWithActivity)); - const notifThreadInfo = await populateThread({ - room: roomWithNotif, - client: cli, - authorId: "@foo:bar", - participantUserIds: ["@fee:bar"], - }); + const notifThreadInfo = await populateThread(getDefaultThreadArgs(roomWithNotif)); roomWithNotif.setThreadUnreadNotificationCount(notifThreadInfo.thread.id, NotificationCountType.Total, 1); const highlightThreadInfo = await populateThread({ - room: roomWithHighlight, - client: cli, - authorId: "@foo:bar", - participantUserIds: ["@fee:bar"], + ...getDefaultThreadArgs(roomWithHighlight), + // timestamp + ts: 5, }); roomWithHighlight.setThreadUnreadNotificationCount( highlightThreadInfo.thread.id, @@ -181,6 +177,52 @@ describe("ThreadsActivityCentre", () => { expect(screen.getByRole("menu")).toMatchSnapshot(); }); + it("should order the room with the same notification level by most recent", async () => { + // Generate two new rooms with threads + const secondRoomWithHighlight = new Room("!room4:server", cli, cli.getSafeUserId(), { + pendingEventOrdering: PendingEventOrdering.Detached, + }); + secondRoomWithHighlight.name = "This is a second real highlight"; + + const secondHighlightThreadInfo = await populateThread({ + ...getDefaultThreadArgs(secondRoomWithHighlight), + // timestamp + ts: 1, + }); + secondRoomWithHighlight.setThreadUnreadNotificationCount( + secondHighlightThreadInfo.thread.id, + NotificationCountType.Highlight, + 1, + ); + + const thirdRoomWithHighlight = new Room("!room5:server", cli, cli.getSafeUserId(), { + pendingEventOrdering: PendingEventOrdering.Detached, + }); + thirdRoomWithHighlight.name = "This is a third real highlight"; + + const thirdHighlightThreadInfo = await populateThread({ + ...getDefaultThreadArgs(thirdRoomWithHighlight), + // timestamp + ts: 7, + }); + thirdRoomWithHighlight.setThreadUnreadNotificationCount( + thirdHighlightThreadInfo.thread.id, + NotificationCountType.Highlight, + 1, + ); + + cli.getVisibleRooms = jest + .fn() + .mockReturnValue([roomWithHighlight, secondRoomWithHighlight, thirdRoomWithHighlight]); + + renderTAC(); + await userEvent.click(getTACButton()); + + // The room should be ordered by the most recent thread + // thirdHighlightThreadInfo (timestamp 7) > highlightThreadInfo (timestamp 5) > secondHighlightThreadInfo (timestamp 1) + expect(screen.getByRole("menu")).toMatchSnapshot(); + }); + it("should block Ctrl/CMD + k shortcut", async () => { cli.getVisibleRooms = jest.fn().mockReturnValue([roomWithHighlight]); diff --git a/test/components/views/spaces/__snapshots__/ThreadsActivityCentre-test.tsx.snap b/test/components/views/spaces/__snapshots__/ThreadsActivityCentre-test.tsx.snap index 5b9e2091b7..0d2841c614 100644 --- a/test/components/views/spaces/__snapshots__/ThreadsActivityCentre-test.tsx.snap +++ b/test/components/views/spaces/__snapshots__/ThreadsActivityCentre-test.tsx.snap @@ -38,7 +38,7 @@ exports[`ThreadsActivityCentre renders notifications matching the snapshot 1`] = > `; + +exports[`ThreadsActivityCentre should order the room with the same notification level by most recent 1`] = ` + +`;