diff --git a/.changeset/rich-hounds-wave.md b/.changeset/rich-hounds-wave.md new file mode 100644 index 0000000000000..e14fc884624e2 --- /dev/null +++ b/.changeset/rich-hounds-wave.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixes error messages not showing in the UI when `Preview Public Channel` permission is not in the user role and an app is preventing the same user to join the room. diff --git a/apps/meteor/client/hooks/useJoinRoom.ts b/apps/meteor/client/hooks/useJoinRoom.ts index b4a16d21b5d92..7204d378ca36f 100644 --- a/apps/meteor/client/hooks/useJoinRoom.ts +++ b/apps/meteor/client/hooks/useJoinRoom.ts @@ -1,4 +1,5 @@ import type { IRoom } from '@rocket.chat/core-typings'; +import { useToastMessageDispatch } from '@rocket.chat/ui-contexts'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { sdk } from '../../app/utils/client/lib/SDKClient'; @@ -11,6 +12,7 @@ type UseJoinRoomMutationFunctionProps = { export const useJoinRoom = () => { const queryClient = useQueryClient(); + const dispatchToastMessage = useToastMessageDispatch(); return useMutation({ mutationFn: async ({ rid, reference, type }: UseJoinRoomMutationFunctionProps) => { @@ -23,5 +25,8 @@ export const useJoinRoom = () => { queryKey: ['rooms', data], }); }, + onError: (error: unknown) => { + dispatchToastMessage({ message: error, type: 'error' }); + }, }); }; diff --git a/apps/meteor/client/views/room/NotSubscribedRoom.tsx b/apps/meteor/client/views/room/NotSubscribedRoom.tsx index fa8ead0a10996..2cf97519a3018 100644 --- a/apps/meteor/client/views/room/NotSubscribedRoom.tsx +++ b/apps/meteor/client/views/room/NotSubscribedRoom.tsx @@ -19,7 +19,6 @@ const NotSubscribedRoom = ({ rid, reference, type }: NotSubscribedRoomProps): Re const { t } = useTranslation(); const handleJoinClick = useJoinRoom(); - // TODO: Handle onJoinClick error const { isMobile } = useLayout(); diff --git a/apps/meteor/client/views/room/composer/ComposerReadOnly.tsx b/apps/meteor/client/views/room/composer/ComposerReadOnly.tsx index 0a2f18d25d246..d2769ea1d21d6 100644 --- a/apps/meteor/client/views/room/composer/ComposerReadOnly.tsx +++ b/apps/meteor/client/views/room/composer/ComposerReadOnly.tsx @@ -1,11 +1,10 @@ import { Button } from '@rocket.chat/fuselage'; import { MessageFooterCallout, MessageFooterCalloutContent } from '@rocket.chat/ui-composer'; -import { useEndpoint } from '@rocket.chat/ui-contexts'; +import { useEndpoint, useToastMessageDispatch } from '@rocket.chat/ui-contexts'; import { useMutation } from '@tanstack/react-query'; import type { ReactElement } from 'react'; import { useTranslation } from 'react-i18next'; -import { dispatchToastMessage } from '../../../lib/toast'; import { useRoom, useUserIsSubscribed } from '../contexts/RoomContext'; const ComposerReadOnly = (): ReactElement => { @@ -14,6 +13,8 @@ const ComposerReadOnly = (): ReactElement => { const isSubscribed = useUserIsSubscribed(); const joinChannel = useEndpoint('POST', '/v1/channels.join'); + const dispatchToastMessage = useToastMessageDispatch(); + const join = useMutation({ mutationFn: () => joinChannel({ roomId: room._id }), diff --git a/apps/meteor/tests/e2e/fixtures/inject-initial-data.ts b/apps/meteor/tests/e2e/fixtures/inject-initial-data.ts index b37354a1c8caa..56a08bf120ef9 100644 --- a/apps/meteor/tests/e2e/fixtures/inject-initial-data.ts +++ b/apps/meteor/tests/e2e/fixtures/inject-initial-data.ts @@ -12,6 +12,7 @@ export default async function injectInitialData() { createUserFixture(Users.user2), createUserFixture(Users.user3), createUserFixture(Users.userE2EE), + createUserFixture(Users.userNotAllowedByApp), ]; await Promise.all( diff --git a/apps/meteor/tests/e2e/fixtures/userStates.ts b/apps/meteor/tests/e2e/fixtures/userStates.ts index 5b42551c65f8a..d2793dbf6a545 100644 --- a/apps/meteor/tests/e2e/fixtures/userStates.ts +++ b/apps/meteor/tests/e2e/fixtures/userStates.ts @@ -120,6 +120,7 @@ export const Users = { user1: generateContext('user1'), user2: generateContext('user2'), user3: generateContext('user3'), + userNotAllowedByApp: generateContext('userNotAllowedByApp'), userE2EE: generateContext('userE2EE'), samluser1: generateContext('samluser1'), samluser2: generateContext('samluser2'), diff --git a/apps/meteor/tests/e2e/page-objects/directory.ts b/apps/meteor/tests/e2e/page-objects/directory.ts new file mode 100644 index 0000000000000..8d2c6ca605fd9 --- /dev/null +++ b/apps/meteor/tests/e2e/page-objects/directory.ts @@ -0,0 +1,22 @@ +import type { Page } from '@playwright/test'; + +export class Directory { + public readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + async searchChannel(name: string) { + await this.page.getByRole('textbox', { name: 'Search' }).fill(name); + } + + getSearchByChannelName(name: string) { + return this.page.locator(`role=table >> role=link >> text="${name}"`); + } + + async openChannel(name: string) { + await this.searchChannel(name); + await this.getSearchByChannelName(name).click(); + } +} diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts index 9fae2918d0660..08806275ef42e 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts @@ -459,4 +459,8 @@ export class HomeContent { get btnClearSelection() { return this.page.getByRole('button', { name: 'Clear selection' }); } + + get btnJoinChannel() { + return this.page.getByRole('button', { name: 'Join channel' }); + } } 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 67d0869f721e9..60c039cbe9530 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts @@ -33,6 +33,10 @@ export class HomeSidenav { return this.page.locator('#modal-root [data-qa="create-direct-modal"] [data-qa-type="user-auto-complete-input"]'); } + get btnDirectory(): Locator { + return this.page.locator('role=button[name="Directory"]'); + } + get btnCreate(): Locator { return this.page.locator('role=button[name="Create"]'); } @@ -140,6 +144,10 @@ export class HomeSidenav { await this.page.locator(`role=menuitemcheckbox[name="${status}"]`).click(); } + async openDirectory(): Promise { + await this.btnDirectory.click(); + } + async openChat(name: string): Promise { await this.searchRoom(name); await this.getSearchItemByName(name).click(); diff --git a/apps/meteor/tests/e2e/preview-public-channel.spec.ts b/apps/meteor/tests/e2e/preview-public-channel.spec.ts new file mode 100644 index 0000000000000..fd24a2aae6202 --- /dev/null +++ b/apps/meteor/tests/e2e/preview-public-channel.spec.ts @@ -0,0 +1,86 @@ +import { IS_EE } from './config/constants'; +import { Users } from './fixtures/userStates'; +import { HomeChannel, Utils } from './page-objects'; +import { Directory } from './page-objects/directory'; +import { createTargetChannel, sendTargetChannelMessage } from './utils'; +import { test, expect } from './utils/test'; + +test.use({ storageState: Users.admin.state }); + +test.describe('Preview public channel', () => { + let poHomeChannel: HomeChannel; + let poDirectory: Directory; + let poUtils: Utils; + let targetChannel: string; + let targetChannelMessage: string; + + test.beforeEach(async ({ page }) => { + poHomeChannel = new HomeChannel(page); + poDirectory = new Directory(page); + poUtils = new Utils(page); + + await page.goto('/home'); + }); + + test.beforeAll(async ({ api }) => { + targetChannel = await createTargetChannel(api); + targetChannelMessage = await sendTargetChannelMessage(api, targetChannel, { msg: 'This message' }); + + await api.post('/permissions.update', { permissions: [{ _id: 'preview-c-room', roles: ['admin', 'user', 'anonymous'] }] }); + }); + + test.afterAll(async ({ api }) => { + await api.post('/channels.delete', { roomName: targetChannel }); + await api.post('/permissions.update', { permissions: [{ _id: 'preview-c-room', roles: ['admin', 'user', 'anonymous'] }] }); + }); + + test.describe('User', () => { + test.use({ storageState: Users.user1.state }); + + test('should let user preview public rooms messages', async () => { + await poHomeChannel.sidenav.openDirectory(); + await poDirectory.openChannel(targetChannel); + + await expect(poHomeChannel.content.lastUserMessageBody).toContainText(targetChannelMessage); + }); + + test('should not let user role preview public rooms', async ({ api }) => { + await api.post('/permissions.update', { permissions: [{ _id: 'preview-c-room', roles: ['admin'] }] }); + + await poHomeChannel.sidenav.openDirectory(); + await poDirectory.openChannel(targetChannel); + + await expect(poHomeChannel.content.btnJoinChannel).toBeVisible(); + await expect(poHomeChannel.content.lastUserMessageBody).not.toBeVisible(); + }); + }); + + test.describe('App', () => { + test.skip(!IS_EE, 'Premium Only'); + test.use({ storageState: Users.userNotAllowedByApp.state }); + + test('should prevent user from join the room', async ({ api }) => { + await api.post('/permissions.update', { permissions: [{ _id: 'preview-c-room', roles: ['admin', 'user', 'anonymous'] }] }); + await poHomeChannel.sidenav.openDirectory(); + await poDirectory.openChannel(targetChannel); + + await expect(poHomeChannel.content.lastUserMessageBody).toContainText(targetChannelMessage); + + await poHomeChannel.btnJoinRoom.click(); + + await expect(poUtils.getAlertByText('TEST OF NOT ALLOWED USER')).toBeVisible(); + }); + + test('should prevent user from join the room without preview permission', async ({ api }) => { + await api.post('/permissions.update', { permissions: [{ _id: 'preview-c-room', roles: ['admin'] }] }); + + await poHomeChannel.sidenav.openDirectory(); + await poDirectory.openChannel(targetChannel); + await expect(poHomeChannel.content.lastUserMessageBody).not.toBeVisible(); + + await poHomeChannel.content.btnJoinChannel.click(); + + await expect(poUtils.getAlertByText('TEST OF NOT ALLOWED USER')).toBeVisible(); + }); + }); +}); diff --git a/apps/meteor/tests/e2e/utils/create-target-channel.ts b/apps/meteor/tests/e2e/utils/create-target-channel.ts index 370f4dc2c8ec7..777bb99e226d5 100644 --- a/apps/meteor/tests/e2e/utils/create-target-channel.ts +++ b/apps/meteor/tests/e2e/utils/create-target-channel.ts @@ -1,5 +1,5 @@ import { faker } from '@faker-js/faker'; -import type { IRoom } from '@rocket.chat/core-typings'; +import type { IRoom, IMessage } from '@rocket.chat/core-typings'; import type { ChannelsCreateProps, GroupsCreateProps } from '@rocket.chat/rest-typings'; import type { BaseTest } from './test'; @@ -15,6 +15,24 @@ export async function createTargetChannel(api: BaseTest['api'], options?: Omit) { + const response = await api.get(`/channels.info?roomName=${roomName}`); + + const { + channel: { _id: rid }, + }: { channel: IRoom } = await response.json(); + + await api.post('/chat.sendMessage', { + message: { + rid, + msg: options?.msg || 'simple message', + ...options, + }, + }); + + return options?.msg || 'simple message'; +} + export async function deleteChannel(api: BaseTest['api'], roomName: string): Promise { await api.post('/channels.delete', { roomName }); }