diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index 66ca40e482..7fb434bf29 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -12,6 +12,7 @@ import { EventTimeline, RoomState, EventType, + IEventRelation, } from 'matrix-js-sdk'; import { MatrixClientPeg as peg } from '../../src/MatrixClientPeg'; @@ -259,6 +260,12 @@ export function mkMembership(opts: MakeEventPassThruProps & { return e; } +export type MessageEventProps = MakeEventPassThruProps & { + room: Room["roomId"]; + relatesTo?: IEventRelation; + msg?: string; +}; + /** * Create an m.room.message event. * @param {Object} opts Values for the message @@ -269,20 +276,20 @@ export function mkMembership(opts: MakeEventPassThruProps & { * @param {string=} opts.msg Optional. The content.body for the event. * @return {Object|MatrixEvent} The event */ -export function mkMessage(opts: MakeEventPassThruProps & { - room: Room["roomId"]; - msg?: string; -}): MatrixEvent { +export function mkMessage({ + msg, relatesTo, ...opts +}: MessageEventProps): MatrixEvent { if (!opts.room || !opts.user) { throw new Error("Missing .room or .user from options"); } - const message = opts.msg ?? "Random->" + Math.random(); + const message = msg ?? "Random->" + Math.random(); const event: MakeEventProps = { ...opts, type: "m.room.message", content: { msgtype: "m.text", body: message, + ['m.relates_to']: relatesTo, }, }; diff --git a/test/test-utils/threads.ts b/test/test-utils/threads.ts new file mode 100644 index 0000000000..1d250d07e9 --- /dev/null +++ b/test/test-utils/threads.ts @@ -0,0 +1,93 @@ + +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { IUnsigned, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk"; +import { Thread } from "matrix-js-sdk/src/models/thread"; + +import { mkMessage, MessageEventProps } from "./test-utils"; + +export const makeThreadEvent = ({ rootEventId, replyToEventId, ...props }: MessageEventProps & { + rootEventId: string; replyToEventId: string; +}): MatrixEvent => mkMessage({ + ...props, + relatesTo: { + event_id: rootEventId, + rel_type: "io.element.thread", + ['m.in_reply_to']: { + event_id: replyToEventId, + }, + }, +}); + +type MakeThreadEventsProps = { + roomId: Room["roomId"]; + // root message user id + authorId: string; + // user ids of thread replies + // cycled through until thread length is fulfilled + participantUserIds: string[]; + // number of messages in the thread, root message included + // optional, default 2 + length?: number; + ts?: number; + // provide to set current_user_participated accurately + currentUserId?: string; +}; + +export const makeThreadEvents = ({ + roomId, authorId, participantUserIds, length = 2, ts = 1, currentUserId, +}): { rootEvent: MatrixEvent, events: MatrixEvent[] } => { + const rootEvent = mkMessage({ + user: authorId, + event: true, + room: roomId, + msg: 'root event message', + ts, + }); + + const rootEventId = rootEvent.getId(); + const events = [rootEvent]; + + for (let i = 1; i < length; i++) { + const prevEvent = events[i - 1]; + const replyToEventId = prevEvent.getId(); + const user = participantUserIds[i % participantUserIds.length]; + events.push(makeThreadEvent({ + user, + room: roomId, + event: true, + msg: `reply ${i} by ${user}`, + rootEventId, + replyToEventId, + // replies are 1ms after each other + ts: ts + i, + })); + } + + rootEvent.setUnsigned({ + latest_event: events[events.length - 1], + count: length, + current_user_participated: [...participantUserIds, authorId].includes(currentUserId), + } as IUnsigned); + + return { rootEvent, events }; +}; + +export const makeThread = (client: MatrixClient, room: Room, props: MakeThreadEventsProps): Thread => { + const { rootEvent, events } = makeThreadEvents(props); + return new Thread(rootEvent, { initialEvents: events, room, client }); +};