diff --git a/.changeset/seven-donuts-confess.md b/.changeset/seven-donuts-confess.md new file mode 100644 index 00000000000..7926c2b699c --- /dev/null +++ b/.changeset/seven-donuts-confess.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes an issue where user custom status text is being overwritten, causing it not being updated in real time diff --git a/apps/meteor/app/api/server/v1/users.ts b/apps/meteor/app/api/server/v1/users.ts index c84c5a361d3..913efc63267 100644 --- a/apps/meteor/app/api/server/v1/users.ts +++ b/apps/meteor/app/api/server/v1/users.ts @@ -1317,10 +1317,14 @@ API.v1.addRoute( return API.v1.forbidden(); } + 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) if (this.bodyParams.message || this.bodyParams.message === '') { await setStatusText(user._id, this.bodyParams.message); + statusText = this.bodyParams.message; } if (this.bodyParams.status) { const validStatus = ['online', 'away', 'offline', 'busy']; @@ -1343,7 +1347,6 @@ API.v1.addRoute( }, ); - const { _id, username, statusText, roles, name } = user; void api.broadcast('presence.status', { user: { status, _id, username, statusText, roles, name }, previousStatus: user.status, diff --git a/apps/meteor/client/sidebar/header/EditStatusModal.tsx b/apps/meteor/client/sidebar/header/EditStatusModal.tsx index 04b678e1606..d4ec342c8f5 100644 --- a/apps/meteor/client/sidebar/header/EditStatusModal.tsx +++ b/apps/meteor/client/sidebar/header/EditStatusModal.tsx @@ -21,7 +21,7 @@ import { import { useLocalStorage, useEffectEvent } from '@rocket.chat/fuselage-hooks'; import { useToastMessageDispatch, useSetting, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts'; import type { ReactElement, ChangeEvent, ComponentProps, FormEvent } from 'react'; -import { useState, useCallback } from 'react'; +import { useState, useCallback, useId } from 'react'; import UserStatusMenu from '../../components/UserStatusMenu'; import { USER_STATUS_TEXT_MAX_LENGTH } from '../../lib/constants'; @@ -39,6 +39,7 @@ const EditStatusModal = ({ onClose, userStatus, userStatusText }: EditStatusModa const initialStatusText = customStatus || userStatusText; const t = useTranslation(); + const modalId = useId(); const [statusText, setStatusText] = useState(initialStatusText); const [statusType, setStatusType] = useState(userStatus); const [statusTextError, setStatusTextError] = useState(); @@ -71,6 +72,7 @@ const EditStatusModal = ({ onClose, userStatus, userStatusText }: EditStatusModa return ( ) => ( - {t('Edit_Status')} + {t('Edit_Status')} - {t('StatusMessage')} + {t('StatusMessage')} { - + {displayName} diff --git a/apps/meteor/tests/e2e/e2e-encryption.spec.ts b/apps/meteor/tests/e2e/e2e-encryption.spec.ts index 6e8b1cce989..cd356bc4296 100644 --- a/apps/meteor/tests/e2e/e2e-encryption.spec.ts +++ b/apps/meteor/tests/e2e/e2e-encryption.spec.ts @@ -1056,7 +1056,7 @@ test.describe.serial('e2ee room setup', () => { await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('hello world'); await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible(); - await poHomeChannel.sidenav.userProfileMenu.click(); + await poHomeChannel.sidenav.btnUserProfileMenu.click(); await poHomeChannel.sidenav.accountProfileOption.click(); await page.locator('role=navigation >> a:has-text("Security")').click(); 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 b278f8473d5..ba46c281085 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts @@ -46,10 +46,18 @@ export class HomeSidenav { return this.page.locator('role=search >> role=searchbox').first(); } - get userProfileMenu(): Locator { + get btnUserProfileMenu(): Locator { return this.page.getByRole('button', { name: 'User menu', exact: true }); } + get userProfileMenu(): Locator { + return this.page.getByRole('menu', { name: 'User menu' }); + } + + getUserProfileMenuOption(name: string): Locator { + return this.userProfileMenu.getByRole('menuitemcheckbox', { name }); + } + get sidebarChannelsList(): Locator { return this.page.getByRole('list', { name: 'Channels' }); } @@ -152,12 +160,12 @@ export class HomeSidenav { } async logout(): Promise { - await this.userProfileMenu.click(); + await this.btnUserProfileMenu.click(); await this.page.locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Logout")]').click(); } async switchStatus(status: 'offline' | 'online'): Promise { - await this.userProfileMenu.click(); + await this.btnUserProfileMenu.click(); await this.page.locator(`role=menuitemcheckbox[name="${status}"]`).click(); } diff --git a/apps/meteor/tests/e2e/page-objects/index.ts b/apps/meteor/tests/e2e/page-objects/index.ts index dfcd5434675..e503d367e87 100644 --- a/apps/meteor/tests/e2e/page-objects/index.ts +++ b/apps/meteor/tests/e2e/page-objects/index.ts @@ -19,4 +19,5 @@ export * from './omnichannel-settings'; export * from './omnichannel-business-hours'; export * from './omnichannel-tags'; export * from './utils'; +export * from './modal'; export * from './marketplace'; diff --git a/apps/meteor/tests/e2e/page-objects/modal.ts b/apps/meteor/tests/e2e/page-objects/modal.ts index c8a66d71fa1..8eaa2a42911 100644 --- a/apps/meteor/tests/e2e/page-objects/modal.ts +++ b/apps/meteor/tests/e2e/page-objects/modal.ts @@ -7,6 +7,10 @@ export class Modal { this.page = page; } + getModalByName(name: string): Locator { + return this.page.getByRole('dialog', { name }); + } + get textInput(): Locator { return this.page.locator('[name="modal_input"]'); } diff --git a/apps/meteor/tests/e2e/presence.spec.ts b/apps/meteor/tests/e2e/presence.spec.ts index 4f5dfe7a6db..61a8efb7714 100644 --- a/apps/meteor/tests/e2e/presence.spec.ts +++ b/apps/meteor/tests/e2e/presence.spec.ts @@ -1,14 +1,17 @@ import { DEFAULT_USER_CREDENTIALS, IS_EE } from './config/constants'; import { Users } from './fixtures/userStates'; -import { Registration } from './page-objects'; +import { Registration, HomeChannel } from './page-objects'; +import { Modal } from './page-objects/modal'; import { setSettingValueById } from './utils/setSettingValueById'; import { test, expect } from './utils/test'; test.describe.serial('Presence', () => { let poRegistration: Registration; + let poHomeChannel: HomeChannel; test.beforeEach(async ({ page }) => { poRegistration = new Registration(page); + poHomeChannel = new HomeChannel(page); await page.goto('/home'); }); @@ -22,12 +25,44 @@ test.describe.serial('Presence', () => { }); test.describe('Login using default settings', () => { - test('expect user to be online after log in', async ({ page }) => { - await poRegistration.username.type('user1'); - await poRegistration.inputPassword.type(DEFAULT_USER_CREDENTIALS.password); + test('should user be online after log in', async () => { + await poRegistration.username.fill('user1'); + await poRegistration.inputPassword.fill(DEFAULT_USER_CREDENTIALS.password); await poRegistration.btnLogin.click(); - await expect(page.getByRole('button', { name: 'User menu' }).locator('.rcx-status-bullet--online')).toBeVisible(); + await expect(poHomeChannel.sidenav.btnUserProfileMenu).toBeVisible(); + }); + }); + + test.describe('Custom status', () => { + 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 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 Modal(user1Page); + + await user1Channel.sidenav.btnUserProfileMenu.click(); + await user1Channel.sidenav.getUserProfileMenuOption('Custom Status').click(); + await user1Modal.getModalByName('Edit Status').getByRole('textbox', { name: 'Status message' }).fill('new status'); + await user1Modal.getModalByName('Edit Status').getByRole('button', { name: 'Save' }).click(); + + 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'); + }); }); }); diff --git a/apps/meteor/tests/e2e/sidebar.spec.ts b/apps/meteor/tests/e2e/sidebar.spec.ts index b45f2caa7f8..f8b273d4f15 100644 --- a/apps/meteor/tests/e2e/sidebar.spec.ts +++ b/apps/meteor/tests/e2e/sidebar.spec.ts @@ -23,7 +23,7 @@ test.describe.serial('sidebar', () => { }); test('should navigate on sidebar toolbar using arrow keys', async ({ page }) => { - await poHomeChannel.sidenav.userProfileMenu.focus(); + await poHomeChannel.sidenav.btnUserProfileMenu.focus(); await page.keyboard.press('Tab'); await page.keyboard.press('ArrowRight'); diff --git a/apps/meteor/tests/e2e/user-required-password-change.spec.ts b/apps/meteor/tests/e2e/user-required-password-change.spec.ts index 0135d21d67d..539a558dcc6 100644 --- a/apps/meteor/tests/e2e/user-required-password-change.spec.ts +++ b/apps/meteor/tests/e2e/user-required-password-change.spec.ts @@ -68,7 +68,7 @@ test.describe('User - Password change required', () => { }); await test.step('verify user is properly logged in', async () => { - await expect(poSidenav.userProfileMenu).toBeVisible(); + await expect(poSidenav.btnUserProfileMenu).toBeVisible(); await expect(poSidenav.sidebarChannelsList).toBeVisible(); }); }); @@ -159,7 +159,7 @@ test.describe('User - Password change not required', () => { await test.step('verify user is properly logged in', async () => { await page.waitForURL('/home'); - await expect(poSidenav.userProfileMenu).toBeVisible(); + await expect(poSidenav.btnUserProfileMenu).toBeVisible(); await expect(poSidenav.sidebarChannelsList).toBeVisible(); }); });