From ba6ce673a18d20509aa9af3358ce3eecdd8b0734 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 25 Jun 2026 18:28:56 +0530 Subject: [PATCH] added changeset Signed-off-by: Abhinav Kumar --- .changeset/moody-eggs-juggle.md | 5 ++ .../messageBox/createComposerAPI.spec.ts | 61 +++++++++++++++++++ .../client/messageBox/createComposerAPI.ts | 10 +-- 3 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 .changeset/moody-eggs-juggle.md create mode 100644 apps/meteor/app/ui-message/client/messageBox/createComposerAPI.spec.ts diff --git a/.changeset/moody-eggs-juggle.md b/.changeset/moody-eggs-juggle.md new file mode 100644 index 0000000000000..1324f520d0d2c --- /dev/null +++ b/.changeset/moody-eggs-juggle.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes an issue where the cursor jumped to the wrong position after inserting a mention at the start or middle of a message. diff --git a/apps/meteor/app/ui-message/client/messageBox/createComposerAPI.spec.ts b/apps/meteor/app/ui-message/client/messageBox/createComposerAPI.spec.ts new file mode 100644 index 0000000000000..d9362c90f8a16 --- /dev/null +++ b/apps/meteor/app/ui-message/client/messageBox/createComposerAPI.spec.ts @@ -0,0 +1,61 @@ +import { createComposerAPI } from './createComposerAPI'; + +jest.mock('../../../../client/lib/chats/uploads', () => ({ + createUploadsAPI: () => ({}), +})); + +const setupComposer = (initialValue: string, cursor: { start: number; end: number }) => { + const input = document.createElement('textarea'); + document.body.appendChild(input); + + const composer = createComposerAPI(input, jest.fn(), '', Number.MAX_SAFE_INTEGER, { current: null }, { rid: 'GENERAL' }); + + input.value = initialValue; + input.setSelectionRange(cursor.start, cursor.end); + + return { composer, input }; +}; + +afterEach(() => { + document.body.innerHTML = ''; +}); + +describe('ChatMessages Composer API - replaceText', () => { + it('should place the cursor right after the mention when inserting at the start of the message', () => { + const { composer, input } = setupComposer('@jhello', { start: 2, end: 2 }); + + composer.replaceText('@john ', { start: 0, end: 2 }); + + expect(input.value).toBe('@john hello'); + expect(input.selectionStart).toBe('@john '.length); + expect(input.selectionEnd).toBe('@john '.length); + }); + + it('should place the cursor right after the mention when inserting in the middle of the message', () => { + const { composer, input } = setupComposer('hi @jthere', { start: 5, end: 5 }); + + composer.replaceText('@john ', { start: 3, end: 5 }); + + expect(input.value).toBe('hi @john there'); + expect(input.selectionStart).toBe('hi @john '.length); + expect(input.selectionEnd).toBe('hi @john '.length); + }); + + it('should place the cursor right after the mention when inserting at the end of the message', () => { + const { composer, input } = setupComposer('hello @j', { start: 8, end: 8 }); + + composer.replaceText('@john ', { start: 6, end: 8 }); + + expect(input.value).toBe('hello @john '); + expect(input.selectionStart).toBe('hello @john '.length); + expect(input.selectionEnd).toBe('hello @john '.length); + }); + + it('should keep the cursor collapsed right after the inserted text', () => { + const { composer, input } = setupComposer('@jhello', { start: 2, end: 2 }); + + composer.replaceText('@john ', { start: 0, end: 2 }); + + expect(input.selectionStart).toBe(input.selectionEnd); + }); +}); diff --git a/apps/meteor/app/ui-message/client/messageBox/createComposerAPI.ts b/apps/meteor/app/ui-message/client/messageBox/createComposerAPI.ts index 2018abfa5fa91..b54885d2d6c47 100644 --- a/apps/meteor/app/ui-message/client/messageBox/createComposerAPI.ts +++ b/apps/meteor/app/ui-message/client/messageBox/createComposerAPI.ts @@ -288,8 +288,6 @@ export const createComposerAPI = ( // Gets the text that is connected to the cursor and replaces it with the given text const replaceText = (text: string, selection: { readonly start: number; readonly end: number }): void => { - const { selectionStart, selectionEnd } = input; - // Selects the text that is connected to the cursor input.setSelectionRange(selection.start ?? 0, selection.end ?? text.length); const textAreaTxt = input.value; @@ -298,11 +296,9 @@ export const createComposerAPI = ( input.value = textAreaTxt.substring(0, selection.start) + text + textAreaTxt.substring(selection.end); } - input.selectionStart = selectionStart + text.length; - input.selectionEnd = selectionStart + text.length; - if (selectionStart !== selectionEnd) { - input.selectionStart = selectionStart; - } + const cursorPosition = (selection.start ?? 0) + text.length; + input.selectionStart = cursorPosition; + input.selectionEnd = cursorPosition; triggerEvent(input, 'input'); triggerEvent(input, 'change');