From f5ad4d0a73d0e81bb66580d85e2289372fe901aa Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Tue, 7 Nov 2023 13:45:02 +0000 Subject: [PATCH] Revert "Revert "Move all related messages into main timeline on redaction"" This reverts commit 257b40bceb304001c03aaec7b140a1fd05c96d9e. --- spec/unit/models/event.spec.ts | 133 +++++++++++++++++++++++++++++++++ src/models/event.ts | 17 ++++- 2 files changed, 149 insertions(+), 1 deletion(-) diff --git a/spec/unit/models/event.spec.ts b/spec/unit/models/event.spec.ts index 696681aff..9e7720a14 100644 --- a/spec/unit/models/event.spec.ts +++ b/spec/unit/models/event.spec.ts @@ -134,6 +134,94 @@ describe("MatrixEvent", () => { expect(threadLiveEventIds(room, 0)).not.toContain(ev.getId()); }); + it("should move reactions to a redacted event into the main timeline", async () => { + // Given an event in a thread with a reaction + const mockClient = createMockClient(); + const room = new Room("!roomid:e.xyz", mockClient, "myname"); + const threadRoot = createEvent("$threadroot:server"); + const ev = createThreadedEvent("$event1:server", threadRoot.getId()!); + const reaction = createReactionEvent("$reaction:server", ev.getId()!); + + await room.addLiveEvents([threadRoot, ev, reaction]); + await room.createThreadsTimelineSets(); + expect(reaction.threadRootId).toEqual(threadRoot.getId()); + expect(mainTimelineLiveEventIds(room)).toEqual([threadRoot.getId()]); + expect(threadLiveEventIds(room, 0)).toEqual([threadRoot.getId(), ev.getId(), reaction.getId()]); + + // When I redact the event + const redaction = createRedaction(ev.getId()!); + ev.makeRedacted(redaction, room); + + // Then the reaction moves into the main timeline + expect(reaction.threadRootId).toBeUndefined(); + expect(mainTimelineLiveEventIds(room)).toEqual([threadRoot.getId(), ev.getId(), reaction.getId()]); + expect(threadLiveEventIds(room, 0)).not.toContain(reaction.getId()); + }); + + it("should move edits of a redacted event into the main timeline", async () => { + // Given an event in a thread with a reaction + const mockClient = createMockClient(); + const room = new Room("!roomid:e.xyz", mockClient, "myname"); + const threadRoot = createEvent("$threadroot:server"); + const ev = createThreadedEvent("$event1:server", threadRoot.getId()!); + const edit = createEditEvent("$edit:server", ev.getId()!); + + await room.addLiveEvents([threadRoot, ev, edit]); + await room.createThreadsTimelineSets(); + expect(edit.threadRootId).toEqual(threadRoot.getId()); + expect(mainTimelineLiveEventIds(room)).toEqual([threadRoot.getId()]); + expect(threadLiveEventIds(room, 0)).toEqual([threadRoot.getId(), ev.getId(), edit.getId()]); + + // When I redact the event + const redaction = createRedaction(ev.getId()!); + ev.makeRedacted(redaction, room); + + // Then the edit moves into the main timeline + expect(edit.threadRootId).toBeUndefined(); + expect(mainTimelineLiveEventIds(room)).toEqual([threadRoot.getId(), ev.getId(), edit.getId()]); + expect(threadLiveEventIds(room, 0)).not.toContain(edit.getId()); + }); + + it("should move reactions to replies to replies a redacted event into the main timeline", async () => { + // Given an event in a thread with a reaction + const mockClient = createMockClient(); + const room = new Room("!roomid:e.xyz", mockClient, "myname"); + const threadRoot = createEvent("$threadroot:server"); + const ev = createThreadedEvent("$event1:server", threadRoot.getId()!); + const reply1 = createReplyEvent("$reply1:server", ev.getId()!); + const reply2 = createReplyEvent("$reply2:server", reply1.getId()!); + const reaction = createReactionEvent("$reaction:server", reply2.getId()!); + + await room.addLiveEvents([threadRoot, ev, reply1, reply2, reaction]); + await room.createThreadsTimelineSets(); + expect(reaction.threadRootId).toEqual(threadRoot.getId()); + expect(mainTimelineLiveEventIds(room)).toEqual([threadRoot.getId()]); + expect(threadLiveEventIds(room, 0)).toEqual([ + threadRoot.getId(), + ev.getId(), + reply1.getId(), + reply2.getId(), + reaction.getId(), + ]); + + // When I redact the event + const redaction = createRedaction(ev.getId()!); + ev.makeRedacted(redaction, room); + + // Then the replies move to the main thread and the reaction disappears + expect(reaction.threadRootId).toBeUndefined(); + expect(mainTimelineLiveEventIds(room)).toEqual([ + threadRoot.getId(), + ev.getId(), + reply1.getId(), + reply2.getId(), + reaction.getId(), + ]); + expect(threadLiveEventIds(room, 0)).not.toContain(reply1.getId()); + expect(threadLiveEventIds(room, 0)).not.toContain(reply2.getId()); + expect(threadLiveEventIds(room, 0)).not.toContain(reaction.getId()); + }); + function createMockClient(): MatrixClient { return { supportsThreads: jest.fn().mockReturnValue(true), @@ -166,6 +254,51 @@ describe("MatrixEvent", () => { }); } + function createEditEvent(eventId: string, repliedToId: string): MatrixEvent { + return new MatrixEvent({ + type: "m.room.message", + content: { + "body": "Edited", + "m.new_content": { + body: "Edited", + }, + "m.relates_to": { + event_id: repliedToId, + rel_type: "m.replace", + }, + }, + event_id: eventId, + }); + } + + function createReplyEvent(eventId: string, repliedToId: string): MatrixEvent { + return new MatrixEvent({ + type: "m.room.message", + content: { + "m.relates_to": { + event_id: repliedToId, + key: "x", + rel_type: "m.in_reply_to", + }, + }, + event_id: eventId, + }); + } + + function createReactionEvent(eventId: string, reactedToId: string): MatrixEvent { + return new MatrixEvent({ + type: "m.reaction", + content: { + "m.relates_to": { + event_id: reactedToId, + key: "x", + rel_type: "m.annotation", + }, + }, + event_id: eventId, + }); + } + function createRedaction(redactedEventid: string): MatrixEvent { return new MatrixEvent({ type: "m.room.redaction", diff --git a/src/models/event.ts b/src/models/event.ts index 3da9d2de0..c76da4a45 100644 --- a/src/models/event.ts +++ b/src/models/event.ts @@ -1210,12 +1210,27 @@ export class MatrixEvent extends TypedEventEmitter