Skip to content

Commit 51218dd

Browse files
t3chguyrichvdh
andauthored
Fix thread list being ordered based on all updates (#3458)
* Add test for thread list stability around non-reply updates * Fix thread list being ordered based on all updates * Fix test * Update spec/integ/matrix-client-event-timeline.spec.ts Co-authored-by: Richard van der Hoff <[email protected]> * Iterate --------- Co-authored-by: Richard van der Hoff <[email protected]>
1 parent b907433 commit 51218dd

File tree

2 files changed

+133
-4
lines changed

2 files changed

+133
-4
lines changed

spec/integ/matrix-client-event-timeline.spec.ts

Lines changed: 131 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1453,7 +1453,7 @@ describe("MatrixClient event timelines", function () {
14531453
expect(room.getPendingEvents()).toHaveLength(1);
14541454
});
14551455

1456-
it("should handle thread updates by reordering the thread list", async () => {
1456+
it("should handle new thread replies by reordering the thread list", async () => {
14571457
// Test data for a second thread
14581458
const THREAD2_ROOT = utils.mkEvent({
14591459
room: roomId,
@@ -1568,7 +1568,8 @@ describe("MatrixClient event timelines", function () {
15681568
// Test adding a second event to the first thread
15691569
const thread = room.getThread(THREAD_ROOT.event_id!)!;
15701570
thread.initialEventsFetched = true;
1571-
const prom = emitPromise(room, ThreadEvent.Update);
1571+
const prom = emitPromise(room, ThreadEvent.NewReply);
1572+
respondToEvent(THREAD_ROOT_UPDATED);
15721573
respondToEvent(THREAD_ROOT_UPDATED);
15731574
respondToEvent(THREAD_ROOT_UPDATED);
15741575
respondToEvent(THREAD_ROOT_UPDATED);
@@ -1583,6 +1584,134 @@ describe("MatrixClient event timelines", function () {
15831584
THREAD_ROOT.event_id,
15841585
]);
15851586
});
1587+
1588+
it("should not reorder the thread list on other thread updates", async () => {
1589+
// Test data for a second thread
1590+
const THREAD2_ROOT = utils.mkEvent({
1591+
room: roomId,
1592+
user: userId,
1593+
type: "m.room.message",
1594+
content: {
1595+
body: "thread root",
1596+
msgtype: "m.text",
1597+
},
1598+
unsigned: {
1599+
"m.relations": {
1600+
"io.element.thread": {
1601+
//"latest_event": undefined,
1602+
count: 1,
1603+
current_user_participated: true,
1604+
},
1605+
},
1606+
},
1607+
event: false,
1608+
});
1609+
1610+
const THREAD2_REPLY = utils.mkEvent({
1611+
room: roomId,
1612+
user: userId,
1613+
type: "m.room.message",
1614+
content: {
1615+
"body": "thread2 reply",
1616+
"msgtype": "m.text",
1617+
"m.relates_to": {
1618+
// We can't use the const here because we change server support mode for test
1619+
rel_type: "io.element.thread",
1620+
event_id: THREAD_ROOT.event_id,
1621+
},
1622+
},
1623+
event: false,
1624+
});
1625+
1626+
// @ts-ignore we know this is a defined path for THREAD ROOT
1627+
THREAD2_ROOT.unsigned["m.relations"]["io.element.thread"].latest_event = THREAD2_REPLY;
1628+
1629+
const THREAD_REPLY_REACTION = utils.mkEvent({
1630+
room: roomId,
1631+
user: userId,
1632+
type: "m.reaction",
1633+
content: {
1634+
"m.relates_to": {
1635+
rel_type: RelationType.Annotation,
1636+
event_id: THREAD_REPLY.event_id,
1637+
key: "🪿",
1638+
},
1639+
},
1640+
event: true,
1641+
});
1642+
THREAD_REPLY_REACTION.localTimestamp += 1000;
1643+
1644+
// Modified thread root event containing latest thread reply in its unsigned
1645+
const THREAD_ROOT_UPDATED = {
1646+
...THREAD_ROOT,
1647+
unsigned: {
1648+
...THREAD_ROOT.unsigned,
1649+
"m.relations": {
1650+
...THREAD_ROOT.unsigned!["m.relations"],
1651+
"io.element.thread": {
1652+
...THREAD_ROOT.unsigned!["m.relations"]!["io.element.thread"],
1653+
count: 2,
1654+
latest_event: THREAD_REPLY,
1655+
},
1656+
},
1657+
},
1658+
};
1659+
1660+
// Response with test data for the thread list request
1661+
const threadsResponse = {
1662+
chunk: [THREAD2_ROOT, THREAD_ROOT],
1663+
state: [],
1664+
next_batch: RANDOM_TOKEN as string | null,
1665+
};
1666+
1667+
// @ts-ignore
1668+
client.clientOpts.threadSupport = true;
1669+
Thread.setServerSideSupport(FeatureSupport.Stable);
1670+
Thread.setServerSideListSupport(FeatureSupport.Stable);
1671+
Thread.setServerSideFwdPaginationSupport(FeatureSupport.Stable);
1672+
1673+
await client.stopClient(); // we don't need the client to be syncing at this time
1674+
const room = client.getRoom(roomId)!;
1675+
1676+
// Set up room threads
1677+
const timelineSets = await room!.createThreadsTimelineSets();
1678+
expect(timelineSets).not.toBeNull();
1679+
respondToThreads(threadsResponse);
1680+
respondToThreads(threadsResponse);
1681+
respondToEvent(THREAD_ROOT);
1682+
respondToEvent(THREAD2_ROOT);
1683+
respondToThread(THREAD_ROOT, [THREAD_REPLY]);
1684+
respondToThread(THREAD2_ROOT, [THREAD2_REPLY]);
1685+
await flushHttp(room.fetchRoomThreads());
1686+
const threadIds = room.getThreads().map((thread) => thread.id);
1687+
expect(threadIds).toContain(THREAD_ROOT.event_id);
1688+
expect(threadIds).toContain(THREAD2_ROOT.event_id);
1689+
const [allThreads] = timelineSets!;
1690+
const timeline = allThreads.getLiveTimeline()!;
1691+
// Test threads are in chronological order
1692+
expect(timeline.getEvents().map((it) => it.event.event_id)).toEqual([
1693+
THREAD_ROOT.event_id,
1694+
THREAD2_ROOT.event_id,
1695+
]);
1696+
1697+
// Test adding a second event to the first thread
1698+
const thread = room.getThread(THREAD_ROOT.event_id!)!;
1699+
thread.initialEventsFetched = true;
1700+
const prom = emitPromise(room, ThreadEvent.Update);
1701+
respondToEvent(THREAD_ROOT_UPDATED);
1702+
respondToEvent(THREAD_ROOT_UPDATED);
1703+
respondToEvent(THREAD_ROOT_UPDATED);
1704+
respondToEvent(THREAD2_ROOT);
1705+
await room.addLiveEvents([THREAD_REPLY_REACTION]);
1706+
await httpBackend.flushAllExpected();
1707+
await prom;
1708+
expect(thread.length).toBe(2);
1709+
// Test thread order is unchanged
1710+
expect(timeline!.getEvents().map((it) => it.event.event_id)).toEqual([
1711+
THREAD_ROOT.event_id,
1712+
THREAD2_ROOT.event_id,
1713+
]);
1714+
});
15861715
});
15871716

15881717
describe("without server compatibility", function () {

src/models/room.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1957,7 +1957,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
19571957
}
19581958
}
19591959

1960-
this.on(ThreadEvent.Update, this.onThreadUpdate);
1960+
this.on(ThreadEvent.NewReply, this.onThreadReply);
19611961
this.on(ThreadEvent.Delete, this.onThreadDelete);
19621962
this.threadsReady = true;
19631963
}
@@ -2055,7 +2055,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
20552055
}
20562056
}
20572057

2058-
private onThreadUpdate(thread: Thread): void {
2058+
private onThreadReply(thread: Thread): void {
20592059
this.updateThreadRootEvents(thread, false, true);
20602060
}
20612061

0 commit comments

Comments
 (0)