({
}
>
-
+
);
}
diff --git a/src/components/views/spaces/threads-activity-centre/useUnreadThreadRooms.ts b/src/components/views/spaces/threads-activity-centre/useUnreadThreadRooms.ts
index 115b2085607..72b5380fbd1 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/rooms/NotificationBadge/StatelessNotificationBadge-test.tsx b/test/components/views/rooms/NotificationBadge/StatelessNotificationBadge-test.tsx
index 66ae273e247..6ee93d82db4 100644
--- a/test/components/views/rooms/NotificationBadge/StatelessNotificationBadge-test.tsx
+++ b/test/components/views/rooms/NotificationBadge/StatelessNotificationBadge-test.tsx
@@ -35,4 +35,23 @@ describe("StatelessNotificationBadge", () => {
expect(container.querySelector(".mx_NotificationBadge_dot")).not.toBeInTheDocument();
expect(container.querySelector(".mx_NotificationBadge_knocked")).toBeInTheDocument();
});
+
+ it("has badge style for notification", () => {
+ const { container } = render(
+ ,
+ );
+ expect(container.querySelector(".mx_NotificationBadge_dot")).not.toBeInTheDocument();
+ });
+
+ it("has dot style for notification when forced", () => {
+ const { container } = render(
+ ,
+ );
+ expect(container.querySelector(".mx_NotificationBadge_dot")).toBeInTheDocument();
+ });
});
diff --git a/test/components/views/spaces/ThreadsActivityCentre-test.tsx b/test/components/views/spaces/ThreadsActivityCentre-test.tsx
index f5f183e7c6c..8deb27ec7e4 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 5b9e2091b75..0d2841c6148 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`] = `
+
+`;
diff --git a/yarn.lock b/yarn.lock
index ce60176a7a5..6bcc5013577 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3013,7 +3013,7 @@
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
-"@vector-im/compound-design-tokens@^1.0.0":
+"@vector-im/compound-design-tokens@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-1.2.0.tgz#ccb15fffc24cc70d83593bfc5348e6a0198cc08a"
integrity sha512-8LSbb38KxvStcOQZDSi7lI4oqtCuHFEgEQi9Q0KUx+5OnklfdyJ638txM1bznX/Cp9lHgMk4dHrTiQHBOE0ZuA==
@@ -6860,7 +6860,7 @@ matrix-events-sdk@0.0.1:
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
version "31.5.0"
- resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/461aeae2815a223c817c9768e26220cec4a69d12"
+ resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8e0ef5ff2cd927efa1bd22cabb075a14b10e39d5"
dependencies:
"@babel/runtime" "^7.12.5"
"@matrix-org/matrix-sdk-crypto-wasm" "^4.6.0"