Skip to content

Commit e8b5bfb

Browse files
Merge branch 'develop' into chore/CORE-1415-changeset
2 parents 493c656 + d0be8ad commit e8b5bfb

File tree

64 files changed

+331
-269
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+331
-269
lines changed

apps/meteor/.storybook/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export default {
99
addons: [
1010
getAbsolutePath('@storybook/addon-essentials'),
1111
getAbsolutePath('@storybook/addon-interactions'),
12-
getAbsolutePath('@storybook/addon-webpack5-compiler-babel'),
12+
getAbsolutePath('@storybook/addon-webpack5-compiler-swc'),
1313
getAbsolutePath('@storybook/addon-styling-webpack'),
1414
getAbsolutePath('@storybook/addon-a11y'),
1515
],
Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,24 @@
1-
import { render, screen } from '@testing-library/react';
1+
import { composeStories } from '@storybook/react';
2+
import { render } from '@testing-library/react';
3+
import { axe } from 'jest-axe';
24

3-
import RetentionPolicyCallout from './RetentionPolicyCallout';
4-
import { createRenteionPolicySettingsMock as createMock } from '../../../tests/mocks/client/mockRetentionPolicySettings';
5-
import { createFakeRoom } from '../../../tests/mocks/data';
5+
import * as stories from './RetentionPolicyCallout.stories';
66

7-
jest.useFakeTimers();
7+
const testCases = Object.values(composeStories(stories)).map((Story) => [Story.storyName || 'Story', Story]);
88

9-
beforeEach(() => {
9+
test.each(testCases)(`renders %s without crashing`, async (_storyname, Story) => {
10+
jest.useFakeTimers();
1011
jest.setSystemTime(new Date(2024, 5, 1, 0, 0, 0));
12+
13+
const { baseElement } = render(<Story />);
14+
expect(baseElement).toMatchSnapshot();
1115
});
1216

13-
describe('RetentionPolicyCallout', () => {
14-
it('Should render callout if settings are valid', () => {
15-
const fakeRoom = createFakeRoom({ t: 'c' });
16-
render(<RetentionPolicyCallout room={fakeRoom} />, {
17-
wrapper: createMock({ appliesToChannels: true, TTLChannels: 60000 }),
18-
});
19-
expect(screen.getByRole('alert')).toHaveTextContent('a minute June 1, 2024 at 12:30 AM');
20-
});
17+
test.each(testCases)('%s should have no a11y violations', async (_storyname, Story) => {
18+
// We have to use real timers here because `jest-axe` is breaking otherwise
19+
jest.useRealTimers();
20+
const { container } = render(<Story />);
2121

22-
it('Should not render callout if settings are invalid', () => {
23-
const fakeRoom = createFakeRoom({ t: 'c' });
24-
render(<RetentionPolicyCallout room={fakeRoom} />, {
25-
wrapper: createMock({ appliesToChannels: true, TTLChannels: 60000, advancedPrecisionCron: '* * * 12 *', advancedPrecision: true }),
26-
});
27-
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
28-
});
22+
const results = await axe(container);
23+
expect(results).toHaveNoViolations();
2924
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { Meta, StoryFn } from '@storybook/react';
2+
3+
import RetentionPolicyCallout from './RetentionPolicyCallout';
4+
import { createRenteionPolicySettingsMock as createMock } from '../../../tests/mocks/client/mockRetentionPolicySettings';
5+
import { createFakeRoom } from '../../../tests/mocks/data';
6+
7+
export default {
8+
component: RetentionPolicyCallout,
9+
} satisfies Meta<typeof RetentionPolicyCallout>;
10+
11+
const fakeRoom = createFakeRoom();
12+
13+
const DefaultWrapper = createMock({ appliesToChannels: true, TTLChannels: 60000 });
14+
15+
export const Default: StoryFn<typeof RetentionPolicyCallout> = () => (
16+
<DefaultWrapper>
17+
<RetentionPolicyCallout room={fakeRoom} />
18+
</DefaultWrapper>
19+
);
20+
21+
const InvalidSettingsWrapper = createMock({
22+
appliesToChannels: true,
23+
TTLChannels: 60000,
24+
advancedPrecisionCron: '* * * 12 * *',
25+
advancedPrecision: true,
26+
});
27+
28+
export const InvalidSettings: StoryFn<typeof RetentionPolicyCallout> = () => (
29+
<InvalidSettingsWrapper>
30+
<RetentionPolicyCallout room={fakeRoom} />
31+
</InvalidSettingsWrapper>
32+
);
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
2+
3+
exports[`renders Default without crashing 1`] = `
4+
<body>
5+
<div>
6+
<section
7+
aria-live="polite"
8+
arial-label="Retention_policy_warning_callout"
9+
class="rcx-box rcx-box--full rcx-callout rcx-callout--warning"
10+
role="alert"
11+
>
12+
<i
13+
aria-hidden="true"
14+
class="rcx-box rcx-box--full rcx-icon--name-warning rcx-icon rcx-callout__icon rcx-css-4pvxx3"
15+
>
16+
17+
</i>
18+
<div
19+
class="rcx-box rcx-box--full rcx-callout__wrapper"
20+
>
21+
<div
22+
class="rcx-box rcx-box--full rcx-callout__wrapper-content"
23+
>
24+
<div
25+
class="rcx-box rcx-box--full rcx-callout__content"
26+
>
27+
<div>
28+
<p>
29+
a minute June 1, 2024 at 12:30 AM
30+
</p>
31+
</div>
32+
</div>
33+
</div>
34+
</div>
35+
</section>
36+
</div>
37+
</body>
38+
`;
39+
40+
exports[`renders InvalidSettings without crashing 1`] = `
41+
<body>
42+
<div>
43+
<section
44+
aria-live="polite"
45+
arial-label="Retention_policy_warning_callout"
46+
class="rcx-box rcx-box--full rcx-callout rcx-callout--warning"
47+
role="alert"
48+
>
49+
<i
50+
aria-hidden="true"
51+
class="rcx-box rcx-box--full rcx-icon--name-warning rcx-icon rcx-callout__icon rcx-css-4pvxx3"
52+
>
53+
54+
</i>
55+
<div
56+
class="rcx-box rcx-box--full rcx-callout__wrapper"
57+
>
58+
<div
59+
class="rcx-box rcx-box--full rcx-callout__wrapper-content"
60+
>
61+
<div
62+
class="rcx-box rcx-box--full rcx-callout__content"
63+
>
64+
<div>
65+
<p>
66+
a minute June 12, 2024 at 12:00 AM
67+
</p>
68+
</div>
69+
</div>
70+
</div>
71+
</div>
72+
</section>
73+
</div>
74+
</body>
75+
`;

apps/meteor/client/components/RoomAutoComplete/RoomAutoComplete.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ const RoomAutoComplete = ({ value, onChange, scope = 'regular', renderRoomIcon,
7171
renderSelected={({ selected: { value, label } }) => (
7272
<>
7373
<Box margin='none' mi={2}>
74-
<RoomAvatar size={AVATAR_SIZE} room={{ type: label?.type || 'c', _id: value, ...label }} />
74+
<RoomAvatar size={AVATAR_SIZE} room={{ ...label, type: label?.type || 'c', _id: value }} />
7575
</Box>
7676
<Box margin='none' mi={2}>
7777
{label?.name}

apps/meteor/client/components/RoomAutoCompleteMultiple/RoomAutoCompleteMultiple.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const RoomAutoCompleteMultiple = ({ value, onChange, ...props }: RoomAutoComplet
5252
multiple
5353
renderSelected={({ selected: { value, label }, onRemove, ...props }): ReactElement => (
5454
<Chip {...props} key={value} value={value} onClick={onRemove}>
55-
<RoomAvatar size='x20' room={{ type: label?.type || 'c', _id: value, ...label }} />
55+
<RoomAvatar size='x20' room={{ ...label, type: label?.type || 'c', _id: value }} />
5656
<Box is='span' margin='none' mis={4}>
5757
{label?.name}
5858
</Box>
@@ -63,7 +63,7 @@ const RoomAutoCompleteMultiple = ({ value, onChange, ...props }: RoomAutoComplet
6363
key={value}
6464
{...props}
6565
label={label.name}
66-
avatar={<RoomAvatar size='x20' room={{ type: label?.type || 'c', _id: value, ...label }} />}
66+
avatar={<RoomAvatar size='x20' room={{ ...label, type: label?.type || 'c', _id: value }} />}
6767
/>
6868
)}
6969
options={options}

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

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
1-
import { isDirectMessageRoom } from '@rocket.chat/core-typings';
1+
import { type RoomType, isDirectMessageRoom } from '@rocket.chat/core-typings';
22
import { AutoComplete, Box, Option, OptionAvatar, OptionContent, Chip } from '@rocket.chat/fuselage';
33
import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
44
import { escapeRegExp } from '@rocket.chat/string-helpers';
5-
import { RoomAvatar, UserAvatar } from '@rocket.chat/ui-avatar';
5+
import { RoomAvatar } from '@rocket.chat/ui-avatar';
66
import { useUser, useUserSubscriptions } from '@rocket.chat/ui-contexts';
7-
import type { ComponentProps } from 'react';
7+
import type { ComponentProps, ReactElement } from 'react';
88
import { memo, useMemo, useState } from 'react';
99

1010
import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
1111
import { Rooms } from '../../stores';
1212

13-
type UserAndRoomAutoCompleteMultipleProps = Omit<ComponentProps<typeof AutoComplete>, 'filter'> & { limit?: number };
13+
type UserAndRoomAutoCompleteMultipleProps = Omit<ComponentProps<typeof AutoComplete>, 'filter'> & {
14+
limit?: number;
15+
};
16+
17+
type OptionType = {
18+
value: string;
19+
label: {
20+
name: string | undefined;
21+
avatarETag: string | undefined;
22+
type: RoomType;
23+
};
24+
}[];
1425

1526
const UserAndRoomAutoCompleteMultiple = ({ value, onChange, limit, ...props }: UserAndRoomAutoCompleteMultipleProps) => {
1627
const user = useUser();
@@ -35,33 +46,31 @@ const UserAndRoomAutoCompleteMultiple = ({ value, onChange, limit, ...props }: U
3546
),
3647
);
3748

38-
const options = useMemo(() => {
39-
if (!user) {
40-
return [];
41-
}
42-
43-
return rooms.reduce<Exclude<UserAndRoomAutoCompleteMultipleProps['options'], undefined>>((acc, room) => {
44-
if (acc.length === limit) return acc;
49+
const options = useMemo(
50+
() =>
51+
rooms.reduce<OptionType>((acc, room) => {
52+
if (acc.length === limit) return acc;
4553

46-
if (isDirectMessageRoom(room) && (room.blocked || room.blocker)) {
47-
return acc;
48-
}
54+
if (isDirectMessageRoom(room) && (room.blocked || room.blocker)) {
55+
return acc;
56+
}
4957

50-
if (roomCoordinator.readOnly(Rooms.state.get(room.rid), user)) return acc;
58+
if (roomCoordinator.readOnly(Rooms.state.get(room.rid), user)) return acc;
5159

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+
return [
61+
...acc,
62+
{
63+
value: room.rid,
64+
label: {
65+
name: room.fname || room.name,
66+
avatarETag: room.avatarETag,
67+
type: room.t,
68+
},
6069
},
61-
},
62-
];
63-
}, []);
64-
}, [limit, rooms, user]);
70+
];
71+
}, []),
72+
[limit, rooms, user],
73+
);
6574

6675
return (
6776
<AutoComplete
@@ -71,13 +80,9 @@ const UserAndRoomAutoCompleteMultiple = ({ value, onChange, limit, ...props }: U
7180
filter={filter}
7281
setFilter={setFilter}
7382
multiple
74-
renderSelected={({ selected: { value, label }, onRemove, ...props }) => (
83+
renderSelected={({ selected: { value, label }, onRemove, ...props }): ReactElement => (
7584
<Chip {...props} height='x20' value={value} onClick={onRemove} mie={4}>
76-
{label.t === 'd' ? (
77-
<UserAvatar size='x20' username={value} />
78-
) : (
79-
<RoomAvatar size='x20' room={{ type: label?.type, _id: value, ...label }} />
80-
)}
85+
<RoomAvatar size='x20' room={{ ...label, _id: value }} />
8186
<Box is='span' margin='none' mis={4}>
8287
{label.name}
8388
</Box>
@@ -86,11 +91,7 @@ const UserAndRoomAutoCompleteMultiple = ({ value, onChange, limit, ...props }: U
8691
renderItem={({ value, label, ...props }) => (
8792
<Option key={value} {...props}>
8893
<OptionAvatar>
89-
{label.t === 'd' ? (
90-
<UserAvatar size='x20' username={value} />
91-
) : (
92-
<RoomAvatar size='x20' room={{ type: label?.type, _id: value, ...label }} />
93-
)}
94+
<RoomAvatar size='x20' room={{ ...label, _id: value }} />
9495
</OptionAvatar>
9596
<OptionContent>{label.name}</OptionContent>
9697
</Option>

apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultipleFederated.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { OptionType } from '@rocket.chat/fuselage';
21
import { MultiSelectFiltered } from '@rocket.chat/fuselage';
32
import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
43
import { useEndpoint } from '@rocket.chat/ui-contexts';
@@ -94,7 +93,7 @@ const UserAutoCompleteMultipleFederated = ({
9493
);
9594

9695
return (
97-
<OptionsContext.Provider value={{ options: options as unknown as OptionType[] }}>
96+
<OptionsContext.Provider value={{ options }}>
9897
<MultiSelectFiltered
9998
{...props}
10099
data-qa-type='user-auto-complete-input'

apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultipleOption.tsx

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
import type { IUser } from '@rocket.chat/core-typings';
21
import { Option, OptionDescription } from '@rocket.chat/fuselage';
32
import { UserAvatar } from '@rocket.chat/ui-avatar';
4-
import type { ReactElement } from 'react';
3+
4+
import type { UserLabel } from './UserAutoCompleteMultipleOptions';
55

66
type UserAutoCompleteMultipleOptionProps = {
7-
label: {
8-
_federated?: boolean;
9-
} & Pick<IUser, 'username' | 'name'>;
7+
label: UserLabel;
8+
value: string | number;
9+
selected?: boolean;
10+
focus?: boolean;
11+
role?: string;
1012
};
1113

12-
const UserAutoCompleteMultipleOption = ({ label, ...props }: UserAutoCompleteMultipleOptionProps): ReactElement => {
14+
const UserAutoCompleteMultipleOption = ({ label, ...props }: UserAutoCompleteMultipleOptionProps) => {
1315
const { name, username, _federated } = label;
1416

1517
return (
@@ -20,13 +22,10 @@ const UserAutoCompleteMultipleOption = ({ label, ...props }: UserAutoCompleteMul
2022
icon={_federated ? 'globe' : undefined}
2123
key={username}
2224
label={
23-
(
24-
<>
25-
{name || username} {!_federated && <OptionDescription>({username})</OptionDescription>}
26-
</>
27-
) as any
25+
<>
26+
{name || username} {!_federated && <OptionDescription>({username})</OptionDescription>}
27+
</>
2828
}
29-
children={undefined}
3029
/>
3130
);
3231
};

apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultipleOptions.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@ import UserAutoCompleteMultipleOption from './UserAutoCompleteMultipleOption';
99
// The select requires a forwarded ref component in the renderOptions property
1010
// but we also need to pass internal state to this renderer, as well as the props that also come from the Select.
1111

12+
export type UserLabel = {
13+
_federated?: boolean;
14+
username: string;
15+
name?: string;
16+
};
17+
1218
type OptionsContextValue = {
13-
options: OptionType[];
19+
options: OptionType<string, UserLabel>[];
1420
};
1521

1622
export const OptionsContext = createContext<OptionsContextValue>({

0 commit comments

Comments
 (0)