Skip to content

Commit 6815086

Browse files
Merge branch 'develop' into feat/improve-pdf-content
2 parents 388b710 + d7edd91 commit 6815086

File tree

4 files changed

+141
-39
lines changed

4 files changed

+141
-39
lines changed

.changeset/new-ways-roll.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@rocket.chat/meteor": patch
3+
---
4+
5+
fixes the selection of room and users performance when using forward message feature
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { useUser, useUserSubscriptions, useRoomAvatarPath } from '@rocket.chat/ui-contexts';
2+
import { render, screen, waitFor } from '@testing-library/react';
3+
import userEvent from '@testing-library/user-event';
4+
5+
import UserAndRoomAutoCompleteMultiple from './UserAndRoomAutoCompleteMultiple';
6+
import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
7+
8+
// Mock dependencies
9+
jest.mock('@rocket.chat/ui-contexts', () => ({
10+
useUser: jest.fn(),
11+
useUserSubscriptions: jest.fn(),
12+
useRoomAvatarPath: jest.fn(),
13+
}));
14+
jest.mock('../../lib/rooms/roomCoordinator', () => ({
15+
roomCoordinator: { readOnly: jest.fn() },
16+
}));
17+
18+
const mockUser = { _id: 'user1', username: 'testuser' };
19+
20+
const mockRooms = [
21+
{
22+
rid: 'room1',
23+
fname: 'General',
24+
name: 'general',
25+
t: 'c',
26+
avatarETag: 'etag1',
27+
},
28+
{
29+
rid: 'room2',
30+
fname: 'Direct',
31+
name: 'direct',
32+
t: 'd',
33+
avatarETag: 'etag2',
34+
blocked: false,
35+
blocker: false,
36+
},
37+
];
38+
39+
describe('UserAndRoomAutoCompleteMultiple', () => {
40+
beforeEach(() => {
41+
(useUser as jest.Mock).mockReturnValue(mockUser);
42+
(useUserSubscriptions as jest.Mock).mockReturnValue(mockRooms);
43+
(useRoomAvatarPath as jest.Mock).mockReturnValue((rid: string) => `/avatar/path/${rid}`);
44+
(roomCoordinator.readOnly as jest.Mock).mockReturnValue(false);
45+
});
46+
47+
it('should render options based on user subscriptions', async () => {
48+
render(<UserAndRoomAutoCompleteMultiple value={[]} onChange={jest.fn()} />);
49+
50+
const input = screen.getByRole('textbox');
51+
await userEvent.click(input);
52+
53+
await waitFor(() => {
54+
expect(screen.getByText('General')).toBeInTheDocument();
55+
});
56+
57+
await waitFor(() => {
58+
expect(screen.getByText('Direct')).toBeInTheDocument();
59+
});
60+
});
61+
62+
it('should filter out read-only rooms', async () => {
63+
(roomCoordinator.readOnly as jest.Mock).mockImplementation((rid) => rid === 'room1');
64+
render(<UserAndRoomAutoCompleteMultiple value={[]} onChange={jest.fn()} />);
65+
66+
const input = screen.getByRole('textbox');
67+
await userEvent.click(input);
68+
69+
await waitFor(() => {
70+
expect(screen.queryByText('General')).not.toBeInTheDocument();
71+
});
72+
await waitFor(() => {
73+
expect(screen.getByText('Direct')).toBeInTheDocument();
74+
});
75+
});
76+
77+
it('should call onChange when selecting an option', async () => {
78+
const handleChange = jest.fn();
79+
render(<UserAndRoomAutoCompleteMultiple value={[]} onChange={handleChange} />);
80+
81+
const input = screen.getByRole('textbox');
82+
await userEvent.click(input);
83+
84+
await waitFor(() => {
85+
expect(screen.getByText('General')).toBeInTheDocument();
86+
});
87+
88+
await userEvent.click(screen.getByText('General'));
89+
expect(handleChange).toHaveBeenCalled();
90+
});
91+
});

apps/meteor/client/components/UserAndRoomAutoCompleteMultiple/UserAndRoomAutoCompleteMultiple.tsx

Lines changed: 44 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,59 +4,64 @@ import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
44
import { escapeRegExp } from '@rocket.chat/string-helpers';
55
import { RoomAvatar, UserAvatar } from '@rocket.chat/ui-avatar';
66
import { useUser, useUserSubscriptions } from '@rocket.chat/ui-contexts';
7-
import type { ComponentProps, ReactElement } from 'react';
8-
import { memo, useCallback, useMemo, useState } from 'react';
7+
import type { ComponentProps } from 'react';
8+
import { memo, useMemo, useState } from 'react';
99

1010
import { Rooms } from '../../../app/models/client';
11-
import { useReactiveValue } from '../../hooks/useReactiveValue';
1211
import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
1312

