Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,18 @@ const MessageToolbarActionMenu = ({ message, context, room, subscription, onChan
usePinMessageAction(message, { room, subscription }),
useStarMessageAction(message, { room }),
useUnstarMessageAction(message, { room }),
usePermalinkAction(message, { id: 'permalink-star', context: ['starred'], order: 10 }),
usePermalinkAction(message, { id: 'permalink-pinned', context: ['pinned'], order: 5 }),
usePermalinkAction(message, {
id: 'permalink',
context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'],
type: 'duplication',
order: 5,
}),
usePermalinkAction(message, { id: 'permalink-star', context: ['starred'], order: 10 }, { room }),
usePermalinkAction(message, { id: 'permalink-pinned', context: ['pinned'], order: 5 }, { room }),
usePermalinkAction(
message,
{
id: 'permalink',
context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'],
type: 'duplication',
order: 5,
},
{ room },
),
useFollowMessageAction(message, { room, context }),
useUnFollowMessageAction(message, { room, context }),
useMarkAsUnreadMessageAction(message, { room, subscription }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const DefaultItems = ({ message, room, subscription }: DefaultItemsProps) => {
<ReactionMessageAction message={message} room={room} subscription={subscription} />
<QuoteMessageAction message={message} subscription={subscription} />
<ReplyInThreadMessageAction message={message} room={room} subscription={subscription} />
<ForwardMessageAction message={message} />
<ForwardMessageAction message={message} room={room} />
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const MobileItems = ({ message, room, subscription }: MobileItemsProps) => {
<ReactionMessageAction message={message} room={room} subscription={subscription} />
<QuoteMessageAction message={message} subscription={subscription} />
<ReplyInThreadMessageAction message={message} room={room} subscription={subscription} />
<ForwardMessageAction message={message} />
<ForwardMessageAction message={message} room={room} />
<JumpToMessageAction id='jump-to-message' message={message} />
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const ThreadsItems = ({ message, room, subscription }: ThreadsItemsProps) => {
<>
<ReactionMessageAction message={message} room={room} subscription={subscription} />
<QuoteMessageAction message={message} subscription={subscription} />
<ForwardMessageAction message={message} />
<ForwardMessageAction message={message} room={room} />
<JumpToMessageAction id='jump-to-message' message={message} />
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { mockAppRoot } from '@rocket.chat/mock-providers';
import { render, screen } from '@testing-library/react';
import { axe } from 'jest-axe';

import ForwardMessageAction from './ForwardMessageAction';
import FakeRoomProvider from '../../../../../../tests/mocks/client/FakeRoomProvider';
import { createFakeRoom } from '../../../../../../tests/mocks/data';

// Mock the getPermaLink function
jest.mock('../../../../../lib/getPermaLink', () => ({
getPermaLink: jest.fn(() => Promise.resolve(null)),
}));

jest.mock('../../../../../views/room/modals/ForwardMessageModal', () => ({
getPermaLink: jest.fn(() => null),
}));

const appRoot = mockAppRoot()
.withTranslations('en', 'core', {
Forward_message: 'Forward message',
Action_not_available_encrypted_content: 'Action not available for encrypted content',
})
.build();

const createMockMessage = (overrides: any = {}) => ({
_id: 'message-id',
rid: 'room-id',
msg: 'Test message',
ts: new Date(),
u: { _id: 'user-id', username: 'testuser' },
...overrides,
});

describe('ForwardMessageAction', () => {
it('should render the forward action for normal messages', () => {
const message = createMockMessage();
const room = createFakeRoom();

render(
<FakeRoomProvider roomOverrides={room}>
<ForwardMessageAction message={message} room={room} />
</FakeRoomProvider>,
{ wrapper: appRoot },
);

expect(screen.getByRole('button', { name: 'Forward message' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Forward message' })).not.toBeDisabled();
});

it('should be disabled for encrypted messages', () => {
const message = createMockMessage({
t: 'e2e',
e2e: 'encrypted',
});
const room = createFakeRoom();

render(
<FakeRoomProvider roomOverrides={room}>
<ForwardMessageAction message={message} room={room} />
</FakeRoomProvider>,
{ wrapper: appRoot },
);

const button = screen.getByRole('button', { name: 'Action not available for encrypted content' });
expect(button).toBeDisabled();
});

it('should be disabled for ABAC rooms', () => {
const message = createMockMessage();
const room = createFakeRoom({
// @ts-expect-error - abacAttributes is not yet implemented in IRoom type
abacAttributes: { someAttribute: 'value' },
});

render(
<FakeRoomProvider roomOverrides={room}>
<ForwardMessageAction message={message} room={room} />
</FakeRoomProvider>,
{ wrapper: appRoot },
);

const button = screen.getByRole('button', { name: 'Not_available_for_ABAC_enabled_rooms' });
expect(button).toBeDisabled();
});

it('should be disabled for both encrypted messages and ABAC rooms', () => {
const message = createMockMessage({
t: 'e2e',
e2e: 'encrypted',
});
const room = createFakeRoom({
// @ts-expect-error - abacAttributes is not yet implemented in IRoom type
abacAttributes: { someAttribute: 'value' },
});

render(
<FakeRoomProvider roomOverrides={room}>
<ForwardMessageAction message={message} room={room} />
</FakeRoomProvider>,
{ wrapper: appRoot },
);

const button = screen.getByRole('button', { name: 'Action not available for encrypted content' });
expect(button).toBeDisabled();
});

it('should have no accessibility violations for normal messages', async () => {
const message = createMockMessage();
const room = createFakeRoom();

const { container } = render(
<FakeRoomProvider roomOverrides={room}>
<ForwardMessageAction message={message} room={room} />
</FakeRoomProvider>,
{ wrapper: appRoot },
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});

it('should have no accessibility violations for encrypted messages', async () => {
const message = createMockMessage({
t: 'e2e',
e2e: 'encrypted',
});
const room = createFakeRoom();

const { container } = render(
<FakeRoomProvider roomOverrides={room}>
<ForwardMessageAction message={message} room={room} />
</FakeRoomProvider>,
{ wrapper: appRoot },
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});

it('should have no accessibility violations for ABAC rooms', async () => {
const message = createMockMessage();
const room = createFakeRoom({
// @ts-expect-error - abacAttributes is not yet implemented in IRoom type
abacAttributes: { someAttribute: 'value' },
});

const { container } = render(
<FakeRoomProvider roomOverrides={room}>
<ForwardMessageAction message={message} room={room} />
</FakeRoomProvider>,
{ wrapper: appRoot },
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { type IMessage, isE2EEMessage } from '@rocket.chat/core-typings';
import { isE2EEMessage } from '@rocket.chat/core-typings';
import type { IRoom, IMessage } from '@rocket.chat/core-typings';
import { useSetModal } from '@rocket.chat/ui-contexts';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';

import { getPermaLink } from '../../../../../lib/getPermaLink';
Expand All @@ -8,21 +10,34 @@ import MessageToolbarItem from '../../MessageToolbarItem';

type ForwardMessageActionProps = {
message: IMessage;
room: IRoom;
};

const ForwardMessageAction = ({ message }: ForwardMessageActionProps) => {
const ForwardMessageAction = ({ message, room }: ForwardMessageActionProps) => {
const setModal = useSetModal();
const { t } = useTranslation();

const encrypted = isE2EEMessage(message);
// @ts-expect-error to be implemented
const isABACEnabled = !!room.abacAttributes;

const getTitle = useMemo(() => {
if (encrypted) {
return t('Action_not_available_encrypted_content', { action: t('Forward_message') });
}
if (isABACEnabled) {
return t('Not_available_for_ABAC_enabled_rooms');
}
return t('Forward_message');
}, [encrypted, isABACEnabled, t]);

return (
<MessageToolbarItem
id='forward-message'
icon='arrow-forward'
title={encrypted ? t('Action_not_available_encrypted_content', { action: t('Forward_message') }) : t('Forward_message')}
title={getTitle}
qa='Forward_message'
disabled={encrypted}
disabled={encrypted || isABACEnabled}
onClick={async () => {
const permalink = await getPermaLink(message._id);
setModal(
Expand Down
Loading
Loading