You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-06 12:02:40 +03:00
Fix issues with getEventTimeline and thread roots (#2444)
* Add additional tests for thread timelines * Fix issues around mixing up event timeline sets with /context/ API * Increase coverage * Increase coverage * Better scope assertions * Iterate PR
This commit is contained in:
committed by
GitHub
parent
2c2686c910
commit
8e896c4da3
@@ -1,5 +1,5 @@
|
|||||||
import * as utils from "../test-utils/test-utils";
|
import * as utils from "../test-utils/test-utils";
|
||||||
import { EventTimeline } from "../../src/matrix";
|
import { EventTimeline, Filter, MatrixEvent } from "../../src/matrix";
|
||||||
import { logger } from "../../src/logger";
|
import { logger } from "../../src/logger";
|
||||||
import { TestClient } from "../TestClient";
|
import { TestClient } from "../TestClient";
|
||||||
import { Thread, THREAD_RELATION_TYPE } from "../../src/models/thread";
|
import { Thread, THREAD_RELATION_TYPE } from "../../src/models/thread";
|
||||||
@@ -500,7 +500,8 @@ describe("MatrixClient event timelines", function() {
|
|||||||
Thread.setServerSideSupport(true);
|
Thread.setServerSideSupport(true);
|
||||||
client.stopClient(); // we don't need the client to be syncing at this time
|
client.stopClient(); // we don't need the client to be syncing at this time
|
||||||
const room = client.getRoom(roomId);
|
const room = client.getRoom(roomId);
|
||||||
const timelineSet = room.getTimelineSets()[0];
|
const thread = room.createThread(THREAD_ROOT.event_id, undefined, [], false);
|
||||||
|
const timelineSet = thread.timelineSet;
|
||||||
|
|
||||||
httpBackend.when("GET", "/rooms/!foo%3Abar/context/" + encodeURIComponent(THREAD_REPLY.event_id))
|
httpBackend.when("GET", "/rooms/!foo%3Abar/context/" + encodeURIComponent(THREAD_REPLY.event_id))
|
||||||
.respond(200, function() {
|
.respond(200, function() {
|
||||||
@@ -538,6 +539,92 @@ describe("MatrixClient event timelines", function() {
|
|||||||
expect(timeline.getEvents().find(e => e.getId() === THREAD_ROOT.event_id)).toBeTruthy();
|
expect(timeline.getEvents().find(e => e.getId() === THREAD_ROOT.event_id)).toBeTruthy();
|
||||||
expect(timeline.getEvents().find(e => e.getId() === THREAD_REPLY.event_id)).toBeTruthy();
|
expect(timeline.getEvents().find(e => e.getId() === THREAD_REPLY.event_id)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should return undefined when event is not within a thread but timelineSet is", async () => {
|
||||||
|
client.clientOpts.experimentalThreadSupport = true;
|
||||||
|
Thread.setServerSideSupport(true);
|
||||||
|
client.stopClient(); // we don't need the client to be syncing at this time
|
||||||
|
const room = client.getRoom(roomId);
|
||||||
|
const threadRoot = new MatrixEvent(THREAD_ROOT);
|
||||||
|
const thread = room.createThread(THREAD_ROOT.event_id, threadRoot, [threadRoot], false);
|
||||||
|
const timelineSet = thread.timelineSet;
|
||||||
|
|
||||||
|
httpBackend.when("GET", "/rooms/!foo%3Abar/event/" + encodeURIComponent(THREAD_ROOT.event_id))
|
||||||
|
.respond(200, function() {
|
||||||
|
return THREAD_ROOT;
|
||||||
|
});
|
||||||
|
|
||||||
|
httpBackend.when("GET", "/rooms/!foo%3Abar/context/" + encodeURIComponent(EVENTS[0].event_id))
|
||||||
|
.respond(200, function() {
|
||||||
|
return {
|
||||||
|
start: "start_token0",
|
||||||
|
events_before: [],
|
||||||
|
event: EVENTS[0],
|
||||||
|
events_after: [],
|
||||||
|
end: "end_token0",
|
||||||
|
state: [],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const timelinePromise = client.getEventTimeline(timelineSet, EVENTS[0].event_id);
|
||||||
|
await httpBackend.flushAllExpected();
|
||||||
|
|
||||||
|
const timeline = await timelinePromise;
|
||||||
|
expect(timeline).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return undefined when event is within a thread but timelineSet is not", async () => {
|
||||||
|
client.clientOpts.experimentalThreadSupport = true;
|
||||||
|
Thread.setServerSideSupport(true);
|
||||||
|
client.stopClient(); // we don't need the client to be syncing at this time
|
||||||
|
const room = client.getRoom(roomId);
|
||||||
|
const timelineSet = room.getTimelineSets()[0];
|
||||||
|
|
||||||
|
httpBackend.when("GET", "/rooms/!foo%3Abar/context/" + encodeURIComponent(THREAD_REPLY.event_id))
|
||||||
|
.respond(200, function() {
|
||||||
|
return {
|
||||||
|
start: "start_token0",
|
||||||
|
events_before: [],
|
||||||
|
event: THREAD_REPLY,
|
||||||
|
events_after: [],
|
||||||
|
end: "end_token0",
|
||||||
|
state: [],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const timelinePromise = client.getEventTimeline(timelineSet, THREAD_REPLY.event_id);
|
||||||
|
await httpBackend.flushAllExpected();
|
||||||
|
|
||||||
|
const timeline = await timelinePromise;
|
||||||
|
expect(timeline).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should should add lazy loading filter when requested", async () => {
|
||||||
|
client.clientOpts.lazyLoadMembers = true;
|
||||||
|
client.stopClient(); // we don't need the client to be syncing at this time
|
||||||
|
const room = client.getRoom(roomId);
|
||||||
|
const timelineSet = room.getTimelineSets()[0];
|
||||||
|
|
||||||
|
const req = httpBackend.when("GET", "/rooms/!foo%3Abar/context/" + encodeURIComponent(EVENTS[0].event_id));
|
||||||
|
req.respond(200, function() {
|
||||||
|
return {
|
||||||
|
start: "start_token0",
|
||||||
|
events_before: [],
|
||||||
|
event: EVENTS[0],
|
||||||
|
events_after: [],
|
||||||
|
end: "end_token0",
|
||||||
|
state: [],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
req.check((request) => {
|
||||||
|
expect(request.opts.qs.filter).toEqual(JSON.stringify(Filter.LAZY_LOADING_MESSAGES_FILTER));
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
client.getEventTimeline(timelineSet, EVENTS[0].event_id),
|
||||||
|
httpBackend.flushAllExpected(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getLatestTimeline", function() {
|
describe("getLatestTimeline", function() {
|
||||||
|
@@ -5245,14 +5245,15 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
* <p>If the EventTimelineSet object already has the given event in its store, the
|
* <p>If the EventTimelineSet object already has the given event in its store, the
|
||||||
* corresponding timeline will be returned. Otherwise, a /context request is
|
* corresponding timeline will be returned. Otherwise, a /context request is
|
||||||
* made, and used to construct an EventTimeline.
|
* made, and used to construct an EventTimeline.
|
||||||
|
* If the event does not belong to this EventTimelineSet then undefined will be returned.
|
||||||
*
|
*
|
||||||
* @param {EventTimelineSet} timelineSet The timelineSet to look for the event in
|
* @param {EventTimelineSet} timelineSet The timelineSet to look for the event in, must be bound to a room
|
||||||
* @param {string} eventId The ID of the event to look for
|
* @param {string} eventId The ID of the event to look for
|
||||||
*
|
*
|
||||||
* @return {Promise} Resolves:
|
* @return {Promise} Resolves:
|
||||||
* {@link module:models/event-timeline~EventTimeline} including the given event
|
* {@link module:models/event-timeline~EventTimeline} including the given event
|
||||||
*/
|
*/
|
||||||
public async getEventTimeline(timelineSet: EventTimelineSet, eventId: string): Promise<EventTimeline> {
|
public async getEventTimeline(timelineSet: EventTimelineSet, eventId: string): Promise<EventTimeline | undefined> {
|
||||||
// don't allow any timeline support unless it's been enabled.
|
// don't allow any timeline support unless it's been enabled.
|
||||||
if (!this.timelineSupport) {
|
if (!this.timelineSupport) {
|
||||||
throw new Error("timeline support is disabled. Set the 'timelineSupport'" +
|
throw new Error("timeline support is disabled. Set the 'timelineSupport'" +
|
||||||
@@ -5297,19 +5298,24 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
...res.events_before.map(mapper),
|
...res.events_before.map(mapper),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (this.supportsExperimentalThreads()) {
|
||||||
|
const { threadId, shouldLiveInRoom } = timelineSet.room.eventShouldLiveIn(event);
|
||||||
|
|
||||||
|
if (!timelineSet.thread && !shouldLiveInRoom) {
|
||||||
|
// Thread response does not belong in this timelineSet
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timelineSet.thread?.id !== threadId) {
|
||||||
|
// Event does not belong in this timelineSet
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
// Where the event is a thread reply (not a root) and running in MSC-enabled mode the Thread timeline only
|
// Where the event is a thread reply (not a root) and running in MSC-enabled mode the Thread timeline only
|
||||||
// functions contiguously, so we have to jump through some hoops to get our target event in it.
|
// functions contiguously, so we have to jump through some hoops to get our target event in it.
|
||||||
// XXX: workaround for https://github.com/vector-im/element-meta/issues/150
|
// XXX: workaround for https://github.com/vector-im/element-meta/issues/150
|
||||||
if (Thread.hasServerSideSupport &&
|
if (Thread.hasServerSideSupport && timelineSet.thread) {
|
||||||
this.supportsExperimentalThreads() &&
|
const thread = timelineSet.thread;
|
||||||
event.isRelation(THREAD_RELATION_TYPE.name)
|
|
||||||
) {
|
|
||||||
const [, threadedEvents] = timelineSet.room.partitionThreadedEvents(events);
|
|
||||||
let thread = timelineSet.room.getThread(event.threadRootId);
|
|
||||||
if (!thread) {
|
|
||||||
thread = timelineSet.room.createThread(event.threadRootId, undefined, threadedEvents, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
const opts: IRelationsRequestOpts = {
|
const opts: IRelationsRequestOpts = {
|
||||||
direction: Direction.Backward,
|
direction: Direction.Backward,
|
||||||
limit: 50,
|
limit: 50,
|
||||||
@@ -5330,6 +5336,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
|
|
||||||
return thread.liveTimeline;
|
return thread.liveTimeline;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Here we handle non-thread timelines only, but still process any thread events to populate thread summaries.
|
// Here we handle non-thread timelines only, but still process any thread events to populate thread summaries.
|
||||||
let timeline = timelineSet.getTimelineForEvent(events[0].getId());
|
let timeline = timelineSet.getTimelineForEvent(events[0].getId());
|
||||||
|
@@ -27,6 +27,7 @@ import { RoomState } from "./room-state";
|
|||||||
import { TypedEventEmitter } from "./typed-event-emitter";
|
import { TypedEventEmitter } from "./typed-event-emitter";
|
||||||
import { RelationsContainer } from "./relations-container";
|
import { RelationsContainer } from "./relations-container";
|
||||||
import { MatrixClient } from "../client";
|
import { MatrixClient } from "../client";
|
||||||
|
import { Thread } from "./thread";
|
||||||
|
|
||||||
const DEBUG = true;
|
const DEBUG = true;
|
||||||
|
|
||||||
@@ -110,7 +111,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
|||||||
* map from event_id to timeline and index.
|
* map from event_id to timeline and index.
|
||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
* @param {?Room} room
|
* @param {Room=} room
|
||||||
* Room for this timelineSet. May be null for non-room cases, such as the
|
* Room for this timelineSet. May be null for non-room cases, such as the
|
||||||
* notification timeline.
|
* notification timeline.
|
||||||
* @param {Object} opts Options inherited from Room.
|
* @param {Object} opts Options inherited from Room.
|
||||||
@@ -119,13 +120,15 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
|||||||
* Set to true to enable improved timeline support.
|
* Set to true to enable improved timeline support.
|
||||||
* @param {Object} [opts.filter = null]
|
* @param {Object} [opts.filter = null]
|
||||||
* The filter object, if any, for this timelineSet.
|
* The filter object, if any, for this timelineSet.
|
||||||
* @param {MatrixClient} client the Matrix client which owns this EventTimelineSet,
|
* @param {MatrixClient=} client the Matrix client which owns this EventTimelineSet,
|
||||||
* can be omitted if room is specified.
|
* can be omitted if room is specified.
|
||||||
|
* @param {Thread=} thread the thread to which this timeline set relates.
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
public readonly room: Room | undefined,
|
public readonly room: Room | undefined,
|
||||||
opts: IOpts = {},
|
opts: IOpts = {},
|
||||||
client?: MatrixClient,
|
client?: MatrixClient,
|
||||||
|
public readonly thread?: Thread,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
@@ -537,7 +537,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
return mRelatesTo?.['m.in_reply_to']?.event_id;
|
return mRelatesTo?.['m.in_reply_to']?.event_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get relationEventId(): string {
|
public get relationEventId(): string | undefined {
|
||||||
return this.getWireContent()
|
return this.getWireContent()
|
||||||
?.["m.relates_to"]
|
?.["m.relates_to"]
|
||||||
?.event_id;
|
?.event_id;
|
||||||
|
@@ -90,7 +90,7 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
|||||||
this.timelineSet = new EventTimelineSet(this.room, {
|
this.timelineSet = new EventTimelineSet(this.room, {
|
||||||
timelineSupport: true,
|
timelineSupport: true,
|
||||||
pendingEvents: true,
|
pendingEvents: true,
|
||||||
});
|
}, this.client, this);
|
||||||
this.reEmitter = new TypedReEmitter(this);
|
this.reEmitter = new TypedReEmitter(this);
|
||||||
|
|
||||||
this.reEmitter.reEmit(this.timelineSet, [
|
this.reEmitter.reEmit(this.timelineSet, [
|
||||||
|
Reference in New Issue
Block a user