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

Commit c5a2d29

Browse files
committed
Unify room unread state determination
Have both the class-based facility and the hook use the new unified logic in `RoomNotifs#determineUnreadState`. Addresses element-hq/element-web#24229 Signed-off-by: Clark Fischer <clark.fischer@gmail.com>
1 parent 8b63464 commit c5a2d29

File tree

3 files changed

+45
-144
lines changed

3 files changed

+45
-144
lines changed

src/hooks/useUnreadNotifications.ts

Lines changed: 9 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2022 The Matrix.org Foundation C.I.C.
2+
Copyright 2022 - 2023 The Matrix.org Foundation C.I.C.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -14,15 +14,12 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import { NotificationCount, NotificationCountType, Room, RoomEvent } from "matrix-js-sdk/src/models/room";
18-
import { Thread } from "matrix-js-sdk/src/models/thread";
17+
import { RoomEvent } from "matrix-js-sdk/src/models/room";
1918
import { useCallback, useEffect, useState } from "react";
2019

21-
import { getUnsentMessages } from "../components/structures/RoomStatusBar";
22-
import { getRoomNotifsState, getUnreadNotificationCount, RoomNotifState } from "../RoomNotifs";
23-
import { NotificationColor } from "../stores/notifications/NotificationColor";
24-
import { doesRoomOrThreadHaveUnreadMessages } from "../Unread";
25-
import { EffectiveMembership, getEffectiveMembership } from "../utils/membership";
20+
import type { NotificationCount, Room } from "matrix-js-sdk/src/models/room";
21+
import { determineUnreadState } from "../RoomNotifs";
22+
import type { NotificationColor } from "../stores/notifications/NotificationColor";
2623
import { useEventEmitter } from "./useEventEmitter";
2724

