Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/components/ChannelListItem/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ describe('ChannelPreview utils', () => {
const channelWithDeletedMessage = generateChannel({
messages: [generateMessage({ deleted_at: new Date().toISOString() })],
});
const channelWithDeletedTypeMessage = generateChannel({
messages: [generateMessage({ type: 'deleted' })],
});
const channelWithDeletedForMeMessage = generateChannel({
messages: [generateMessage({ deleted_for_me: true })],
});
const channelWithLocationMessage = generateChannel({
messages: [
generateMessage({
Expand Down Expand Up @@ -85,6 +91,12 @@ describe('ChannelPreview utils', () => {
it.each([
['Nothing yet...', 'channelWithEmptyMessage', channelWithEmptyMessage],
['Message deleted', 'channelWithDeletedMessage', channelWithDeletedMessage],
['Message deleted', 'channelWithDeletedTypeMessage', channelWithDeletedTypeMessage],
[
'Message deleted',
'channelWithDeletedForMeMessage',
channelWithDeletedForMeMessage,
],
['🏙 Attachment...', 'channelWithAttachmentMessage', channelWithAttachmentMessage],
['📍Shared location', 'channelWithLocationMessage', channelWithLocationMessage],
[
Expand Down
3 changes: 2 additions & 1 deletion src/components/ChannelListItem/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { getTranslatedMessageText } from '../../context/MessageTranslationViewCo
import type { TranslationContextValue } from '../../context/TranslationContext';
import type { PluggableList } from 'unified';
import { htmlToTextPlugin, imageToLink, plusPlusToEmphasis } from '../Message';
import { isMessageDeleted } from '../Message/utils';
import remarkGfm from 'remark-gfm';

const remarkPlugins: PluggableList = [
Expand Down Expand Up @@ -54,7 +55,7 @@ export const getLatestMessagePreview = (
return t('Nothing yet...');
}

if (latestMessage.deleted_at) {
if (isMessageDeleted(latestMessage)) {
return t('Message deleted');
}

Expand Down
4 changes: 2 additions & 2 deletions src/components/Message/MessageUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
countEmojis,
isMessageBlocked,
isMessageBounced,
isMessageDeleted,
isMessageEdited,
isMessageErrorRetryable,
messageHasAttachments,
Expand Down Expand Up @@ -95,8 +96,7 @@ const MessageUIWithContext = ({
() => isMessageAIGenerated?.(message),
[isMessageAIGenerated, message],
);
const isDeleted =
!!message.deleted_at || message.type === 'deleted' || message.deleted_for_me;
const isDeleted = isMessageDeleted(message);

const finalAttachments = useMemo(
() =>
Expand Down
5 changes: 4 additions & 1 deletion src/components/Message/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export const getMessageActions = (
if (actions && typeof actions === 'boolean') {
// If value of actions is true, then populate all the possible values
messageActions = Object.keys(MESSAGE_ACTIONS);
} else if (actions && actions.length > 0) {
} else if (actions && Array.isArray(actions) && actions.length > 0) {
messageActions = [...actions];
} else {
return [];
Expand Down Expand Up @@ -405,5 +405,8 @@ export const isMessageBlocked = (
(message.moderation_details?.action === 'MESSAGE_RESPONSE_ACTION_REMOVE' ||
message.moderation?.action === 'remove');

export const isMessageDeleted = (message: LocalMessage): boolean =>
Boolean(message.deleted_at || message.type === 'deleted' || message.deleted_for_me);

export const isMessageEdited = (message: Pick<LocalMessage, 'message_text_updated_at'>) =>
!!message.message_text_updated_at;
4 changes: 3 additions & 1 deletion src/components/MessageActions/MessageActions.defaults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
IconUnpin,
IconUserCheck,
} from '../Icons';
import { isUserMuted } from '../Message/utils';
import { isMessageDeleted, isUserMuted } from '../Message/utils';
import { useMessageComposerController } from '../MessageComposer/hooks/useMessageComposerController';
import { savePreEditSnapshot } from '../MessageComposer/preEditSnapshot';
import { useNotificationApi } from '../Notifications';
Expand Down Expand Up @@ -500,6 +500,8 @@ const DefaultMessageActionComponents = {
const { t } = useTranslationContext();
const [openModal, setOpenModal] = useState(false);

if (isMessageDeleted(message)) return null;

return (
<>
<ContextMenuButton
Expand Down
48 changes: 48 additions & 0 deletions src/components/MessageActions/__tests__/MessageActions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,54 @@ describe('<MessageActions />', () => {
expect(handleDelete).toHaveBeenCalledTimes(1);
});

it('should not show Delete when the message is already deleted', async () => {
const message = generateMessage({
deleted_at: new Date().toISOString(),
user: alice,
});
await renderMessageActions({
channelStateOpts: {
channelCapabilities: { 'delete-own-message': true },
},
customMessageContext: { message },
});
await toggleOpenMessageActions();

expect(screen.queryByText('Delete message')).not.toBeInTheDocument();
});

it('should not show Delete when the message type is deleted', async () => {
const message = generateMessage({
type: 'deleted',
user: alice,
});
await renderMessageActions({
channelStateOpts: {
channelCapabilities: { 'delete-own-message': true },
},
customMessageContext: { message },
});
await toggleOpenMessageActions();

expect(screen.queryByText('Delete message')).not.toBeInTheDocument();
});

it('should not show Delete when the message is deleted for me', async () => {
const message = generateMessage({
deleted_for_me: true,
user: alice,
});
await renderMessageActions({
channelStateOpts: {
channelCapabilities: { 'delete-own-message': true },
},
customMessageContext: { message },
});
await toggleOpenMessageActions();

expect(screen.queryByText('Delete message')).not.toBeInTheDocument();
});

it('should include Edit in dropdown actions when user has edit capability', async () => {
const message = generateMessage({ user: alice });
const { container } = await renderMessageActions({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useUserRole } from '../../Message/hooks';
import {
ACTIONS_NOT_WORKING_IN_THREAD,
isMessageBounced,
isMessageDeleted,
isMessageErrorRetryable,
isNetworkSendFailure,
} from '../../Message/utils';
Expand All @@ -15,14 +16,15 @@ import type { MessageActionSetItem } from '../MessageActions';
* Base filter hook which covers actions of type `delete`, `edit`,
* `flag`, `markUnread`, `mute`, `quote`, `react` and `reply`, whether
* the rendered message is a reply (replies are limited to certain actions) and
* whether the message has appropriate type and status.
* whether the message has appropriate type and status (including soft-deleted).
*/
export const useBaseMessageActionSetFilter = (
messageActionSet: MessageActionSetItem[],
disable = false,
) => {
const { initialMessage: isInitialMessage, message } = useMessageContext();
const { channelConfig } = useChannelStateContext();
const messageIsDeleted = isMessageDeleted(message);
const {
canBlockUser,
canDelete,
Expand Down Expand Up @@ -68,15 +70,17 @@ export const useBaseMessageActionSetFilter = (
return (
(type === 'resendMessage' && canSendMessage && (allowRetry || isBounced)) ||
(type === 'edit' && ((isBounced && canEdit) || hasNetworkSendFailure)) ||
(type === 'delete' && ((isBounced && canDelete) || hasNetworkSendFailure))
(type === 'delete' &&
!messageIsDeleted &&
((isBounced && canDelete) || hasNetworkSendFailure))
);
}

if (
type === 'resendMessage' ||
(type === 'blockUser' && !canBlockUser) ||
(type === 'copyMessageText' && !message.text) ||
(type === 'delete' && !canDelete) ||
(type === 'delete' && (!canDelete || messageIsDeleted)) ||
(type === 'edit' && !canEdit) ||
(type === 'flag' && !canFlag) ||
(type === 'markUnread' && !canMarkUnread) ||
Expand Down Expand Up @@ -106,6 +110,7 @@ export const useBaseMessageActionSetFilter = (
channelConfig,
isBounced,
isInitialMessage,
messageIsDeleted,
isMessageThreadReply,
message.error,
message.status,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,20 +155,27 @@ describe('useLatestMessagePreview', () => {
});

describe('deleted message', () => {
it('returns deleted type with delivery status and sender name', () => {
const message = generateMessage({
deleted_at: new Date().toISOString(),
user: ownUser,
});
const { result } = renderPreviewHook({
latestMessage: message,
messageDeliveryStatus: MessageDeliveryStatus.DELIVERED,
});
expect(result.current.type).toBe('deleted');
expect(result.current.text).toBe('Message deleted');
expect(result.current.deliveryStatus).toBe('delivered');
expect(result.current.senderName).toBe('You');
});
it.each([
['deleted_at timestamp', { deleted_at: new Date().toISOString() }],
['deleted type', { type: 'deleted' as const }],
['deleted for current user', { deleted_for_me: true }],
])(
'returns deleted type with delivery status and sender name for %s',
(_label, messageOverrides) => {
const message = generateMessage({
...messageOverrides,
user: ownUser,
});
const { result } = renderPreviewHook({
latestMessage: message,
messageDeliveryStatus: MessageDeliveryStatus.DELIVERED,
});
expect(result.current.type).toBe('deleted');
expect(result.current.text).toBe('Message deleted');
expect(result.current.deliveryStatus).toBe('delivered');
expect(result.current.senderName).toBe('You');
},
);
});

describe('poll message', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
useChatContext,
useTranslationContext,
} from '../../../context';
import { isMessageDeleted } from '../../Message/utils';

import type { MessageDeliveryStatus } from '../../ChannelListItem';

Expand Down Expand Up @@ -161,7 +162,7 @@ export const useLatestMessagePreview = ({
senderName = latestMessage.user?.name || latestMessage.user?.id;
}

if (latestMessage.deleted_at) {
if (isMessageDeleted(latestMessage)) {
return {
deliveryStatus,
senderName,
Expand Down
Loading