Skip to content

Commit 0f7e52b

Browse files
authored
regression: Room breaking if advanced cron timer is invalid (#32687)
1 parent 957cc80 commit 0f7e52b

8 files changed

+130
-51
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { render, screen } from '@testing-library/react';
2+
import React from 'react';
3+
import '@testing-library/jest-dom/extend-expect';
4+
5+
import { createRenteionPolicySettingsMock as createMock } from '../../../tests/mocks/client/mockRetentionPolicySettings';
6+
import { createFakeRoom } from '../../../tests/mocks/data';
7+
import { setDate } from '../../../tests/mocks/mockDate';
8+
import RetentionPolicyCallout from './RetentionPolicyCallout';
9+
10+
jest.useFakeTimers();
11+
12+
describe('RetentionPolicyCallout', () => {
13+
it('Should render callout if settings are valid', () => {
14+
setDate();
15+
const fakeRoom = createFakeRoom({ t: 'c' });
16+
render(<RetentionPolicyCallout room={fakeRoom} />, { wrapper: createMock({ appliesToChannels: true, TTLChannels: 60000 }) });
17+
expect(screen.getByRole('alert')).toHaveTextContent('a minute June 1, 2024, 12:30 AM');
18+
});
19+
20+
it('Should not render callout if settings are invalid', () => {
21+
setDate();
22+
const fakeRoom = createFakeRoom({ t: 'c' });
23+
render(<RetentionPolicyCallout room={fakeRoom} />, {
24+
wrapper: createMock({ appliesToChannels: true, TTLChannels: 60000, advancedPrecisionCron: '* * * 12 *', advancedPrecision: true }),
25+
});
26+
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
27+
});
28+
});

apps/meteor/client/components/InfoPanel/RetentionPolicyCallout.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useTranslation } from '@rocket.chat/ui-contexts';
44
import React from 'react';
55

66
import { usePruneWarningMessage } from '../../hooks/usePruneWarningMessage';
7+
import { withErrorBoundary } from '../withErrorBoundary';
78

89
const RetentionPolicyCallout = ({ room }: { room: IRoom }) => {
910
const message = usePruneWarningMessage(room);
@@ -18,4 +19,4 @@ const RetentionPolicyCallout = ({ room }: { room: IRoom }) => {
1819
);
1920
};
2021

21-
export default RetentionPolicyCallout;
22+
export default withErrorBoundary(RetentionPolicyCallout);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { ComponentType, ReactNode, ComponentProps } from 'react';
2+
import React from 'react';
3+
import { ErrorBoundary } from 'react-error-boundary';
4+
5+
function withErrorBoundary<T extends object>(Component: ComponentType<T>, fallback: ReactNode = null) {
6+
const WrappedComponent = function (props: ComponentProps<typeof Component>) {
7+
return (
8+
<ErrorBoundary fallback={<>{fallback}</>}>
9+
<Component {...props} />
10+
</ErrorBoundary>
11+
);
12+
};
13+
14+
WrappedComponent.displayName = `withErrorBoundary(${Component.displayName ?? Component.name ?? 'Component'})`;
15+
16+
return WrappedComponent;
17+
}
18+
19+
export { withErrorBoundary };

apps/meteor/client/hooks/usePruneWarningMessage.spec.ts

+2-49
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,11 @@
11
import type { IRoomWithRetentionPolicy } from '@rocket.chat/core-typings';
2-
import { mockAppRoot } from '@rocket.chat/mock-providers';
32
import { renderHook } from '@testing-library/react-hooks';
43

4+
import { createRenteionPolicySettingsMock as createMock } from '../../tests/mocks/client/mockRetentionPolicySettings';
55
import { createFakeRoom } from '../../tests/mocks/data';
6+
import { setDate } from '../../tests/mocks/mockDate';
67
import { usePruneWarningMessage } from './usePruneWarningMessage';
78

