diff --git a/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultipleFederated.tsx b/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultipleFederated.tsx index 677f4dffda2e..24380550ded3 100644 --- a/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultipleFederated.tsx +++ b/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultipleFederated.tsx @@ -1,9 +1,8 @@ import { MultiSelectFiltered, Icon, Box, Chip } from '@rocket.chat/fuselage'; -import type { Options } from '@rocket.chat/fuselage'; import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; import { useEndpoint } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; -import React, { memo, ReactElement, useState, ComponentProps } from 'react'; +import React, { memo, ReactElement, useState, useCallback, useMemo } from 'react'; import UserAvatar from '../avatar/UserAvatar'; import AutocompleteOptions, { OptionsContext } from './UserAutoCompleteMultipleOptions'; @@ -55,33 +54,58 @@ const UserAutoCompleteMultipleFederated = ({ { keepPreviousData: true }, ); - const options = data || []; + const options = useMemo(() => data || [], [data]); - const onAddSelected: ComponentProps['onSelect'] = ([value]) => { - setFilter(''); - const cachedOption = options.find(([curVal]) => curVal === value)?.[1]; - if (!cachedOption) { - throw new Error('UserAutoCompleteMultiple - onAddSelected - failed to cache option'); - } - setSelectedCache({ ...selectedCache, [value]: cachedOption }); - }; + const onAddUser = useCallback( + (username: string): void => { + const user = options.find(([val]) => val === username)?.[1]; + if (!user) { + throw new Error('UserAutoCompleteMultiple - onAddSelected - failed to cache option'); + } + setSelectedCache((selectedCache) => ({ ...selectedCache, [username]: user })); + }, + [setSelectedCache, options], + ); + + const onRemoveUser = useCallback( + (username: string): void => + setSelectedCache((selectedCache) => { + const users = { ...selectedCache }; + delete users[username]; + return users; + }), + [setSelectedCache], + ); + + const handleOnChange = useCallback( + (usernames: string[]) => { + onChange(usernames); + const newAddedUsername = usernames.filter((username) => !value.includes(username))[0]; + const removedUsername = value.filter((username) => !usernames.includes(username))[0]; + setFilter(''); + newAddedUsername && onAddUser(newAddedUsername); + removedUsername && onRemoveUser(removedUsername); + }, + [onChange, setFilter, onAddUser, onRemoveUser, value], + ); return ( - + void }): ReactElement => { - const currentCachedOption = selectedCache[value]; + const currentCachedOption = selectedCache[value] || {}; return ( {currentCachedOption._federated ? : } - {currentCachedOption.name || currentCachedOption.username} + {currentCachedOption.name || currentCachedOption.username || value} ); diff --git a/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultipleOptions.tsx b/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultipleOptions.tsx index 79b92c3bf31a..69a25a5381e0 100644 --- a/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultipleOptions.tsx +++ b/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultipleOptions.tsx @@ -12,27 +12,22 @@ type Options = Array<[UserAutoCompleteOptionType['username'], UserAutoCompleteOp type OptionsContextValue = { options: ComponentProps['options']; - onSelect: ComponentProps['onSelect']; }; export const OptionsContext = createContext({ options: [], - onSelect: () => undefined, }); const UserAutoCompleteMultipleOptions = forwardRef(function UserAutoCompleteMultipleOptions( - { onSelect: _onSelect, ...props }: ComponentProps, + { onSelect, ...props }: ComponentProps, ref: Ref, ): ReactElement { - const { options, onSelect } = useContext(OptionsContext); + const { options } = useContext(OptionsContext); return ( { - onSelect(val); - _onSelect(val); - }} + onSelect={onSelect} ref={ref} renderItem={UserAutoCompleteMultipleOption} /> diff --git a/apps/meteor/client/sidebar/header/CreateDirectMessage.tsx b/apps/meteor/client/sidebar/header/CreateDirectMessage.tsx index 00ed4895690f..20babcb7ce7d 100644 --- a/apps/meteor/client/sidebar/header/CreateDirectMessage.tsx +++ b/apps/meteor/client/sidebar/header/CreateDirectMessage.tsx @@ -34,7 +34,7 @@ const CreateDirectMessage: FC = ({ onClose }) => { }); return ( - + {t('Direct_Messages')} diff --git a/apps/meteor/tests/e2e/create-channel.spec.ts b/apps/meteor/tests/e2e/create-channel.spec.ts index e7b88e98f644..349f71b01ad3 100644 --- a/apps/meteor/tests/e2e/create-channel.spec.ts +++ b/apps/meteor/tests/e2e/create-channel.spec.ts @@ -20,7 +20,7 @@ test.describe.serial('channel-management', () => { await poHomeChannel.sidenav.openNewByLabel('Channel'); await poHomeChannel.sidenav.checkboxPrivateChannel.click(); await poHomeChannel.sidenav.inputChannelName.type(channelName); - await poHomeChannel.sidenav.btnCreateChannel.click(); + await poHomeChannel.sidenav.btnCreate.click(); await expect(page).toHaveURL(`/channel/${channelName}`); }); @@ -30,7 +30,7 @@ test.describe.serial('channel-management', () => { await poHomeChannel.sidenav.openNewByLabel('Channel'); await poHomeChannel.sidenav.inputChannelName.type(channelName); - await poHomeChannel.sidenav.btnCreateChannel.click(); + await poHomeChannel.sidenav.btnCreate.click(); await expect(page).toHaveURL(`/group/${channelName}`); }); diff --git a/apps/meteor/tests/e2e/create-direct.spec.ts b/apps/meteor/tests/e2e/create-direct.spec.ts new file mode 100644 index 000000000000..e36894f64759 --- /dev/null +++ b/apps/meteor/tests/e2e/create-direct.spec.ts @@ -0,0 +1,26 @@ +import { test, expect } from './utils/test'; +import { HomeChannel } from './page-objects'; + +test.use({ storageState: 'admin-session.json' }); + +test.describe.serial('channel-direct-message', () => { + let poHomeChannel: HomeChannel; + + test.beforeEach(async ({ page }) => { + poHomeChannel = new HomeChannel(page); + + await page.goto('/home'); + }); + + test('expect create a direct room', async ({ page }) => { + await poHomeChannel.sidenav.openNewByLabel('Direct Messages'); + + await poHomeChannel.sidenav.inputDirectUsername.click(); + await page.keyboard.type('rocket.cat'); + await page.waitForTimeout(200); + await page.keyboard.press('Enter'); + await poHomeChannel.sidenav.btnCreate.click(); + + await expect(page).toHaveURL('direct/rocket.catrocketchat.internal.admin.test'); + }); +}); 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 0722895e6224..8876f16a822f 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts @@ -17,7 +17,11 @@ export class HomeSidenav { return this.page.locator('#modal-root [data-qa="create-channel-modal"] [data-qa-type="channel-name-input"]'); } - get btnCreateChannel(): Locator { + get inputDirectUsername(): Locator { + return this.page.locator('#modal-root [data-qa="create-direct-modal"] [data-qa-type="user-auto-complete-input"]'); + } + + get btnCreate(): Locator { return this.page.locator('//*[@id="modal-root"]//button[contains(text(), "Create")]'); } @@ -56,6 +60,6 @@ export class HomeSidenav { await this.openNewByLabel('Channel'); await this.checkboxPrivateChannel.click(); await this.inputChannelName.type(name); - await this.btnCreateChannel.click(); + await this.btnCreate.click(); } }