Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 86adc52

Browse files
committed
Add RoomNotifs#determineUnreadState
Intended as a singular replacement for the divergent implementations before. Signed-off-by: Clark Fischer <clark.fischer@gmail.com>
1 parent 4866e4f commit 86adc52

File tree

2 files changed

+154
-39
lines changed

2 files changed

+154
-39
lines changed

src/RoomNotifs.ts

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/*
2-
Copyright 2016 OpenMarket Ltd
3-
Copyright 2019 The Matrix.org Foundation C.I.C.
2+
Copyright 2016, 2019, 2023 The Matrix.org Foundation C.I.C.
43
54
Licensed under the Apache License, Version 2.0 (the "License");
65
you may not use this file except in compliance with the License.
@@ -16,18 +15,18 @@ limitations under the License.
1615
*/
1716

1817
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
19-
import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
20-
import {
21-
ConditionKind,
22-
IPushRule,
23-
PushRuleActionName,
24-
PushRuleKind,
25-
TweakName,
26-
} from "matrix-js-sdk/src/@types/PushRules";
18+
import { NotificationCountType } from "matrix-js-sdk/src/models/room";
19+
import { ConditionKind, PushRuleActionName, PushRuleKind, TweakName } from "matrix-js-sdk/src/@types/PushRules";
2720
import { EventType } from "matrix-js-sdk/src/@types/event";
28-
import { MatrixClient } from "matrix-js-sdk/src/matrix";
2921

22+
import type { IPushRule } from "matrix-js-sdk/src/@types/PushRules";
23+
import type { Room } from "matrix-js-sdk/src/models/room";
24+
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
3025
import { MatrixClientPeg } from "./MatrixClientPeg";
26+
import { NotificationColor } from "./stores/notifications/NotificationColor";
27+
import { getUnsentMessages } from "./components/structures/RoomStatusBar";
28+
import { doesRoomHaveUnreadMessages, doesRoomOrThreadHaveUnreadMessages } from "./Unread";
29+
import { getEffectiveMembership, EffectiveMembership } from "./utils/membership";
3130

