diff --git a/src/Unread.ts b/src/Unread.ts index 03a816c680..520d6c97f9 100644 --- a/src/Unread.ts +++ b/src/Unread.ts @@ -159,15 +159,13 @@ function makeHasReceipt( // If we found an event matching our receipt, then it's easy: this event // has a receipt if its ID is the same as the one in the receipt. return (ev) => ev.getId() == readUpToId; - } else { - // If we didn't, we have to guess by saying if this event is before the - // receipt's ts, then it we pretend it has a receipt. - const receipt = roomOrThread.getReadReceiptForUserId(myUserId); - if (receipt) { - const receiptTimestamp = receipt.data.ts; - return (ev) => ev.getTs() < receiptTimestamp; - } else { - return (_ev) => false; - } } + + // If we didn't, we have to guess by saying if this event is before the + // receipt's ts, then it we pretend it has a receipt. + const receiptTs = roomOrThread.getReadReceiptForUserId(myUserId)?.data.ts ?? 0; + const unthreadedReceiptTs = roomOrThread.getLastUnthreadedReceiptFor(myUserId)?.ts ?? 0; + // We pick the more recent of the two receipts as the latest + const receiptTimestamp = Math.max(receiptTs, unthreadedReceiptTs); + return (ev) => ev.getTs() < receiptTimestamp; } diff --git a/test/Unread-test.ts b/test/Unread-test.ts index ca67257357..300acfa54b 100644 --- a/test/Unread-test.ts +++ b/test/Unread-test.ts @@ -22,7 +22,11 @@ import { logger } from "matrix-js-sdk/src/logger"; import { haveRendererForEvent } from "../src/events/EventTileFactory"; import { makeBeaconEvent, mkEvent, stubClient } from "./test-utils"; import { mkThread } from "./test-utils/threads"; -import { doesRoomHaveUnreadMessages, eventTriggersUnreadCount } from "../src/Unread"; +import { + doesRoomHaveUnreadMessages, + doesRoomOrThreadHaveUnreadMessages, + eventTriggersUnreadCount, +} from "../src/Unread"; import { MatrixClientPeg } from "../src/MatrixClientPeg"; jest.mock("../src/events/EventTileFactory", () => ({ @@ -122,7 +126,7 @@ describe("Unread", () => { let room: Room; let event: MatrixEvent; const roomId = "!abc:server.org"; - const myId = client.getUserId()!; + const myId = client.getSafeUserId(); beforeAll(() => { client.supportsThreads = () => true; @@ -429,4 +433,73 @@ describe("Unread", () => { ); }); }); + + describe("doesRoomOrThreadHaveUnreadMessages()", () => { + let room: Room; + let event: MatrixEvent; + const roomId = "!abc:server.org"; + const myId = client.getSafeUserId(); + + beforeAll(() => { + client.supportsThreads = () => true; + }); + + beforeEach(() => { + room = new Room(roomId, client, myId); + jest.spyOn(logger, "warn"); + event = mkEvent({ + event: true, + type: "m.room.message", + user: aliceId, + room: roomId, + content: {}, + }); + room.addLiveEvents([event]); + + // Don't care about the code path of hidden events. + mocked(haveRendererForEvent).mockClear().mockReturnValue(true); + }); + + it("should consider unthreaded read receipts for main timeline", () => { + // Send unthreaded receipt into room pointing at the latest event + room.addReceipt( + new MatrixEvent({ + type: "m.receipt", + room_id: "!foo:bar", + content: { + [event.getId()!]: { + [ReceiptType.Read]: { + [myId]: { ts: 1 }, + }, + }, + }, + }), + ); + + expect(doesRoomOrThreadHaveUnreadMessages(room)).toBe(false); + }); + + it("should consider unthreaded read receipts for thread timelines", () => { + // Provide an unthreaded read receipt with ts greater than the latest thread event + const receipt = new MatrixEvent({ + type: "m.receipt", + room_id: "!foo:bar", + content: { + [event.getId()!]: { + [ReceiptType.Read]: { + [myId]: { ts: 10000000000 }, + }, + }, + }, + }); + room.addReceipt(receipt); + + const { thread } = mkThread({ room, client, authorId: myId, participantUserIds: [aliceId] }); + + expect(thread.replyToEvent!.getTs()).toBeLessThan( + receipt.getContent()[event.getId()!][ReceiptType.Read][myId].ts, + ); + expect(doesRoomOrThreadHaveUnreadMessages(thread)).toBe(false); + }); + }); });