14-
type UserAndRoomAutoCompleteMultipleProps = Omit<ComponentProps<typeof AutoComplete>, 'filter'>;
13+
type UserAndRoomAutoCompleteMultipleProps = Omit<ComponentProps<typeof AutoComplete>, 'filter'> & { limit?: number };
1514

16-
const UserAndRoomAutoCompleteMultiple = ({ value, onChange, ...props }: UserAndRoomAutoCompleteMultipleProps): ReactElement => {
15+
const UserAndRoomAutoCompleteMultiple = ({ value, onChange, limit, ...props }: UserAndRoomAutoCompleteMultipleProps) => {
1716
const user = useUser();
1817
const [filter, setFilter] = useState('');
1918
const debouncedFilter = useDebouncedValue(filter, 1000);
2019

21-
const subscriptions = useUserSubscriptions(
22-
useMemo(
23-
() => ({
24-
open: { $ne: false },
25-
$or: [
26-
{ lowerCaseFName: new RegExp(escapeRegExp(debouncedFilter), 'i') },
27-
{ lowerCaseName: new RegExp(escapeRegExp(debouncedFilter), 'i') },
28-
],
29-
}),
20+
const rooms = useUserSubscriptions(
21+
...useMemo<Parameters<typeof useUserSubscriptions>>(
22+
() => [
23+
{
24+
open: { $ne: false },
25+
$or: [
26+
{ lowerCaseFName: new RegExp(escapeRegExp(debouncedFilter), 'i') },
27+
{ lowerCaseName: new RegExp(escapeRegExp(debouncedFilter), 'i') },
28+
],
29+
},
30+
// We are using a higher limit here to take advantage of the amount that
31+
// will be filtered below into a smaller set respecting the limit prop.
32+
{ limit: 100 },
33+
],
3034
[debouncedFilter],
3135
),
3236
);
3337

34-
const rooms = useReactiveValue(
35-
useCallback(
36-
() =>
37-
subscriptions.filter((subscription) => {
38-
if (!user) {
39-
return;
40-
}
38+
const options = useMemo(() => {
39+
if (!user) {
40+
return [];
41+
}
4142

42-
if (isDirectMessageRoom(subscription) && (subscription.blocked || subscription.blocker)) {
43-
return;
44-
}
43+
return rooms.reduce<Exclude<UserAndRoomAutoCompleteMultipleProps['options'], undefined>>((acc, room) => {
44+
if (acc.length === limit) return acc;
4545

46-
return !roomCoordinator.readOnly(Rooms.state.get(subscription.rid), user);
47-
}),
48-
[subscriptions, user],
49-
),
50-
);
46+
if (isDirectMessageRoom(room) && (room.blocked || room.blocker)) {
47+
return acc;
48+
}
5149

52-
const options = useMemo(
53-
() =>
54-
rooms.map(({ rid, fname, name, avatarETag, t }) => ({
55-
value: rid,
56-
label: { name: fname || name, avatarETag, type: t },
57-
})),
58-
[rooms],
59-
);
50+
if (roomCoordinator.readOnly(Rooms.state.get(room.rid), user)) return acc;
51+
52+
return [
53+
...acc,
54+
{
55+
value: room.rid,
56+
label: {
57+
name: room.fname || room.name,
58+
avatarETag: room.avatarETag,
59+
type: room.t,
60+
},
61+
},
62+
];
63+
}, []);
64+
}, [limit, rooms, user]);
6065

6166
return (
6267
<AutoComplete
@@ -66,7 +71,7 @@ const UserAndRoomAutoCompleteMultiple = ({ value, onChange, ...props }: UserAndR
6671
filter={filter}
6772
setFilter={setFilter}
6873
multiple
69-
renderSelected={({ selected: { value, label }, onRemove, ...props }): ReactElement => (
74+
renderSelected={({ selected: { value, label }, onRemove, ...props }) => (
7075
<Chip {...props} height='x20' value={value} onClick={onRemove} mie={4}>
7176
{label.t === 'd' ? (
7277
<UserAvatar size='x20' username={value} />
@@ -78,7 +83,7 @@ const UserAndRoomAutoCompleteMultiple = ({ value, onChange, ...props }: UserAndR
7883
</Box>
7984
</Chip>
8085
)}
81-
renderItem={({ value, label, ...props }): ReactElement => (
86+
renderItem={({ value, label, ...props }) => (
8287
<Option key={value} {...props}>
8388
<OptionAvatar>
8489
{label.t === 'd' ? (

apps/meteor/client/views/room/modals/ForwardMessageModal/ForwardMessageModal.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ const ForwardMessageModal = ({ onClose, permalink, message }: ForwardMessageProp
110110
aria-describedby={`${usersAndRoomsField}-hint`}
111111
name={name}
112112
value={value}
113+
limit={25}
113114
onChange={onChange}
114115
/>
115116
)}

0 commit comments

Comments
 (0)