diff --git a/app/soapbox/features/notifications/components/__tests__/notification.test.tsx b/app/soapbox/features/notifications/components/__tests__/notification.test.tsx
index f11d43f09..14c43f50c 100644
--- a/app/soapbox/features/notifications/components/__tests__/notification.test.tsx
+++ b/app/soapbox/features/notifications/components/__tests__/notification.test.tsx
@@ -28,6 +28,31 @@ describe('', () => {
expect(screen.getByTestId('notification')).toBeInTheDocument();
expect(screen.getByTestId('account')).toContainHTML('neko@rdrama.cc');
+ expect(screen.getByTestId('message')).toHaveTextContent('Nekobit followed you');
+ });
+
+ describe('grouped notifications', () => {
+ it('renders a grouped follow notification for more than 2', async() => {
+ const { notification, state } = normalize(require('soapbox/__fixtures__/notification-follow.json'));
+ const groupedNotification = { ...notification.toJS(), total_count: 5 };
+
+ render(, undefined, state);
+
+ expect(screen.getByTestId('notification')).toBeInTheDocument();
+ expect(screen.getByTestId('account')).toContainHTML('neko@rdrama.cc');
+ expect(screen.getByTestId('message')).toHaveTextContent('Nekobit + 4 others followed you');
+ });
+
+ it('renders a grouped follow notification for 1', async() => {
+ const { notification, state } = normalize(require('soapbox/__fixtures__/notification-follow.json'));
+ const groupedNotification = { ...notification.toJS(), total_count: 2 };
+
+ render(, undefined, state);
+
+ expect(screen.getByTestId('notification')).toBeInTheDocument();
+ expect(screen.getByTestId('account')).toContainHTML('neko@rdrama.cc');
+ expect(screen.getByTestId('message')).toHaveTextContent('Nekobit + 1 other followed you');
+ });
});
it('renders a favourite notification', async() => {
diff --git a/app/soapbox/features/notifications/components/notification.tsx b/app/soapbox/features/notifications/components/notification.tsx
index 9dd8a8bd0..1a9dad8eb 100644
--- a/app/soapbox/features/notifications/components/notification.tsx
+++ b/app/soapbox/features/notifications/components/notification.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import { HotKeys } from 'react-hotkeys';
-import { FormattedMessage, useIntl } from 'react-intl';
+import { defineMessages, FormattedMessage, IntlShape, MessageDescriptor } from 'react-intl';
+import { useIntl } from 'react-intl';
import { useHistory } from 'react-router-dom';
import { useAppSelector } from 'soapbox/hooks';
@@ -24,11 +25,6 @@ const notificationForScreenReader = (intl: ReturnType, message:
return output.join(', ');
};
-// Workaround for dynamic messages (https://github.com/formatjs/babel-plugin-react-intl/issues/119#issuecomment-326202499)
-function FormattedMessageFixed(props: any) {
- return ;
-}
-
const buildLink = (account: Account): JSX.Element => (
= {
user_approved: require('@tabler/icons/icons/user-plus.svg'),
};
-const messages: Record = {
+const messages: Record = defineMessages({
follow: {
id: 'notification.follow',
defaultMessage: '{name} followed you',
@@ -100,18 +96,36 @@ const messages: Record
id: 'notification.user_approved',
defaultMessage: 'Welcome to {instance}!',
},
-};
+});
-const buildMessage = (type: NotificationType, account: Account, targetName: string, instanceTitle: string): JSX.Element => {
+const buildMessage = (
+ intl: IntlShape,
+ type: NotificationType,
+ account: Account,
+ totalCount: number | null,
+ targetName: string,
+ instanceTitle: string,
+): React.ReactNode => {
const link = buildLink(account);
+ const name = intl.formatMessage({
+ id: 'notification.name',
+ defaultMessage: '{link}{others}',
+ }, {
+ link,
+ others: totalCount && totalCount > 0 ? (
+
+ ) : '',
+ });
- return (
-
- );
+ return intl.formatMessage(messages[type], {
+ name,
+ targetName,
+ instance: instanceTitle,
+ });
};
interface INotificaton {
@@ -268,7 +282,7 @@ const Notification: React.FC = (props) => {
const targetName = notification.target && typeof notification.target === 'object' ? notification.target.acct : '';
- const message: React.ReactNode = type && account && typeof account === 'object' ? buildMessage(type, account, targetName, instance.title) : null;
+ const message: React.ReactNode = type && account && typeof account === 'object' ? buildMessage(intl, type, account, notification.total_count, targetName, instance.title) : null;
return (
@@ -300,6 +314,7 @@ const Notification: React.FC = (props) => {
theme='muted'
size='sm'
truncate
+ data-testid='message'
>
{message}
diff --git a/app/soapbox/normalizers/notification.ts b/app/soapbox/normalizers/notification.ts
index cee69f7ce..2bac3f2b3 100644
--- a/app/soapbox/normalizers/notification.ts
+++ b/app/soapbox/normalizers/notification.ts
@@ -34,6 +34,7 @@ export const NotificationRecord = ImmutableRecord({
status: null as EmbeddedEntity,
target: null as EmbeddedEntity, // move
type: '' as NotificationType | '',
+ total_count: null as number | null, // grouped notifications
});
export const normalizeNotification = (notification: Record) => {
diff --git a/app/soapbox/reducers/__tests__/notifications-test.js b/app/soapbox/reducers/__tests__/notifications-test.js
index aa7d6f714..7061c60bb 100644
--- a/app/soapbox/reducers/__tests__/notifications-test.js
+++ b/app/soapbox/reducers/__tests__/notifications-test.js
@@ -274,6 +274,7 @@ describe('notifications reducer', () => {
status: '9vvNxoo5EFbbnfdXQu',
emoji: '😢',
chat_message: null,
+ total_count: null,
})],
['10743', ImmutableMap({
id: '10743',
@@ -284,6 +285,7 @@ describe('notifications reducer', () => {
status: '9vvNxoo5EFbbnfdXQu',
emoji: null,
chat_message: null,
+ total_count: null,
})],
['10741', ImmutableMap({
id: '10741',
@@ -294,6 +296,7 @@ describe('notifications reducer', () => {
status: '9vvNxoo5EFbbnfdXQu',
emoji: null,
chat_message: null,
+ total_count: null,
})],
['10734', ImmutableMap({
id: '10734',
@@ -339,6 +342,7 @@ describe('notifications reducer', () => {
status: '9vvNxoo5EFbbnfdXQu',
emoji: '😢',
chat_message: null,
+ total_count: null,
})],
['10743', ImmutableMap({
id: '10743',
@@ -349,6 +353,7 @@ describe('notifications reducer', () => {
status: '9vvNxoo5EFbbnfdXQu',
emoji: null,
chat_message: null,
+ total_count: null,
})],
['10741', ImmutableMap({
id: '10741',
@@ -359,6 +364,7 @@ describe('notifications reducer', () => {
status: '9vvNxoo5EFbbnfdXQu',
emoji: null,
chat_message: null,
+ total_count: null,
})],
]),
unread: 1,