163 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
			
		
		
	
	
			163 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
| /*
 | |
| Copyright 2024 New Vector Ltd.
 | |
| Copyright 2022 The Matrix.org Foundation C.I.C.
 | |
| 
 | |
| SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
 | |
| Please see LICENSE files in the repository root for full details.
 | |
| */
 | |
| 
 | |
| import { MatrixClient, MatrixEvent, MatrixEventEvent, RelationType, Room, Thread } from "matrix-js-sdk/src/matrix";
 | |
| 
 | |
| 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: "m.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,
 | |
| }: MakeThreadEventsProps): { rootEvent: MatrixEvent; events: MatrixEvent[] } => {
 | |
|     const rootEvent = mkMessage({
 | |
|         user: authorId,
 | |
|         event: true,
 | |
|         room: roomId,
 | |
|         msg: "root event message " + Math.random(),
 | |
|         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({
 | |
|         "m.relations": {
 | |
|             [RelationType.Thread]: {
 | |
|                 latest_event: events[events.length - 1],
 | |
|                 count: length,
 | |
|                 current_user_participated: [...participantUserIds, authorId].includes(currentUserId!),
 | |
|             },
 | |
|         },
 | |
|     });
 | |
| 
 | |
|     return { rootEvent, events };
 | |
| };
 | |
| 
 | |
| type MakeThreadProps = {
 | |
|     room: Room;
 | |
|     client: MatrixClient;
 | |
|     authorId: string;
 | |
|     participantUserIds: string[];
 | |
|     length?: number;
 | |
|     ts?: number;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Create a thread but don't actually populate it with events - see
 | |
|  * populateThread for what you probably want to do.
 | |
|  *
 | |
|  * Leaving this here in case it is needed by some people, but I (andyb) would
 | |
|  * expect us to move to use populateThread exclusively.
 | |
|  */
 | |
| export const mkThread = ({
 | |
|     room,
 | |
|     client,
 | |
|     authorId,
 | |
|     participantUserIds,
 | |
|     length = 2,
 | |
|     ts = 1,
 | |
| }: MakeThreadProps): { thread: Thread; rootEvent: MatrixEvent; events: MatrixEvent[] } => {
 | |
|     const { rootEvent, events } = makeThreadEvents({
 | |
|         roomId: room.roomId,
 | |
|         authorId,
 | |
|         participantUserIds,
 | |
|         length,
 | |
|         ts,
 | |
|         currentUserId: client.getUserId()!,
 | |
|     });
 | |
|     expect(rootEvent).toBeTruthy();
 | |
| 
 | |
|     for (const evt of events) {
 | |
|         room?.reEmitter.reEmit(evt, [MatrixEventEvent.BeforeRedaction]);
 | |
|     }
 | |
| 
 | |
|     const thread = room.createThread(rootEvent.getId()!, rootEvent, events, true);
 | |
| 
 | |
|     return { thread, rootEvent, events };
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Create a thread, and make sure the events added to the thread and the room's
 | |
|  * timeline as if they came in via sync.
 | |
|  *
 | |
|  * Note that mkThread doesn't actually add the events properly to the room.
 | |
|  */
 | |
| export const populateThread = async ({
 | |
|     room,
 | |
|     client,
 | |
|     authorId,
 | |
|     participantUserIds,
 | |
|     length = 2,
 | |
|     ts = 1,
 | |
| }: MakeThreadProps): Promise<{ thread: Thread; rootEvent: MatrixEvent; events: MatrixEvent[] }> => {
 | |
|     const ret = mkThread({ room, client, authorId, participantUserIds, length, ts });
 | |
| 
 | |
|     // So that we do not have to mock the thread loading, tell the thread
 | |
|     // that it is already loaded, and send the events again to the room
 | |
|     // so they are added to the thread timeline.
 | |
|     ret.thread.initialEventsFetched = true;
 | |
|     await room.addLiveEvents(ret.events);
 | |
|     return ret;
 | |
| };
 |