Sort muted rooms to the bottom of their section of the room list (#10592)
* muted-to-the-bottom POC * split muted rooms in natural algorithm * add previous event to account data dispatch * add muted to notification state * sort muted rooms to the bottom * only split muted rooms when sorting is RECENT * remove debugs * use RoomNotifState better * add default notifications test util * test getChangedOverrideRoomPushRules * remove file * test roomudpate in roomliststore * unit test ImportanceAlgorithm * strict fixes * test recent x importance with muted rooms * unit test NaturalAlgorithm * test naturalalgorithm with muted rooms * strict fixes * comments * add push rules test utility * strict fixes * more strict * tidy comment * document previousevent on account data dispatch event * simplify (?) room mute rule utilities, comments * remove debug
This commit is contained in:
parent
3ca957b541
commit
44e0732144
15 changed files with 765 additions and 27 deletions
|
@ -14,16 +14,25 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { EventType, MatrixEvent, PendingEventOrdering, Room } from "matrix-js-sdk/src/matrix";
|
||||
import {
|
||||
ConditionKind,
|
||||
EventType,
|
||||
IPushRule,
|
||||
MatrixEvent,
|
||||
PendingEventOrdering,
|
||||
PushRuleActionName,
|
||||
Room,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { MatrixDispatcher } from "../../../src/dispatcher/dispatcher";
|
||||
import defaultDispatcher, { MatrixDispatcher } from "../../../src/dispatcher/dispatcher";
|
||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||
import SettingsStore, { CallbackFn } from "../../../src/settings/SettingsStore";
|
||||
import { ListAlgorithm, SortAlgorithm } from "../../../src/stores/room-list/algorithms/models";
|
||||
import { OrderedDefaultTagIDs, RoomUpdateCause } from "../../../src/stores/room-list/models";
|
||||
import RoomListStore, { RoomListStoreClass } from "../../../src/stores/room-list/RoomListStore";
|
||||
import DMRoomMap from "../../../src/utils/DMRoomMap";
|
||||
import { stubClient, upsertRoomStateEvents } from "../../test-utils";
|
||||
import { flushPromises, stubClient, upsertRoomStateEvents } from "../../test-utils";
|
||||
import { DEFAULT_PUSH_RULES, makePushRule } from "../../test-utils/pushRules";
|
||||
|
||||
describe("RoomListStore", () => {
|
||||
const client = stubClient();
|
||||
|
@ -69,12 +78,15 @@ describe("RoomListStore", () => {
|
|||
});
|
||||
upsertRoomStateEvents(roomNoPredecessor, [createNoPredecessor]);
|
||||
const oldRoom = new Room(oldRoomId, client, userId, {});
|
||||
const normalRoom = new Room("!normal:server.org", client, userId);
|
||||
client.getRoom = jest.fn().mockImplementation((roomId) => {
|
||||
switch (roomId) {
|
||||
case newRoomId:
|
||||
return roomWithCreatePredecessor;
|
||||
case oldRoomId:
|
||||
return oldRoom;
|
||||
case normalRoom.roomId:
|
||||
return normalRoom;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
@ -274,4 +286,70 @@ describe("RoomListStore", () => {
|
|||
expect(client.getVisibleRooms).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("room updates", () => {
|
||||
const makeStore = async () => {
|
||||
const store = new RoomListStoreClass(defaultDispatcher);
|
||||
await store.start();
|
||||
return store;
|
||||
};
|
||||
|
||||
describe("push rules updates", () => {
|
||||
const makePushRulesEvent = (overrideRules: IPushRule[] = []): MatrixEvent => {
|
||||
return new MatrixEvent({
|
||||
type: EventType.PushRules,
|
||||
content: {
|
||||
global: {
|
||||
...DEFAULT_PUSH_RULES.global,
|
||||
override: overrideRules,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
it("triggers a room update when room mutes have changed", async () => {
|
||||
const rule = makePushRule(normalRoom.roomId, {
|
||||
actions: [PushRuleActionName.DontNotify],
|
||||
conditions: [{ kind: ConditionKind.EventMatch, key: "room_id", pattern: normalRoom.roomId }],
|
||||
});
|
||||
const event = makePushRulesEvent([rule]);
|
||||
const previousEvent = makePushRulesEvent();
|
||||
|
||||
const store = await makeStore();
|
||||
// @ts-ignore private property alg
|
||||
const algorithmSpy = jest.spyOn(store.algorithm, "handleRoomUpdate").mockReturnValue(undefined);
|
||||
// @ts-ignore cheat and call protected fn
|
||||
store.onAction({ action: "MatrixActions.accountData", event, previousEvent });
|
||||
// flush setImmediate
|
||||
await flushPromises();
|
||||
|
||||
expect(algorithmSpy).toHaveBeenCalledWith(normalRoom, RoomUpdateCause.PossibleMuteChange);
|
||||
});
|
||||
|
||||
it("handles when a muted room is unknown by the room list", async () => {
|
||||
const rule = makePushRule(normalRoom.roomId, {
|
||||
actions: [PushRuleActionName.DontNotify],
|
||||
conditions: [{ kind: ConditionKind.EventMatch, key: "room_id", pattern: normalRoom.roomId }],
|
||||
});
|
||||
const unknownRoomRule = makePushRule("!unknown:server.org", {
|
||||
conditions: [{ kind: ConditionKind.EventMatch, key: "room_id", pattern: "!unknown:server.org" }],
|
||||
});
|
||||
const event = makePushRulesEvent([unknownRoomRule, rule]);
|
||||
const previousEvent = makePushRulesEvent();
|
||||
|
||||
const store = await makeStore();
|
||||
// @ts-ignore private property alg
|
||||
const algorithmSpy = jest.spyOn(store.algorithm, "handleRoomUpdate").mockReturnValue(undefined);
|
||||
|
||||
// @ts-ignore cheat and call protected fn
|
||||
store.onAction({ action: "MatrixActions.accountData", event, previousEvent });
|
||||
// flush setImmediate
|
||||
await flushPromises();
|
||||
|
||||
// only one call to update made for normalRoom
|
||||
expect(algorithmSpy).toHaveBeenCalledTimes(1);
|
||||
expect(algorithmSpy).toHaveBeenCalledWith(normalRoom, RoomUpdateCause.PossibleMuteChange);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixEvent, Room, RoomEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { ConditionKind, MatrixEvent, PushRuleActionName, Room, RoomEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { RoomNotificationStateStore } from "../../../../../src/stores/notifications/RoomNotificationStateStore";
|
||||
|
@ -25,6 +25,8 @@ import { DefaultTagID, RoomUpdateCause } from "../../../../../src/stores/room-li
|
|||
import { NotificationColor } from "../../../../../src/stores/notifications/NotificationColor";
|
||||
import { AlphabeticAlgorithm } from "../../../../../src/stores/room-list/algorithms/tag-sorting/AlphabeticAlgorithm";
|
||||
import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../../test-utils";
|
||||
import { RecentAlgorithm } from "../../../../../src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
|
||||
import { DEFAULT_PUSH_RULES, makePushRule } from "../../../../test-utils/pushRules";
|
||||
|
||||
describe("ImportanceAlgorithm", () => {
|
||||
const userId = "@alice:server.org";
|
||||
|
@ -57,6 +59,21 @@ describe("ImportanceAlgorithm", () => {
|
|||
const roomE = makeRoom("!eee:server.org", "Echo", 3);
|
||||
const roomX = makeRoom("!xxx:server.org", "Xylophone", 99);
|
||||
|
||||
const muteRoomARule = makePushRule(roomA.roomId, {
|
||||
actions: [PushRuleActionName.DontNotify],
|
||||
conditions: [{ kind: ConditionKind.EventMatch, key: "room_id", pattern: roomA.roomId }],
|
||||
});
|
||||
const muteRoomBRule = makePushRule(roomB.roomId, {
|
||||
actions: [PushRuleActionName.DontNotify],
|
||||
conditions: [{ kind: ConditionKind.EventMatch, key: "room_id", pattern: roomB.roomId }],
|
||||
});
|
||||
client.pushRules = {
|
||||
global: {
|
||||
...DEFAULT_PUSH_RULES.global,
|
||||
override: [...DEFAULT_PUSH_RULES.global.override!, muteRoomARule, muteRoomBRule],
|
||||
},
|
||||
};
|
||||
|
||||
const unreadStates: Record<string, ReturnType<(typeof RoomNotifs)["determineUnreadState"]>> = {
|
||||
red: { symbol: null, count: 1, color: NotificationColor.Red },
|
||||
grey: { symbol: null, count: 1, color: NotificationColor.Grey },
|
||||
|
@ -240,6 +257,18 @@ describe("ImportanceAlgorithm", () => {
|
|||
).toThrow("Unsupported update cause: something unexpected");
|
||||
});
|
||||
|
||||
it("ignores a mute change", () => {
|
||||
// muted rooms are not pushed to the bottom when sort is alpha
|
||||
const algorithm = setupAlgorithm(sortAlgorithm, [roomC, roomB, roomE, roomD, roomA]);
|
||||
jest.spyOn(AlphabeticAlgorithm.prototype, "sortRooms").mockClear();
|
||||
|
||||
const shouldTriggerUpdate = algorithm.handleRoomUpdate(roomE, RoomUpdateCause.PossibleMuteChange);
|
||||
|
||||
expect(shouldTriggerUpdate).toBe(false);
|
||||
// no sorting
|
||||
expect(AlphabeticAlgorithm.prototype.sortRooms).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("time and read receipt updates", () => {
|
||||
it("throws for when a room is not indexed", () => {
|
||||
const algorithm = setupAlgorithm(sortAlgorithm, [roomC, roomB, roomE, roomD, roomA]);
|
||||
|
@ -295,4 +324,110 @@ describe("ImportanceAlgorithm", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("When sortAlgorithm is recent", () => {
|
||||
const sortAlgorithm = SortAlgorithm.Recent;
|
||||
|
||||
// mock recent algorithm sorting
|
||||
const fakeRecentOrder = [roomC, roomB, roomE, roomD, roomA];
|
||||
|
||||
beforeEach(async () => {
|
||||
// destroy roomMap so we can start fresh
|
||||
// @ts-ignore private property
|
||||
RoomNotificationStateStore.instance.roomMap = new Map<Room, RoomNotificationState>();
|
||||
|
||||
jest.spyOn(RecentAlgorithm.prototype, "sortRooms")
|
||||
.mockClear()
|
||||
.mockImplementation((rooms: Room[]) =>
|
||||
fakeRecentOrder.filter((sortedRoom) => rooms.includes(sortedRoom)),
|
||||
);
|
||||
jest.spyOn(RoomNotifs, "determineUnreadState")
|
||||
.mockClear()
|
||||
.mockImplementation((room) => {
|
||||
switch (room) {
|
||||
// b, c and e have red notifs
|
||||
case roomB:
|
||||
case roomE:
|
||||
case roomC:
|
||||
return unreadStates.red;
|
||||
default:
|
||||
return unreadStates.none;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("orders rooms by recent when they have the same notif state", () => {
|
||||
jest.spyOn(RoomNotifs, "determineUnreadState").mockReturnValue({
|
||||
symbol: null,
|
||||
count: 0,
|
||||
color: NotificationColor.None,
|
||||
});
|
||||
const algorithm = setupAlgorithm(sortAlgorithm);
|
||||
|
||||
// sorted according to recent
|
||||
expect(algorithm.orderedRooms).toEqual([roomC, roomB, roomA]);
|
||||
});
|
||||
|
||||
it("orders rooms by notification state then recent", () => {
|
||||
const algorithm = setupAlgorithm(sortAlgorithm, [roomC, roomB, roomE, roomD, roomA]);
|
||||
|
||||
expect(algorithm.orderedRooms).toEqual([
|
||||
// recent within red
|
||||
roomC,
|
||||
roomE,
|
||||
// recent within none
|
||||
roomD,
|
||||
// muted
|
||||
roomB,
|
||||
roomA,
|
||||
]);
|
||||
});
|
||||
|
||||
describe("handleRoomUpdate", () => {
|
||||
it("removes a room", () => {
|
||||
const algorithm = setupAlgorithm(sortAlgorithm);
|
||||
jest.spyOn(RecentAlgorithm.prototype, "sortRooms").mockClear();
|
||||
|
||||
const shouldTriggerUpdate = algorithm.handleRoomUpdate(roomA, RoomUpdateCause.RoomRemoved);
|
||||
|
||||
expect(shouldTriggerUpdate).toBe(true);
|
||||
expect(algorithm.orderedRooms).toEqual([roomC, roomB]);
|
||||
// no re-sorting on a remove
|
||||
expect(RecentAlgorithm.prototype.sortRooms).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("warns and returns without change when removing a room that is not indexed", () => {
|
||||
jest.spyOn(logger, "warn").mockReturnValue(undefined);
|
||||
const algorithm = setupAlgorithm(sortAlgorithm);
|
||||
|
||||
const shouldTriggerUpdate = algorithm.handleRoomUpdate(roomD, RoomUpdateCause.RoomRemoved);
|
||||
|
||||
expect(shouldTriggerUpdate).toBe(false);
|
||||
expect(logger.warn).toHaveBeenCalledWith(`Tried to remove unknown room from ${tagId}: ${roomD.roomId}`);
|
||||
});
|
||||
|
||||
it("adds a new room", () => {
|
||||
const algorithm = setupAlgorithm(sortAlgorithm);
|
||||
jest.spyOn(RecentAlgorithm.prototype, "sortRooms").mockClear();
|
||||
|
||||
const shouldTriggerUpdate = algorithm.handleRoomUpdate(roomE, RoomUpdateCause.NewRoom);
|
||||
|
||||
expect(shouldTriggerUpdate).toBe(true);
|
||||
// inserted according to notif state and mute
|
||||
expect(algorithm.orderedRooms).toEqual([roomC, roomE, roomB, roomA]);
|
||||
// only sorted within category
|
||||
expect(RecentAlgorithm.prototype.sortRooms).toHaveBeenCalledWith([roomE, roomC], tagId);
|
||||
});
|
||||
|
||||
it("re-sorts on a mute change", () => {
|
||||
const algorithm = setupAlgorithm(sortAlgorithm, [roomC, roomB, roomE, roomD, roomA]);
|
||||
jest.spyOn(RecentAlgorithm.prototype, "sortRooms").mockClear();
|
||||
|
||||
const shouldTriggerUpdate = algorithm.handleRoomUpdate(roomE, RoomUpdateCause.PossibleMuteChange);
|
||||
|
||||
expect(shouldTriggerUpdate).toBe(true);
|
||||
expect(RecentAlgorithm.prototype.sortRooms).toHaveBeenCalledWith([roomC, roomE], tagId);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,14 +14,20 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { Room } from "matrix-js-sdk/src/matrix";
|
||||
import { ConditionKind, EventType, MatrixEvent, PushRuleActionName, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { ClientEvent } from "matrix-js-sdk/src/client";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { NaturalAlgorithm } from "../../../../../src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm";
|
||||
import { SortAlgorithm } from "../../../../../src/stores/room-list/algorithms/models";
|
||||
import { DefaultTagID, RoomUpdateCause } from "../../../../../src/stores/room-list/models";
|
||||
import { AlphabeticAlgorithm } from "../../../../../src/stores/room-list/algorithms/tag-sorting/AlphabeticAlgorithm";
|
||||
import { RecentAlgorithm } from "../../../../../src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
|
||||
import { RoomNotificationStateStore } from "../../../../../src/stores/notifications/RoomNotificationStateStore";
|
||||
import * as RoomNotifs from "../../../../../src/RoomNotifs";
|
||||
import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../../test-utils";
|
||||
import { DEFAULT_PUSH_RULES, makePushRule } from "../../../../test-utils/pushRules";
|
||||
import { NotificationColor } from "../../../../../src/stores/notifications/NotificationColor";
|
||||
|
||||
describe("NaturalAlgorithm", () => {
|
||||
const userId = "@alice:server.org";
|
||||
|
@ -43,6 +49,21 @@ describe("NaturalAlgorithm", () => {
|
|||
const roomE = makeRoom("!eee:server.org", "Echo");
|
||||
const roomX = makeRoom("!xxx:server.org", "Xylophone");
|
||||
|
||||
const muteRoomARule = makePushRule(roomA.roomId, {
|
||||
actions: [PushRuleActionName.DontNotify],
|
||||
conditions: [{ kind: ConditionKind.EventMatch, key: "room_id", pattern: roomA.roomId }],
|
||||
});
|
||||
const muteRoomDRule = makePushRule(roomD.roomId, {
|
||||
actions: [PushRuleActionName.DontNotify],
|
||||
conditions: [{ kind: ConditionKind.EventMatch, key: "room_id", pattern: roomD.roomId }],
|
||||
});
|
||||
client.pushRules = {
|
||||
global: {
|
||||
...DEFAULT_PUSH_RULES.global,
|
||||
override: [...DEFAULT_PUSH_RULES.global!.override!, muteRoomARule, muteRoomDRule],
|
||||
},
|
||||
};
|
||||
|
||||
const setupAlgorithm = (sortAlgorithm: SortAlgorithm, rooms?: Room[]) => {
|
||||
const algorithm = new NaturalAlgorithm(tagId, sortAlgorithm);
|
||||
algorithm.setRooms(rooms || [roomA, roomB, roomC]);
|
||||
|
@ -80,7 +101,7 @@ describe("NaturalAlgorithm", () => {
|
|||
|
||||
const shouldTriggerUpdate = algorithm.handleRoomUpdate(roomD, RoomUpdateCause.RoomRemoved);
|
||||
|
||||
expect(shouldTriggerUpdate).toBe(true);
|
||||
expect(shouldTriggerUpdate).toBe(false);
|
||||
expect(logger.warn).toHaveBeenCalledWith(`Tried to remove unknown room from ${tagId}: ${roomD.roomId}`);
|
||||
});
|
||||
|
||||
|
@ -99,6 +120,29 @@ describe("NaturalAlgorithm", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("adds a new muted room", () => {
|
||||
const algorithm = setupAlgorithm(sortAlgorithm, [roomA, roomB, roomE]);
|
||||
jest.spyOn(AlphabeticAlgorithm.prototype, "sortRooms").mockClear();
|
||||
|
||||
const shouldTriggerUpdate = algorithm.handleRoomUpdate(roomD, RoomUpdateCause.NewRoom);
|
||||
|
||||
expect(shouldTriggerUpdate).toBe(true);
|
||||
// muted room mixed in main category
|
||||
expect(algorithm.orderedRooms).toEqual([roomA, roomB, roomD, roomE]);
|
||||
// only sorted within category
|
||||
expect(AlphabeticAlgorithm.prototype.sortRooms).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("ignores a mute change update", () => {
|
||||
const algorithm = setupAlgorithm(sortAlgorithm);
|
||||
jest.spyOn(AlphabeticAlgorithm.prototype, "sortRooms").mockClear();
|
||||
|
||||
const shouldTriggerUpdate = algorithm.handleRoomUpdate(roomA, RoomUpdateCause.PossibleMuteChange);
|
||||
|
||||
expect(shouldTriggerUpdate).toBe(false);
|
||||
expect(AlphabeticAlgorithm.prototype.sortRooms).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("throws for an unhandled update cause", () => {
|
||||
const algorithm = setupAlgorithm(sortAlgorithm);
|
||||
|
||||
|
@ -133,4 +177,113 @@ describe("NaturalAlgorithm", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("When sortAlgorithm is recent", () => {
|
||||
const sortAlgorithm = SortAlgorithm.Recent;
|
||||
|
||||
// mock recent algorithm sorting
|
||||
const fakeRecentOrder = [roomC, roomA, roomB, roomD, roomE];
|
||||
|
||||
beforeEach(async () => {
|
||||
// destroy roomMap so we can start fresh
|
||||
// @ts-ignore private property
|
||||
RoomNotificationStateStore.instance.roomMap = new Map<Room, RoomNotificationState>();
|
||||
|
||||
jest.spyOn(RecentAlgorithm.prototype, "sortRooms")
|
||||
.mockClear()
|
||||
.mockImplementation((rooms: Room[]) =>
|
||||
fakeRecentOrder.filter((sortedRoom) => rooms.includes(sortedRoom)),
|
||||
);
|
||||
|
||||
jest.spyOn(RoomNotifs, "determineUnreadState").mockReturnValue({
|
||||
symbol: null,
|
||||
count: 0,
|
||||
color: NotificationColor.None,
|
||||
});
|
||||
});
|
||||
|
||||
it("orders rooms by recent with muted rooms to the bottom", () => {
|
||||
const algorithm = setupAlgorithm(sortAlgorithm);
|
||||
|
||||
// sorted according to recent
|
||||
expect(algorithm.orderedRooms).toEqual([roomC, roomB, roomA]);
|
||||
});
|
||||
|
||||
describe("handleRoomUpdate", () => {
|
||||
it("removes a room", () => {
|
||||
const algorithm = setupAlgorithm(sortAlgorithm);
|
||||
jest.spyOn(RecentAlgorithm.prototype, "sortRooms").mockClear();
|
||||
|
||||
const shouldTriggerUpdate = algorithm.handleRoomUpdate(roomA, RoomUpdateCause.RoomRemoved);
|
||||
|
||||
expect(shouldTriggerUpdate).toBe(true);
|
||||
expect(algorithm.orderedRooms).toEqual([roomC, roomB]);
|
||||
// no re-sorting on a remove
|
||||
expect(RecentAlgorithm.prototype.sortRooms).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("warns and returns without change when removing a room that is not indexed", () => {
|
||||
jest.spyOn(logger, "warn").mockReturnValue(undefined);
|
||||
const algorithm = setupAlgorithm(sortAlgorithm);
|
||||
|
||||
const shouldTriggerUpdate = algorithm.handleRoomUpdate(roomD, RoomUpdateCause.RoomRemoved);
|
||||
|
||||
expect(shouldTriggerUpdate).toBe(false);
|
||||
expect(logger.warn).toHaveBeenCalledWith(`Tried to remove unknown room from ${tagId}: ${roomD.roomId}`);
|
||||
});
|
||||
|
||||
it("adds a new room", () => {
|
||||
const algorithm = setupAlgorithm(sortAlgorithm);
|
||||
jest.spyOn(RecentAlgorithm.prototype, "sortRooms").mockClear();
|
||||
|
||||
const shouldTriggerUpdate = algorithm.handleRoomUpdate(roomE, RoomUpdateCause.NewRoom);
|
||||
|
||||
expect(shouldTriggerUpdate).toBe(true);
|
||||
// inserted according to mute then recentness
|
||||
expect(algorithm.orderedRooms).toEqual([roomC, roomB, roomE, roomA]);
|
||||
// only sorted within category, muted roomA is not resorted
|
||||
expect(RecentAlgorithm.prototype.sortRooms).toHaveBeenCalledWith([roomC, roomB, roomE], tagId);
|
||||
});
|
||||
|
||||
it("does not re-sort on possible mute change when room did not change effective mutedness", () => {
|
||||
const algorithm = setupAlgorithm(sortAlgorithm, [roomC, roomB, roomE, roomD, roomA]);
|
||||
jest.spyOn(RecentAlgorithm.prototype, "sortRooms").mockClear();
|
||||
|
||||
const shouldTriggerUpdate = algorithm.handleRoomUpdate(roomE, RoomUpdateCause.PossibleMuteChange);
|
||||
|
||||
expect(shouldTriggerUpdate).toBe(false);
|
||||
expect(RecentAlgorithm.prototype.sortRooms).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("re-sorts on a mute change", () => {
|
||||
const algorithm = setupAlgorithm(sortAlgorithm, [roomC, roomB, roomE, roomD, roomA]);
|
||||
jest.spyOn(RecentAlgorithm.prototype, "sortRooms").mockClear();
|
||||
|
||||
// mute roomE
|
||||
const muteRoomERule = makePushRule(roomE.roomId, {
|
||||
actions: [PushRuleActionName.DontNotify],
|
||||
conditions: [{ kind: ConditionKind.EventMatch, key: "room_id", pattern: roomE.roomId }],
|
||||
});
|
||||
const pushRulesEvent = new MatrixEvent({ type: EventType.PushRules });
|
||||
client.pushRules!.global!.override!.push(muteRoomERule);
|
||||
client.emit(ClientEvent.AccountData, pushRulesEvent);
|
||||
|
||||
const shouldTriggerUpdate = algorithm.handleRoomUpdate(roomE, RoomUpdateCause.PossibleMuteChange);
|
||||
|
||||
expect(shouldTriggerUpdate).toBe(true);
|
||||
expect(algorithm.orderedRooms).toEqual([
|
||||
// unmuted, sorted by recent
|
||||
roomC,
|
||||
roomB,
|
||||
// muted, sorted by recent
|
||||
roomA,
|
||||
roomD,
|
||||
roomE,
|
||||
]);
|
||||
// only sorted muted category
|
||||
expect(RecentAlgorithm.prototype.sortRooms).toHaveBeenCalledTimes(1);
|
||||
expect(RecentAlgorithm.prototype.sortRooms).toHaveBeenCalledWith([roomA, roomD, roomE], tagId);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
96
test/stores/room-list/utils/roomMute-test.ts
Normal file
96
test/stores/room-list/utils/roomMute-test.ts
Normal file
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { ConditionKind, EventType, IPushRule, MatrixEvent, PushRuleActionName } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { getChangedOverrideRoomMutePushRules } from "../../../../src/stores/room-list/utils/roomMute";
|
||||
import { DEFAULT_PUSH_RULES, getDefaultRuleWithKind, makePushRule } from "../../../test-utils/pushRules";
|
||||
|
||||
describe("getChangedOverrideRoomMutePushRules()", () => {
|
||||
const makePushRulesEvent = (overrideRules: IPushRule[] = []): MatrixEvent => {
|
||||
return new MatrixEvent({
|
||||
type: EventType.PushRules,
|
||||
content: {
|
||||
global: {
|
||||
...DEFAULT_PUSH_RULES.global,
|
||||
override: overrideRules,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
it("returns undefined when dispatched action is not accountData", () => {
|
||||
const action = { action: "MatrixActions.Event.decrypted", event: new MatrixEvent({}) };
|
||||
expect(getChangedOverrideRoomMutePushRules(action)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined when dispatched action is not pushrules", () => {
|
||||
const action = { action: "MatrixActions.accountData", event: new MatrixEvent({ type: "not-push-rules" }) };
|
||||
expect(getChangedOverrideRoomMutePushRules(action)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined when actions event is falsy", () => {
|
||||
const action = { action: "MatrixActions.accountData" };
|
||||
expect(getChangedOverrideRoomMutePushRules(action)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined when actions previousEvent is falsy", () => {
|
||||
const pushRulesEvent = makePushRulesEvent();
|
||||
const action = { action: "MatrixActions.accountData", event: pushRulesEvent };
|
||||
expect(getChangedOverrideRoomMutePushRules(action)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("filters out non-room specific rules", () => {
|
||||
// an override rule that exists in default rules
|
||||
const { rule } = getDefaultRuleWithKind(".m.rule.contains_display_name");
|
||||
const updatedRule = {
|
||||
...rule,
|
||||
actions: [PushRuleActionName.DontNotify],
|
||||
enabled: false,
|
||||
};
|
||||
const previousEvent = makePushRulesEvent([rule]);
|
||||
const pushRulesEvent = makePushRulesEvent([updatedRule]);
|
||||
const action = { action: "MatrixActions.accountData", event: pushRulesEvent, previousEvent: previousEvent };
|
||||
// contains_display_name changed, but is not room-specific
|
||||
expect(getChangedOverrideRoomMutePushRules(action)).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns ruleIds for added room rules", () => {
|
||||
const roomId1 = "!room1:server.org";
|
||||
const rule = makePushRule(roomId1, {
|
||||
actions: [PushRuleActionName.DontNotify],
|
||||
conditions: [{ kind: ConditionKind.EventMatch, key: "room_id", pattern: roomId1 }],
|
||||
});
|
||||
const previousEvent = makePushRulesEvent();
|
||||
const pushRulesEvent = makePushRulesEvent([rule]);
|
||||
const action = { action: "MatrixActions.accountData", event: pushRulesEvent, previousEvent: previousEvent };
|
||||
// contains_display_name changed, but is not room-specific
|
||||
expect(getChangedOverrideRoomMutePushRules(action)).toEqual([rule.rule_id]);
|
||||
});
|
||||
|
||||
it("returns ruleIds for removed room rules", () => {
|
||||
const roomId1 = "!room1:server.org";
|
||||
const rule = makePushRule(roomId1, {
|
||||
actions: [PushRuleActionName.DontNotify],
|
||||
conditions: [{ kind: ConditionKind.EventMatch, key: "room_id", pattern: roomId1 }],
|
||||
});
|
||||
const previousEvent = makePushRulesEvent([rule]);
|
||||
const pushRulesEvent = makePushRulesEvent();
|
||||
const action = { action: "MatrixActions.accountData", event: pushRulesEvent, previousEvent: previousEvent };
|
||||
// contains_display_name changed, but is not room-specific
|
||||
expect(getChangedOverrideRoomMutePushRules(action)).toEqual([rule.rule_id]);
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue