Skip to content

Commit

Permalink
Move all related messages into main timeline on redaction
Browse files Browse the repository at this point in the history
  • Loading branch information
andybalaam committed Oct 17, 2023
1 parent 7be4d15 commit f540515
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 1 deletion.
133 changes: 133 additions & 0 deletions spec/unit/models/event.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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",
Expand Down
17 changes: 16 additions & 1 deletion src/models/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1192,12 +1192,27 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat

// If the redacted event was in a thread
if (room && this.threadRootId && this.threadRootId !== this.getId()) {
this.moveToMainTimeline(room);
this.moveAllRelatedToMainTimeline(room);
}

this.invalidateExtensibleEvent();
}

private moveAllRelatedToMainTimeline(room: Room): void {
const thread = this.thread;
this.moveToMainTimeline(room);

// If we dont have access to the thread, we can only move this
// event, not things related to it.
if (thread) {
for (const event of thread.events) {
if (event.getRelation()?.event_id === this.getId()) {
event.moveAllRelatedToMainTimeline(room);
}
}
}
}

private moveToMainTimeline(room: Room): void {
// Remove it from its thread
this.thread?.timelineSet.removeEvent(this.getId()!);
Expand Down

0 comments on commit f540515

Please sign in to comment.