Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issue with getEventTimeline returning undefined for thread roots in main timeline #2454

Merged
merged 7 commits into from
Jun 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 56 additions & 17 deletions spec/integ/matrix-client-event-timeline.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,23 @@ const EVENTS = [
}),
];

const THREAD_ROOT = utils.mkMessage({
const THREAD_ROOT = utils.mkEvent({
room: roomId,
user: userId,
msg: "thread root",
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,
},
},
},
});

const THREAD_REPLY = utils.mkEvent({
Expand All @@ -91,6 +104,8 @@ const THREAD_REPLY = utils.mkEvent({
},
});

THREAD_ROOT.unsigned["m.relations"]["io.element.thread"].latest_event = THREAD_REPLY;

// start the client, and wait for it to initialise
function startClient(httpBackend, client) {
httpBackend.when("GET", "/versions").respond(200, {});
Expand Down Expand Up @@ -540,20 +555,46 @@ describe("MatrixClient event timelines", function() {
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 () => {
it("should return relevant timeline from non-thread timelineSet when asking for the thread root", async () => {
t3chguy marked this conversation as resolved.
Show resolved Hide resolved
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;
const timelineSet = room.getTimelineSets()[0];

httpBackend.when("GET", "/rooms/!foo%3Abar/event/" + encodeURIComponent(THREAD_ROOT.event_id))
httpBackend.when("GET", "/rooms/!foo%3Abar/context/" + encodeURIComponent(THREAD_ROOT.event_id))
.respond(200, function() {
return THREAD_ROOT;
return {
start: "start_token0",
events_before: [],
event: THREAD_ROOT,
events_after: [],
end: "end_token0",
state: [],
};
});

const [timeline] = await Promise.all([
client.getEventTimeline(timelineSet, THREAD_ROOT.event_id),
httpBackend.flushAllExpected(),
]);

expect(timeline).not.toBe(thread.liveTimeline);
expect(timelineSet.getTimelines()).toContain(timeline);
expect(timeline.getEvents().find(e => e.getId() === THREAD_ROOT.event_id)).toBeTruthy();
});

it("should return undefined when event is not in the thread that the given timelineSet is representing", () => {
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
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/context/" + encodeURIComponent(EVENTS[0].event_id))
.respond(200, function() {
return {
Expand All @@ -566,14 +607,13 @@ describe("MatrixClient event timelines", function() {
};
});

const timelinePromise = client.getEventTimeline(timelineSet, EVENTS[0].event_id);
await httpBackend.flushAllExpected();

const timeline = await timelinePromise;
expect(timeline).toBeUndefined();
return Promise.all([
expect(client.getEventTimeline(timelineSet, EVENTS[0].event_id)).resolves.toBeUndefined(),
httpBackend.flushAllExpected(),
]);
});

it("should return undefined when event is within a thread but timelineSet is not", async () => {
it("should return undefined when event is within a thread but timelineSet is not", () => {
t3chguy marked this conversation as resolved.
Show resolved Hide resolved
client.clientOpts.experimentalThreadSupport = true;
Thread.setServerSideSupport(true);
client.stopClient(); // we don't need the client to be syncing at this time
Expand All @@ -592,11 +632,10 @@ describe("MatrixClient event timelines", function() {
};
});

const timelinePromise = client.getEventTimeline(timelineSet, THREAD_REPLY.event_id);
await httpBackend.flushAllExpected();

const timeline = await timelinePromise;
expect(timeline).toBeUndefined();
return Promise.all([
expect(client.getEventTimeline(timelineSet, THREAD_REPLY.event_id)).resolves.toBeUndefined(),
httpBackend.flushAllExpected(),
]);
});

it("should should add lazy loading filter when requested", async () => {
Expand Down
24 changes: 18 additions & 6 deletions spec/test-utils/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ interface IEventOpts {
sender?: string;
skey?: string;
content: IContent;
event?: boolean;
user?: string;
unsigned?: IUnsigned;
redacts?: string;
Expand All @@ -93,7 +92,9 @@ let testEventIndex = 1; // counter for events, easier for comparison of randomly
* @param {MatrixClient} client If passed along with opts.event=true will be used to set up re-emitters.
* @return {Object} a JSON object representing this event.
*/
export function mkEvent(opts: IEventOpts, client?: MatrixClient): object | MatrixEvent {
export function mkEvent(opts: IEventOpts & { event: true }, client?: MatrixClient): MatrixEvent;
export function mkEvent(opts: IEventOpts & { event?: false }, client?: MatrixClient): object;
export function mkEvent(opts: IEventOpts & { event?: boolean }, client?: MatrixClient): object | MatrixEvent {
if (!opts.type || !opts.content) {
throw new Error("Missing .type or .content =>" + JSON.stringify(opts));
}
Expand Down Expand Up @@ -143,7 +144,9 @@ interface IPresenceOpts {
* @param {Object} opts Values for the presence.
* @return {Object|MatrixEvent} The event
*/
export function mkPresence(opts: IPresenceOpts): object | MatrixEvent {
export function mkPresence(opts: IPresenceOpts & { event: true }): MatrixEvent;
export function mkPresence(opts: IPresenceOpts & { event?: false }): object;
export function mkPresence(opts: IPresenceOpts & { event?: boolean }): object | MatrixEvent {
const event = {
event_id: "$" + Math.random() + "-" + Math.random(),
type: "m.presence",
Expand Down Expand Up @@ -182,7 +185,9 @@ interface IMembershipOpts {
* @param {boolean} opts.event True to make a MatrixEvent.
* @return {Object|MatrixEvent} The event
*/
export function mkMembership(opts: IMembershipOpts): object | MatrixEvent {
export function mkMembership(opts: IMembershipOpts & { event: true }): MatrixEvent;
export function mkMembership(opts: IMembershipOpts & { event?: false }): object;
export function mkMembership(opts: IMembershipOpts & { event?: boolean }): object | MatrixEvent {
const eventOpts: IEventOpts = {
...opts,
type: EventType.RoomMember,
Expand Down Expand Up @@ -220,7 +225,9 @@ interface IMessageOpts {
* @param {MatrixClient} client If passed along with opts.event=true will be used to set up re-emitters.
* @return {Object|MatrixEvent} The event
*/
export function mkMessage(opts: IMessageOpts, client?: MatrixClient): object | MatrixEvent {
export function mkMessage(opts: IMessageOpts & { event: true }, client?: MatrixClient): MatrixEvent;
export function mkMessage(opts: IMessageOpts & { event?: false }, client?: MatrixClient): object;
export function mkMessage(opts: IMessageOpts & { event?: boolean }, client?: MatrixClient): object | MatrixEvent {
const eventOpts: IEventOpts = {
...opts,
type: EventType.RoomMessage,
Expand Down Expand Up @@ -252,7 +259,12 @@ interface IReplyMessageOpts extends IMessageOpts {
* @param {MatrixClient} client If passed along with opts.event=true will be used to set up re-emitters.
* @return {Object|MatrixEvent} The event
*/
export function mkReplyMessage(opts: IReplyMessageOpts, client?: MatrixClient): object | MatrixEvent {
export function mkReplyMessage(opts: IReplyMessageOpts & { event: true }, client?: MatrixClient): MatrixEvent;
export function mkReplyMessage(opts: IReplyMessageOpts & { event?: false }, client?: MatrixClient): object;
export function mkReplyMessage(
opts: IReplyMessageOpts & { event?: boolean },
client?: MatrixClient,
): object | MatrixEvent {
const eventOpts: IEventOpts = {
...opts,
type: EventType.RoomMessage,
Expand Down
77 changes: 74 additions & 3 deletions spec/unit/event-timeline-set.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import {
Room,
DuplicateStrategy,
} from '../../src';
import { Thread } from "../../src/models/thread";
import { ReEmitter } from "../../src/ReEmitter";

describe('EventTimelineSet', () => {
const roomId = '!foo:bar';
Expand Down Expand Up @@ -54,6 +56,7 @@ describe('EventTimelineSet', () => {

beforeEach(() => {
client = utils.mock(MatrixClient, 'MatrixClient');
client.reEmitter = utils.mock(ReEmitter, 'ReEmitter');
room = new Room(roomId, client, userA);
eventTimelineSet = new EventTimelineSet(room);
eventTimeline = new EventTimeline(eventTimelineSet);
Expand All @@ -62,14 +65,14 @@ describe('EventTimelineSet', () => {
user: userA,
msg: 'Hi!',
event: true,
}) as MatrixEvent;
});
replyEvent = utils.mkReplyMessage({
room: roomId,
user: userA,
msg: 'Hoo!',
event: true,
replyToMessage: messageEvent,
}) as MatrixEvent;
});
});

describe('addLiveEvent', () => {
Expand All @@ -91,7 +94,7 @@ describe('EventTimelineSet', () => {
// make a duplicate
const duplicateMessageEvent = utils.mkMessage({
room: roomId, user: userA, msg: "dupe", event: true,
}) as MatrixEvent;
});
duplicateMessageEvent.event.event_id = messageEvent.getId();

// Adding the duplicate event should replace the `messageEvent`
Expand Down Expand Up @@ -220,4 +223,72 @@ describe('EventTimelineSet', () => {
});
});
});

describe("canContain", () => {
const mkThreadResponse = (root: MatrixEvent) => utils.mkEvent({
event: true,
type: EventType.RoomMessage,
user: userA,
room: roomId,
content: {
"body": "Thread response :: " + Math.random(),
"m.relates_to": {
"event_id": root.getId(),
"m.in_reply_to": {
"event_id": root.getId(),
},
"rel_type": "m.thread",
},
},
}, room.client);

let thread: Thread;

beforeEach(() => {
(client.supportsExperimentalThreads as jest.Mock).mockReturnValue(true);
thread = new Thread("!thread_id:server", messageEvent, { room, client });
});

it("should throw if timeline set has no room", () => {
const eventTimelineSet = new EventTimelineSet(undefined, {}, client);
expect(() => eventTimelineSet.canContain(messageEvent)).toThrowError();
});

it("should return false if timeline set is for thread but event is not threaded", () => {
const eventTimelineSet = new EventTimelineSet(room, {}, client, thread);
expect(eventTimelineSet.canContain(replyEvent)).toBeFalsy();
});

it("should return false if timeline set it for thread but event it for a different thread", () => {
const eventTimelineSet = new EventTimelineSet(room, {}, client, thread);
const event = mkThreadResponse(replyEvent);
expect(eventTimelineSet.canContain(event)).toBeFalsy();
});

it("should return false if timeline set is not for a thread but event is a thread response", () => {
const eventTimelineSet = new EventTimelineSet(room, {}, client);
const event = mkThreadResponse(replyEvent);
expect(eventTimelineSet.canContain(event)).toBeFalsy();
});

it("should return true if the timeline set is not for a thread and the event is a thread root", () => {
const eventTimelineSet = new EventTimelineSet(room, {}, client);
expect(eventTimelineSet.canContain(messageEvent)).toBeTruthy();
});

it("should return true if the timeline set is for a thread and the event is its thread root", () => {
const thread = new Thread(messageEvent.getId(), messageEvent, { room, client });
const eventTimelineSet = new EventTimelineSet(room, {}, client, thread);
messageEvent.setThread(thread);
expect(eventTimelineSet.canContain(messageEvent)).toBeTruthy();
});

it("should return true if the timeline set is for a thread and the event is a response to it", () => {
const thread = new Thread(messageEvent.getId(), messageEvent, { room, client });
const eventTimelineSet = new EventTimelineSet(room, {}, client, thread);
messageEvent.setThread(thread);
const event = mkThreadResponse(messageEvent);
expect(eventTimelineSet.canContain(event)).toBeTruthy();
});
});
});
21 changes: 9 additions & 12 deletions spec/unit/filter-component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import {
MatrixEvent,
RelationType,
} from "../../src";
import { RelationType } from "../../src";
import { FilterComponent } from "../../src/filter-component";
import { mkEvent } from '../test-utils/test-utils';

Expand All @@ -14,7 +11,7 @@ describe("Filter Component", function() {
content: { },
room: 'roomId',
event: true,
}) as MatrixEvent;
});

const checkResult = filter.check(event);

Expand All @@ -28,7 +25,7 @@ describe("Filter Component", function() {
content: { },
room: 'roomId',
event: true,
}) as MatrixEvent;
});

const checkResult = filter.check(event);

Expand All @@ -55,7 +52,7 @@ describe("Filter Component", function() {
},
},
},
}) as MatrixEvent;
});

expect(filter.check(threadRootNotParticipated)).toBe(false);
});
Expand All @@ -80,7 +77,7 @@ describe("Filter Component", function() {
user: '@someone-else:server.org',
room: 'roomId',
event: true,
}) as MatrixEvent;
});

expect(filter.check(threadRootParticipated)).toBe(true);
});
Expand All @@ -100,7 +97,7 @@ describe("Filter Component", function() {
[RelationType.Reference]: {},
},
},
}) as MatrixEvent;
});

expect(filter.check(referenceRelationEvent)).toBe(false);
});
Expand All @@ -123,7 +120,7 @@ describe("Filter Component", function() {
},
room: 'roomId',
event: true,
}) as MatrixEvent;
});

const eventWithMultipleRelations = mkEvent({
"type": "m.room.message",
Expand All @@ -148,7 +145,7 @@ describe("Filter Component", function() {
},
"room": 'roomId',
"event": true,
}) as MatrixEvent;
});

const noMatchEvent = mkEvent({
"type": "m.room.message",
Expand All @@ -160,7 +157,7 @@ describe("Filter Component", function() {
},
"room": 'roomId',
"event": true,
}) as MatrixEvent;
});

expect(filter.check(threadRootEvent)).toBe(true);
expect(filter.check(eventWithMultipleRelations)).toBe(true);
Expand Down
Loading