8-
const createMock = ({
9-
enabled = true,
10-
filesOnly = false,
11-
doNotPrunePinned = false,
12-
appliesToChannels = false,
13-
TTLChannels = 60000,
14-
appliesToGroups = false,
15-
TTLGroups = 60000,
16-
appliesToDMs = false,
17-
TTLDMs = 60000,
18-
precision = '0',
19-
advancedPrecision = false,
20-
advancedPrecisionCron = '*/30 * * * *',
21-
} = {}) => {
22-
return mockAppRoot()
23-
.withTranslations('en', 'core', {
24-
RetentionPolicy_RoomWarning_NextRunDate: '{{maxAge}} {{nextRunDate}}',
25-
RetentionPolicy_RoomWarning_Unpinned_NextRunDate: 'Unpinned {{maxAge}} {{nextRunDate}}',
26-
RetentionPolicy_RoomWarning_FilesOnly_NextRunDate: 'FilesOnly {{maxAge}} {{nextRunDate}}',
27-
RetentionPolicy_RoomWarning_UnpinnedFilesOnly_NextRunDate: 'UnpinnedFilesOnly {{maxAge}} {{nextRunDate}}',
28-
})
29-
.withSetting('RetentionPolicy_Enabled', enabled)
30-
.withSetting('RetentionPolicy_FilesOnly', filesOnly)
31-
.withSetting('RetentionPolicy_DoNotPrunePinned', doNotPrunePinned)
32-
.withSetting('RetentionPolicy_AppliesToChannels', appliesToChannels)
33-
.withSetting('RetentionPolicy_TTL_Channels', TTLChannels)
34-
.withSetting('RetentionPolicy_AppliesToGroups', appliesToGroups)
35-
.withSetting('RetentionPolicy_TTL_Groups', TTLGroups)
36-
.withSetting('RetentionPolicy_AppliesToDMs', appliesToDMs)
37-
.withSetting('RetentionPolicy_TTL_DMs', TTLDMs)
38-
.withSetting('RetentionPolicy_Precision', precision)
39-
.withSetting('RetentionPolicy_Advanced_Precision', advancedPrecision)
40-
.withSetting('RetentionPolicy_Advanced_Precision_Cron', advancedPrecisionCron)
41-
.build();
42-
};
43-
449
jest.useFakeTimers();
4510