2825
export const useUnreadNotifications = (
@@ -53,40 +50,10 @@ export const useUnreadNotifications = (
5350
useEventEmitter(room, RoomEvent.MyMembership, () => updateNotificationState());
5451

5552
const updateNotificationState = useCallback(() => {
56-
if (getUnsentMessages(room, threadId).length > 0) {
57-
setSymbol("!");
58-
setCount(1);
59-
setColor(NotificationColor.Unsent);
60-
} else if (getEffectiveMembership(room.getMyMembership()) === EffectiveMembership.Invite) {
61-
setSymbol("!");
62-
setCount(1);
63-
setColor(NotificationColor.Red);
64-
} else if (getRoomNotifsState(room.client, room.roomId) === RoomNotifState.Mute) {
65-
setSymbol(null);
66-
setCount(0);
67-
setColor(NotificationColor.None);
68-
} else {
69-
const redNotifs = getUnreadNotificationCount(room, NotificationCountType.Highlight, threadId);
70-
const greyNotifs = getUnreadNotificationCount(room, NotificationCountType.Total, threadId);
71-
72-
const trueCount = greyNotifs || redNotifs;
73-
setCount(trueCount);
74-
setSymbol(null);
75-
if (redNotifs > 0) {
76-
setColor(NotificationColor.Red);
77-
} else if (greyNotifs > 0) {
78-
setColor(NotificationColor.Grey);
79-
} else {
80-
// We don't have any notified messages, but we might have unread messages. Let's
81-
// find out.
82-
let roomOrThread: Room | Thread = room;
83-
if (threadId) {
84-
roomOrThread = room.getThread(threadId)!;
85-
}
86-
const hasUnread = doesRoomOrThreadHaveUnreadMessages(roomOrThread);
87-
setColor(hasUnread ? NotificationColor.Bold : NotificationColor.None);
88-
}
89-
}
53+
const { symbol, count, color } = determineUnreadState(room, threadId);
54+
setSymbol(symbol);
55+
setCount(count);
56+
setColor(color);
9057
}, [room, threadId]);
9158

9259
useEffect(() => {

src/stores/notifications/RoomNotificationState.ts

Lines changed: 11 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2020 The Matrix.org Foundation C.I.C.
2+
Copyright 2020, 2023 The Matrix.org Foundation C.I.C.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -14,21 +14,19 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event";
18-
import { NotificationCountType, Room, RoomEvent } from "matrix-js-sdk/src/models/room";
17+
import { MatrixEventEvent } from "matrix-js-sdk/src/models/event";
18+
import { RoomEvent } from "matrix-js-sdk/src/models/room";
1919
import { ClientEvent } from "matrix-js-sdk/src/client";
2020
import { Feature, ServerSupport } from "matrix-js-sdk/src/feature";
2121

22-
import { NotificationColor } from "./NotificationColor";
23-
import { IDestroyable } from "../../utils/IDestroyable";
22+
import type { Room } from "matrix-js-sdk/src/models/room";
23+
import type { MatrixEvent } from "matrix-js-sdk/src/models/event";
24+
import type { IDestroyable } from "../../utils/IDestroyable";
2425
import { MatrixClientPeg } from "../../MatrixClientPeg";
25-
import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership";
2626
import { readReceiptChangeIsFor } from "../../utils/read-receipts";
2727
import * as RoomNotifs from "../../RoomNotifs";
28-
import * as Unread from "../../Unread";
2928
import { NotificationState, NotificationStateEvents } from "./NotificationState";
30-
import { getUnsentMessages } from "../../components/structures/RoomStatusBar";
31-
import { ThreadsRoomNotificationState } from "./ThreadsRoomNotificationState";
29+
import type { ThreadsRoomNotificationState } from "./ThreadsRoomNotificationState";
3230

3331
export class RoomNotificationState extends NotificationState implements IDestroyable {
3432
public constructor(public readonly room: Room, private readonly threadsState?: ThreadsRoomNotificationState) {
@@ -49,10 +47,6 @@ export class RoomNotificationState extends NotificationState implements IDestroy
4947
this.updateNotificationState();
5048
}
5149

52-
private get roomIsInvite(): boolean {
53-
return getEffectiveMembership(this.room.getMyMembership()) === EffectiveMembership.Invite;
54-
}
55-
5650
public destroy(): void {
5751
super.destroy();
5852
const cli = this.room.client;
@@ -112,58 +106,10 @@ export class RoomNotificationState extends NotificationState implements IDestroy
112106
private updateNotificationState(): void {
113107
const snapshot = this.snapshot();
114108

115-
if (getUnsentMessages(this.room).length > 0) {
116-
// When there are unsent messages we show a red `!`
117-
this._color = NotificationColor.Unsent;
118-
this._symbol = "!";
119-
this._count = 1; // not used, technically
120-
} else if (
121-
RoomNotifs.getRoomNotifsState(this.room.client, this.room.roomId) === RoomNotifs.RoomNotifState.Mute
122-
) {
123-
// When muted we suppress all notification states, even if we have context on them.
124-
this._color = NotificationColor.None;
125-
this._symbol = null;
126-
this._count = 0;
127-
} else if (this.roomIsInvite) {
128-
this._color = NotificationColor.Red;
129-
this._symbol = "!";
130-
this._count = 1; // not used, technically
131-
} else {
132-
const redNotifs = RoomNotifs.getUnreadNotificationCount(this.room, NotificationCountType.Highlight);
133-
const greyNotifs = RoomNotifs.getUnreadNotificationCount(this.room, NotificationCountType.Total);
134-
135-
// For a 'true count' we pick the grey notifications first because they include the
136-
// red notifications. If we don't have a grey count for some reason we use the red
137-
// count. If that count is broken for some reason, assume zero. This avoids us showing
138-
// a badge for 'NaN' (which formats as 'NaNB' for NaN Billion).
139-
const trueCount = greyNotifs ? greyNotifs : redNotifs ? redNotifs : 0;
140-
141-
// Note: we only set the symbol if we have an actual count. We don't want to show
142-
// zero on badges.
143-
144-
if (redNotifs > 0) {
145-
this._color = NotificationColor.Red;
146-
this._count = trueCount;
147-
this._symbol = null; // symbol calculated by component
148-
} else if (greyNotifs > 0) {
149-
this._color = NotificationColor.Grey;
150-
this._count = trueCount;
151-
this._symbol = null; // symbol calculated by component
152-
} else {
153-
// We don't have any notified messages, but we might have unread messages. Let's
154-
// find out.
155-
const hasUnread = Unread.doesRoomHaveUnreadMessages(this.room);
156-
if (hasUnread) {
157-
this._color = NotificationColor.Bold;
158-
} else {
159-
this._color = NotificationColor.None;
160-
}
161-
162-
// no symbol or count for this state
163-
this._count = 0;
164-
this._symbol = null;
165-
}
166-
}
109+
const { color, symbol, count } = RoomNotifs.determineUnreadState(this.room);
110+
this._color = color;
111+
this._symbol = symbol;
112+
this._count = count;
167113

168114
// finally, publish an update if needed
169115
this.emitIfUpdated(snapshot);

test/components/views/rooms/NotificationBadge/UnreadNotificationBadge-test.tsx

Lines changed: 25 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2022 The Matrix.org Foundation C.I.C.
2+
Copyright 2022 - 2023 The Matrix.org Foundation C.I.C.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -23,36 +23,26 @@ import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
2323
import { EventStatus } from "matrix-js-sdk/src/models/event-status";
2424
import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts";
2525

26+
import type { MatrixClient } from "matrix-js-sdk/src/client";
2627
import { mkThread } from "../../../../test-utils/threads";
2728
import { UnreadNotificationBadge } from "../../../../../src/components/views/rooms/NotificationBadge/UnreadNotificationBadge";
28-
import { mkEvent, mkMessage, stubClient } from "../../../../test-utils/test-utils";
29-
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
29+
import { mkEvent, mkMessage, muteRoom, stubClient } from "../../../../test-utils/test-utils";
3030
import * as RoomNotifs from "../../../../../src/RoomNotifs";
3131

32-
jest.mock("../../../../../src/RoomNotifs");
33-
jest.mock("../../../../../src/RoomNotifs", () => ({
34-
...(jest.requireActual("../../../../../src/RoomNotifs") as Object),
35-
getRoomNotifsState: jest.fn(),
36-
}));
37-
3832
const ROOM_ID = "!roomId:example.org";
3933
let THREAD_ID: string;
4034

4135
describe("UnreadNotificationBadge", () => {
42-
stubClient();
43-
const client = MatrixClientPeg.get();
36+
let client: MatrixClient;
4437
let room: Room;
4538

4639
function getComponent(threadId?: string) {
4740
return <UnreadNotificationBadge room={room} threadId={threadId} />;
4841
}
4942

50-
beforeAll(() => {
51-
client.supportsExperimentalThreads = () => true;
52-
});
53-
5443
beforeEach(() => {
55-
jest.clearAllMocks();
44+
client = stubClient();
45+
client.supportsExperimentalThreads = () => true;
5646

5747
room = new Room(ROOM_ID, client, client.getUserId()!, {
5848
pendingEventOrdering: PendingEventOrdering.Detached,
@@ -145,41 +135,39 @@ describe("UnreadNotificationBadge", () => {
145135
});
146136

147137
it("adds a warning for invites", () => {
148-
jest.spyOn(room, "getMyMembership").mockReturnValue("invite");
138+
room.updateMyMembership("invite");
149139
render(getComponent());
150140
expect(screen.queryByText("!")).not.toBeNull();
151141
});
152142

153143
it("hides counter for muted rooms", () => {
154-
jest.spyOn(RoomNotifs, "getRoomNotifsState").mockReset().mockReturnValue(RoomNotifs.RoomNotifState.Mute);
144+
muteRoom(room);
155145

156146
const { container } = render(getComponent());
157147
expect(container.querySelector(".mx_NotificationBadge")).toBeNull();
158148
});
159149

160150
it("activity renders unread notification badge", () => {
161-
act(() => {
162-
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total, 0);
163-
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight, 0);
151+
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total, 0);
152+
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight, 0);
164153

165-
// Add another event on the thread which is not sent by us.
166-
const event = mkEvent({
167-
event: true,
168-
type: "m.room.message",
169-
user: "@alice:server.org",
170-
room: room.roomId,
171-
content: {
172-
"msgtype": MsgType.Text,
173-
"body": "Hello from Bob",
174-
"m.relates_to": {
175-
event_id: THREAD_ID,
176-
rel_type: RelationType.Thread,
177-
},
154+
// Add another event on the thread which is not sent by us.
155+
const event = mkEvent({
156+
event: true,
157+
type: "m.room.message",
158+
user: "@alice:server.org",
159+
room: room.roomId,
160+
content: {
161+
"msgtype": MsgType.Text,
162+
"body": "Hello from Bob",
163+
"m.relates_to": {
164+
event_id: THREAD_ID,
165+
rel_type: RelationType.Thread,
178166
},
179-
ts: 5,
180-
});
181-
room.addLiveEvents([event]);
167+
},
168+
ts: 5,
182169
});
170+
room.addLiveEvents([event]);
183171

184172
const { container } = render(getComponent(THREAD_ID));
185173
expect(container.querySelector(".mx_NotificationBadge_dot")).toBeTruthy();

0 commit comments

Comments
 (0)