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 2 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 @@ -192,9 +193,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 @@ -70,12 +71,14 @@ export const useUnreadNotifications = (room: Room, threadId?: string): {
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 @@ -55,7 +56,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 @@ -81,7 +82,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 @@ -94,6 +95,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 @@ -133,7 +133,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,15 +17,16 @@ 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 {
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 { mkThread } from "../../../../test-utils/threads";
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
import * as RoomNotifs from "../../../../../src/RoomNotifs";

Expand All @@ -36,28 +37,35 @@ 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 @@ -129,4 +137,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();
});
});