3231
export enum RoomNotifState {
3332
AllMessagesLoud = "all_messages_loud",
@@ -36,7 +35,7 @@ export enum RoomNotifState {
3635
Mute = "mute",
3736
}
3837

39-
export function getRoomNotifsState(client: MatrixClient, roomId: string): RoomNotifState {
38+
export function getRoomNotifsState(client: MatrixClient, roomId: string): RoomNotifState | null {
4039
if (client.isGuest()) return RoomNotifState.AllMessages;
4140

4241
// look through the override rules for a rule affecting this room:
@@ -177,7 +176,7 @@ function setRoomNotifsStateUnmuted(roomId: string, newState: RoomNotifState): Pr
177176
return Promise.all(promises);
178177
}
179178

180-
function findOverrideMuteRule(roomId: string): IPushRule {
179+
function findOverrideMuteRule(roomId: string): IPushRule | null {
181180
const cli = MatrixClientPeg.get();
182181
if (!cli?.pushRules?.global?.override) {
183182
return null;
@@ -201,3 +200,44 @@ function isRuleForRoom(roomId: string, rule: IPushRule): boolean {
201200
function isMuteRule(rule: IPushRule): boolean {
202201
return rule.actions.length === 1 && rule.actions[0] === PushRuleActionName.DontNotify;
203202
}
203+
204+
export function determineUnreadState(
205+
room: Room,
206+
threadId?: string,
207+
): { color: NotificationColor; symbol: string | null; count: number } {
208+
if (getUnsentMessages(room, threadId).length > 0) {
209+
return { symbol: "!", count: 1, color: NotificationColor.Unsent };
210+
}
211+
212+
if (getEffectiveMembership(room.getMyMembership()) === EffectiveMembership.Invite) {
213+
return { symbol: "!", count: 1, color: NotificationColor.Red };
214+
}
215+
216+
if (getRoomNotifsState(room.client, room.roomId) === RoomNotifState.Mute) {
217+
return { symbol: null, count: 0, color: NotificationColor.None };
218+
}
219+
220+
const redNotifs = getUnreadNotificationCount(room, NotificationCountType.Highlight, threadId);
221+
const greyNotifs = getUnreadNotificationCount(room, NotificationCountType.Total, threadId);
222+
223+
const trueCount = greyNotifs || redNotifs;
224+
if (redNotifs > 0) {
225+
return { symbol: null, count: trueCount, color: NotificationColor.Red };
226+
}
227+
228+
if (greyNotifs > 0) {
229+
return { symbol: null, count: trueCount, color: NotificationColor.Grey };
230+
}
231+
232+
// We don't have any notified messages, but we might have unread messages. Let's
233+
// find out.
234+
let hasUnread = false;
235+
if (threadId) hasUnread = doesRoomOrThreadHaveUnreadMessages(room.getThread(threadId)!);
236+
else hasUnread = doesRoomHaveUnreadMessages(room);
237+
238+
return {
239+
symbol: null,
240+
count: trueCount,
241+
color: hasUnread ? NotificationColor.Bold : NotificationColor.None,
242+
};
243+
}

test/RoomNotifs-test.ts

Lines changed: 101 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,27 @@ limitations under the License.
1717
import { mocked } from "jest-mock";
1818
import { PushRuleActionName, TweakName } from "matrix-js-sdk/src/@types/PushRules";
1919
import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
20-
import { MatrixClient } from "matrix-js-sdk/src/matrix";
20+
import { EventStatus, PendingEventOrdering } from "matrix-js-sdk/src/matrix";
2121

22+
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
2223
import { mkEvent, mkRoom, muteRoom, stubClient } from "./test-utils";
23-
import { MatrixClientPeg } from "../src/MatrixClientPeg";
24-
import { getRoomNotifsState, RoomNotifState, getUnreadNotificationCount } from "../src/RoomNotifs";
24+
import {
25+
getRoomNotifsState,
26+
RoomNotifState,
27+
getUnreadNotificationCount,
28+
determineUnreadState,
29+
} from "../src/RoomNotifs";
30+
import { NotificationColor } from "../src/stores/notifications/NotificationColor";
2531

2632
describe("RoomNotifs test", () => {
33+
let client: jest.Mocked<MatrixClient>;
34+
2735
beforeEach(() => {
28-
stubClient();
36+
client = stubClient() as jest.Mocked<MatrixClient>;
2937
});
3038

3139
it("getRoomNotifsState handles rules with no conditions", () => {
32-
const cli = MatrixClientPeg.get();
33-
mocked(cli).pushRules = {
40+
mocked(client).pushRules = {
3441
global: {
3542
override: [
3643
{
@@ -42,53 +49,47 @@ describe("RoomNotifs test", () => {
4249
],
4350
},
4451
};
45-
expect(getRoomNotifsState(cli, "!roomId:server")).toBe(null);
52+
expect(getRoomNotifsState(client, "!roomId:server")).toBe(null);
4653
});
4754

4855
it("getRoomNotifsState handles guest users", () => {
49-
const cli = MatrixClientPeg.get();
50-
mocked(cli).isGuest.mockReturnValue(true);
51-
expect(getRoomNotifsState(cli, "!roomId:server")).toBe(RoomNotifState.AllMessages);
56+
mocked(client).isGuest.mockReturnValue(true);
57+
expect(getRoomNotifsState(client, "!roomId:server")).toBe(RoomNotifState.AllMessages);
5258
});
5359

5460
it("getRoomNotifsState handles mute state", () => {
55-
const cli = MatrixClientPeg.get();
56-
const room = mkRoom(cli, "!roomId:server");
61+
const room = mkRoom(client, "!roomId:server");
5762
muteRoom(room);
58-
expect(getRoomNotifsState(cli, room.roomId)).toBe(RoomNotifState.Mute);
63+
expect(getRoomNotifsState(client, room.roomId)).toBe(RoomNotifState.Mute);
5964
});
6065

6166
it("getRoomNotifsState handles mentions only", () => {
62-
const cli = MatrixClientPeg.get();
63-
cli.getRoomPushRule = () => ({
67+
(client as any).getRoomPushRule = () => ({
6468
rule_id: "!roomId:server",
6569
enabled: true,
6670
default: false,
6771
actions: [PushRuleActionName.DontNotify],
6872
});
69-
expect(getRoomNotifsState(cli, "!roomId:server")).toBe(RoomNotifState.MentionsOnly);
73+
expect(getRoomNotifsState(client, "!roomId:server")).toBe(RoomNotifState.MentionsOnly);
7074
});
7175

7276
it("getRoomNotifsState handles noisy", () => {
73-
const cli = MatrixClientPeg.get();
74-
cli.getRoomPushRule = () => ({
77+
(client as any).getRoomPushRule = () => ({
7578
rule_id: "!roomId:server",
7679
enabled: true,
7780
default: false,
7881
actions: [{ set_tweak: TweakName.Sound, value: "default" }],
7982
});
80-
expect(getRoomNotifsState(cli, "!roomId:server")).toBe(RoomNotifState.AllMessagesLoud);
83+
expect(getRoomNotifsState(client, "!roomId:server")).toBe(RoomNotifState.AllMessagesLoud);
8184
});
8285

8386
describe("getUnreadNotificationCount", () => {
8487
const ROOM_ID = "!roomId:example.org";
8588
const THREAD_ID = "$threadId";
8689

87-
let cli: jest.Mocked<MatrixClient>;
8890
let room: Room;
8991
beforeEach(() => {
90-
cli = MatrixClientPeg.get() as jest.Mocked<MatrixClient>;
91-
room = new Room(ROOM_ID, cli, cli.getUserId()!);
92+
room = new Room(ROOM_ID, client, client.getUserId()!);
9293
});
9394

9495
it("counts room notification type", () => {
@@ -109,19 +110,19 @@ describe("RoomNotifs test", () => {
109110
room.setUnreadNotificationCount(NotificationCountType.Highlight, 1);
110111

111112
const OLD_ROOM_ID = "!oldRoomId:example.org";
112-
const oldRoom = new Room(OLD_ROOM_ID, cli, cli.getUserId()!);
113+
const oldRoom = new Room(OLD_ROOM_ID, client, client.getUserId()!);
113114
oldRoom.setUnreadNotificationCount(NotificationCountType.Total, 10);
114115
oldRoom.setUnreadNotificationCount(NotificationCountType.Highlight, 6);
115116

116-
cli.getRoom.mockReset().mockReturnValue(oldRoom);
117+
client.getRoom.mockReset().mockReturnValue(oldRoom);
117118

118119
const predecessorEvent = mkEvent({
119120
event: true,
120121
type: "m.room.create",
121122
room: ROOM_ID,
122-
user: cli.getUserId()!,
123+
user: client.getUserId()!,
123124
content: {
124-
creator: cli.getUserId(),
125+
creator: client.getUserId(),
125126
room_version: "5",
126127
predecessor: {
127128
room_id: OLD_ROOM_ID,
@@ -149,4 +150,78 @@ describe("RoomNotifs test", () => {
149150
expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, THREAD_ID)).toBe(1);
150151
});
151152
});
153+
154+
describe("determineUnreadState", () => {
155+
let room: Room;
156+
157+
beforeEach(() => {
158+
room = new Room("!room-id:example.com", client, "@user:example.com", {
159+
pendingEventOrdering: PendingEventOrdering.Detached,
160+
});
161+
});
162+
163+
it("shows nothing by default", async () => {
164+
const { color, symbol, count } = determineUnreadState(room);
165+
166+
expect(symbol).toBe(null);
167+
expect(color).toBe(NotificationColor.None);
168+
expect(count).toBe(0);
169+
});
170+
171+
it("indicates if there are unsent messages", async () => {
172+
const event = mkEvent({
173+
event: true,
174+
type: "m.message",
175+
user: "@user:example.org",
176+
content: {},
177+
});
178+
event.status = EventStatus.NOT_SENT;
179+
room.addPendingEvent(event, "txn");
180+
181+
const { color, symbol, count } = determineUnreadState(room);
182+
183+
expect(symbol).toBe("!");
184+
expect(color).toBe(NotificationColor.Unsent);
185+
expect(count).toBeGreaterThan(0);
186+
});
187+
188+
it("indicates the user has been invited to a channel", async () => {
189+
room.updateMyMembership("invite");
190+
191+
const { color, symbol, count } = determineUnreadState(room);
192+
193+
expect(symbol).toBe("!");
194+
expect(color).toBe(NotificationColor.Red);
195+
expect(count).toBeGreaterThan(0);
196+
});
197+
198+
it("shows nothing for muted channels", async () => {
199+
room.setUnreadNotificationCount(NotificationCountType.Highlight, 99);
200+
room.setUnreadNotificationCount(NotificationCountType.Total, 99);
201+
muteRoom(room);
202+
203+
const { color, count } = determineUnreadState(room);
204+
205+
expect(color).toBe(NotificationColor.None);
206+
expect(count).toBe(0);
207+
});
208+
209+
it("uses the correct number of unreads", async () => {
210+
room.setUnreadNotificationCount(NotificationCountType.Total, 999);
211+
212+
const { color, count } = determineUnreadState(room);
213+
214+
expect(color).toBe(NotificationColor.Grey);
215+
expect(count).toBe(999);
216+
});
217+
218+
it("uses the correct number of highlights", async () => {
219+
room.setUnreadNotificationCount(NotificationCountType.Highlight, 888);
220+
221+
const { color, count } = determineUnreadState(room);
222+
223+
expect(color).toBe(NotificationColor.Red);
224+
expect(count).toBe(888);
225+
});
226+
});
152227
});

0 commit comments

Comments
 (0)