RoomListStore: Sort low priority rooms to the bottom of the list (#30070)
* Sort low priority rooms to the bottom of the list * Write test
This commit is contained in:
@@ -9,6 +9,7 @@ import type { Room } from "matrix-js-sdk/src/matrix";
|
|||||||
import { type Sorter, SortingAlgorithm } from ".";
|
import { type Sorter, SortingAlgorithm } from ".";
|
||||||
import { getLastTs } from "../../../room-list/algorithms/tag-sorting/RecentAlgorithm";
|
import { getLastTs } from "../../../room-list/algorithms/tag-sorting/RecentAlgorithm";
|
||||||
import { RoomNotificationStateStore } from "../../../notifications/RoomNotificationStateStore";
|
import { RoomNotificationStateStore } from "../../../notifications/RoomNotificationStateStore";
|
||||||
|
import { DefaultTagID } from "../../../room-list/models";
|
||||||
|
|
||||||
export class RecencySorter implements Sorter {
|
export class RecencySorter implements Sorter {
|
||||||
public constructor(private myUserId: string) {}
|
public constructor(private myUserId: string) {}
|
||||||
@@ -19,11 +20,9 @@ export class RecencySorter implements Sorter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public comparator(roomA: Room, roomB: Room, cache?: any): number {
|
public comparator(roomA: Room, roomB: Room, cache?: any): number {
|
||||||
// Check mute status first; muted rooms should be at the bottom
|
// First check if the rooms are low priority or muted
|
||||||
const isRoomAMuted = RoomNotificationStateStore.instance.getRoomState(roomA).muted;
|
const exceptionalOrdering = this.getScore(roomA) - this.getScore(roomB);
|
||||||
const isRoomBMuted = RoomNotificationStateStore.instance.getRoomState(roomB).muted;
|
if (exceptionalOrdering !== 0) return exceptionalOrdering;
|
||||||
if (isRoomAMuted && !isRoomBMuted) return 1;
|
|
||||||
if (isRoomBMuted && !isRoomAMuted) return -1;
|
|
||||||
|
|
||||||
// Then check recency; recent rooms should be at the top
|
// Then check recency; recent rooms should be at the top
|
||||||
const roomALastTs = this.getTs(roomA, cache);
|
const roomALastTs = this.getTs(roomA, cache);
|
||||||
@@ -35,6 +34,28 @@ export class RecencySorter implements Sorter {
|
|||||||
return SortingAlgorithm.Recency;
|
return SortingAlgorithm.Recency;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This sorter mostly sorts rooms by recency but there are two exceptions:
|
||||||
|
* 1. Muted rooms are sorted to the bottom of the list.
|
||||||
|
* 2. Low priority rooms are sorted to the bottom of the list but before muted rooms.
|
||||||
|
*
|
||||||
|
* The following method provides a numerical value that takes care of this
|
||||||
|
* exceptional ordering. For two rooms A and B, it works as follows:
|
||||||
|
* - If getScore(A) - getScore(B) > 0, A should come after B
|
||||||
|
* - If getScore(A) - getScore(B) < 0, A should come before B
|
||||||
|
* - If getScore(A) - getScore(B) = 0, no special ordering needed, just use recency
|
||||||
|
*/
|
||||||
|
private getScore(room: Room): number {
|
||||||
|
const isLowPriority = !!room.tags[DefaultTagID.LowPriority];
|
||||||
|
const isMuted = RoomNotificationStateStore.instance.getRoomState(room).muted;
|
||||||
|
// These constants are chosen so that the following order is maintained:
|
||||||
|
// Low priority rooms -> Low priority and muted rooms -> Muted rooms
|
||||||
|
if (isMuted && isLowPriority) return 5;
|
||||||
|
else if (isMuted) return 10;
|
||||||
|
else if (isLowPriority) return 2;
|
||||||
|
else return 0;
|
||||||
|
}
|
||||||
|
|
||||||
private getTs(room: Room, cache?: { [roomId: string]: number }): number {
|
private getTs(room: Room, cache?: { [roomId: string]: number }): number {
|
||||||
const ts = cache?.[room.roomId] ?? getLastTs(room, this.myUserId);
|
const ts = cache?.[room.roomId] ?? getLastTs(room, this.myUserId);
|
||||||
if (cache) {
|
if (cache) {
|
||||||
|
|||||||
@@ -798,4 +798,53 @@ describe("RoomListStoreV3", () => {
|
|||||||
expect(store.getSortedRooms()[34]).toEqual(unmutedRoom);
|
expect(store.getSortedRooms()[34]).toEqual(unmutedRoom);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Low priority rooms", () => {
|
||||||
|
async function getRoomListStoreWithRooms() {
|
||||||
|
const client = stubClient();
|
||||||
|
const rooms = getMockedRooms(client);
|
||||||
|
|
||||||
|
// Let's say that rooms 34, 84, 64, 14, 57 are low priority
|
||||||
|
const lowPriorityIndices = [34, 84, 64, 14, 57];
|
||||||
|
const lowPriorityRooms = lowPriorityIndices.map((i) => rooms[i]);
|
||||||
|
for (const room of lowPriorityRooms) {
|
||||||
|
room.tags[DefaultTagID.LowPriority] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's say that rooms 14, 57, 65, 78, 82, 5, 36 are muted
|
||||||
|
const mutedIndices = [14, 57, 65, 78, 82, 5, 36];
|
||||||
|
const mutedRooms = mutedIndices.map((i) => rooms[i]);
|
||||||
|
jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockImplementation((room) => {
|
||||||
|
const state = {
|
||||||
|
muted: mutedRooms.includes(room),
|
||||||
|
} as unknown as RoomNotificationState;
|
||||||
|
return state;
|
||||||
|
});
|
||||||
|
|
||||||
|
client.getVisibleRooms = jest.fn().mockReturnValue(rooms);
|
||||||
|
jest.spyOn(AsyncStoreWithClient.prototype, "matrixClient", "get").mockReturnValue(client);
|
||||||
|
const store = new RoomListStoreV3Class(dispatcher);
|
||||||
|
await store.start();
|
||||||
|
|
||||||
|
// We expect the following order: Low Priority -> Low Priority & Muted -> Muted
|
||||||
|
const expectedRoomIds = [84, 64, 34, 57, 14, 82, 78, 65, 36, 5].map((i) => rooms[i].roomId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
client,
|
||||||
|
rooms,
|
||||||
|
expectedRoomIds,
|
||||||
|
store,
|
||||||
|
dispatcher,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
it("Low priority rooms are pushed to the bottom of the list just before muted rooms", async () => {
|
||||||
|
const { store, expectedRoomIds } = await getRoomListStoreWithRooms();
|
||||||
|
const result = store
|
||||||
|
.getSortedRooms()
|
||||||
|
.slice(90)
|
||||||
|
.map((r) => r.roomId);
|
||||||
|
expect(result).toEqual(expectedRoomIds);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user