diff --git a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.spec.tsx b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.spec.tsx new file mode 100644 index 0000000000000..c1104b47caece --- /dev/null +++ b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.spec.tsx @@ -0,0 +1,174 @@ +import type { IRoomWithRetentionPolicy } from '@rocket.chat/core-typings'; +import { mockAppRoot } from '@rocket.chat/mock-providers'; +import { render, screen, waitFor, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import EditRoomInfo from './EditRoomInfo'; +import { createFakeRoom } from '../../../../../../tests/mocks/data'; + +jest.mock('../../../../../lib/rooms/roomCoordinator', () => ({ + roomCoordinator: { + getRoomDirectives: () => ({ + allowRoomSettingChange: () => true, + }), + getRoomName: (_t: string, room: { name?: string }) => room.name ?? 'test-room', + }, +})); + +jest.mock('../../../../../components/avatar/RoomAvatarEditor', () => ({ + __esModule: true, + default: () => null, +})); + +const noop = () => undefined; + +const createRoom = (sysMes?: string[]) => + createFakeRoom({ + t: 'c', + name: 'test-room', + ...(sysMes && { sysMes: sysMes as IRoomWithRetentionPolicy['sysMes'] }), + }); + +const buildAppRoot = (saveFn: jest.Mock, room: IRoomWithRetentionPolicy) => + mockAppRoot() + .withJohnDoe() + .withPermission('edit-room') + .withEndpoint('POST', '/v1/rooms.saveRoomSettings', saveFn as never) + .withEndpoint('POST', '/v1/rooms.changeArchivationState', () => ({ success: true }) as never) + .withEndpoint('GET', '/v1/rooms.nameExists', () => ({ exists: false }) as never) + .withSetting('UTF8_Channel_Names_Validation', '[0-9a-zA-Z-_.]+') + .withSetting('UI_Allow_room_names_with_special_chars', true) + .withSetting('RetentionPolicy_Enabled', false) + .withTranslations('en', 'core', { + Save: 'Save', + Hide_System_Messages: 'Hide system messages', + Advanced_settings: 'Advanced settings', + Select_messages_to_hide: 'Select messages to hide', + }) + .withRoom(room) + .build(); + +const openAdvancedSettings = async () => { + await userEvent.click(await screen.findByRole('button', { name: /Advanced settings/i })); +}; + +const findHideSysMesToggle = () => screen.findByLabelText(/Hide system messages/i); + +const clickSave = async () => { + await userEvent.click(screen.getByRole('button', { name: 'Save' })); +}; + +const removeSystemMessageChip = async (translationKey: string) => { + const chip = screen.getByRole('button', { name: new RegExp(translationKey, 'i') }); + await userEvent.click(chip); +}; + +const selectSystemMessageOption = async (translationKey: string) => { + const trigger = screen.getByRole('button', { name: /Select messages to hide/i }); + await userEvent.click(trigger); + + const listboxes = await screen.findAllByRole('listbox', { hidden: true }); + const listbox = listboxes.find((el) => within(el).queryAllByRole('option', { hidden: true }).length > 0); + + if (!listbox) { + throw new Error('Could not find a listbox containing options'); + } + + const optionEl = within(listbox).getByText(translationKey).closest('li[role="option"]'); + + if (!optionEl) { + throw new Error(`Could not find option with text "${translationKey}"`); + } + + await userEvent.click(optionEl); +}; + +it('should send systemMessages: [] when hideSysMes is toggled OFF', async () => { + const saveFn = jest.fn((_data: Record) => ({ rid: 'room-id', success: true })); + const room = createRoom(['au', 'ru']); + + render(, { wrapper: buildAppRoot(saveFn, room) }); + await openAdvancedSettings(); + + const toggle = await findHideSysMesToggle(); + expect(toggle).toBeChecked(); + + await userEvent.click(toggle); + expect(toggle).not.toBeChecked(); + + await clickSave(); + await waitFor(() => expect(saveFn).toHaveBeenCalled()); + + expect(saveFn.mock.calls[0][0]).toEqual(expect.objectContaining({ systemMessages: [] })); +}); + +it('should send selected systemMessages when user selects message types to hide', async () => { + const saveFn = jest.fn((_data: Record) => ({ rid: 'room-id', success: true })); + const room = createRoom(); + + render(, { wrapper: buildAppRoot(saveFn, room) }); + await openAdvancedSettings(); + + const toggle = await findHideSysMesToggle(); + await userEvent.click(toggle); + expect(toggle).toBeChecked(); + + await selectSystemMessageOption('Message_HideType_au'); + + await clickSave(); + await waitFor(() => expect(saveFn).toHaveBeenCalled()); + + expect(saveFn.mock.calls[0][0]).toEqual(expect.objectContaining({ systemMessages: expect.arrayContaining(['au']) })); +}); + +it('should send existing systemMessages when toggle is turned ON without changing selection', async () => { + const saveFn = jest.fn((_data: Record) => ({ rid: 'room-id', success: true })); + const room = createRoom(); + + render(, { wrapper: buildAppRoot(saveFn, room) }); + await openAdvancedSettings(); + + const toggle = await findHideSysMesToggle(); + expect(toggle).not.toBeChecked(); + + await userEvent.click(toggle); + expect(toggle).toBeChecked(); + + await clickSave(); + await waitFor(() => expect(saveFn).toHaveBeenCalled()); + + expect(saveFn.mock.calls[0][0]).toEqual(expect.objectContaining({ systemMessages: [] })); +}); + +it('should send updated systemMessages when only the multi-select is changed', async () => { + const saveFn = jest.fn((_data: Record) => ({ rid: 'room-id', success: true })); + const room = createRoom(['ru']); + + render(, { wrapper: buildAppRoot(saveFn, room) }); + await openAdvancedSettings(); + + const toggle = await findHideSysMesToggle(); + expect(toggle).toBeChecked(); + + await removeSystemMessageChip('Message_HideType_ru'); + await selectSystemMessageOption('Message_HideType_au'); + + await clickSave(); + await waitFor(() => expect(saveFn).toHaveBeenCalled()); + + expect(saveFn.mock.calls[0][0]).toEqual(expect.objectContaining({ systemMessages: expect.arrayContaining(['au']) })); +}); + +it('should NOT send systemMessages when something else changed', async () => { + const saveFn = jest.fn((_data: Record) => ({ rid: 'room-id', success: true })); + const room = createRoom(['au']); + + render(, { wrapper: buildAppRoot(saveFn, room) }); + + await userEvent.type(await screen.findByRole('textbox', { name: 'Topic' }), 'new topic'); + + await clickSave(); + await waitFor(() => expect(saveFn).toHaveBeenCalled()); + + expect(saveFn.mock.calls[0][0]).not.toHaveProperty('systemMessages'); +}); diff --git a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx index e3f2d079b0fd2..ffa26af663e1d 100644 --- a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx +++ b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx @@ -170,10 +170,9 @@ const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) => rid: room._id, ...data, ...((data.joinCode || 'joinCodeRequired' in data) && { joinCode: joinCodeRequired ? data.joinCode : '' }), - ...(data.systemMessages && - !hideSysMes && { - systemMessages: data.systemMessages, - }), + ...((dirtyFields.hideSysMes || dirtyFields.systemMessages) && { + systemMessages: hideSysMes ? (data.systemMessages ?? defaultValues.systemMessages) : [], + }), retentionEnabled, retentionOverrideGlobal, ...(retentionEnabled &&