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

Mark the threads icon as unread due to activity. #9739

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Unread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export function doesRoomHaveUnreadMessages(room: Room): boolean {
return false;
}

function doesRoomOrThreadHaveUnreadMessages(room: Room | Thread): boolean {
export function doesRoomOrThreadHaveUnreadMessages(room: Room | Thread): boolean {
const myUserId = MatrixClientPeg.get().getUserId();

// as we don't send RRs for our own messages, make sure we special case that
Expand Down
13 changes: 11 additions & 2 deletions src/components/views/right_panel/RoomHeaderButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { NotificationStateEvents } from "../../../stores/notifications/Notificat
import PosthogTrackers from "../../../PosthogTrackers";
import { ButtonEvent } from "../elements/AccessibleButton";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { doesRoomOrThreadHaveUnreadMessages } from "../../../Unread";

const ROOM_INFO_PHASES = [
RightPanelPhases.RoomSummary,
Expand Down Expand Up @@ -191,9 +192,17 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
return NotificationColor.Red;
case NotificationCountType.Total:
return NotificationColor.Grey;
default:
return NotificationColor.None;
}
// We don't have any notified messages, but we might have unread messages. Let's
// find out.
for (const thread of this.props.room!.getThreads()) {
// If the current thread has unread messages, we're done.
if (doesRoomOrThreadHaveUnreadMessages(thread)) {
return NotificationColor.Bold;
}
}
// Otherwise, no notification color.
return NotificationColor.None;
}

private onUpdateStatus = (notificationState: SummarizedNotificationState): void => {
Expand Down
13 changes: 8 additions & 5 deletions src/hooks/useUnreadNotifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ limitations under the License.
*/

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

import { getUnsentMessages } from "../components/structures/RoomStatusBar";
import { getRoomNotifsState, getUnreadNotificationCount, RoomNotifState } from "../RoomNotifs";
import { NotificationColor } from "../stores/notifications/NotificationColor";
import { doesRoomHaveUnreadMessages } from "../Unread";
import { doesRoomOrThreadHaveUnreadMessages } from "../Unread";
import { EffectiveMembership, getEffectiveMembership } from "../utils/membership";
import { useEventEmitter } from "./useEventEmitter";

Expand Down Expand Up @@ -75,12 +76,14 @@ export const useUnreadNotifications = (
setColor(NotificationColor.Red);
} else if (greyNotifs > 0) {
setColor(NotificationColor.Grey);
} else if (!threadId) {
// TODO: No support for `Bold` on threads at the moment

} else {
// We don't have any notified messages, but we might have unread messages. Let's
// find out.
const hasUnread = doesRoomHaveUnreadMessages(room);
let roomOrThread: Room | Thread = room;
if (threadId) {
roomOrThread = room.getThread(threadId)!;
}
const hasUnread = doesRoomOrThreadHaveUnreadMessages(roomOrThread);
setColor(hasUnread ? NotificationColor.Bold : NotificationColor.None);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import RoomHeaderButtons from "../../../../src/components/views/right_panel/Room
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import SettingsStore from "../../../../src/settings/SettingsStore";
import { stubClient } from "../../../test-utils";
import { mkThread } from "../../../test-utils/threads";

describe("RoomHeaderButtons-test.tsx", function () {
const ROOM_ID = "!roomId:example.org";
Expand Down Expand Up @@ -52,7 +53,7 @@ describe("RoomHeaderButtons-test.tsx", function () {
return container.querySelector(".mx_RightPanel_threadsButton");
}

function isIndicatorOfType(container, type: "red" | "gray") {
function isIndicatorOfType(container, type: "red" | "gray" | "bold") {
return container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator").className.includes(type);
}

Expand All @@ -76,7 +77,7 @@ describe("RoomHeaderButtons-test.tsx", function () {
expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull();
});

it("room wide notification does not change the thread button", () => {
it.only("thread notification does change the thread button", () => {
const { container } = getComponent(room);

room.setThreadUnreadNotificationCount("$123", NotificationCountType.Total, 1);
Expand All @@ -89,6 +90,10 @@ describe("RoomHeaderButtons-test.tsx", function () {
room.setThreadUnreadNotificationCount("$123", NotificationCountType.Highlight, 0);

expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull();

// Thread activity should appear on the icon.
mkThread({ room, client, authorId: client.getUserId()!, participantUserIds: ["@alice:example.org"] });
expect(isIndicatorOfType(getComponent(room), "bold")).toBe(true);
Comment on lines +94 to +96
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assertion is failing -- from adding some debug logs it seems like the thread is getting created after the assertion. I'm not sure what's going on.

});

it("does not explode without a room", () => {
Expand Down
3 changes: 2 additions & 1 deletion test/components/views/rooms/EventTile-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ describe("EventTile", () => {
it("shows an unread notification bage", () => {
const { container } = getComponent({}, TimelineRenderingType.ThreadsList);

expect(container.getElementsByClassName("mx_NotificationBadge")).toHaveLength(0);
// By default, the thread will assume there's unread activity in it.
expect(container.getElementsByClassName("mx_NotificationBadge")).toHaveLength(1);

act(() => {
room.setThreadUnreadNotificationCount(mxEvent.getId(), NotificationCountType.Total, 3);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ limitations under the License.
import React from "react";
import "jest-mock";
import { screen, act, render } from "@testing-library/react";
import { MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/client";
import { MsgType } from "matrix-js-sdk/src/matrix";
import { PendingEventOrdering } from "matrix-js-sdk/src/client";
import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
import { mocked } from "jest-mock";
import { EventStatus } from "matrix-js-sdk/src/models/event-status";

import { mkThread } from "../../../../test-utils/threads";
import { UnreadNotificationBadge } from "../../../../../src/components/views/rooms/NotificationBadge/UnreadNotificationBadge";
import { mkMessage, stubClient } from "../../../../test-utils/test-utils";
import { mkEvent, mkMessage, stubClient } from "../../../../test-utils/test-utils";
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
import * as RoomNotifs from "../../../../../src/RoomNotifs";

Expand All @@ -34,28 +35,38 @@ jest.mock("../../../../../src/RoomNotifs", () => ({
}));

const ROOM_ID = "!roomId:example.org";
let THREAD_ID;
let THREAD_ID: string;

describe("UnreadNotificationBadge", () => {
let mockClient: MatrixClient;
stubClient();
const client = MatrixClientPeg.get();
let room: Room;

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

beforeAll(() => {
client.supportsExperimentalThreads = () => true;
});

beforeEach(() => {
jest.clearAllMocks();

stubClient();
mockClient = mocked(MatrixClientPeg.get());

room = new Room(ROOM_ID, mockClient, mockClient.getUserId() ?? "", {
room = new Room(ROOM_ID, client, client.getUserId()!, {
pendingEventOrdering: PendingEventOrdering.Detached,
});
room.setUnreadNotificationCount(NotificationCountType.Total, 1);
room.setUnreadNotificationCount(NotificationCountType.Highlight, 0);

const { rootEvent } = mkThread({
room,
client,
authorId: client.getUserId()!,
participantUserIds: [client.getUserId()!],
});
THREAD_ID = rootEvent.getId()!;

room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total, 1);
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight, 0);

Expand Down Expand Up @@ -125,4 +136,33 @@ describe("UnreadNotificationBadge", () => {
const { container } = render(getComponent());
expect(container.querySelector(".mx_NotificationBadge")).toBeNull();
});

it("activity renders unread notification badge", () => {
act(() => {
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total, 0);
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight, 0);

// Add another event on the thread which is not sent by us.
const event = mkEvent({
event: true,
type: "m.room.message",
user: "@alice:server.org",
room: room.roomId,
content: {
"msgtype": MsgType.Text,
"body": "Hello from Bob",
"m.relates_to": {
event_id: THREAD_ID,
rel_type: "m.thread",
},
},
});
room.addLiveEvents([event]);
});

const { container } = render(getComponent(THREAD_ID));
expect(container.querySelector(".mx_NotificationBadge_dot")).toBeTruthy();
expect(container.querySelector(".mx_NotificationBadge_visible")).toBeTruthy();
expect(container.querySelector(".mx_NotificationBadge_highlighted")).toBeFalsy();
});
});