From aedb132f63981badb780d9892cf156b6fb55e55f Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Fri, 7 Nov 2025 13:07:15 -0300 Subject: [PATCH 01/10] wip --- apps/meteor/server/modules/listeners/listeners.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/server/modules/listeners/listeners.module.ts b/apps/meteor/server/modules/listeners/listeners.module.ts index a9e220211a9d5..b7c06c0bfb8fd 100644 --- a/apps/meteor/server/modules/listeners/listeners.module.ts +++ b/apps/meteor/server/modules/listeners/listeners.module.ts @@ -167,7 +167,7 @@ export class ListenersModule { id: _id, diff: { status, - ...(statusText && { statusText }), + statusText, }, unset: {}, }); From f1875110f4d1a6add1b0de663a124c2b6b9f9e3c Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Fri, 7 Nov 2025 13:27:21 -0300 Subject: [PATCH 02/10] test: add e2e test --- apps/meteor/tests/e2e/presence.spec.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/meteor/tests/e2e/presence.spec.ts b/apps/meteor/tests/e2e/presence.spec.ts index 5546f8fb4cf3a..80e2c7f5eb54b 100644 --- a/apps/meteor/tests/e2e/presence.spec.ts +++ b/apps/meteor/tests/e2e/presence.spec.ts @@ -54,7 +54,6 @@ test.describe.serial('Presence', () => { await user1Channel.sidenav.btnUserProfileMenu.click(); await user1Channel.sidenav.getUserProfileMenuOption('Custom Status').click(); await user1Modal.changeStatusMessage('new status'); - await user1Page.close(); }); @@ -64,6 +63,23 @@ test.describe.serial('Presence', () => { await expect(poHomeChannel.content.channelHeader).toContainText('new status'); }); }); + + test('should be able to erase custom status', async ({ page }) => { + const editStatusModal = new EditStatusModal(page); + + await poHomeChannel.sidenav.btnUserProfileMenu.click(); + await poHomeChannel.sidenav.getUserProfileMenuOption('Custom Status').click(); + await editStatusModal.changeStatusMessage('new status'); + + await poHomeChannel.sidenav.btnUserProfileMenu.click(); + await expect(poHomeChannel.sidenav.userProfileMenu).toContainText('new status'); + + await poHomeChannel.sidenav.getUserProfileMenuOption('Custom Status').click(); + await editStatusModal.changeStatusMessage(''); + + await poHomeChannel.sidenav.btnUserProfileMenu.click(); + await expect(poHomeChannel.sidenav.userProfileMenu).not.toContainText('new status'); + }); }); test.describe('Login using with "Methods by REST" disabled', () => { From fef48202d48860348bdce3cdfe969dd95ab21a43 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Mon, 10 Nov 2025 16:42:04 -0300 Subject: [PATCH 03/10] chore: changeset --- .changeset/grumpy-colts-collect.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/grumpy-colts-collect.md diff --git a/.changeset/grumpy-colts-collect.md b/.changeset/grumpy-colts-collect.md new file mode 100644 index 0000000000000..9406052d0b132 --- /dev/null +++ b/.changeset/grumpy-colts-collect.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes an issue where custom status is not updating immediately if the value is empty From 625f0cf3ba6e9b8a2c193808b30577504823fda9 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Wed, 12 Nov 2025 19:29:30 -0300 Subject: [PATCH 04/10] test: fix e2e test review --- .../fragments/edit-status-modal.ts | 2 -- .../page-objects/fragments/home-sidenav.ts | 9 ++++++ apps/meteor/tests/e2e/presence.spec.ts | 28 ++++++++----------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/apps/meteor/tests/e2e/page-objects/fragments/edit-status-modal.ts b/apps/meteor/tests/e2e/page-objects/fragments/edit-status-modal.ts index 6d7eda04176a3..7bcb8ded381c2 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/edit-status-modal.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/edit-status-modal.ts @@ -3,8 +3,6 @@ import type { Page } from 'playwright-core'; import { Modal } from './modal'; export class EditStatusModal extends Modal { - protected readonly page: Page; - constructor(page: Page) { super(page.getByRole('dialog', { name: 'Edit Status' })); } diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts index 86153c41ebb47..9d2eee446e28a 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts @@ -1,6 +1,7 @@ import type { Locator, Page } from '@playwright/test'; import { LoginPage } from '../login'; +import { EditStatusModal } from './edit-status-modal'; import { ToastMessages } from './toast-messages'; import { expect } from '../../utils/test'; @@ -251,4 +252,12 @@ export class HomeSidenav { get homepageHeader(): Locator { return this.page.locator('main').getByRole('heading', { name: 'Home' }); } + + async changeUserCustomStatus(text: string): Promise { + const editStatusModal = new EditStatusModal(this.page); + + await this.btnUserProfileMenu.click(); + await this.getUserProfileMenuOption('Custom Status').click(); + await editStatusModal.changeStatusMessage(text); + } } diff --git a/apps/meteor/tests/e2e/presence.spec.ts b/apps/meteor/tests/e2e/presence.spec.ts index 80e2c7f5eb54b..f31e8af573199 100644 --- a/apps/meteor/tests/e2e/presence.spec.ts +++ b/apps/meteor/tests/e2e/presence.spec.ts @@ -1,7 +1,8 @@ +import { faker } from '@faker-js/faker'; + import { DEFAULT_USER_CREDENTIALS, IS_EE } from './config/constants'; import { Users } from './fixtures/userStates'; import { Registration, HomeChannel } from './page-objects'; -import { EditStatusModal } from './page-objects/fragments/edit-status-modal'; import { setSettingValueById } from './utils/setSettingValueById'; import { test, expect } from './utils/test'; import { links } from '../../client/lib/links'; @@ -36,49 +37,42 @@ test.describe.serial('Presence', () => { }); test.describe('Custom status', () => { + const customStatus = faker.string.alpha(10); test.use({ storageState: Users.admin.state }); test('should user custom status be reactive', async ({ browser }) => { await test.step('user1 custom status should be empty', async () => { await poHomeChannel.sidenav.openChat('user1'); - await expect(poHomeChannel.content.channelHeader).not.toContainText('new status'); + await expect(poHomeChannel.content.channelHeader).not.toContainText(customStatus); }); await test.step('update user1 custom status', async () => { const user1Page = await browser.newPage({ storageState: Users.user1.state }); await user1Page.goto('/home'); const user1Channel = new HomeChannel(user1Page); - const user1Modal = new EditStatusModal(user1Page); - await user1Channel.sidenav.btnUserProfileMenu.click(); - await user1Channel.sidenav.getUserProfileMenuOption('Custom Status').click(); - await user1Modal.changeStatusMessage('new status'); + await user1Channel.sidenav.changeUserCustomStatus(customStatus); await user1Page.close(); }); await test.step('should user1 custom status be updated', async () => { await poHomeChannel.sidenav.openChat('user1'); - await expect(poHomeChannel.content.channelHeader).toContainText('new status'); + await expect(poHomeChannel.content.channelHeader).toContainText(customStatus); }); }); test('should be able to erase custom status', async ({ page }) => { - const editStatusModal = new EditStatusModal(page); - - await poHomeChannel.sidenav.btnUserProfileMenu.click(); - await poHomeChannel.sidenav.getUserProfileMenuOption('Custom Status').click(); - await editStatusModal.changeStatusMessage('new status'); - + await poHomeChannel.sidenav.changeUserCustomStatus(customStatus); await poHomeChannel.sidenav.btnUserProfileMenu.click(); - await expect(poHomeChannel.sidenav.userProfileMenu).toContainText('new status'); + await expect(poHomeChannel.sidenav.userProfileMenu).toContainText(customStatus); + await page.keyboard.press('Escape'); - await poHomeChannel.sidenav.getUserProfileMenuOption('Custom Status').click(); - await editStatusModal.changeStatusMessage(''); + await poHomeChannel.sidenav.changeUserCustomStatus(''); await poHomeChannel.sidenav.btnUserProfileMenu.click(); - await expect(poHomeChannel.sidenav.userProfileMenu).not.toContainText('new status'); + await expect(poHomeChannel.sidenav.userProfileMenu).not.toContainText(customStatus); }); }); From 5b842c98cadf9be8e9b96c9506eec5f702a95d3e Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Thu, 13 Nov 2025 10:26:31 -0300 Subject: [PATCH 05/10] test: dismiss toast when saving status --- .../tests/e2e/page-objects/fragments/edit-status-modal.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/meteor/tests/e2e/page-objects/fragments/edit-status-modal.ts b/apps/meteor/tests/e2e/page-objects/fragments/edit-status-modal.ts index 7bcb8ded381c2..46633ec9a907b 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/edit-status-modal.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/edit-status-modal.ts @@ -1,10 +1,14 @@ import type { Page } from 'playwright-core'; import { Modal } from './modal'; +import { ToastMessages } from './toast-messages'; export class EditStatusModal extends Modal { + readonly toastMessages: ToastMessages; + constructor(page: Page) { super(page.getByRole('dialog', { name: 'Edit Status' })); + this.toastMessages = new ToastMessages(page); } private get statusMessageInput() { @@ -14,5 +18,6 @@ export class EditStatusModal extends Modal { async changeStatusMessage(statusMessage: string): Promise { await this.statusMessageInput.fill(statusMessage); await this.save(); + await this.toastMessages.dismissToast(); } } From d3f62d2bf85654896788fbafc379be63abe14dd9 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Thu, 13 Nov 2025 15:16:15 -0300 Subject: [PATCH 06/10] fix: do not update statusText twice --- apps/meteor/app/api/server/v1/users.ts | 19 ++++++------ .../lib/server/functions/saveUser/saveUser.ts | 2 +- .../app/lib/server/functions/setStatusText.ts | 30 ++++++++++++++----- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/apps/meteor/app/api/server/v1/users.ts b/apps/meteor/app/api/server/v1/users.ts index 048b289b3fcec..86769ddb2183b 100644 --- a/apps/meteor/app/api/server/v1/users.ts +++ b/apps/meteor/app/api/server/v1/users.ts @@ -1345,18 +1345,17 @@ API.v1.addRoute( } const { _id, username, roles, name } = user; - let { statusText } = user; - - // TODO refactor to not update the user twice (one inside of `setStatusText` and then later just the status + statusDefault) + let { statusText, status } = user; if (this.bodyParams.message || this.bodyParams.message === '') { - await setStatusText(user._id, this.bodyParams.message); + await setStatusText(user._id, this.bodyParams.message, { emit: false }); statusText = this.bodyParams.message; } + if (this.bodyParams.status) { const validStatus = ['online', 'away', 'offline', 'busy']; if (validStatus.includes(this.bodyParams.status)) { - const { status } = this.bodyParams; + status = this.bodyParams.status; if (status === 'offline' && !settings.get('Accounts_AllowInvisibleStatusOption')) { throw new Meteor.Error('error-status-not-allowed', 'Invisible status is disabled', { @@ -1374,11 +1373,6 @@ API.v1.addRoute( }, ); - void api.broadcast('presence.status', { - user: { status, _id, username, statusText, roles, name }, - previousStatus: user.status, - }); - void wrapExceptions(() => Calendar.cancelUpcomingStatusChanges(user._id)).suppress(); } else { throw new Meteor.Error('error-invalid-status', 'Valid status types include online, away, offline, and busy.', { @@ -1387,6 +1381,11 @@ API.v1.addRoute( } } + void api.broadcast('presence.status', { + user: { status, _id, username, statusText, roles, name }, + previousStatus: user.status, + }); + return API.v1.success(); }, }, diff --git a/apps/meteor/app/lib/server/functions/saveUser/saveUser.ts b/apps/meteor/app/lib/server/functions/saveUser/saveUser.ts index e77e56cc84b9d..5c78b0bd96649 100644 --- a/apps/meteor/app/lib/server/functions/saveUser/saveUser.ts +++ b/apps/meteor/app/lib/server/functions/saveUser/saveUser.ts @@ -125,7 +125,7 @@ const _saveUser = (session?: ClientSession) => } if (typeof userData.statusText === 'string') { - await setStatusText(userData._id, userData.statusText, updater, session); + await setStatusText(userData._id, userData.statusText, { updater, session }); } if (userData.email) { diff --git a/apps/meteor/app/lib/server/functions/setStatusText.ts b/apps/meteor/app/lib/server/functions/setStatusText.ts index 7c81bae0112ed..8ce241e8683b5 100644 --- a/apps/meteor/app/lib/server/functions/setStatusText.ts +++ b/apps/meteor/app/lib/server/functions/setStatusText.ts @@ -9,7 +9,19 @@ import { onceTransactionCommitedSuccessfully } from '../../../../server/database import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { RateLimiter } from '../lib'; -async function _setStatusText(userId: string, statusText: string, updater?: Updater, session?: ClientSession): Promise { +async function _setStatusText( + userId: string, + statusText: string, + { + updater, + session, + emit = true, + }: { + updater?: Updater; + session?: ClientSession; + emit?: boolean; + }, +): Promise { if (!userId) { return false; } @@ -35,13 +47,15 @@ async function _setStatusText(userId: string, statusText: string, updater?: Upda await Users.updateStatusText(user._id, statusText, { session }); } - const { _id, username, status, name, roles } = user; - await onceTransactionCommitedSuccessfully(() => { - void api.broadcast('presence.status', { - user: { _id, username, status, statusText, name, roles }, - previousStatus: status, - }); - }, session); + if (emit) { + const { _id, username, status, name, roles } = user; + await onceTransactionCommitedSuccessfully(() => { + void api.broadcast('presence.status', { + user: { _id, username, status, statusText, name, roles }, + previousStatus: status, + }); + }, session); + } return true; } From c9c9a79fdd7cc682239d86c04b3c6d733ff21c61 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Thu, 13 Nov 2025 15:16:21 -0300 Subject: [PATCH 07/10] test: wait for update --- apps/meteor/tests/e2e/presence.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/tests/e2e/presence.spec.ts b/apps/meteor/tests/e2e/presence.spec.ts index f31e8af573199..a8980c3f81354 100644 --- a/apps/meteor/tests/e2e/presence.spec.ts +++ b/apps/meteor/tests/e2e/presence.spec.ts @@ -72,7 +72,7 @@ test.describe.serial('Presence', () => { await poHomeChannel.sidenav.changeUserCustomStatus(''); await poHomeChannel.sidenav.btnUserProfileMenu.click(); - await expect(poHomeChannel.sidenav.userProfileMenu).not.toContainText(customStatus); + await expect(async () => expect(poHomeChannel.sidenav.userProfileMenu).not.toContainText(customStatus)).toPass(); }); }); From 5c0b33f5e2d9d1ce3e972d1309945d3d024291f5 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Wed, 3 Dec 2025 14:15:48 -0300 Subject: [PATCH 08/10] chore: set default empty obj --- apps/meteor/app/lib/server/functions/setStatusText.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/app/lib/server/functions/setStatusText.ts b/apps/meteor/app/lib/server/functions/setStatusText.ts index 8ce241e8683b5..8a5276d584ead 100644 --- a/apps/meteor/app/lib/server/functions/setStatusText.ts +++ b/apps/meteor/app/lib/server/functions/setStatusText.ts @@ -20,7 +20,7 @@ async function _setStatusText( updater?: Updater; session?: ClientSession; emit?: boolean; - }, + } = {}, ): Promise { if (!userId) { return false; From c50714d2599a6baaedd842b9bfbe5aa2222d4fc5 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Fri, 5 Dec 2025 09:31:55 -0300 Subject: [PATCH 09/10] chore: force statusText when update --- apps/meteor/server/modules/listeners/listeners.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/server/modules/listeners/listeners.module.ts b/apps/meteor/server/modules/listeners/listeners.module.ts index b7c06c0bfb8fd..a9bb67fef4826 100644 --- a/apps/meteor/server/modules/listeners/listeners.module.ts +++ b/apps/meteor/server/modules/listeners/listeners.module.ts @@ -167,7 +167,7 @@ export class ListenersModule { id: _id, diff: { status, - statusText, + ...(statusText ? { statusText } : { statusText: '' }), }, unset: {}, }); From 71763aba66b809d76da669319069c6145bef0990 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Fri, 5 Dec 2025 16:09:33 -0300 Subject: [PATCH 10/10] fix: update statusText handling in listeners module for better reactivity --- apps/meteor/server/modules/listeners/listeners.module.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/meteor/server/modules/listeners/listeners.module.ts b/apps/meteor/server/modules/listeners/listeners.module.ts index a9bb67fef4826..55838445f9514 100644 --- a/apps/meteor/server/modules/listeners/listeners.module.ts +++ b/apps/meteor/server/modules/listeners/listeners.module.ts @@ -167,9 +167,11 @@ export class ListenersModule { id: _id, diff: { status, - ...(statusText ? { statusText } : { statusText: '' }), + ...(statusText && { statusText }), + }, + unset: { + ...(!statusText && { statusText: 1 }), }, - unset: {}, }); notifications.notifyLoggedInThisInstance('user-status', [_id, username, STATUS_MAP[status], statusText, name, roles]);