diff --git a/src/components/views/rooms/EditMessageComposer.tsx b/src/components/views/rooms/EditMessageComposer.tsx index beb076f96a..066d146f13 100644 --- a/src/components/views/rooms/EditMessageComposer.tsx +++ b/src/components/views/rooms/EditMessageComposer.tsx @@ -67,7 +67,8 @@ function getTextReplyFallback(mxEvent: MatrixEvent): string { return ""; } -function createEditContent(model: EditorModel, editedEvent: MatrixEvent): IContent { +// exported for tests +export function createEditContent(model: EditorModel, editedEvent: MatrixEvent): IContent { const isEmote = containsEmote(model); if (isEmote) { model = stripEmoteCommand(model); @@ -103,15 +104,16 @@ function createEditContent(model: EditorModel, editedEvent: MatrixEvent): IConte contentBody.formatted_body = `${htmlPrefix} * ${formattedBody}`; } - const relation = { - "m.new_content": newContent, - "m.relates_to": { - rel_type: "m.replace", - event_id: editedEvent.getId(), + return Object.assign( + { + "m.new_content": newContent, + "m.relates_to": { + rel_type: "m.replace", + event_id: editedEvent.getId(), + }, }, - }; - - return Object.assign(relation, contentBody); + contentBody, + ); } interface IEditMessageComposerProps extends MatrixClientProps { diff --git a/test/components/views/rooms/EditMessageComposer-test.tsx b/test/components/views/rooms/EditMessageComposer-test.tsx new file mode 100644 index 0000000000..414833ae51 --- /dev/null +++ b/test/components/views/rooms/EditMessageComposer-test.tsx @@ -0,0 +1,150 @@ +/* +Copyright 2023 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 { createEditContent } from "../../../../src/components/views/rooms/EditMessageComposer"; +import EditorModel from "../../../../src/editor/model"; +import { createPartCreator } from "../../../editor/mock"; +import { mkEvent } from "../../../test-utils"; +import DocumentOffset from "../../../../src/editor/offset"; + +describe("", () => { + const editedEvent = mkEvent({ + type: "m.room.message", + user: "@alice:test", + room: "!abc:test", + content: { body: "original message", msgtype: "m.text" }, + event: true, + }); + + 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.relates_to": { + event_id: editedEvent.getId(), + rel_type: "m.replace", + }, + }); + }); + + 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 world", + "m.new_content": { + body: "hello *world*", + msgtype: "m.text", + format: "org.matrix.custom.html", + formatted_body: "hello world", + }, + "m.relates_to": { + event_id: editedEvent.getId(), + rel_type: "m.replace", + }, + }); + }); + + 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 quickly", + "m.new_content": { + body: "blinks __quickly__", + msgtype: "m.emote", + format: "org.matrix.custom.html", + formatted_body: "blinks quickly", + }, + "m.relates_to": { + event_id: editedEvent.getId(), + rel_type: "m.replace", + }, + }); + }); + + 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.relates_to": { + event_id: editedEvent.getId(), + rel_type: "m.replace", + }, + }); + }); + + 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.relates_to": { + event_id: editedEvent.getId(), + rel_type: "m.replace", + }, + }); + }); + }); +}); diff --git a/test/components/views/rooms/SendMessageComposer-test.tsx b/test/components/views/rooms/SendMessageComposer-test.tsx index db05bb77e0..70c7a788e2 100644 --- a/test/components/views/rooms/SendMessageComposer-test.tsx +++ b/test/components/views/rooms/SendMessageComposer-test.tsx @@ -26,7 +26,7 @@ import SendMessageComposer, { import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; import RoomContext, { TimelineRenderingType } from "../../../../src/contexts/RoomContext"; import EditorModel from "../../../../src/editor/model"; -import { createPartCreator, createRenderer } from "../../../editor/mock"; +import { createPartCreator } from "../../../editor/mock"; import { createTestClient, mkEvent, mkStubRoom } from "../../../test-utils"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import defaultDispatcher from "../../../../src/dispatcher/dispatcher"; @@ -85,7 +85,7 @@ describe("", () => { const permalinkCreator = jest.fn() as any; it("sends plaintext messages correctly", () => { - const model = new EditorModel([], createPartCreator(), createRenderer()); + const model = new EditorModel([], createPartCreator()); const documentOffset = new DocumentOffset(11, true); model.update("hello world", "insertText", documentOffset); @@ -98,7 +98,7 @@ describe("", () => { }); it("sends markdown messages correctly", () => { - const model = new EditorModel([], createPartCreator(), createRenderer()); + const model = new EditorModel([], createPartCreator()); const documentOffset = new DocumentOffset(13, true); model.update("hello *world*", "insertText", documentOffset); @@ -113,7 +113,7 @@ describe("", () => { }); it("strips /me from messages and marks them as m.emote accordingly", () => { - const model = new EditorModel([], createPartCreator(), createRenderer()); + const model = new EditorModel([], createPartCreator()); const documentOffset = new DocumentOffset(22, true); model.update("/me blinks __quickly__", "insertText", documentOffset); @@ -128,7 +128,7 @@ describe("", () => { }); it("allows emoting with non-text parts", () => { - const model = new EditorModel([], createPartCreator(), createRenderer()); + 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 @@ -142,7 +142,7 @@ describe("", () => { }); it("allows sending double-slash escaped slash commands correctly", () => { - const model = new EditorModel([], createPartCreator(), createRenderer()); + const model = new EditorModel([], createPartCreator()); const documentOffset = new DocumentOffset(32, true); model.update("//dev/null is my favourite place", "insertText", documentOffset); @@ -349,7 +349,7 @@ describe("", () => { describe("isQuickReaction", () => { it("correctly detects quick reaction", () => { - const model = new EditorModel([], createPartCreator(), createRenderer()); + const model = new EditorModel([], createPartCreator()); model.update("+😊", "insertText", new DocumentOffset(3, true)); const isReaction = isQuickReaction(model); @@ -358,7 +358,7 @@ describe("", () => { }); it("correctly detects quick reaction with space", () => { - const model = new EditorModel([], createPartCreator(), createRenderer()); + const model = new EditorModel([], createPartCreator()); model.update("+ 😊", "insertText", new DocumentOffset(4, true)); const isReaction = isQuickReaction(model); @@ -367,10 +367,10 @@ describe("", () => { }); it("correctly rejects quick reaction with extra text", () => { - const model = new EditorModel([], createPartCreator(), createRenderer()); - const model2 = new EditorModel([], createPartCreator(), createRenderer()); - const model3 = new EditorModel([], createPartCreator(), createRenderer()); - const model4 = new EditorModel([], createPartCreator(), createRenderer()); + const model = new EditorModel([], createPartCreator()); + const model2 = new EditorModel([], createPartCreator()); + const model3 = new EditorModel([], createPartCreator()); + const model4 = new EditorModel([], createPartCreator()); model.update("+😊hello", "insertText", new DocumentOffset(8, true)); model2.update(" +😊", "insertText", new DocumentOffset(4, true)); model3.update("+ 😊😊", "insertText", new DocumentOffset(6, true));