552 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			TypeScript
		
	
	
			
		
		
	
	
			552 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			TypeScript
		
	
	
| /*
 | |
| Copyright 2024 New Vector Ltd.
 | |
| Copyright 2023 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 React from "react";
 | |
| import { fireEvent, render, screen } from "jest-matrix-react";
 | |
| import userEvent from "@testing-library/user-event";
 | |
| import { Room } from "matrix-js-sdk/src/matrix";
 | |
| import { ReplacementEvent, RoomMessageEventContent } from "matrix-js-sdk/src/types";
 | |
| 
 | |
| import EditMessageComposerWithMatrixClient, {
 | |
|     createEditContent,
 | |
| } from "../../../../../src/components/views/rooms/EditMessageComposer";
 | |
| import EditorModel from "../../../../../src/editor/model";
 | |
| import { createPartCreator } from "../../../editor/mock";
 | |
| import {
 | |
|     getMockClientWithEventEmitter,
 | |
|     getRoomContext,
 | |
|     mkEvent,
 | |
|     mockClientMethodsUser,
 | |
|     setupRoomWithEventsTimeline,
 | |
| } from "../../../../test-utils";
 | |
| import DocumentOffset from "../../../../../src/editor/offset";
 | |
| import SettingsStore from "../../../../../src/settings/SettingsStore";
 | |
| import EditorStateTransfer from "../../../../../src/utils/EditorStateTransfer";
 | |
| import RoomContext from "../../../../../src/contexts/RoomContext";
 | |
| import { IRoomState } from "../../../../../src/components/structures/RoomView";
 | |
| import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
 | |
| import Autocompleter, { IProviderCompletions } from "../../../../../src/autocomplete/Autocompleter";
 | |
| import NotifProvider from "../../../../../src/autocomplete/NotifProvider";
 | |
| import DMRoomMap from "../../../../../src/utils/DMRoomMap";
 | |
| 
 | |
| describe("<EditMessageComposer/>", () => {
 | |
|     const userId = "@alice:server.org";
 | |
|     const roomId = "!room:server.org";
 | |
|     const mockClient = getMockClientWithEventEmitter({
 | |
|         ...mockClientMethodsUser(userId),
 | |
|         getRoom: jest.fn(),
 | |
|         sendMessage: jest.fn(),
 | |
|     });
 | |
|     const room = new Room(roomId, mockClient, userId);
 | |
| 
 | |
|     const editedEvent = mkEvent({
 | |
|         type: "m.room.message",
 | |
|         user: "@alice:test",
 | |
|         room: "!abc:test",
 | |
|         content: { body: "original message", msgtype: "m.text" },
 | |
|         event: true,
 | |
|     });
 | |
| 
 | |
|     const eventWithMentions = mkEvent({
 | |
|         type: "m.room.message",
 | |
|         user: userId,
 | |
|         room: roomId,
 | |
|         content: {
 | |
|             "msgtype": "m.text",
 | |
|             "body": "hey Bob and Charlie",
 | |
|             "format": "org.matrix.custom.html",
 | |
|             "formatted_body":
 | |
|                 'hey <a href="https://matrix.to/#/@bob:server.org">Bob</a> and <a href="https://matrix.to/#/@charlie:server.org">Charlie</a>',
 | |
|             "m.mentions": {
 | |
|                 user_ids: ["@bob:server.org", "@charlie:server.org"],
 | |
|             },
 | |
|         },
 | |
|         event: true,
 | |
|     });
 | |
| 
 | |
|     // message composer emojipicker uses this
 | |
|     // which would require more irrelevant mocking
 | |
|     jest.spyOn(SettingsStore, "setValue").mockResolvedValue(undefined);
 | |
| 
 | |
|     const defaultRoomContext = getRoomContext(room, {});
 | |
| 
 | |
|     const getComponent = (editState: EditorStateTransfer, roomContext: IRoomState = defaultRoomContext) =>
 | |
|         render(<EditMessageComposerWithMatrixClient editState={editState} />, {
 | |
|             wrapper: ({ children }) => (
 | |
|                 <MatrixClientContext.Provider value={mockClient}>
 | |
|                     <RoomContext.Provider value={roomContext}>{children}</RoomContext.Provider>
 | |
|                 </MatrixClientContext.Provider>
 | |
|             ),
 | |
|         });
 | |
| 
 | |
|     beforeEach(() => {
 | |
|         mockClient.getRoom.mockReturnValue(room);
 | |
|         mockClient.sendMessage.mockClear();
 | |
| 
 | |
|         userEvent.setup();
 | |
| 
 | |
|         DMRoomMap.makeShared(mockClient);
 | |
| 
 | |
|         jest.spyOn(Autocompleter.prototype, "getCompletions").mockResolvedValue([
 | |
|             {
 | |
|                 completions: [
 | |
|                     {
 | |
|                         completion: "@dan:server.org",
 | |
|                         completionId: "@dan:server.org",
 | |
|                         type: "user",
 | |
|                         suffix: " ",
 | |
|                         component: <span>Dan</span>,
 | |
|                     },
 | |
|                 ],
 | |
|                 command: {
 | |
|                     command: ["@d"],
 | |
|                 },
 | |
|                 provider: new NotifProvider(room),
 | |
|             } as unknown as IProviderCompletions,
 | |
|         ]);
 | |
|     });
 | |
| 
 | |
|     const editText = async (text: string, shouldClear?: boolean): Promise<void> => {
 | |
|         const input = screen.getByRole("textbox");
 | |
|         if (shouldClear) {
 | |
|             await userEvent.clear(input);
 | |
|         }
 | |
|         await userEvent.type(input, text);
 | |
|     };
 | |
| 
 | |
|     it("should edit a simple message", async () => {
 | |
|         const editState = new EditorStateTransfer(editedEvent);
 | |
|         getComponent(editState);
 | |
|         await editText(" + edit");
 | |
| 
 | |
|         fireEvent.click(screen.getByText("Save"));
 | |
| 
 | |
|         const expectedBody = {
 | |
|             ...editedEvent.getContent(),
 | |
|             "body": " * original message + edit",
 | |
|             "m.new_content": {
 | |
|                 "body": "original message + edit",
 | |
|                 "msgtype": "m.text",
 | |
|                 "m.mentions": {},
 | |
|             },
 | |
|             "m.relates_to": {
 | |
|                 event_id: editedEvent.getId(),
 | |
|                 rel_type: "m.replace",
 | |
|             },
 | |
|             "m.mentions": {},
 | |
|         };
 | |
|         expect(mockClient.sendMessage).toHaveBeenCalledWith(editedEvent.getRoomId()!, null, expectedBody);
 | |
|     });
 | |
| 
 | |
|     it("should throw when room for message is not found", () => {
 | |
|         mockClient.getRoom.mockReturnValue(null);
 | |
|         const editState = new EditorStateTransfer(editedEvent);
 | |
|         expect(() => getComponent(editState, { ...defaultRoomContext, room: undefined })).toThrow(
 | |
|             "Cannot render without room",
 | |
|         );
 | |
|     });
 | |
| 
 | |
|     describe("createEditContent", () => {
 | |
|         it("sends plaintext messages correctly", () => {
 | |
|             const model = new EditorModel([], createPartCreator());
 | |
|             const documentOffset = new DocumentOffset(11, true);
 | |
|             model.update("hello world", "insertText", documentOffset);
 | |
| 
 | |
|             const content = createEditContent(model, editedEvent);
 | |
| 
 | |
|             expect(content).toEqual({
 | |
|                 "body": " * hello world",
 | |
|                 "msgtype": "m.text",
 | |
|                 "m.new_content": {
 | |
|                     "body": "hello world",
 | |
|                     "msgtype": "m.text",
 | |
|                     "m.mentions": {},
 | |
|                 },
 | |
|                 "m.relates_to": {
 | |
|                     event_id: editedEvent.getId(),
 | |
|                     rel_type: "m.replace",
 | |
|                 },
 | |
|                 "m.mentions": {},
 | |
|             });
 | |
|         });
 | |
| 
 | |
|         it("sends markdown messages correctly", () => {
 | |
|             const model = new EditorModel([], createPartCreator());
 | |
|             const documentOffset = new DocumentOffset(13, true);
 | |
|             model.update("hello *world*", "insertText", documentOffset);
 | |
| 
 | |
|             const content = createEditContent(model, editedEvent);
 | |
| 
 | |
|             expect(content).toEqual({
 | |
|                 "body": " * hello *world*",
 | |
|                 "msgtype": "m.text",
 | |
|                 "format": "org.matrix.custom.html",
 | |
|                 "formatted_body": " * hello <em>world</em>",
 | |
|                 "m.new_content": {
 | |
|                     "body": "hello *world*",
 | |
|                     "msgtype": "m.text",
 | |
|                     "format": "org.matrix.custom.html",
 | |
|                     "formatted_body": "hello <em>world</em>",
 | |
|                     "m.mentions": {},
 | |
|                 },
 | |
|                 "m.relates_to": {
 | |
|                     event_id: editedEvent.getId(),
 | |
|                     rel_type: "m.replace",
 | |
|                 },
 | |
|                 "m.mentions": {},
 | |
|             });
 | |
|         });
 | |
| 
 | |
|         it("strips /me from messages and marks them as m.emote accordingly", () => {
 | |
|             const model = new EditorModel([], createPartCreator());
 | |
|             const documentOffset = new DocumentOffset(22, true);
 | |
|             model.update("/me blinks __quickly__", "insertText", documentOffset);
 | |
| 
 | |
|             const content = createEditContent(model, editedEvent);
 | |
| 
 | |
|             expect(content).toEqual({
 | |
|                 "body": " * blinks __quickly__",
 | |
|                 "msgtype": "m.emote",
 | |
|                 "format": "org.matrix.custom.html",
 | |
|                 "formatted_body": " * blinks <strong>quickly</strong>",
 | |
|                 "m.new_content": {
 | |
|                     "body": "blinks __quickly__",
 | |
|                     "msgtype": "m.emote",
 | |
|                     "format": "org.matrix.custom.html",
 | |
|                     "formatted_body": "blinks <strong>quickly</strong>",
 | |
|                     "m.mentions": {},
 | |
|                 },
 | |
|                 "m.relates_to": {
 | |
|                     event_id: editedEvent.getId(),
 | |
|                     rel_type: "m.replace",
 | |
|                 },
 | |
|                 "m.mentions": {},
 | |
|             });
 | |
|         });
 | |
| 
 | |
|         it("allows emoting with non-text parts", () => {
 | |
|             const model = new EditorModel([], createPartCreator());
 | |
|             const documentOffset = new DocumentOffset(16, true);
 | |
|             model.update("/me ✨sparkles✨", "insertText", documentOffset);
 | |
|             expect(model.parts.length).toEqual(4); // Emoji count as non-text
 | |
| 
 | |
|             const content = createEditContent(model, editedEvent);
 | |
| 
 | |
|             expect(content).toEqual({
 | |
|                 "body": " * ✨sparkles✨",
 | |
|                 "msgtype": "m.emote",
 | |
|                 "m.new_content": {
 | |
|                     "body": "✨sparkles✨",
 | |
|                     "msgtype": "m.emote",
 | |
|                     "m.mentions": {},
 | |
|                 },
 | |
|                 "m.relates_to": {
 | |
|                     event_id: editedEvent.getId(),
 | |
|                     rel_type: "m.replace",
 | |
|                 },
 | |
|                 "m.mentions": {},
 | |
|             });
 | |
|         });
 | |
| 
 | |
|         it("allows sending double-slash escaped slash commands correctly", () => {
 | |
|             const model = new EditorModel([], createPartCreator());
 | |
|             const documentOffset = new DocumentOffset(32, true);
 | |
| 
 | |
|             model.update("//dev/null is my favourite place", "insertText", documentOffset);
 | |
| 
 | |
|             const content = createEditContent(model, editedEvent);
 | |
| 
 | |
|             // TODO Edits do not properly strip the double slash used to skip
 | |
|             // command processing.
 | |
|             expect(content).toEqual({
 | |
|                 "body": " * //dev/null is my favourite place",
 | |
|                 "msgtype": "m.text",
 | |
|                 "m.new_content": {
 | |
|                     "body": "//dev/null is my favourite place",
 | |
|                     "msgtype": "m.text",
 | |
|                     "m.mentions": {},
 | |
|                 },
 | |
|                 "m.relates_to": {
 | |
|                     event_id: editedEvent.getId(),
 | |
|                     rel_type: "m.replace",
 | |
|                 },
 | |
|                 "m.mentions": {},
 | |
|             });
 | |
|         });
 | |
|     });
 | |
| 
 | |
|     describe("when message is not a reply", () => {
 | |
|         it("should attach an empty mentions object for a message with no mentions", async () => {
 | |
|             const editState = new EditorStateTransfer(editedEvent);
 | |
|             getComponent(editState);
 | |
|             const editContent = " + edit";
 | |
|             await editText(editContent);
 | |
| 
 | |
|             fireEvent.click(screen.getByText("Save"));
 | |
| 
 | |
|             const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent &
 | |
|                 ReplacementEvent<RoomMessageEventContent>;
 | |
| 
 | |
|             // both content.mentions and new_content.mentions are empty
 | |
|             expect(messageContent["m.mentions"]).toEqual({});
 | |
|             expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({});
 | |
|         });
 | |
| 
 | |
|         it("should retain mentions in the original message that are not removed by the edit", async () => {
 | |
|             const editState = new EditorStateTransfer(eventWithMentions);
 | |
|             getComponent(editState);
 | |
|             // Remove charlie from the message
 | |
|             const editContent = "{backspace}{backspace}friends";
 | |
|             await editText(editContent);
 | |
| 
 | |
|             fireEvent.click(screen.getByText("Save"));
 | |
| 
 | |
|             const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent &
 | |
|                 ReplacementEvent<RoomMessageEventContent>;
 | |
| 
 | |
|             // no new mentions were added, so nothing in top level mentions
 | |
|             expect(messageContent["m.mentions"]).toEqual({});
 | |
|             // bob is still mentioned, charlie removed
 | |
|             expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({
 | |
|                 user_ids: ["@bob:server.org"],
 | |
|             });
 | |
|         });
 | |
| 
 | |
|         it("should remove mentions that are removed by the edit", async () => {
 | |
|             const editState = new EditorStateTransfer(eventWithMentions);
 | |
|             getComponent(editState);
 | |
|             const editContent = "new message!";
 | |
|             // clear the original message
 | |
|             await editText(editContent, true);
 | |
| 
 | |
|             fireEvent.click(screen.getByText("Save"));
 | |
| 
 | |
|             const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent &
 | |
|                 ReplacementEvent<RoomMessageEventContent>;
 | |
| 
 | |
|             // no new mentions were added, so nothing in top level mentions
 | |
|             expect(messageContent["m.mentions"]).toEqual({});
 | |
|             // bob is not longer mentioned in the edited message, so empty mentions in new_content
 | |
|             expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({});
 | |
|         });
 | |
| 
 | |
|         it("should add mentions that were added in the edit", async () => {
 | |
|             const editState = new EditorStateTransfer(editedEvent);
 | |
|             getComponent(editState);
 | |
|             const editContent = " and @d";
 | |
|             await editText(editContent);
 | |
| 
 | |
|             // wait for autocompletion to render
 | |
|             await screen.findByText("Dan");
 | |
|             // submit autocomplete for mention
 | |
|             await editText("{enter}");
 | |
| 
 | |
|             fireEvent.click(screen.getByText("Save"));
 | |
| 
 | |
|             const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent &
 | |
|                 ReplacementEvent<RoomMessageEventContent>;
 | |
| 
 | |
|             // new mention in the edit
 | |
|             expect(messageContent["m.mentions"]).toEqual({
 | |
|                 user_ids: ["@dan:server.org"],
 | |
|             });
 | |
|             expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({
 | |
|                 user_ids: ["@dan:server.org"],
 | |
|             });
 | |
|         });
 | |
| 
 | |
|         it("should add and remove mentions from the edit", async () => {
 | |
|             const editState = new EditorStateTransfer(eventWithMentions);
 | |
|             getComponent(editState);
 | |
|             // Remove charlie from the message
 | |
|             await editText("{backspace}{backspace}");
 | |
|             // and replace with @room
 | |
|             await editText("@d");
 | |
|             // wait for autocompletion to render
 | |
|             await screen.findByText("Dan");
 | |
|             // submit autocomplete for @dan mention
 | |
|             await editText("{enter}");
 | |
| 
 | |
|             fireEvent.click(screen.getByText("Save"));
 | |
| 
 | |
|             const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent &
 | |
|                 ReplacementEvent<RoomMessageEventContent>;
 | |
| 
 | |
|             // new mention in the edit
 | |
|             expect(messageContent["m.mentions"]).toEqual({
 | |
|                 user_ids: ["@dan:server.org"],
 | |
|             });
 | |
|             // all mentions in the edited version of the event
 | |
|             expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({
 | |
|                 user_ids: ["@bob:server.org", "@dan:server.org"],
 | |
|             });
 | |
|         });
 | |
|     });
 | |
| 
 | |
|     describe("when message is replying", () => {
 | |
|         const originalEvent = mkEvent({
 | |
|             type: "m.room.message",
 | |
|             user: "@ernie:test",
 | |
|             room: roomId,
 | |
|             content: { body: "original message", msgtype: "m.text" },
 | |
|             event: true,
 | |
|         });
 | |
| 
 | |
|         const replyEvent = mkEvent({
 | |
|             type: "m.room.message",
 | |
|             user: "@bert:test",
 | |
|             room: roomId,
 | |
|             content: {
 | |
|                 "body": "reply with plain message",
 | |
|                 "msgtype": "m.text",
 | |
|                 "m.relates_to": {
 | |
|                     "m.in_reply_to": {
 | |
|                         event_id: originalEvent.getId(),
 | |
|                     },
 | |
|                 },
 | |
|                 "m.mentions": {
 | |
|                     user_ids: [originalEvent.getSender()!],
 | |
|                 },
 | |
|             },
 | |
|             event: true,
 | |
|         });
 | |
| 
 | |
|         const replyWithMentions = mkEvent({
 | |
|             type: "m.room.message",
 | |
|             user: "@bert:test",
 | |
|             room: roomId,
 | |
|             content: {
 | |
|                 "body": 'reply that mentions <a href="https://matrix.to/#/@bob:server.org">Bob</a>',
 | |
|                 "msgtype": "m.text",
 | |
|                 "m.relates_to": {
 | |
|                     "m.in_reply_to": {
 | |
|                         event_id: originalEvent.getId(),
 | |
|                     },
 | |
|                 },
 | |
|                 "m.mentions": {
 | |
|                     user_ids: [
 | |
|                         // sender of event we replied to
 | |
|                         originalEvent.getSender()!,
 | |
|                         // mentions from this event
 | |
|                         "@bob:server.org",
 | |
|                     ],
 | |
|                 },
 | |
|             },
 | |
|             event: true,
 | |
|         });
 | |
| 
 | |
|         beforeEach(() => {
 | |
|             setupRoomWithEventsTimeline(room, [originalEvent, replyEvent]);
 | |
|         });
 | |
| 
 | |
|         it("should retain parent event sender in mentions when editing with plain text", async () => {
 | |
|             const editState = new EditorStateTransfer(replyEvent);
 | |
|             getComponent(editState);
 | |
|             const editContent = " + edit";
 | |
|             await editText(editContent);
 | |
| 
 | |
|             fireEvent.click(screen.getByText("Save"));
 | |
| 
 | |
|             const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent &
 | |
|                 ReplacementEvent<RoomMessageEventContent>;
 | |
| 
 | |
|             // no new mentions from edit
 | |
|             expect(messageContent["m.mentions"]).toEqual({});
 | |
|             // edited reply still mentions the parent event sender
 | |
|             expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({
 | |
|                 user_ids: [originalEvent.getSender()],
 | |
|             });
 | |
|         });
 | |
| 
 | |
|         it("should retain parent event sender in mentions when adding a mention", async () => {
 | |
|             const editState = new EditorStateTransfer(replyEvent);
 | |
|             getComponent(editState);
 | |
|             await editText(" and @d");
 | |
|             // wait for autocompletion to render
 | |
|             await screen.findByText("Dan");
 | |
|             // submit autocomplete for @dan mention
 | |
|             await editText("{enter}");
 | |
| 
 | |
|             fireEvent.click(screen.getByText("Save"));
 | |
| 
 | |
|             const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent &
 | |
|                 ReplacementEvent<RoomMessageEventContent>;
 | |
| 
 | |
|             // new mention in edit
 | |
|             expect(messageContent["m.mentions"]).toEqual({
 | |
|                 user_ids: ["@dan:server.org"],
 | |
|             });
 | |
|             // edited reply still mentions the parent event sender
 | |
|             // plus new mention @dan
 | |
|             expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({
 | |
|                 user_ids: [originalEvent.getSender(), "@dan:server.org"],
 | |
|             });
 | |
|         });
 | |
| 
 | |
|         it("should retain parent event sender in mentions when removing all mentions from content", async () => {
 | |
|             const editState = new EditorStateTransfer(replyWithMentions);
 | |
|             getComponent(editState);
 | |
|             // replace text to remove all mentions
 | |
|             await editText("no mentions here", true);
 | |
| 
 | |
|             fireEvent.click(screen.getByText("Save"));
 | |
| 
 | |
|             const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent &
 | |
|                 ReplacementEvent<RoomMessageEventContent>;
 | |
| 
 | |
|             // no mentions in edit
 | |
|             expect(messageContent["m.mentions"]).toEqual({});
 | |
|             // edited reply still mentions the parent event sender
 | |
|             // existing @bob mention removed
 | |
|             expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({
 | |
|                 user_ids: [originalEvent.getSender()],
 | |
|             });
 | |
|         });
 | |
| 
 | |
|         it("should retain parent event sender in mentions when removing mention of said user", async () => {
 | |
|             const replyThatMentionsParentEventSender = mkEvent({
 | |
|                 type: "m.room.message",
 | |
|                 user: "@bert:test",
 | |
|                 room: roomId,
 | |
|                 content: {
 | |
|                     "body": `reply that mentions the sender of the message we replied to <a href="https://matrix.to/#/${originalEvent.getSender()!}">Ernie</a>`,
 | |
|                     "msgtype": "m.text",
 | |
|                     "m.relates_to": {
 | |
|                         "m.in_reply_to": {
 | |
|                             event_id: originalEvent.getId(),
 | |
|                         },
 | |
|                     },
 | |
|                     "m.mentions": {
 | |
|                         user_ids: [
 | |
|                             // sender of event we replied to
 | |
|                             originalEvent.getSender()!,
 | |
|                         ],
 | |
|                     },
 | |
|                 },
 | |
|                 event: true,
 | |
|             });
 | |
|             const editState = new EditorStateTransfer(replyThatMentionsParentEventSender);
 | |
|             getComponent(editState);
 | |
|             // replace text to remove all mentions
 | |
|             await editText("no mentions here", true);
 | |
| 
 | |
|             fireEvent.click(screen.getByText("Save"));
 | |
| 
 | |
|             const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent &
 | |
|                 ReplacementEvent<RoomMessageEventContent>;
 | |
| 
 | |
|             // no mentions in edit
 | |
|             expect(messageContent["m.mentions"]).toEqual({});
 | |
|             // edited reply still mentions the parent event sender
 | |
|             expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({
 | |
|                 user_ids: [originalEvent.getSender()],
 | |
|             });
 | |
|         });
 | |
|     });
 | |
| });
 |