From f24db71753e1b0819781dbc5835adf1aac090d40 Mon Sep 17 00:00:00 2001 From: Adarsh Singh <63918341+adarsh-sgh@users.noreply.github.com> Date: Wed, 8 Feb 2023 21:59:12 +0530 Subject: [PATCH] fix: correctly identify emoticons (#10108) Signed-off-by: Adarsh Singh --- .../views/rooms/BasicMessageComposer.tsx | 9 ++- .../views/rooms/BasicMessageComposer-test.tsx | 58 ++++++++++++++----- 2 files changed, 50 insertions(+), 17 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index 9f50e7b96f..eaf97a39f4 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -191,16 +191,19 @@ export default class BasicMessageEditor extends React.Component public replaceEmoticon(caretPosition: DocumentPosition, regex: RegExp): number { const { model } = this.props; const range = model.startRange(caretPosition); - // expand range max 8 characters backwards from caretPosition, + // expand range max 9 characters backwards from caretPosition, // as a space to look for an emoticon - let n = 8; + let n = 9; range.expandBackwardsWhile((index, offset) => { const part = model.parts[index]; n -= 1; return n >= 0 && [Type.Plain, Type.PillCandidate, Type.Newline].includes(part.type); }); const emoticonMatch = regex.exec(range.text); - if (emoticonMatch) { + // ignore matches at start of proper substrings + // so xd will not match if the string was "mixd 123456" + // and we are lookinh at xd 123456 part of the string + if (emoticonMatch && (n >= 0 || emoticonMatch.index !== 0)) { const query = emoticonMatch[1].replace("-", ""); // try both exact match and lower-case, this means that xd won't match xD but :P will match :p const data = EMOTICON_TO_EMOJI.get(query) || EMOTICON_TO_EMOJI.get(query.toLowerCase()); diff --git a/test/components/views/rooms/BasicMessageComposer-test.tsx b/test/components/views/rooms/BasicMessageComposer-test.tsx index 3ab85957cb..34d32b627d 100644 --- a/test/components/views/rooms/BasicMessageComposer-test.tsx +++ b/test/components/views/rooms/BasicMessageComposer-test.tsx @@ -24,34 +24,64 @@ import * as TestUtils from "../../../test-utils"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import EditorModel from "../../../../src/editor/model"; import { createPartCreator, createRenderer } from "../../../editor/mock"; +import SettingsStore from "../../../../src/settings/SettingsStore"; describe("BasicMessageComposer", () => { const renderer = createRenderer(); const pc = createPartCreator(); - beforeEach(() => { - TestUtils.stubClient(); - }); + TestUtils.stubClient(); - it("should allow a user to paste a URL without it being mangled", () => { + const client: MatrixClient = MatrixClientPeg.get(); + + const roomId = "!1234567890:domain"; + const userId = client.getSafeUserId(); + const room = new Room(roomId, client, userId); + + it("should allow a user to paste a URL without it being mangled", async () => { const model = new EditorModel([], pc, renderer); - const client: MatrixClient = MatrixClientPeg.get(); - - const roomId = "!1234567890:domain"; - const userId = client.getSafeUserId(); - - const room = new Room(roomId, client, userId); - + render(); const testUrl = "https://element.io"; const mockDataTransfer = generateMockDataTransferForString(testUrl); - - render(); - userEvent.paste(mockDataTransfer); + await userEvent.paste(mockDataTransfer); expect(model.parts).toHaveLength(1); expect(model.parts[0].text).toBe(testUrl); expect(screen.getByText(testUrl)).toBeInTheDocument(); }); + + it("should replaceEmoticons properly", async () => { + jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string) => { + return settingName === "MessageComposerInput.autoReplaceEmoji"; + }); + userEvent.setup(); + const model = new EditorModel([], pc, renderer); + render(); + + const tranformations = [ + { before: "4:3 video", after: "4:3 video" }, + { before: "regexp 12345678", after: "regexp 12345678" }, + { before: "--:--)", after: "--:--)" }, + + { before: "we <3 matrix", after: "we ❤️ matrix" }, + { before: "hello world :-)", after: "hello world 🙂" }, + { before: ":) hello world", after: "🙂 hello world" }, + { before: ":D 4:3 video :)", after: "😄 4:3 video 🙂" }, + + { before: ":-D", after: "😄" }, + { before: ":D", after: "😄" }, + { before: ":3", after: "😽" }, + ]; + const input = screen.getByRole("textbox"); + + for (const { before, after } of tranformations) { + await userEvent.clear(input); + //add a space after the text to trigger the replacement + await userEvent.type(input, before + " "); + const transformedText = model.parts.map((part) => part.text).join(""); + expect(transformedText).toBe(after + " "); + } + }); }); function generateMockDataTransferForString(string: string): DataTransfer {