Skip to content

Commit

Permalink
chore: refactor sidebar unread handler (#33792)
Browse files Browse the repository at this point in the history
  • Loading branch information
juliajforesti authored Nov 6, 2024
1 parent dfd59e5 commit c68cd1b
Show file tree
Hide file tree
Showing 5 changed files with 330 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useOmnichannelPriorities } from '../../omnichannel/hooks/useOmnichannel
import RoomMenu from '../RoomMenu';
import { OmnichannelBadges } from '../badges/OmnichannelBadges';
import type { useAvatarTemplate } from '../hooks/useAvatarTemplate';
import { useUnreadDisplay } from '../hooks/useUnreadDisplay';
import { normalizeSidebarMessage } from './normalizeSidebarMessage';

export const getMessage = (room: IRoom, lastMessage: IMessage | undefined, t: TFunction): string | undefined => {
Expand All @@ -34,24 +35,6 @@ export const getMessage = (room: IRoom, lastMessage: IMessage | undefined, t: TF
return `${lastMessage.u.name || lastMessage.u.username}: ${normalizeSidebarMessage(lastMessage, t)}`;
};

export const getBadgeTitle = (userMentions: number, threadUnread: number, groupMentions: number, unread: number, t: TFunction) => {
const title = [] as string[];
if (userMentions) {
title.push(t('mentions_counter', { count: userMentions }));
}
if (threadUnread) {
title.push(t('threads_counter', { count: threadUnread }));
}
if (groupMentions) {
title.push(t('group_mentions_counter', { count: groupMentions }));
}
const count = unread - userMentions - groupMentions;
if (count > 0) {
title.push(t('unread_messages_counter', { count }));
}
return title.join(', ');
};

type RoomListRowProps = {
extended: boolean;
t: TFunction;
Expand Down Expand Up @@ -109,22 +92,10 @@ const SidebarItemTemplateWithData = ({
const href = roomCoordinator.getRouteLink(room.t, room) || '';
const title = roomCoordinator.getRoomName(room.t, room) || '';

const {
lastMessage,
hideUnreadStatus,
hideMentionStatus,
unread = 0,
alert,
userMentions,
groupMentions,
tunread = [],
tunreadUser = [],
rid,
t: type,
cl,
} = room;
const { unreadTitle, unreadVariant, showUnread, unreadCount, highlightUnread: highlighted } = useUnreadDisplay(room);

const { lastMessage, unread = 0, alert, rid, t: type, cl } = room;

const highlighted = Boolean(!hideUnreadStatus && (alert || unread));
const icon = (
<SidebarV2ItemIcon
highlighted={highlighted}
Expand All @@ -149,20 +120,11 @@ const SidebarItemTemplateWithData = ({
const message = extended && getMessage(room, lastMessage, t);
const subtitle = message ? <span className='message-body--unstyled' dangerouslySetInnerHTML={{ __html: message }} /> : null;

const threadUnread = tunread.length > 0;
const variant =
((userMentions || tunreadUser.length) && 'danger') || (threadUnread && 'primary') || (groupMentions && 'warning') || 'secondary';

const isUnread = unread > 0 || threadUnread;
const showBadge = !hideUnreadStatus || (!hideMentionStatus && (Boolean(userMentions) || tunreadUser.length > 0));

const badgeTitle = getBadgeTitle(userMentions, tunread.length, groupMentions, unread, t);

const badges = (
<>
{showBadge && isUnread && (
<SidebarV2ItemBadge variant={variant} title={badgeTitle}>
{unread + tunread?.length}
{showUnread && (
<SidebarV2ItemBadge variant={unreadVariant} title={unreadTitle}>
{unreadCount.total}
</SidebarV2ItemBadge>
)}
{isOmnichannelRoom(room) && <OmnichannelBadges room={room} />}
Expand Down Expand Up @@ -197,7 +159,7 @@ const SidebarItemTemplateWithData = ({
((): ReactElement => (
<RoomMenu
alert={alert}
threadUnread={threadUnread}
threadUnread={unreadCount.threads > 0}
rid={rid}
unread={!!unread}
roomOpen={selected}
Expand Down
237 changes: 237 additions & 0 deletions apps/meteor/client/sidebarv2/hooks/useUnreadDisplay.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
import { mockAppRoot } from '@rocket.chat/mock-providers';
import { renderHook } from '@testing-library/react';

import { createFakeSubscription } from '../../../tests/mocks/data';
import { useUnreadDisplay } from './useUnreadDisplay';

const dmUnread = createFakeSubscription({
t: 'd',
unread: 3,
userMentions: 0,
groupMentions: 0,
tunread: undefined,
tunreadUser: undefined,
});

const dmThread = createFakeSubscription({
t: 'd',
unread: 3,
userMentions: 0,
groupMentions: 0,
tunread: ['1'],
tunreadUser: undefined,
});

const alert = createFakeSubscription({
t: 'p',
unread: 0,
userMentions: 0,
groupMentions: 0,
tunread: undefined,
tunreadUser: undefined,
alert: true,
});

const mentionAndGroupMention = createFakeSubscription({
t: 'p',
unread: 2,
userMentions: 1,
groupMentions: 1,
tunread: undefined,
tunreadUser: undefined,
alert: true,
});

const groupMention = createFakeSubscription({
t: 'p',
unread: 2,
userMentions: 0,
groupMentions: 2,
tunread: undefined,
tunreadUser: undefined,
alert: true,
});

const tunread = createFakeSubscription({
t: 'p',
unread: 0,
userMentions: 0,
groupMentions: 0,
tunread: ['1'],
tunreadUser: undefined,
alert: true,
});

const tunreadUser = createFakeSubscription({
t: 'p',
unread: 1,
userMentions: 0,
groupMentions: 0,
tunread: ['1'],
tunreadUser: ['1'],
alert: true,
});

const hideUnreadStatus = createFakeSubscription({
t: 'p',
hideUnreadStatus: true,
});

const hideUnreadAndMention = createFakeSubscription({
t: 'p',
hideUnreadStatus: true,
hideMentionStatus: true,
});

const noUnread = createFakeSubscription({
t: 'p',
unread: 0,
userMentions: 0,
groupMentions: 0,
tunread: undefined,
tunreadUser: undefined,
});

const wrapper = mockAppRoot()
.withTranslations('en', 'core', {
mentions_counter_one: '{{count}} mention',
mentions_counter_other: '{{count}} mentions',
threads_counter_one: '{{count}} unread threaded message',
threads_counter_other: '{{count}} unread threaded messages',
group_mentions_counter_one: '{{count}} group mention',
group_mentions_counter_other: '{{count}} group mentions',
unread_messages_counter_one: '{{count}} unread message',
unread_messages_counter_other: '{{count}} unread messages',
})
.build();

it('should return correct unread data for [Direct message unread]', async () => {
const { result } = renderHook(() => useUnreadDisplay(dmUnread), {
legacyRoot: true,
wrapper,
});
expect(result.current.unreadVariant).toBe('secondary');
expect(result.current.unreadTitle).toBe('3 unread messages');

expect(result.current.unreadCount).toHaveProperty('mentions', 0);
expect(result.current.unreadCount).toHaveProperty('threads', 0);
expect(result.current.unreadCount).toHaveProperty('groupMentions', 0);
expect(result.current.unreadCount).toHaveProperty('total', 3);
});

it('should return correct unread data for [Direct message with thread unread]', async () => {
const { result } = renderHook(() => useUnreadDisplay(dmThread), {
legacyRoot: true,
wrapper,
});
expect(result.current.unreadVariant).toBe('primary');
expect(result.current.unreadTitle).toBe('1 unread threaded message, 3 unread messages');

expect(result.current.unreadCount).toHaveProperty('mentions', 0);
expect(result.current.unreadCount).toHaveProperty('threads', 1);
expect(result.current.unreadCount).toHaveProperty('groupMentions', 0);
expect(result.current.unreadCount).toHaveProperty('total', 4);
});

it('should return correct unread data for [Channel with unread messages alert only]', async () => {
const { result } = renderHook(() => useUnreadDisplay(alert), {
legacyRoot: true,
wrapper,
});

expect(result.current.highlightUnread).toBe(true);
expect(result.current.showUnread).toBe(false);

expect(result.current.unreadCount).toHaveProperty('mentions', 0);
expect(result.current.unreadCount).toHaveProperty('threads', 0);
expect(result.current.unreadCount).toHaveProperty('groupMentions', 0);
expect(result.current.unreadCount).toHaveProperty('total', 0);
});

it('should return correct unread data for [Mention and group mention]', async () => {
const { result } = renderHook(() => useUnreadDisplay(mentionAndGroupMention), {
legacyRoot: true,
wrapper,
});
expect(result.current.unreadVariant).toBe('danger');
expect(result.current.unreadTitle).toBe('1 mention, 1 group mention');

expect(result.current.unreadCount).toHaveProperty('mentions', 1);
expect(result.current.unreadCount).toHaveProperty('threads', 0);
expect(result.current.unreadCount).toHaveProperty('groupMentions', 1);
expect(result.current.unreadCount).toHaveProperty('total', 2);
});

it('should return correct unread data for [Group mention]', async () => {
const { result } = renderHook(() => useUnreadDisplay(groupMention), {
legacyRoot: true,
wrapper,
});
expect(result.current.unreadVariant).toBe('warning');
expect(result.current.unreadTitle).toBe('2 group mentions');

expect(result.current.unreadCount).toHaveProperty('mentions', 0);
expect(result.current.unreadCount).toHaveProperty('threads', 0);
expect(result.current.unreadCount).toHaveProperty('groupMentions', 2);
expect(result.current.unreadCount).toHaveProperty('total', 2);
});

it('should return correct unread data for [Thread unread]', async () => {
const { result } = renderHook(() => useUnreadDisplay(tunread), {
legacyRoot: true,
wrapper,
});
expect(result.current.unreadVariant).toBe('primary');
expect(result.current.unreadTitle).toBe('1 unread threaded message');

expect(result.current.unreadCount).toHaveProperty('mentions', 0);
expect(result.current.unreadCount).toHaveProperty('groupMentions', 0);
expect(result.current.unreadCount).toHaveProperty('threads', 1);
expect(result.current.unreadCount).toHaveProperty('total', 1);
});

it('should return correct unread data for [Thread and thread user mention]', async () => {
const { result } = renderHook(() => useUnreadDisplay(tunreadUser), {
legacyRoot: true,
wrapper,
});
expect(result.current.unreadVariant).toBe('danger');
expect(result.current.unreadTitle).toBe('1 mention, 1 unread threaded message');

expect(result.current.unreadCount).toHaveProperty('mentions', 1);
expect(result.current.unreadCount).toHaveProperty('groupMentions', 0);
expect(result.current.unreadCount).toHaveProperty('threads', 1);
expect(result.current.unreadCount).toHaveProperty('total', 2);
});

it('should not highlight unread if hideUnreadStatus is enabled', async () => {
const { result } = renderHook(() => useUnreadDisplay(hideUnreadStatus), {
legacyRoot: true,
});

expect(result.current.highlightUnread).toBe(false);
expect(result.current.showUnread).toBe(true);
});

it('should not show unread if hideUnreadStatus and hideMentionStatus is enabled', async () => {
const { result } = renderHook(() => useUnreadDisplay(hideUnreadAndMention), {
legacyRoot: true,
});

expect(result.current.highlightUnread).toBe(false);
expect(result.current.showUnread).toBe(false);
});

it("should not show unread if there isn't any unread message", async () => {
const { result } = renderHook(() => useUnreadDisplay(noUnread), {
legacyRoot: true,
});

expect(result.current.highlightUnread).toBe(false);
expect(result.current.showUnread).toBe(false);

expect(result.current.unreadCount).toHaveProperty('mentions', 0);
expect(result.current.unreadCount).toHaveProperty('groupMentions', 0);
expect(result.current.unreadCount).toHaveProperty('threads', 0);
expect(result.current.unreadCount).toHaveProperty('total', 0);
});
15 changes: 15 additions & 0 deletions apps/meteor/client/sidebarv2/hooks/useUnreadDisplay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
import { useTranslation } from 'react-i18next';

import { getSubscriptionUnreadData } from '../../../lib/getSubscriptionUnreadData';

export const useUnreadDisplay = (
unreadData: Pick<
SubscriptionWithRoom,
'alert' | 'userMentions' | 'unread' | 'tunread' | 'tunreadUser' | 'groupMentions' | 'hideMentionStatus' | 'hideUnreadStatus'
>,
) => {
const { t } = useTranslation();

return getSubscriptionUnreadData(unreadData, t);
};
24 changes: 7 additions & 17 deletions apps/meteor/client/views/room/Sidepanel/hooks/useItemData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { useTranslation } from 'react-i18next';

import { RoomIcon } from '../../../../components/RoomIcon';
import { roomCoordinator } from '../../../../lib/rooms/roomCoordinator';
import { getBadgeTitle, getMessage } from '../../../../sidebarv2/RoomList/SidebarItemTemplateWithData';
import { getMessage } from '../../../../sidebarv2/RoomList/SidebarItemTemplateWithData';
import { useAvatarTemplate } from '../../../../sidebarv2/hooks/useAvatarTemplate';
import { useUnreadDisplay } from '../../../../sidebarv2/hooks/useUnreadDisplay';

export const useItemData = (
room: ISubscription & IRoom,
Expand All @@ -15,7 +16,7 @@ export const useItemData = (
const { t } = useTranslation();
const AvatarTemplate = useAvatarTemplate();

const highlighted = Boolean(!room.hideUnreadStatus && (room.alert || room.unread));
const { unreadTitle, unreadVariant, showUnread, highlightUnread: highlighted, unreadCount } = useUnreadDisplay(room);

const icon = useMemo(
() => <SidebarItemIcon highlighted={highlighted} icon={<RoomIcon room={room} placement='sidebar' size='x20' />} />,
Expand All @@ -24,28 +25,17 @@ export const useItemData = (
const time = 'lastMessage' in room ? room.lastMessage?.ts : undefined;
const message = viewMode === 'extended' && getMessage(room, room.lastMessage, t);

const threadUnread = Number(room.tunread?.length) > 0;
const isUnread = room.unread > 0 || threadUnread;
const showBadge =
!room.hideUnreadStatus || (!room.hideMentionStatus && (Boolean(room.userMentions) || Number(room.tunreadUser?.length) > 0));
const badgeTitle = getBadgeTitle(room.userMentions, Number(room.tunread?.length), room.groupMentions, room.unread, t);
const variant =
((room.userMentions || room.tunreadUser?.length) && 'danger') ||
(threadUnread && 'primary') ||
(room.groupMentions && 'warning') ||
'secondary';

const badges = useMemo(
() => (
<>
{showBadge && isUnread && (
<SidebarItemBadge variant={variant} title={badgeTitle}>
{room.unread + (room.tunread?.length || 0)}
{showUnread && (
<SidebarItemBadge variant={unreadVariant} title={unreadTitle}>
{unreadCount.total}
</SidebarItemBadge>
)}
</>
),
[badgeTitle, isUnread, room.tunread?.length, room.unread, showBadge, variant],
[showUnread, unreadCount.total, unreadTitle, unreadVariant],
);

const itemData = useMemo(
Expand Down
Loading

0 comments on commit c68cd1b

Please sign in to comment.