diff --git a/apps/meteor/client/views/room/providers/ComposerPopupProvider.spec.tsx b/apps/meteor/client/views/room/providers/ComposerPopupProvider.spec.tsx new file mode 100644 index 0000000000000..a4fb0c7d40578 --- /dev/null +++ b/apps/meteor/client/views/room/providers/ComposerPopupProvider.spec.tsx @@ -0,0 +1,85 @@ +import { mockAppRoot } from '@rocket.chat/mock-providers'; +import { render, waitFor } from '@testing-library/react'; +import { useEffect } from 'react'; + +import ComposerPopupProvider from './ComposerPopupProvider'; +import { createFakeRoom } from '../../../../tests/mocks/data'; +import { useComposerPopupOptions } from '../contexts/ComposerPopupContext'; + +jest.mock('../../../../app/utils/client', () => ({ + slashCommands: { + commands: {}, + }, +})); + +type PopupOptionsConsumerProps = { + onReady: (options: ReturnType) => void; +}; + +const PopupOptionsConsumer = ({ onReady }: PopupOptionsConsumerProps) => { + const options = useComposerPopupOptions(); + + useEffect(() => { + onReady(options); + }, [onReady, options]); + + return null; +}; + +const renderProvider = async (permissions: string[] = []) => { + const room = createFakeRoom({ _id: 'permission-scoped-room', t: 'c' }); + + const appRoot = permissions.reduce((builder, permission) => builder.withPermission(permission), mockAppRoot().withRoom(room)).build(); + + let popupOptions: ReturnType | undefined; + + render( + + { + popupOptions = options; + }} + /> + , + { wrapper: appRoot }, + ); + + await waitFor(() => expect(popupOptions).toBeDefined()); + + const mentionPopup = popupOptions?.find(({ trigger }) => trigger === '@'); + expect(mentionPopup).toBeDefined(); + + const items = await mentionPopup?.getItemsFromLocal?.(''); + + return items?.map(({ _id }) => _id) ?? []; +}; + +describe('ComposerPopupProvider', () => { + it('does not show @all or @here in autocomplete when user does not have permissions', async () => { + const view = await renderProvider(); + + expect(view).not.toContain('all'); + expect(view).not.toContain('here'); + }); + + it('shows only @all when user has mention-all permission', async () => { + const view = await renderProvider(['mention-all']); + + expect(view).toContain('all'); + expect(view).not.toContain('here'); + }); + + it('shows only @here when user has mention-here permission', async () => { + const view = await renderProvider(['mention-here']); + + expect(view).toContain('here'); + expect(view).not.toContain('all'); + }); + + it('shows both @all and @here when user has both permissions', async () => { + const view = await renderProvider(['mention-all', 'mention-here']); + + expect(view).toContain('all'); + expect(view).toContain('here'); + }); +}); diff --git a/apps/meteor/client/views/room/providers/ComposerPopupProvider.tsx b/apps/meteor/client/views/room/providers/ComposerPopupProvider.tsx index afce0c7cd751a..e4d28c55b5574 100644 --- a/apps/meteor/client/views/room/providers/ComposerPopupProvider.tsx +++ b/apps/meteor/client/views/room/providers/ComposerPopupProvider.tsx @@ -3,7 +3,7 @@ import { isOmnichannelRoom } from '@rocket.chat/core-typings'; import { useLocalStorage } from '@rocket.chat/fuselage-hooks'; import { escapeRegExp } from '@rocket.chat/string-helpers'; import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts'; -import { useMethod, useSetting, useUserId, useUserPreference } from '@rocket.chat/ui-contexts'; +import { useAtLeastOnePermission, useMethod, useSetting, useUserId, useUserPreference } from '@rocket.chat/ui-contexts'; import { useQueryClient } from '@tanstack/react-query'; import { useMemo, useState } from 'react'; import type { ReactNode } from 'react'; @@ -84,6 +84,8 @@ const ComposerPopupProvider = ({ children, room }: ComposerPopupProviderProps) = const queryClient = useQueryClient(); const uid = useUserId(); const call = useMethod('getSlashCommandPreviews'); + const canMentionAll = useAtLeastOnePermission(['mention-all'], rid); + const canMentionHere = useAtLeastOnePermission(['mention-here'], rid); const value: ComposerPopupContextValue = useMemo(() => { return [ @@ -106,7 +108,7 @@ const ComposerPopupProvider = ({ children, room }: ComposerPopupProviderProps) = suggestion: true, })); - if (!filterRegex || filterRegex.test('all')) { + if (canMentionAll && (!filterRegex || filterRegex.test('all'))) { items.push({ _id: 'all', username: 'all', @@ -116,7 +118,7 @@ const ComposerPopupProvider = ({ children, room }: ComposerPopupProviderProps) = }); } - if (!filterRegex || filterRegex.test('here')) { + if (canMentionHere && (!filterRegex || filterRegex.test('here'))) { items.push({ _id: 'here', username: 'here', @@ -388,6 +390,8 @@ const ComposerPopupProvider = ({ children, room }: ComposerPopupProviderProps) = ].filter(Boolean); }, [ call, + canMentionAll, + canMentionHere, cannedResponseEnabled, encrypted, i18n,