fix: User custom status text being overwritten (#36601)

Co-authored-by: Diego Sampaio <chinello@gmail.com>
This commit is contained in:
Douglas Fabris 2025-08-04 13:18:59 -03:00 committed by GitHub
parent fbf6c08c3c
commit a27efe9da9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 81 additions and 17 deletions

View File

@ -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

View File

@ -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,

View File

@ -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<string | undefined>();
@ -71,6 +72,7 @@ const EditStatusModal = ({ onClose, userStatus, userStatusText }: EditStatusModa
return (
<Modal
aria-labelledby={`${modalId}-title`}
wrapperFunction={(props: ComponentProps<typeof Box>) => (
<Box
is='form'
@ -84,15 +86,17 @@ const EditStatusModal = ({ onClose, userStatus, userStatusText }: EditStatusModa
>
<ModalHeader>
<ModalIcon name='info' />
<ModalTitle>{t('Edit_Status')}</ModalTitle>
<ModalTitle id={`${modalId}-title`}>{t('Edit_Status')}</ModalTitle>
<ModalClose onClick={onClose} />
</ModalHeader>
<ModalContent fontScale='p2'>
<FieldGroup>
<Field>
<FieldLabel>{t('StatusMessage')}</FieldLabel>
<FieldLabel htmlFor={`${modalId}-status-message`}>{t('StatusMessage')}</FieldLabel>
<FieldRow>
<TextInput
id={`${modalId}-status-message`}
aria-label={t('StatusMessage')}
error={statusTextError}
disabled={!allowUserStatusMessageChange}
flexGrow={1}

View File

@ -21,7 +21,11 @@ const UserMenuHeader = ({ user }: { user: IUser }) => {
<Box mis={4} display='flex' overflow='hidden' flexDirection='column' fontScale='p2' mb='neg-x4' flexGrow={1} flexShrink={1}>
<Box withTruncatedText w='full' display='flex' alignItems='center' flexDirection='row'>
<Margins inline={4}>
<UserStatus status={presenceDisabled ? 'disabled' : user.status} />
<UserStatus
role='status'
aria-label={presenceDisabled ? t('user_status_disabled') : t(user?.status ?? 'offline')}
status={presenceDisabled ? 'disabled' : user.status}
/>
<Box is='span' withTruncatedText display='inline-block' fontWeight='700'>
{displayName}
</Box>

View File

@ -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();

View File

@ -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<void> {
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<void> {
await this.userProfileMenu.click();
await this.btnUserProfileMenu.click();
await this.page.locator(`role=menuitemcheckbox[name="${status}"]`).click();
}

View File

@ -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';

View File

@ -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"]');
}

View File

@ -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');
});
});
});

View File

@ -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');

View File

@ -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();
});
});