From d936e51f977e05a1e379a3276ce310b39a78d9c6 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Thu, 2 Apr 2026 12:10:38 -0300 Subject: [PATCH 1/2] fix: sending thread message scrolling the main channel --- apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts b/apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts index 9080264a65f36..cbdc00ce0ea1e 100644 --- a/apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts +++ b/apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts @@ -71,6 +71,10 @@ export const useHasNewMessages = ( clientCallbacks.add( 'afterSaveMessage', (msg: IMessage) => { + if (msg.tmid) { + return; + } + if (msg.u._id === uid) { sendToBottom(); setHasNewMessages(false); From a7df2c297dedc336da31ed236cf7c976646ad62d Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Thu, 2 Apr 2026 13:25:09 -0300 Subject: [PATCH 2/2] test: implemented e2e tests --- .../e2e/messaging-scroll-to-bottom.spec.ts | 121 ++++++++++++++++++ .../page-objects/fragments/home-content.ts | 15 +++ 2 files changed, 136 insertions(+) create mode 100644 apps/meteor/tests/e2e/messaging-scroll-to-bottom.spec.ts diff --git a/apps/meteor/tests/e2e/messaging-scroll-to-bottom.spec.ts b/apps/meteor/tests/e2e/messaging-scroll-to-bottom.spec.ts new file mode 100644 index 0000000000000..57b426ba200b0 --- /dev/null +++ b/apps/meteor/tests/e2e/messaging-scroll-to-bottom.spec.ts @@ -0,0 +1,121 @@ +import { faker } from '@faker-js/faker'; +import type { Locator } from 'playwright-core'; + +import { Users } from './fixtures/userStates'; +import { HomeChannel } from './page-objects'; +import { deleteChannel } from './utils'; +import type { BaseTest } from './utils/test'; +import { expect, test } from './utils/test'; + +test.use({ storageState: Users.admin.state }); +test.describe.serial('Messaging scroll to bottom', () => { + let targetChannel: { name: string; _id: string }; + let poHomeChannel: HomeChannel; + let mainMessage: { _id: string }; + + const fillMessages = async (api: BaseTest['api']) => { + const firstResponse = await api.post('/chat.postMessage', { + roomId: targetChannel._id, + text: faker.lorem.paragraphs(6), + }); + + const { message: firstMessage } = await firstResponse.json(); + mainMessage = firstMessage; + + await Promise.all( + Array.from({ length: 4 }).map(() => + api.post('/chat.postMessage', { + roomId: targetChannel._id, + text: faker.lorem.paragraphs(6), + }), + ), + ); + + await Promise.all( + Array.from({ length: 5 }).map(() => + api.post('/chat.postMessage', { + roomId: targetChannel._id, + text: faker.lorem.paragraphs(6), + tmid: firstMessage._id, + }), + ), + ); + }; + + const scrollToTop = async (scroller: Locator) => { + await scroller.evaluate((el) => { + el.scrollTop = 0; + }); + await expect(scroller).toHaveJSProperty('scrollTop', 0); + }; + + const expectScrolledToBottom = async (scroller: Locator) => { + await expect.poll(() => scroller.evaluate((el) => Math.abs(el.scrollTop - (el.scrollHeight - el.clientHeight)) < 2)).toBe(true); + }; + + test.beforeAll(async ({ api }) => { + await api + .post('/channels.create', { name: faker.string.uuid() }) + .then((res) => res.json()) + .then((data) => { + targetChannel = data.channel; + }); + + await fillMessages(api); + }); + + test.beforeEach(async ({ page }) => { + poHomeChannel = new HomeChannel(page); + await page.goto(`/channel/${targetChannel._id}/thread/${mainMessage._id}`); + await poHomeChannel.content.waitForChannel(); + await poHomeChannel.content.waitForThread(); + }); + + test.afterAll(async ({ api }) => { + await deleteChannel(api, targetChannel.name); + }); + + test('should scroll the main message list to bottom when sending a message in the main channel', async ({ page }) => { + await page.setViewportSize({ width: 1023, height: 700 }); + + await scrollToTop(poHomeChannel.content.mainMessageListScroller); + + await poHomeChannel.content.sendMessage('main channel message'); + + await expectScrolledToBottom(poHomeChannel.content.mainMessageListScroller); + }); + + test('should scroll the thread message list to bottom when sending a message in the thread', async ({ page }) => { + await page.setViewportSize({ width: 1023, height: 700 }); + + await scrollToTop(poHomeChannel.content.threadMessageListScroller); + + await poHomeChannel.content.sendMessageInThread('new thread reply'); + + await expectScrolledToBottom(poHomeChannel.content.threadMessageListScroller); + }); + + test('should not scroll the main channel message list when sending a message in the thread', async ({ page }) => { + await page.setViewportSize({ width: 1023, height: 700 }); + + await scrollToTop(poHomeChannel.content.mainMessageListScroller); + await scrollToTop(poHomeChannel.content.threadMessageListScroller); + + await poHomeChannel.content.sendMessageInThread('another thread reply'); + + await expectScrolledToBottom(poHomeChannel.content.threadMessageListScroller); + await expect(poHomeChannel.content.mainMessageListScroller).toHaveJSProperty('scrollTop', 0); + }); + + test('should not scroll the thread message list when sending a message in the main channel', async ({ page }) => { + await page.setViewportSize({ width: 1023, height: 700 }); + + await scrollToTop(poHomeChannel.content.mainMessageListScroller); + await scrollToTop(poHomeChannel.content.threadMessageListScroller); + + await poHomeChannel.content.sendMessage('another main channel message'); + + await expectScrolledToBottom(poHomeChannel.content.mainMessageListScroller); + await expect(poHomeChannel.content.threadMessageListScroller).toHaveJSProperty('scrollTop', 0); + }); +}); diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts index d05b356dd5b0e..a9e4c0e18b65c 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts @@ -54,6 +54,14 @@ export class HomeContent { return this.mainMessageList.locator('[role="listitem"][aria-roledescription="message"]'); } + get mainMessageListScroller(): Locator { + return this.page.locator('[data-overlayscrollbars-viewport]', { has: this.mainMessageList }); + } + + get threadMessageListScroller(): Locator { + return this.page.locator('[data-overlayscrollbars-viewport]', { has: this.threadMessageList }); + } + get systemMessageListItems(): Locator { return this.mainMessageList.locator('[role="listitem"][aria-roledescription="system message"]'); } @@ -548,6 +556,13 @@ export class HomeContent { await expect(messageList).not.toHaveAttribute('aria-busy', 'true'); } + async waitForThread(): Promise { + const messageList = this.page.getByRole('list', { name: 'Thread message list', exact: true }); + await messageList.waitFor(); + + await expect(messageList).not.toHaveAttribute('aria-busy', 'true'); + } + async openReplyInThread(): Promise { await this.lastUserMessage.hover(); await this.lastUserMessage.getByRole('button', { name: 'Reply in thread' }).waitFor();