From 0468876aa0fc0df4206e75681f29c76bb70a1079 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 16 Oct 2025 09:17:46 +0100 Subject: [PATCH] Move some message utils out to their own file (#31035) * Move some message utils out to their own file In another attempt at import cycle breaking * Also add the file * Move tests --- src/ContentMessages.ts | 2 +- .../views/rooms/EditMessageComposer.tsx | 2 +- .../views/rooms/SendMessageComposer.tsx | 103 +--------- .../views/rooms/VoiceRecordComposerTile.tsx | 2 +- .../rooms/wysiwyg_composer/utils/message.ts | 2 +- src/utils/messages.ts | 109 ++++++++++ .../views/rooms/SendMessageComposer-test.tsx | 183 +---------------- test/unit-tests/utils/messages-test.ts | 194 ++++++++++++++++++ 8 files changed, 310 insertions(+), 287 deletions(-) create mode 100644 src/utils/messages.ts create mode 100644 test/unit-tests/utils/messages-test.ts diff --git a/src/ContentMessages.ts b/src/ContentMessages.ts index c22a6bab33..bf39d9ea1f 100644 --- a/src/ContentMessages.ts +++ b/src/ContentMessages.ts @@ -53,7 +53,7 @@ import ErrorDialog from "./components/views/dialogs/ErrorDialog"; import UploadFailureDialog from "./components/views/dialogs/UploadFailureDialog"; import UploadConfirmDialog from "./components/views/dialogs/UploadConfirmDialog"; import { createThumbnail } from "./utils/image-media"; -import { attachMentions, attachRelation } from "./components/views/rooms/SendMessageComposer"; +import { attachMentions, attachRelation } from "./utils/messages.ts"; import { doMaybeLocalRoomAction } from "./utils/local-room"; import { SdkContextClass } from "./contexts/SDKContext"; import { blobIsAnimated } from "./utils/Image.ts"; diff --git a/src/components/views/rooms/EditMessageComposer.tsx b/src/components/views/rooms/EditMessageComposer.tsx index 92d60b7571..f6677286b8 100644 --- a/src/components/views/rooms/EditMessageComposer.tsx +++ b/src/components/views/rooms/EditMessageComposer.tsx @@ -43,7 +43,7 @@ import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; import { PosthogAnalytics } from "../../../PosthogAnalytics"; import { editorRoomKey, editorStateKey } from "../../../Editing"; import type DocumentOffset from "../../../editor/offset"; -import { attachMentions, attachRelation } from "./SendMessageComposer"; +import { attachMentions, attachRelation } from "../../../utils/messages"; import { filterBoolean } from "../../../utils/arrays"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index aae9cfd0d0..51b14168df 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -8,10 +8,8 @@ Please see LICENSE files in the repository root for full details. import React, { createRef, type KeyboardEvent, type SyntheticEvent } from "react"; import { - type IContent, type MatrixEvent, type IEventRelation, - type IMentions, type Room, EventType, MsgType, @@ -35,7 +33,7 @@ import { unescapeMessage, } from "../../../editor/serialize"; import BasicMessageComposer, { REGEX_EMOTICON } from "./BasicMessageComposer"; -import { CommandPartCreator, type Part, type PartCreator, type SerializedPart, Type } from "../../../editor/parts"; +import { CommandPartCreator, type Part, type PartCreator, type SerializedPart } from "../../../editor/parts"; import { findEditableEvent } from "../../../utils/EventUtils"; import SendHistoryManager from "../../../SendHistoryManager"; import { CommandCategories } from "../../../SlashCommands"; @@ -61,108 +59,11 @@ import { type Caret } from "../../../editor/caret"; import { type IDiff } from "../../../editor/diff"; import { getBlobSafeMimeType } from "../../../utils/blobs"; import { EMOJI_REGEX } from "../../../HtmlUtils"; +import { attachMentions, attachRelation } from "../../../utils/messages"; // The prefix used when persisting editor drafts to localstorage. export const EDITOR_STATE_STORAGE_PREFIX = "mx_cider_state_"; -/** - * Build the mentions information based on the editor model (and any related events): - * - * 1. Search the model parts for room or user pills and fill in the mentions object. - * 2. If this is a reply to another event, include any user mentions from that - * (but do not include a room mention). - * - * @param sender - The Matrix ID of the user sending the event. - * @param content - The event content. - * @param model - The editor model to search for mentions, null if there is no editor. - * @param replyToEvent - The event being replied to or undefined if it is not a reply. - * @param editedContent - The content of the parent event being edited. - */ -export function attachMentions( - sender: string, - content: IContent, - model: EditorModel | null, - replyToEvent: MatrixEvent | undefined, - editedContent: IContent | null = null, -): void { - // We always attach the mentions even if the home server doesn't yet support - // intentional mentions. This is safe because m.mentions is an additive change - // that should simply be ignored by incapable home servers. - - // The mentions property *always* gets included to disable legacy push rules. - const mentions: IMentions = (content["m.mentions"] = {}); - - const userMentions = new Set(); - let roomMention = false; - - // If there's a reply, initialize the mentioned users as the sender of that event. - if (replyToEvent) { - userMentions.add(replyToEvent.sender!.userId); - } - - // If user provided content is available, check to see if any users are mentioned. - if (model) { - // Add any mentioned users in the current content. - for (const part of model.parts) { - if (part.type === Type.UserPill) { - userMentions.add(part.resourceId); - } else if (part.type === Type.AtRoomPill) { - roomMention = true; - } - } - } - - // Ensure the *current* user isn't listed in the mentioned users. - userMentions.delete(sender); - - // Finally, if this event is editing a previous event, only include users who - // were not previously mentioned and a room mention if the previous event was - // not a room mention. - if (editedContent) { - // First, the new event content gets the *full* set of users. - const newContent = content["m.new_content"]; - const newMentions: IMentions = (newContent["m.mentions"] = {}); - - // Only include the users/room if there is any content. - if (userMentions.size) { - newMentions.user_ids = [...userMentions]; - } - if (roomMention) { - newMentions.room = true; - } - - // Fetch the mentions from the original event and remove any previously - // mentioned users. - const prevMentions = editedContent["m.mentions"]; - if (Array.isArray(prevMentions?.user_ids)) { - prevMentions!.user_ids.forEach((userId) => userMentions.delete(userId)); - } - - // If the original event mentioned the room, nothing to do here. - if (prevMentions?.room) { - roomMention = false; - } - } - - // Only include the users/room if there is any content. - if (userMentions.size) { - mentions.user_ids = [...userMentions]; - } - if (roomMention) { - mentions.room = true; - } -} - -// Merges favouring the given relation -export function attachRelation(content: IContent, relation?: IEventRelation): void { - if (relation) { - content["m.relates_to"] = { - ...(content["m.relates_to"] || {}), - ...relation, - }; - } -} - // exported for tests export function createMessageContent( sender: string, diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx index b329cd84ea..9c13f1c872 100644 --- a/src/components/views/rooms/VoiceRecordComposerTile.tsx +++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx @@ -29,7 +29,7 @@ import InlineSpinner from "../elements/InlineSpinner"; import { PlaybackManager } from "../../../audio/PlaybackManager"; import { doMaybeLocalRoomAction } from "../../../utils/local-room"; import defaultDispatcher from "../../../dispatcher/dispatcher"; -import { attachMentions, attachRelation } from "./SendMessageComposer"; +import { attachMentions, attachRelation } from "../../../utils/messages"; import { addReplyToMessageContent } from "../../../utils/Reply"; import RoomContext from "../../../contexts/RoomContext"; import { type IUpload, type VoiceMessageRecording } from "../../../audio/VoiceMessageRecording"; diff --git a/src/components/views/rooms/wysiwyg_composer/utils/message.ts b/src/components/views/rooms/wysiwyg_composer/utils/message.ts index b794ba6fe4..8b9791d4ff 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/message.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/message.ts @@ -33,7 +33,7 @@ import { CommandCategories, getCommand } from "../../../../../SlashCommands"; import { runSlashCommand, shouldSendAnyway } from "../../../../../editor/commands"; import { Action } from "../../../../../dispatcher/actions"; import { addReplyToMessageContent } from "../../../../../utils/Reply"; -import { attachRelation } from "../../SendMessageComposer"; +import { attachRelation } from "../../../../../utils/messages"; export interface SendMessageParams { mxClient: MatrixClient; diff --git a/src/utils/messages.ts b/src/utils/messages.ts new file mode 100644 index 0000000000..b6040b536b --- /dev/null +++ b/src/utils/messages.ts @@ -0,0 +1,109 @@ +/* +Copyright 2025 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import { type MatrixEvent, type IContent, type IMentions, type IEventRelation } from "matrix-js-sdk/src/matrix"; + +import type EditorModel from "../editor/model"; +import { Type } from "../editor/parts"; + +/** + * Build the mentions information based on the editor model (and any related events): + * + * 1. Search the model parts for room or user pills and fill in the mentions object. + * 2. If this is a reply to another event, include any user mentions from that + * (but do not include a room mention). + * + * @param sender - The Matrix ID of the user sending the event. + * @param content - The event content. + * @param model - The editor model to search for mentions, null if there is no editor. + * @param replyToEvent - The event being replied to or undefined if it is not a reply. + * @param editedContent - The content of the parent event being edited. + */ +export function attachMentions( + sender: string, + content: IContent, + model: EditorModel | null, + replyToEvent: MatrixEvent | undefined, + editedContent: IContent | null = null, +): void { + // We always attach the mentions even if the home server doesn't yet support + // intentional mentions. This is safe because m.mentions is an additive change + // that should simply be ignored by incapable home servers. + + // The mentions property *always* gets included to disable legacy push rules. + const mentions: IMentions = (content["m.mentions"] = {}); + + const userMentions = new Set(); + let roomMention = false; + + // If there's a reply, initialize the mentioned users as the sender of that event. + if (replyToEvent) { + userMentions.add(replyToEvent.sender!.userId); + } + + // If user provided content is available, check to see if any users are mentioned. + if (model) { + // Add any mentioned users in the current content. + for (const part of model.parts) { + if (part.type === Type.UserPill) { + userMentions.add(part.resourceId); + } else if (part.type === Type.AtRoomPill) { + roomMention = true; + } + } + } + + // Ensure the *current* user isn't listed in the mentioned users. + userMentions.delete(sender); + + // Finally, if this event is editing a previous event, only include users who + // were not previously mentioned and a room mention if the previous event was + // not a room mention. + if (editedContent) { + // First, the new event content gets the *full* set of users. + const newContent = content["m.new_content"]; + const newMentions: IMentions = (newContent["m.mentions"] = {}); + + // Only include the users/room if there is any content. + if (userMentions.size) { + newMentions.user_ids = [...userMentions]; + } + if (roomMention) { + newMentions.room = true; + } + + // Fetch the mentions from the original event and remove any previously + // mentioned users. + const prevMentions = editedContent["m.mentions"]; + if (Array.isArray(prevMentions?.user_ids)) { + prevMentions!.user_ids.forEach((userId) => userMentions.delete(userId)); + } + + // If the original event mentioned the room, nothing to do here. + if (prevMentions?.room) { + roomMention = false; + } + } + + // Only include the users/room if there is any content. + if (userMentions.size) { + mentions.user_ids = [...userMentions]; + } + if (roomMention) { + mentions.room = true; + } +} + +// Merges favouring the given relation +export function attachRelation(content: IContent, relation?: IEventRelation): void { + if (relation) { + content["m.relates_to"] = { + ...(content["m.relates_to"] || {}), + ...relation, + }; + } +} diff --git a/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx b/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx index 412065353d..685d4f5b5c 100644 --- a/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx +++ b/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx @@ -8,12 +8,11 @@ Please see LICENSE files in the repository root for full details. import React from "react"; import { fireEvent, render, waitFor } from "jest-matrix-react"; -import { type IContent, type MatrixClient, MsgType } from "matrix-js-sdk/src/matrix"; +import { type MatrixClient, MsgType } from "matrix-js-sdk/src/matrix"; import { mocked } from "jest-mock"; import userEvent from "@testing-library/user-event"; import SendMessageComposer, { - attachMentions, createMessageContent, isQuickReaction, } from "../../../../../src/components/views/rooms/SendMessageComposer"; @@ -157,186 +156,6 @@ describe("", () => { }); }); - describe("attachMentions", () => { - const partsCreator = createPartCreator(); - - it("no mentions", () => { - const model = new EditorModel([], partsCreator); - const content: IContent = {}; - attachMentions("@alice:test", content, model, undefined); - expect(content).toEqual({ - "m.mentions": {}, - }); - }); - - it("test user mentions", () => { - const model = new EditorModel([partsCreator.userPill("Bob", "@bob:test")], partsCreator); - const content: IContent = {}; - attachMentions("@alice:test", content, model, undefined); - expect(content).toEqual({ - "m.mentions": { user_ids: ["@bob:test"] }, - }); - }); - - it("test reply", () => { - // Replying to an event adds the sender to the list of mentioned users. - const model = new EditorModel([], partsCreator); - let replyToEvent = mkEvent({ - type: "m.room.message", - user: "@bob:test", - room: "!abc:test", - content: { "m.mentions": {} }, - event: true, - }); - let content: IContent = {}; - attachMentions("@alice:test", content, model, replyToEvent); - expect(content).toEqual({ - "m.mentions": { user_ids: ["@bob:test"] }, - }); - - // It no longer adds any other mentioned users - replyToEvent = mkEvent({ - type: "m.room.message", - user: "@bob:test", - room: "!abc:test", - content: { "m.mentions": { user_ids: ["@alice:test", "@charlie:test"] } }, - event: true, - }); - content = {}; - attachMentions("@alice:test", content, model, replyToEvent); - expect(content).toEqual({ - "m.mentions": { user_ids: ["@bob:test"] }, - }); - }); - - it("test room mention", () => { - const model = new EditorModel([partsCreator.atRoomPill("@room")], partsCreator); - const content: IContent = {}; - attachMentions("@alice:test", content, model, undefined); - expect(content).toEqual({ - "m.mentions": { room: true }, - }); - }); - - it("test reply to room mention", () => { - // Replying to a room mention shouldn't automatically be a room mention. - const model = new EditorModel([], partsCreator); - const replyToEvent = mkEvent({ - type: "m.room.message", - user: "@alice:test", - room: "!abc:test", - content: { "m.mentions": { room: true } }, - event: true, - }); - const content: IContent = {}; - attachMentions("@alice:test", content, model, replyToEvent); - expect(content).toEqual({ - "m.mentions": {}, - }); - }); - - it("test broken mentions", () => { - // Replying to a room mention shouldn't automatically be a room mention. - const model = new EditorModel([], partsCreator); - const replyToEvent = mkEvent({ - type: "m.room.message", - user: "@alice:test", - room: "!abc:test", - // @ts-ignore - Purposefully testing invalid data. - content: { "m.mentions": { user_ids: "@bob:test" } }, - event: true, - }); - const content: IContent = {}; - attachMentions("@alice:test", content, model, replyToEvent); - expect(content).toEqual({ - "m.mentions": {}, - }); - }); - - describe("attachMentions with edit", () => { - it("no mentions", () => { - const model = new EditorModel([], partsCreator); - const content: IContent = { "m.new_content": {} }; - const prevContent: IContent = {}; - attachMentions("@alice:test", content, model, undefined, prevContent); - expect(content).toEqual({ - "m.mentions": {}, - "m.new_content": { "m.mentions": {} }, - }); - }); - - it("mentions do not propagate", () => { - const model = new EditorModel([], partsCreator); - const content: IContent = { "m.new_content": {} }; - const prevContent: IContent = { - "m.mentions": { user_ids: ["@bob:test"], room: true }, - }; - attachMentions("@alice:test", content, model, undefined, prevContent); - expect(content).toEqual({ - "m.mentions": {}, - "m.new_content": { "m.mentions": {} }, - }); - }); - - it("test user mentions", () => { - const model = new EditorModel([partsCreator.userPill("Bob", "@bob:test")], partsCreator); - const content: IContent = { "m.new_content": {} }; - const prevContent: IContent = {}; - attachMentions("@alice:test", content, model, undefined, prevContent); - expect(content).toEqual({ - "m.mentions": { user_ids: ["@bob:test"] }, - "m.new_content": { "m.mentions": { user_ids: ["@bob:test"] } }, - }); - }); - - it("test prev user mentions", () => { - const model = new EditorModel([partsCreator.userPill("Bob", "@bob:test")], partsCreator); - const content: IContent = { "m.new_content": {} }; - const prevContent: IContent = { "m.mentions": { user_ids: ["@bob:test"] } }; - attachMentions("@alice:test", content, model, undefined, prevContent); - expect(content).toEqual({ - "m.mentions": {}, - "m.new_content": { "m.mentions": { user_ids: ["@bob:test"] } }, - }); - }); - - it("test room mention", () => { - const model = new EditorModel([partsCreator.atRoomPill("@room")], partsCreator); - const content: IContent = { "m.new_content": {} }; - const prevContent: IContent = {}; - attachMentions("@alice:test", content, model, undefined, prevContent); - expect(content).toEqual({ - "m.mentions": { room: true }, - "m.new_content": { "m.mentions": { room: true } }, - }); - }); - - it("test prev room mention", () => { - const model = new EditorModel([partsCreator.atRoomPill("@room")], partsCreator); - const content: IContent = { "m.new_content": {} }; - const prevContent: IContent = { "m.mentions": { room: true } }; - attachMentions("@alice:test", content, model, undefined, prevContent); - expect(content).toEqual({ - "m.mentions": {}, - "m.new_content": { "m.mentions": { room: true } }, - }); - }); - - it("test broken mentions", () => { - // Replying to a room mention shouldn't automatically be a room mention. - const model = new EditorModel([], partsCreator); - const content: IContent = { "m.new_content": {} }; - // @ts-ignore - Purposefully testing invalid data. - const prevContent: IContent = { "m.mentions": { user_ids: "@bob:test" } }; - attachMentions("@alice:test", content, model, undefined, prevContent); - expect(content).toEqual({ - "m.mentions": {}, - "m.new_content": { "m.mentions": {} }, - }); - }); - }); - }); - describe("functions correctly mounted", () => { const mockClient = createTestClient(); jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient); diff --git a/test/unit-tests/utils/messages-test.ts b/test/unit-tests/utils/messages-test.ts new file mode 100644 index 0000000000..12d0242e3c --- /dev/null +++ b/test/unit-tests/utils/messages-test.ts @@ -0,0 +1,194 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2020 The Matrix.org Foundation C.I.C. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import { type IContent } from "matrix-js-sdk/src/matrix"; + +import { attachMentions } from "../../../src/utils/messages"; +import EditorModel from "../../../src/editor/model"; +import { mkEvent } from "../../test-utils"; +import { createPartCreator } from "../editor/mock"; + +describe("attachMentions", () => { + const partsCreator = createPartCreator(); + + it("no mentions", () => { + const model = new EditorModel([], partsCreator); + const content: IContent = {}; + attachMentions("@alice:test", content, model, undefined); + expect(content).toEqual({ + "m.mentions": {}, + }); + }); + + it("test user mentions", () => { + const model = new EditorModel([partsCreator.userPill("Bob", "@bob:test")], partsCreator); + const content: IContent = {}; + attachMentions("@alice:test", content, model, undefined); + expect(content).toEqual({ + "m.mentions": { user_ids: ["@bob:test"] }, + }); + }); + + it("test reply", () => { + // Replying to an event adds the sender to the list of mentioned users. + const model = new EditorModel([], partsCreator); + let replyToEvent = mkEvent({ + type: "m.room.message", + user: "@bob:test", + room: "!abc:test", + content: { "m.mentions": {} }, + event: true, + }); + let content: IContent = {}; + attachMentions("@alice:test", content, model, replyToEvent); + expect(content).toEqual({ + "m.mentions": { user_ids: ["@bob:test"] }, + }); + + // It no longer adds any other mentioned users + replyToEvent = mkEvent({ + type: "m.room.message", + user: "@bob:test", + room: "!abc:test", + content: { "m.mentions": { user_ids: ["@alice:test", "@charlie:test"] } }, + event: true, + }); + content = {}; + attachMentions("@alice:test", content, model, replyToEvent); + expect(content).toEqual({ + "m.mentions": { user_ids: ["@bob:test"] }, + }); + }); + + it("test room mention", () => { + const model = new EditorModel([partsCreator.atRoomPill("@room")], partsCreator); + const content: IContent = {}; + attachMentions("@alice:test", content, model, undefined); + expect(content).toEqual({ + "m.mentions": { room: true }, + }); + }); + + it("test reply to room mention", () => { + // Replying to a room mention shouldn't automatically be a room mention. + const model = new EditorModel([], partsCreator); + const replyToEvent = mkEvent({ + type: "m.room.message", + user: "@alice:test", + room: "!abc:test", + content: { "m.mentions": { room: true } }, + event: true, + }); + const content: IContent = {}; + attachMentions("@alice:test", content, model, replyToEvent); + expect(content).toEqual({ + "m.mentions": {}, + }); + }); + + it("test broken mentions", () => { + // Replying to a room mention shouldn't automatically be a room mention. + const model = new EditorModel([], partsCreator); + const replyToEvent = mkEvent({ + type: "m.room.message", + user: "@alice:test", + room: "!abc:test", + // @ts-ignore - Purposefully testing invalid data. + content: { "m.mentions": { user_ids: "@bob:test" } }, + event: true, + }); + const content: IContent = {}; + attachMentions("@alice:test", content, model, replyToEvent); + expect(content).toEqual({ + "m.mentions": {}, + }); + }); + + describe("attachMentions with edit", () => { + it("no mentions", () => { + const model = new EditorModel([], partsCreator); + const content: IContent = { "m.new_content": {} }; + const prevContent: IContent = {}; + attachMentions("@alice:test", content, model, undefined, prevContent); + expect(content).toEqual({ + "m.mentions": {}, + "m.new_content": { "m.mentions": {} }, + }); + }); + + it("mentions do not propagate", () => { + const model = new EditorModel([], partsCreator); + const content: IContent = { "m.new_content": {} }; + const prevContent: IContent = { + "m.mentions": { user_ids: ["@bob:test"], room: true }, + }; + attachMentions("@alice:test", content, model, undefined, prevContent); + expect(content).toEqual({ + "m.mentions": {}, + "m.new_content": { "m.mentions": {} }, + }); + }); + + it("test user mentions", () => { + const model = new EditorModel([partsCreator.userPill("Bob", "@bob:test")], partsCreator); + const content: IContent = { "m.new_content": {} }; + const prevContent: IContent = {}; + attachMentions("@alice:test", content, model, undefined, prevContent); + expect(content).toEqual({ + "m.mentions": { user_ids: ["@bob:test"] }, + "m.new_content": { "m.mentions": { user_ids: ["@bob:test"] } }, + }); + }); + + it("test prev user mentions", () => { + const model = new EditorModel([partsCreator.userPill("Bob", "@bob:test")], partsCreator); + const content: IContent = { "m.new_content": {} }; + const prevContent: IContent = { "m.mentions": { user_ids: ["@bob:test"] } }; + attachMentions("@alice:test", content, model, undefined, prevContent); + expect(content).toEqual({ + "m.mentions": {}, + "m.new_content": { "m.mentions": { user_ids: ["@bob:test"] } }, + }); + }); + + it("test room mention", () => { + const model = new EditorModel([partsCreator.atRoomPill("@room")], partsCreator); + const content: IContent = { "m.new_content": {} }; + const prevContent: IContent = {}; + attachMentions("@alice:test", content, model, undefined, prevContent); + expect(content).toEqual({ + "m.mentions": { room: true }, + "m.new_content": { "m.mentions": { room: true } }, + }); + }); + + it("test prev room mention", () => { + const model = new EditorModel([partsCreator.atRoomPill("@room")], partsCreator); + const content: IContent = { "m.new_content": {} }; + const prevContent: IContent = { "m.mentions": { room: true } }; + attachMentions("@alice:test", content, model, undefined, prevContent); + expect(content).toEqual({ + "m.mentions": {}, + "m.new_content": { "m.mentions": { room: true } }, + }); + }); + + it("test broken mentions", () => { + // Replying to a room mention shouldn't automatically be a room mention. + const model = new EditorModel([], partsCreator); + const content: IContent = { "m.new_content": {} }; + // @ts-ignore - Purposefully testing invalid data. + const prevContent: IContent = { "m.mentions": { user_ids: "@bob:test" } }; + attachMentions("@alice:test", content, model, undefined, prevContent); + expect(content).toEqual({ + "m.mentions": {}, + "m.new_content": { "m.mentions": {} }, + }); + }); + }); +});