Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/seven-donuts-confess.md
Original file line number Diff line number Diff line change
@@ -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
5 changes: 4 additions & 1 deletion apps/meteor/app/api/server/v1/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand All @@ -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,
Expand Down
10 changes: 7 additions & 3 deletions apps/meteor/client/sidebar/header/EditStatusModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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>();
Expand Down Expand Up @@ -71,6 +72,7 @@ const EditStatusModal = ({ onClose, userStatus, userStatusText }: EditStatusModa

return (
<Modal
aria-labelledby={`${modalId}-title`}
wrapperFunction={(props: ComponentProps<typeof Box>) => (
<Box
is='form'
Expand All @@ -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}
Expand Down
6 changes: 5 additions & 1 deletion apps/meteor/client/sidebar/header/UserMenuHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/tests/e2e/e2e-encryption.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
14 changes: 11 additions & 3 deletions apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' });
}
Expand Down Expand Up @@ -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();
}

Expand Down
1 change: 1 addition & 0 deletions apps/meteor/tests/e2e/page-objects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
4 changes: 4 additions & 0 deletions apps/meteor/tests/e2e/page-objects/modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"]');
}
Expand Down
45 changes: 40 additions & 5 deletions apps/meteor/tests/e2e/presence.spec.ts
Original file line number Diff line number Diff line change
@@ -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');
});
Expand All @@ -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');
});
});
});

Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/tests/e2e/sidebar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/tests/e2e/user-required-password-change.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
Expand Down Expand Up @@ -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();
});
});
Expand Down
Loading