4611
const getRetentionRoomProps = (props: Partial<IRoomWithRetentionPolicy['retention']> = {}) => {
@@ -57,18 +22,6 @@ const getRetentionRoomProps = (props: Partial<IRoomWithRetentionPolicy['retentio
5722
};
5823
};
5924

60-
const setDate = (minutes = 1, hours = 0, date = 1) => {
61-
// June 12, 2024, 12:00 AM
62-
const fakeDate = new Date();
63-
fakeDate.setFullYear(2024);
64-
fakeDate.setMonth(5);
65-
fakeDate.setDate(date);
66-
fakeDate.setHours(hours);
67-
fakeDate.setMinutes(minutes);
68-
fakeDate.setSeconds(0);
69-
jest.setSystemTime(fakeDate);
70-
};
71-
7225
describe('usePruneWarningMessage hook', () => {
7326
describe('Cron timer and precision', () => {
7427
it('Should update the message after the nextRunDate has passaed', async () => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { render, screen } from '@testing-library/react';
2+
import React from 'react';
3+
import '@testing-library/jest-dom/extend-expect';
4+
5+
import { createRenteionPolicySettingsMock as createMock } from '../../../../tests/mocks/client/mockRetentionPolicySettings';
6+
import { createFakeRoom } from '../../../../tests/mocks/data';
7+
import { setDate } from '../../../../tests/mocks/mockDate';
8+
import RetentionPolicyWarning from './RetentionPolicyWarning';
9+
10+
jest.useFakeTimers();
11+
12+
describe('RetentionPolicyWarning', () => {
13+
it('Should render callout if settings are valid', () => {
14+
setDate();
15+
const fakeRoom = createFakeRoom({ t: 'c' });
16+
render(<RetentionPolicyWarning room={fakeRoom} />, { wrapper: createMock({ appliesToChannels: true, TTLChannels: 60000 }) });
17+
expect(screen.getByRole('alert')).toHaveTextContent('a minute June 1, 2024, 12:30 AM');
18+
});
19+
20+
it('Should not render callout if settings are invalid', () => {
21+
setDate();
22+
const fakeRoom = createFakeRoom({ t: 'c' });
23+
render(<RetentionPolicyWarning room={fakeRoom} />, {
24+
wrapper: createMock({ appliesToChannels: true, TTLChannels: 60000, advancedPrecisionCron: '* * * 12 *', advancedPrecision: true }),
25+
});
26+
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
27+
});
28+
});

apps/meteor/client/views/room/body/RetentionPolicyWarning.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useTranslation } from '@rocket.chat/ui-contexts';
44
import type { ReactElement } from 'react';
55
import React from 'react';
66

7+
import { withErrorBoundary } from '../../../components/withErrorBoundary';
78
import { usePruneWarningMessage } from '../../../hooks/usePruneWarningMessage';
89

910
const RetentionPolicyWarning = ({ room }: { room: IRoom }): ReactElement => {
@@ -23,4 +24,4 @@ const RetentionPolicyWarning = ({ room }: { room: IRoom }): ReactElement => {
2324
);
2425
};
2526

26-
export default RetentionPolicyWarning;
27+
export default withErrorBoundary(RetentionPolicyWarning);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { mockAppRoot } from '@rocket.chat/mock-providers';
2+
3+
export const createRenteionPolicySettingsMock = ({
4+
enabled = true,
5+
filesOnly = false,
6+
doNotPrunePinned = false,
7+
appliesToChannels = false,
8+
TTLChannels = 60000,
9+
appliesToGroups = false,
10+
TTLGroups = 60000,
11+
appliesToDMs = false,
12+
TTLDMs = 60000,
13+
precision = '0',
14+
advancedPrecision = false,
15+
advancedPrecisionCron = '*/30 * * * *',
16+
} = {}) => {
17+
return mockAppRoot()
18+
.withTranslations('en', 'core', {
19+
RetentionPolicy_RoomWarning_NextRunDate: '{{maxAge}} {{nextRunDate}}',
20+
RetentionPolicy_RoomWarning_Unpinned_NextRunDate: 'Unpinned {{maxAge}} {{nextRunDate}}',
21+
RetentionPolicy_RoomWarning_FilesOnly_NextRunDate: 'FilesOnly {{maxAge}} {{nextRunDate}}',
22+
RetentionPolicy_RoomWarning_UnpinnedFilesOnly_NextRunDate: 'UnpinnedFilesOnly {{maxAge}} {{nextRunDate}}',
23+
})
24+
.withSetting('RetentionPolicy_Enabled', enabled)
25+
.withSetting('RetentionPolicy_FilesOnly', filesOnly)
26+
.withSetting('RetentionPolicy_DoNotPrunePinned', doNotPrunePinned)
27+
.withSetting('RetentionPolicy_AppliesToChannels', appliesToChannels)
28+
.withSetting('RetentionPolicy_TTL_Channels', TTLChannels)
29+
.withSetting('RetentionPolicy_AppliesToGroups', appliesToGroups)
30+
.withSetting('RetentionPolicy_TTL_Groups', TTLGroups)
31+
.withSetting('RetentionPolicy_AppliesToDMs', appliesToDMs)
32+
.withSetting('RetentionPolicy_TTL_DMs', TTLDMs)
33+
.withSetting('RetentionPolicy_Precision', precision)
34+
.withSetting('RetentionPolicy_Advanced_Precision', advancedPrecision)
35+
.withSetting('RetentionPolicy_Advanced_Precision_Cron', advancedPrecisionCron)
36+
.build();
37+
};

apps/meteor/tests/mocks/mockDate.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// you must use jest.useFakeTimers for this to work.
2+
export const setDate = (minutes = 1, hours = 0, date = 1) => {
3+
// June 12, 2024, 12:00 AM
4+
const fakeDate = new Date();
5+
fakeDate.setFullYear(2024);
6+
fakeDate.setMonth(5);
7+
fakeDate.setDate(date);
8+
fakeDate.setHours(hours);
9+
fakeDate.setMinutes(minutes);
10+
fakeDate.setSeconds(0);
11+
jest.setSystemTime(fakeDate);
12+
};

0 commit comments

Comments
 (0)