1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-07-30 04:23:07 +03:00

Extend logic for local notification processing to threads (#4111)

* Move code for processing our own receipts to Room

This is some code to process our own receipts and recalculate our
notification counts.

There was no reason for this to be in client. Room is still rather
large, but at least it makes somewhat more sense there.

Moving as a refactor before I start work on it.

* Add test for the client-side e2e notifications code

* Extend logic for local notification processing to threads

There's collection of logic for for processing receipts and recomputing
notifications for encrypted rooms, but we didn't do the same for threads.
As a reasult, when I tried pulling some of the logic over in
https://github.com/matrix-org/matrix-js-sdk/pull/4106
clearing notifications on threads just broke.

This extends the logic of reprocessing local notifications when a receipt
arrives to threads.

Based on https://github.com/matrix-org/matrix-js-sdk/pull/4109

* simplify object literal

* Add tests & null guard

* Remove unused imports

* Add another skipped test

* Unused import

* enable tests

* Fix thread support nightmare

* Try this way

* Unused import

* Comment the bear trap

* expand comment
This commit is contained in:
David Baker
2024-03-21 12:22:19 +00:00
committed by GitHub
parent dc2d03dea5
commit e517d009bf
4 changed files with 308 additions and 36 deletions

View File

@ -39,6 +39,7 @@ import {
IndexedDBStore,
RelationType,
EventType,
MatrixEventEvent,
} from "../../src";
import { ReceiptType } from "../../src/@types/read_receipts";
import { UNREAD_THREAD_NOTIFICATIONS } from "../../src/@types/sync";
@ -1800,7 +1801,7 @@ describe("MatrixClient syncing", () => {
expect(room.getRoomUnreadNotificationCount(NotificationCountType.Total)).toBe(0);
});
it("should recalculate highlights on receipt for encrypted rooms", async () => {
it("should recalculate highlights on unthreaded receipt for encrypted rooms", async () => {
const myUserId = client!.getUserId()!;
const firstEventId = syncData.rooms.join[roomId].timeline.events[1].event_id;
@ -1850,6 +1851,235 @@ describe("MatrixClient syncing", () => {
// the room should now have one highlight since our receipt was before the ping message
expect(room.getRoomUnreadNotificationCount(NotificationCountType.Highlight)).toBe(1);
});
it("should recalculate highlights on main thread receipt for encrypted rooms", async () => {
const myUserId = client!.getUserId()!;
const firstEventId = syncData.rooms.join[roomId].timeline.events[1].event_id;
// add a receipt for the first event in the room (let's say the user has already read that one)
syncData.rooms.join[roomId].ephemeral.events = [
{
content: {
[firstEventId]: {
"m.read": {
[myUserId]: { ts: 1, thread_id: "main" },
},
},
},
type: "m.receipt",
},
];
// Now add a highlighting event after that receipt
const pingEvent = utils.mkMessage({
room: roomId,
user: otherUserId,
msg: client?.getUserId() + " ping",
}) as IRoomEvent;
syncData.rooms.join[roomId].timeline.events.push(pingEvent);
// fudge this to make it a highlight
client!.getPushActionsForEvent = (ev: MatrixEvent): IActionsObject | null => {
if (ev.getId() === pingEvent.event_id) {
return {
notify: true,
tweaks: {
highlight: true,
},
};
}
return null;
};
httpBackend!.when("GET", "/sync").respond(200, syncData);
client!.startClient();
await Promise.all([httpBackend!.flushAllExpected(), awaitSyncEvent()]);
const room = client!.getRoom(roomId)!;
expect(room).toBeInstanceOf(Room);
// the room should now have one highlight since our receipt was before the ping message
expect(room.getRoomUnreadNotificationCount(NotificationCountType.Highlight)).toBe(1);
});
describe("notification processing in threads", () => {
let threadEvent1: IRoomEvent;
let threadEvent2: IRoomEvent;
let firstEventId: string;
beforeEach(() => {
firstEventId = syncData.rooms.join[roomId].timeline.events[1].event_id;
// Add a threaded event off of the first event
threadEvent1 = utils.mkEvent({
type: EventType.RoomMessage,
user: otherUserId,
room: roomId,
ts: 500,
content: {
"body": "first thread response",
"m.relates_to": {
"event_id": firstEventId,
"m.in_reply_to": {
event_id: firstEventId,
},
"rel_type": "io.element.thread",
},
},
}) as IRoomEvent;
syncData.rooms.join[roomId].timeline.events.push(threadEvent1);
// ...and another
threadEvent2 = utils.mkEvent({
type: EventType.RoomMessage,
user: otherUserId,
room: roomId,
ts: 1500,
content: {
"body": "second thread response",
"m.relates_to": {
"event_id": firstEventId,
"m.in_reply_to": {
event_id: firstEventId,
},
"rel_type": "io.element.thread",
},
},
}) as IRoomEvent;
syncData.rooms.join[roomId].timeline.events.push(threadEvent2);
// fudge to make these highlights
client!.getPushActionsForEvent = (ev: MatrixEvent): IActionsObject | null => {
if ([threadEvent1.event_id, threadEvent2.event_id].includes(ev.getId()!)) {
return {
notify: true,
tweaks: {
highlight: true,
},
};
}
return null;
};
});
it("checks threads with notifications on unthreaded receipts", async () => {
const myUserId = client!.getUserId()!;
// add a receipt for a random, ficticious thread, otherwise the client will
// think that the thread is before any threaded receipts and ignore it.
syncData.rooms.join[roomId].ephemeral.events = [
{
content: {
[firstEventId]: {
"m.read": {
[myUserId]: { ts: 1, thread_id: "some_other_thread" },
},
},
},
type: "m.receipt",
},
];
httpBackend!.when("GET", "/sync").respond(200, syncData);
client!.startClient({ threadSupport: true });
await Promise.all([httpBackend!.flushAllExpected(), awaitSyncEvent()]);
const room = client!.getRoom(roomId)!;
// pretend that the client has decrypted an event to trigger it to compute
// local notifications
client?.emit(MatrixEventEvent.Decrypted, room.findEventById(firstEventId)!);
client?.emit(MatrixEventEvent.Decrypted, room.findEventById(threadEvent1.event_id)!);
client?.emit(MatrixEventEvent.Decrypted, room.findEventById(threadEvent2.event_id)!);
expect(room).toBeInstanceOf(Room);
// we should now have one highlight: the unread message that pings
expect(
room.getThreadUnreadNotificationCount(firstEventId, NotificationCountType.Highlight),
).toEqual(2);
const syncData2 = {
rooms: {
join: {
[roomId]: {
ephemeral: {
events: [
{
content: {
[firstEventId]: {
"m.read": {
[myUserId]: { ts: 1 },
},
},
},
type: "m.receipt",
},
],
},
},
},
},
} as unknown as ISyncResponse;
httpBackend!.when("GET", "/sync").respond(200, syncData2);
await Promise.all([httpBackend!.flush("/sync", 1), utils.syncPromise(client!)]);
expect(room.getRoomUnreadNotificationCount(NotificationCountType.Highlight)).toBe(0);
});
it("should recalculate highlights on threaded receipt for encrypted rooms", async () => {
const myUserId = client!.getUserId()!;
// add a receipt for the first message in the threadm leaving the second one unread
syncData.rooms.join[roomId].ephemeral.events = [
{
content: {
[threadEvent1.event_id]: {
"m.read": {
[myUserId]: { ts: 1, thread_id: firstEventId },
},
},
},
type: "m.receipt",
},
];
// fudge to make both thread replies highlights
client!.getPushActionsForEvent = (ev: MatrixEvent): IActionsObject | null => {
if ([threadEvent1.event_id, threadEvent2.event_id].includes(ev.getId()!)) {
return {
notify: true,
tweaks: {
highlight: true,
},
};
}
return null;
};
httpBackend!.when("GET", "/sync").respond(200, syncData);
client!.startClient({ threadSupport: true });
await Promise.all([httpBackend!.flushAllExpected(), awaitSyncEvent()]);
const room = client!.getRoom(roomId)!;
expect(room).toBeInstanceOf(Room);
// pretend that the client has decrypted an event to trigger it to compute
// local notifications
client?.emit(MatrixEventEvent.Decrypted, room.findEventById(firstEventId)!);
// the room should now have one highlight: the second thread message
expect(room.getThreadUnreadNotificationCount(firstEventId, NotificationCountType.Highlight)).toBe(
1,
);
});
});
});
});