From 80f835d42c84053220fde37c856efdd9b368a567 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 8 Jun 2023 12:22:35 +0100 Subject: [PATCH 1/5] Add test for thread list stability around non-reply updates --- .../matrix-client-event-timeline.spec.ts | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/spec/integ/matrix-client-event-timeline.spec.ts b/spec/integ/matrix-client-event-timeline.spec.ts index c7083db1968..b3967bfa28c 100644 --- a/spec/integ/matrix-client-event-timeline.spec.ts +++ b/spec/integ/matrix-client-event-timeline.spec.ts @@ -1583,6 +1583,134 @@ describe("MatrixClient event timelines", function () { THREAD_ROOT.event_id, ]); }); + + it("should handle not reorder the thread list on other thread updates", async () => { + // Test data for a second thread + const THREAD2_ROOT = utils.mkEvent({ + room: roomId, + user: userId, + type: "m.room.message", + content: { + body: "thread root", + msgtype: "m.text", + }, + unsigned: { + "m.relations": { + "io.element.thread": { + //"latest_event": undefined, + count: 1, + current_user_participated: true, + }, + }, + }, + event: false, + }); + + const THREAD2_REPLY = utils.mkEvent({ + room: roomId, + user: userId, + type: "m.room.message", + content: { + "body": "thread2 reply", + "msgtype": "m.text", + "m.relates_to": { + // We can't use the const here because we change server support mode for test + rel_type: "io.element.thread", + event_id: THREAD_ROOT.event_id, + }, + }, + event: false, + }); + + // @ts-ignore we know this is a defined path for THREAD ROOT + THREAD2_ROOT.unsigned["m.relations"]["io.element.thread"].latest_event = THREAD2_REPLY; + + const THREAD_REPLY_REACTION = utils.mkEvent({ + room: roomId, + user: userId, + type: "m.reaction", + content: { + "m.relates_to": { + rel_type: RelationType.Annotation, + event_id: THREAD_REPLY.event_id, + key: "🪿", + }, + }, + event: true, + }); + THREAD_REPLY_REACTION.localTimestamp += 1000; + + // Test data for the first thread, with the second reply + const THREAD_ROOT_UPDATED = { + ...THREAD_ROOT, + unsigned: { + ...THREAD_ROOT.unsigned, + "m.relations": { + ...THREAD_ROOT.unsigned!["m.relations"], + "io.element.thread": { + ...THREAD_ROOT.unsigned!["m.relations"]!["io.element.thread"], + count: 2, + latest_event: THREAD_REPLY, + }, + }, + }, + }; + + // Response with test data for the thread list request + const threadsResponse = { + chunk: [THREAD2_ROOT, THREAD_ROOT], + state: [], + next_batch: RANDOM_TOKEN as string | null, + }; + + // @ts-ignore + client.clientOpts.threadSupport = true; + Thread.setServerSideSupport(FeatureSupport.Stable); + Thread.setServerSideListSupport(FeatureSupport.Stable); + Thread.setServerSideFwdPaginationSupport(FeatureSupport.Stable); + + await client.stopClient(); // we don't need the client to be syncing at this time + const room = client.getRoom(roomId)!; + + // Setup room threads + const timelineSets = await room!.createThreadsTimelineSets(); + expect(timelineSets).not.toBeNull(); + respondToThreads(threadsResponse); + respondToThreads(threadsResponse); + respondToEvent(THREAD_ROOT); + respondToEvent(THREAD2_ROOT); + respondToThread(THREAD_ROOT, [THREAD_REPLY]); + respondToThread(THREAD2_ROOT, [THREAD2_REPLY]); + await flushHttp(room.fetchRoomThreads()); + const threadIds = room.getThreads().map((thread) => thread.id); + expect(threadIds).toContain(THREAD_ROOT.event_id); + expect(threadIds).toContain(THREAD2_ROOT.event_id); + const [allThreads] = timelineSets!; + const timeline = allThreads.getLiveTimeline()!; + // Test threads are in chronological order + expect(timeline.getEvents().map((it) => it.event.event_id)).toEqual([ + THREAD_ROOT.event_id, + THREAD2_ROOT.event_id, + ]); + + // Test adding a second event to the first thread + const thread = room.getThread(THREAD_ROOT.event_id!)!; + thread.initialEventsFetched = true; + const prom = emitPromise(room, ThreadEvent.Update); + respondToEvent(THREAD_ROOT_UPDATED); + respondToEvent(THREAD_ROOT_UPDATED); + respondToEvent(THREAD_ROOT_UPDATED); + respondToEvent(THREAD2_ROOT); + await room.addLiveEvents([THREAD_REPLY_REACTION]); + await httpBackend.flushAllExpected(); + await prom; + expect(thread.length).toBe(2); + // Test thread order is unchanged + expect(timeline!.getEvents().map((it) => it.event.event_id)).toEqual([ + THREAD_ROOT.event_id, + THREAD2_ROOT.event_id, + ]); + }); }); describe("without server compatibility", function () { From cb742c554460c8a990e283d8bf8f21052c4e0d79 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 8 Jun 2023 12:22:58 +0100 Subject: [PATCH 2/5] Fix thread list being ordered based on all updates --- src/models/room.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/models/room.ts b/src/models/room.ts index e64d9f938a2..48c8b00ff1b 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -1957,7 +1957,7 @@ export class Room extends ReadReceipt { } } - this.on(ThreadEvent.Update, this.onThreadUpdate); + this.on(ThreadEvent.NewReply, this.onThreadReply); this.on(ThreadEvent.Delete, this.onThreadDelete); this.threadsReady = true; } @@ -2055,7 +2055,7 @@ export class Room extends ReadReceipt { } } - private onThreadUpdate(thread: Thread): void { + private onThreadReply(thread: Thread): void { this.updateThreadRootEvents(thread, false, true); } From 0bbec691358a3b703a237e6d0810f6c990684fa4 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 8 Jun 2023 12:26:33 +0100 Subject: [PATCH 3/5] Fix test --- spec/integ/matrix-client-event-timeline.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/integ/matrix-client-event-timeline.spec.ts b/spec/integ/matrix-client-event-timeline.spec.ts index b3967bfa28c..a9c8e384ccc 100644 --- a/spec/integ/matrix-client-event-timeline.spec.ts +++ b/spec/integ/matrix-client-event-timeline.spec.ts @@ -1453,7 +1453,7 @@ describe("MatrixClient event timelines", function () { expect(room.getPendingEvents()).toHaveLength(1); }); - it("should handle thread updates by reordering the thread list", async () => { + it("should handle new thread replies by reordering the thread list", async () => { // Test data for a second thread const THREAD2_ROOT = utils.mkEvent({ room: roomId, @@ -1568,7 +1568,8 @@ describe("MatrixClient event timelines", function () { // Test adding a second event to the first thread const thread = room.getThread(THREAD_ROOT.event_id!)!; thread.initialEventsFetched = true; - const prom = emitPromise(room, ThreadEvent.Update); + const prom = emitPromise(room, ThreadEvent.NewReply); + respondToEvent(THREAD_ROOT_UPDATED); respondToEvent(THREAD_ROOT_UPDATED); respondToEvent(THREAD_ROOT_UPDATED); respondToEvent(THREAD_ROOT_UPDATED); From 932806ff05ca9c4f9d44bd34c919db30ac3ae5bf Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 8 Jun 2023 13:37:59 +0100 Subject: [PATCH 4/5] Update spec/integ/matrix-client-event-timeline.spec.ts Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> --- spec/integ/matrix-client-event-timeline.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/integ/matrix-client-event-timeline.spec.ts b/spec/integ/matrix-client-event-timeline.spec.ts index a9c8e384ccc..3996283d820 100644 --- a/spec/integ/matrix-client-event-timeline.spec.ts +++ b/spec/integ/matrix-client-event-timeline.spec.ts @@ -1585,7 +1585,7 @@ describe("MatrixClient event timelines", function () { ]); }); - it("should handle not reorder the thread list on other thread updates", async () => { + it("should not reorder the thread list on other thread updates", async () => { // Test data for a second thread const THREAD2_ROOT = utils.mkEvent({ room: roomId, From 8553dc239067949f351f53c3eeadf047b5db8cd6 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 9 Jun 2023 09:22:54 +0100 Subject: [PATCH 5/5] Iterate --- spec/integ/matrix-client-event-timeline.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/integ/matrix-client-event-timeline.spec.ts b/spec/integ/matrix-client-event-timeline.spec.ts index a9c8e384ccc..f2826520869 100644 --- a/spec/integ/matrix-client-event-timeline.spec.ts +++ b/spec/integ/matrix-client-event-timeline.spec.ts @@ -1641,7 +1641,7 @@ describe("MatrixClient event timelines", function () { }); THREAD_REPLY_REACTION.localTimestamp += 1000; - // Test data for the first thread, with the second reply + // Modified thread root event containing latest thread reply in its unsigned const THREAD_ROOT_UPDATED = { ...THREAD_ROOT, unsigned: { @@ -1673,7 +1673,7 @@ describe("MatrixClient event timelines", function () { await client.stopClient(); // we don't need the client to be syncing at this time const room = client.getRoom(roomId)!; - // Setup room threads + // Set up room threads const timelineSets = await room!.createThreadsTimelineSets(); expect(timelineSets).not.toBeNull(); respondToThreads(threadsResponse);