From 25a10b65bcaca2f0efdb8f1acfdf97b251bfde24 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Fri, 19 Dec 2025 10:01:10 +0530 Subject: [PATCH] fix: Global Search "Jump to message" failing due to incorrect URL generation (#37717) Co-authored-by: Douglas Fabris <27704687+dougfabris@users.noreply.github.com> --- .changeset/olive-pens-think.md | 5 ++ .../client/lib/rooms/roomCoordinator.tsx | 10 +-- apps/meteor/client/lib/utils/goToRoomById.ts | 11 ++- .../client/lib/utils/legacyJumpToMessage.ts | 23 ++---- apps/meteor/tests/e2e/global-search.spec.ts | 78 +++++++++++++++++++ .../page-objects/fragments/home-flextab.ts | 4 + .../fragments/searchMessages-flextab.ts | 20 +++++ .../e2e/page-objects/fragments/toolbar.ts | 4 + 8 files changed, 129 insertions(+), 26 deletions(-) create mode 100644 .changeset/olive-pens-think.md create mode 100644 apps/meteor/tests/e2e/global-search.spec.ts create mode 100644 apps/meteor/tests/e2e/page-objects/fragments/searchMessages-flextab.ts diff --git a/.changeset/olive-pens-think.md b/.changeset/olive-pens-think.md new file mode 100644 index 00000000000..994b14dad08 --- /dev/null +++ b/.changeset/olive-pens-think.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes incorrect URL generation in Global Search "Jump to message" feature, resolving navigation issues when jumping to messages across different channels. diff --git a/apps/meteor/client/lib/rooms/roomCoordinator.tsx b/apps/meteor/client/lib/rooms/roomCoordinator.tsx index 54a317a321d..54b0cf2145f 100644 --- a/apps/meteor/client/lib/rooms/roomCoordinator.tsx +++ b/apps/meteor/client/lib/rooms/roomCoordinator.tsx @@ -67,18 +67,18 @@ class RoomCoordinatorClient extends RoomCoordinator { roomType: RoomType, subData: RoomIdentification, queryParams?: Record, - options: { replace?: boolean } = {}, + options: { replace?: boolean; routeParamsOverrides?: Record } = {}, ): void { const config = this.getRoomTypeConfig(roomType); if (!config?.route) { return; } - let routeData = {}; + let _routeData = {}; if (config.route.link) { - routeData = config.route.link(subData); + _routeData = config.route.link(subData); } else if (subData?.name) { - routeData = { + _routeData = { name: subData.name, }; } else { @@ -88,7 +88,7 @@ class RoomCoordinatorClient extends RoomCoordinator { router.navigate( { pattern: config.route.path ?? '/home', - params: routeData, + params: { ..._routeData, ...options.routeParamsOverrides }, search: queryParams, }, options, diff --git a/apps/meteor/client/lib/utils/goToRoomById.ts b/apps/meteor/client/lib/utils/goToRoomById.ts index 35a4761c594..8a5eca30aae 100644 --- a/apps/meteor/client/lib/utils/goToRoomById.ts +++ b/apps/meteor/client/lib/utils/goToRoomById.ts @@ -8,7 +8,12 @@ import { roomCoordinator } from '../rooms/roomCoordinator'; const getRoomById = memoize((rid: IRoom['_id']) => callWithErrorHandling('getRoomById', rid)); -export const goToRoomById = async (rid: IRoom['_id']): Promise => { +export type GoToRoomByIdOptions = { + replace?: boolean; + routeParamsOverrides?: Record; +}; + +export const goToRoomById = async (rid: IRoom['_id'], options: GoToRoomByIdOptions = {}): Promise => { if (!rid) { return; } @@ -16,10 +21,10 @@ export const goToRoomById = async (rid: IRoom['_id']): Promise => { const subscription = Subscriptions.state.find((record) => record.rid === rid); if (subscription) { - roomCoordinator.openRouteLink(subscription.t, subscription, router.getSearchParameters()); + roomCoordinator.openRouteLink(subscription.t, subscription, router.getSearchParameters(), options); return; } const room = await getRoomById(rid); - roomCoordinator.openRouteLink(room.t, { rid: room._id, ...room }, router.getSearchParameters()); + roomCoordinator.openRouteLink(room.t, { rid: room._id, ...room }, router.getSearchParameters(), options); }; diff --git a/apps/meteor/client/lib/utils/legacyJumpToMessage.ts b/apps/meteor/client/lib/utils/legacyJumpToMessage.ts index fdaffcb65af..8b880e517db 100644 --- a/apps/meteor/client/lib/utils/legacyJumpToMessage.ts +++ b/apps/meteor/client/lib/utils/legacyJumpToMessage.ts @@ -4,7 +4,6 @@ import { isThreadMessage } from '@rocket.chat/core-typings'; import { goToRoomById } from './goToRoomById'; import { RoomHistoryManager } from '../../../app/ui-utils/client'; import { router } from '../../providers/RouterProvider'; -import { Rooms } from '../../stores'; import { RoomManager } from '../RoomManager'; /** @deprecated */ @@ -15,24 +14,12 @@ export const legacyJumpToMessage = async (message: IMessage) => { if (tab === 'thread' && (context === message.tmid || context === message._id)) { return; } - router.navigate( - { - name: router.getRouteName()!, - params: { - tab: 'thread', - context: message.tmid || message._id, - rid: message.rid, - name: Rooms.state.get(message.rid)?.name ?? '', - }, - search: { - ...router.getSearchParameters(), - msg: message._id, - }, - }, - { replace: false }, - ); - await RoomHistoryManager.getSurroundingMessages(message); + await goToRoomById(message.rid, { + routeParamsOverrides: { tab: 'thread', context: message.tmid || message._id }, + replace: RoomManager.opened === message.rid, + }); + await RoomHistoryManager.getSurroundingMessages(message); return; } diff --git a/apps/meteor/tests/e2e/global-search.spec.ts b/apps/meteor/tests/e2e/global-search.spec.ts new file mode 100644 index 00000000000..b65bce451f0 --- /dev/null +++ b/apps/meteor/tests/e2e/global-search.spec.ts @@ -0,0 +1,78 @@ +import type { IMessage } from '@rocket.chat/core-typings'; +import { Random } from '@rocket.chat/random'; + +import { Users } from './fixtures/userStates'; +import { HomeChannel } from './page-objects'; +import { setSettingValueById } from './utils'; +import type { BaseTest } from './utils/test'; +import { expect, test } from './utils/test'; + +test.use({ storageState: Users.admin.state }); + +test.describe.serial('Global Search', () => { + let targetChannel: { name: string; _id: string }; + let targetGroup: { name: string; _id: string }; + let threadMessage: IMessage; + let poHomeChannel: HomeChannel; + + const fillMessages = async (api: BaseTest['api']) => { + const { message: parentMessage } = await ( + await api.post('/chat.postMessage', { roomId: targetChannel._id, text: 'This is main message in channel' }) + ).json(); + + const { message: childMessage } = await ( + await api.post('/chat.postMessage', { roomId: targetChannel._id, text: `This is thread message in channel`, tmid: parentMessage._id }) + ).json(); + threadMessage = childMessage; + }; + + test.beforeAll(async ({ api }) => { + await Promise.all([ + api + .post('/channels.create', { name: Random.id() }) + .then((res) => res.json()) + .then((data) => { + targetChannel = data.channel; + }), + api + .post('/groups.create', { + name: Random.id(), + extraData: { encrypted: false }, + }) + .then((res) => res.json()) + .then((data) => { + targetGroup = data.group; + }), + setSettingValueById(api, 'Search.defaultProvider.GlobalSearchEnabled', true), + ]); + await fillMessages(api); + }); + + test.afterAll(({ api }) => + Promise.all([ + api.post('/channels.delete', { roomId: targetChannel._id }), + api.post('/groups.delete', { roomId: targetGroup._id }), + setSettingValueById(api, 'Search.defaultProvider.GlobalSearchEnabled', false), + ]), + ); + + test.beforeEach(async ({ page }) => { + poHomeChannel = new HomeChannel(page); + await page.goto('/home'); + }); + + test('should open the correct message when jumping from global search in group to channel thread', async ({ page }) => { + await poHomeChannel.sidenav.openChat(targetGroup.name); + await poHomeChannel.roomToolbar.btnSearchMessages.click(); + + await poHomeChannel.tabs.searchMessages.search(threadMessage.msg.slice(10), { global: true }); // fill partial text to match search + + const message = await poHomeChannel.tabs.searchMessages.getResultItem(threadMessage.msg); + await message.hover(); + const jumpToMessageButton = message.getByRole('button', { name: 'Jump to message' }); + await jumpToMessageButton.click(); + + await expect(page.locator('header').getByRole('button').filter({ hasText: targetChannel.name })).toBeVisible(); // match channel name in room header + await expect(page.getByText(threadMessage.msg)).toBeVisible(); + }); +}); diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts index ef8fcaa74a1..7daa79e5a91 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts @@ -7,6 +7,7 @@ import { HomeFlextabNotificationPreferences } from './home-flextab-notificationP import { HomeFlextabOtr } from './home-flextab-otr'; import { HomeFlextabPruneMessages } from './home-flextab-pruneMessages'; import { HomeFlextabRoom } from './home-flextab-room'; +import { SearchMessagesFlexTab } from './searchMessages-flextab'; export class HomeFlextab { private readonly page: Page; @@ -25,6 +26,8 @@ export class HomeFlextab { readonly pruneMessages: HomeFlextabPruneMessages; + readonly searchMessages: SearchMessagesFlexTab; + constructor(page: Page) { this.page = page; this.members = new HomeFlextabMembers(page); @@ -34,6 +37,7 @@ export class HomeFlextab { this.otr = new HomeFlextabOtr(page); this.exportMessages = new ExportMessagesTab(page); this.pruneMessages = new HomeFlextabPruneMessages(page); + this.searchMessages = new SearchMessagesFlexTab(page); } get toolbarPrimaryActions(): Locator { diff --git a/apps/meteor/tests/e2e/page-objects/fragments/searchMessages-flextab.ts b/apps/meteor/tests/e2e/page-objects/fragments/searchMessages-flextab.ts new file mode 100644 index 00000000000..3a138453d96 --- /dev/null +++ b/apps/meteor/tests/e2e/page-objects/fragments/searchMessages-flextab.ts @@ -0,0 +1,20 @@ +import type { Page } from '@playwright/test'; + +import { FlexTab } from './flextab'; + +export class SearchMessagesFlexTab extends FlexTab { + constructor(page: Page) { + super(page.getByRole('dialog', { name: 'Search Messages' })); + } + + async search(text: string, { global = false }: { global?: boolean } = {}) { + if (global) { + await this.root.getByText('Global search').click(); + } + await this.root.getByPlaceholder('Search Messages').fill(text); + } + + async getResultItem(messageText: string) { + return this.root.getByRole('listitem', { name: messageText }); + } +} diff --git a/apps/meteor/tests/e2e/page-objects/fragments/toolbar.ts b/apps/meteor/tests/e2e/page-objects/fragments/toolbar.ts index f55910a6d2b..0aac7851300 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/toolbar.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/toolbar.ts @@ -60,6 +60,10 @@ export class RoomToolbar extends Toolbar { return this.root.getByRole('button', { name: 'Options' }); } + get btnSearchMessages(): Locator { + return this.root.getByRole('button', { name: 'Search Messages' }); + } + get btnDisableE2EEncryption(): Locator { return this.root.getByRole('button', { name: 'Disable E2E encryption' }); }