Make clear notifications work with threads (#9575)
This commit is contained in:
parent
e66027cd0c
commit
c10339ad68
5 changed files with 241 additions and 237 deletions
|
@ -42,7 +42,7 @@ import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import TagComposer from "../elements/TagComposer";
|
import TagComposer from "../elements/TagComposer";
|
||||||
import { objectClone } from "../../../utils/objects";
|
import { objectClone } from "../../../utils/objects";
|
||||||
import { arrayDiff } from "../../../utils/arrays";
|
import { arrayDiff } from "../../../utils/arrays";
|
||||||
import { getLocalNotificationAccountDataEventType } from "../../../utils/notifications";
|
import { clearAllNotifications, getLocalNotificationAccountDataEventType } from "../../../utils/notifications";
|
||||||
|
|
||||||
// TODO: this "view" component still has far too much application logic in it,
|
// TODO: this "view" component still has far too much application logic in it,
|
||||||
// which should be factored out to other files.
|
// which should be factored out to other files.
|
||||||
|
@ -112,6 +112,8 @@ interface IState {
|
||||||
desktopNotifications: boolean;
|
desktopNotifications: boolean;
|
||||||
desktopShowBody: boolean;
|
desktopShowBody: boolean;
|
||||||
audioNotifications: boolean;
|
audioNotifications: boolean;
|
||||||
|
|
||||||
|
clearingNotifications: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Notifications extends React.PureComponent<IProps, IState> {
|
export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||||
|
@ -126,6 +128,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||||
desktopNotifications: SettingsStore.getValue("notificationsEnabled"),
|
desktopNotifications: SettingsStore.getValue("notificationsEnabled"),
|
||||||
desktopShowBody: SettingsStore.getValue("notificationBodyEnabled"),
|
desktopShowBody: SettingsStore.getValue("notificationBodyEnabled"),
|
||||||
audioNotifications: SettingsStore.getValue("audioNotificationsEnabled"),
|
audioNotifications: SettingsStore.getValue("audioNotificationsEnabled"),
|
||||||
|
clearingNotifications: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.settingWatchers = [
|
this.settingWatchers = [
|
||||||
|
@ -177,8 +180,12 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||||
])).reduce((p, c) => Object.assign(c, p), {});
|
])).reduce((p, c) => Object.assign(c, p), {});
|
||||||
|
|
||||||
this.setState<keyof Omit<IState,
|
this.setState<keyof Omit<IState,
|
||||||
"deviceNotificationsEnabled" | "desktopNotifications" | "desktopShowBody" | "audioNotifications">
|
"deviceNotificationsEnabled" |
|
||||||
>({
|
"desktopNotifications" |
|
||||||
|
"desktopShowBody" |
|
||||||
|
"audioNotifications" |
|
||||||
|
"clearingNotifications"
|
||||||
|
>>({
|
||||||
...newState,
|
...newState,
|
||||||
phase: Phase.Ready,
|
phase: Phase.Ready,
|
||||||
});
|
});
|
||||||
|
@ -433,17 +440,14 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private onClearNotificationsClicked = () => {
|
private onClearNotificationsClicked = async (): Promise<void> => {
|
||||||
const client = MatrixClientPeg.get();
|
try {
|
||||||
client.getRooms().forEach(r => {
|
this.setState({ clearingNotifications: true });
|
||||||
if (r.getUnreadNotificationCount() > 0) {
|
const client = MatrixClientPeg.get();
|
||||||
const events = r.getLiveTimeline().getEvents();
|
await clearAllNotifications(client);
|
||||||
if (events.length) {
|
} finally {
|
||||||
// noinspection JSIgnoredPromiseFromCall
|
this.setState({ clearingNotifications: false });
|
||||||
client.sendReadReceipt(events[events.length - 1]);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private async setKeywords(keywords: string[], originalRules: IAnnotatedPushRule[]) {
|
private async setKeywords(keywords: string[], originalRules: IAnnotatedPushRule[]) {
|
||||||
|
@ -531,7 +535,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
private renderTopSection() {
|
private renderTopSection() {
|
||||||
const masterSwitch = <LabelledToggleSwitch
|
const masterSwitch = <LabelledToggleSwitch
|
||||||
data-test-id='notif-master-switch'
|
data-testid='notif-master-switch'
|
||||||
value={!this.isInhibited}
|
value={!this.isInhibited}
|
||||||
label={_t("Enable notifications for this account")}
|
label={_t("Enable notifications for this account")}
|
||||||
caption={_t("Turn off to disable notifications on all your devices and sessions")}
|
caption={_t("Turn off to disable notifications on all your devices and sessions")}
|
||||||
|
@ -546,7 +550,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
const emailSwitches = (this.state.threepids || []).filter(t => t.medium === ThreepidMedium.Email)
|
const emailSwitches = (this.state.threepids || []).filter(t => t.medium === ThreepidMedium.Email)
|
||||||
.map(e => <LabelledToggleSwitch
|
.map(e => <LabelledToggleSwitch
|
||||||
data-test-id='notif-email-switch'
|
data-testid='notif-email-switch'
|
||||||
key={e.address}
|
key={e.address}
|
||||||
value={this.state.pushers.some(p => p.kind === "email" && p.pushkey === e.address)}
|
value={this.state.pushers.some(p => p.kind === "email" && p.pushkey === e.address)}
|
||||||
label={_t("Enable email notifications for %(email)s", { email: e.address })}
|
label={_t("Enable email notifications for %(email)s", { email: e.address })}
|
||||||
|
@ -558,7 +562,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||||
{ masterSwitch }
|
{ masterSwitch }
|
||||||
|
|
||||||
<LabelledToggleSwitch
|
<LabelledToggleSwitch
|
||||||
data-test-id='notif-device-switch'
|
data-testid='notif-device-switch'
|
||||||
value={this.state.deviceNotificationsEnabled}
|
value={this.state.deviceNotificationsEnabled}
|
||||||
label={_t("Enable notifications for this device")}
|
label={_t("Enable notifications for this device")}
|
||||||
onChange={checked => this.updateDeviceNotifications(checked)}
|
onChange={checked => this.updateDeviceNotifications(checked)}
|
||||||
|
@ -567,21 +571,21 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
{ this.state.deviceNotificationsEnabled && (<>
|
{ this.state.deviceNotificationsEnabled && (<>
|
||||||
<LabelledToggleSwitch
|
<LabelledToggleSwitch
|
||||||
data-test-id='notif-setting-notificationsEnabled'
|
data-testid='notif-setting-notificationsEnabled'
|
||||||
value={this.state.desktopNotifications}
|
value={this.state.desktopNotifications}
|
||||||
onChange={this.onDesktopNotificationsChanged}
|
onChange={this.onDesktopNotificationsChanged}
|
||||||
label={_t('Enable desktop notifications for this session')}
|
label={_t('Enable desktop notifications for this session')}
|
||||||
disabled={this.state.phase === Phase.Persisting}
|
disabled={this.state.phase === Phase.Persisting}
|
||||||
/>
|
/>
|
||||||
<LabelledToggleSwitch
|
<LabelledToggleSwitch
|
||||||
data-test-id='notif-setting-notificationBodyEnabled'
|
data-testid='notif-setting-notificationBodyEnabled'
|
||||||
value={this.state.desktopShowBody}
|
value={this.state.desktopShowBody}
|
||||||
onChange={this.onDesktopShowBodyChanged}
|
onChange={this.onDesktopShowBodyChanged}
|
||||||
label={_t('Show message in desktop notification')}
|
label={_t('Show message in desktop notification')}
|
||||||
disabled={this.state.phase === Phase.Persisting}
|
disabled={this.state.phase === Phase.Persisting}
|
||||||
/>
|
/>
|
||||||
<LabelledToggleSwitch
|
<LabelledToggleSwitch
|
||||||
data-test-id='notif-setting-audioNotificationsEnabled'
|
data-testid='notif-setting-audioNotificationsEnabled'
|
||||||
value={this.state.audioNotifications}
|
value={this.state.audioNotifications}
|
||||||
onChange={this.onAudioNotificationsChanged}
|
onChange={this.onAudioNotificationsChanged}
|
||||||
label={_t('Enable audible notifications for this session')}
|
label={_t('Enable audible notifications for this session')}
|
||||||
|
@ -605,8 +609,10 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||||
) {
|
) {
|
||||||
clearNotifsButton = <AccessibleButton
|
clearNotifsButton = <AccessibleButton
|
||||||
onClick={this.onClearNotificationsClicked}
|
onClick={this.onClearNotificationsClicked}
|
||||||
|
disabled={this.state.clearingNotifications}
|
||||||
kind='danger'
|
kind='danger'
|
||||||
className='mx_UserNotifSettings_clearNotifsButton'
|
className='mx_UserNotifSettings_clearNotifsButton'
|
||||||
|
data-testid="clear-notifications"
|
||||||
>{ _t("Clear notifications") }</AccessibleButton>;
|
>{ _t("Clear notifications") }</AccessibleButton>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -653,7 +659,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||||
const fieldsetRows = this.state.vectorPushRules[category].map(r =>
|
const fieldsetRows = this.state.vectorPushRules[category].map(r =>
|
||||||
<fieldset
|
<fieldset
|
||||||
key={category + r.ruleId}
|
key={category + r.ruleId}
|
||||||
data-test-id={category + r.ruleId}
|
data-testid={category + r.ruleId}
|
||||||
className='mx_UserNotifSettings_gridRowContainer'
|
className='mx_UserNotifSettings_gridRowContainer'
|
||||||
>
|
>
|
||||||
<legend className='mx_UserNotifSettings_gridRowLabel'>{ r.description }</legend>
|
<legend className='mx_UserNotifSettings_gridRowLabel'>{ r.description }</legend>
|
||||||
|
@ -678,7 +684,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<div data-test-id={`notif-section-${category}`} className='mx_UserNotifSettings_grid'>
|
<div data-testid={`notif-section-${category}`} className='mx_UserNotifSettings_grid'>
|
||||||
<span className='mx_UserNotifSettings_gridRowLabel mx_UserNotifSettings_gridRowHeading'>{ sectionName }</span>
|
<span className='mx_UserNotifSettings_gridRowLabel mx_UserNotifSettings_gridRowHeading'>{ sectionName }</span>
|
||||||
<span className='mx_UserNotifSettings_gridColumnLabel'>{ VectorStateToLabel[VectorState.Off] }</span>
|
<span className='mx_UserNotifSettings_gridColumnLabel'>{ VectorStateToLabel[VectorState.Off] }</span>
|
||||||
<span className='mx_UserNotifSettings_gridColumnLabel'>{ VectorStateToLabel[VectorState.On] }</span>
|
<span className='mx_UserNotifSettings_gridColumnLabel'>{ VectorStateToLabel[VectorState.On] }</span>
|
||||||
|
@ -715,7 +721,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||||
// Ends up default centered
|
// Ends up default centered
|
||||||
return <Spinner />;
|
return <Spinner />;
|
||||||
} else if (this.state.phase === Phase.Error) {
|
} else if (this.state.phase === Phase.Error) {
|
||||||
return <p data-test-id='error-message'>{ _t("There was an error loading your notification settings.") }</p>;
|
return <p data-testid='error-message'>{ _t("There was an error loading your notification settings.") }</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className='mx_UserNotifSettings'>
|
return <div className='mx_UserNotifSettings'>
|
||||||
|
|
|
@ -17,6 +17,8 @@ limitations under the License.
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { LOCAL_NOTIFICATION_SETTINGS_PREFIX } from "matrix-js-sdk/src/@types/event";
|
import { LOCAL_NOTIFICATION_SETTINGS_PREFIX } from "matrix-js-sdk/src/@types/event";
|
||||||
import { LocalNotificationSettings } from "matrix-js-sdk/src/@types/local_notifications";
|
import { LocalNotificationSettings } from "matrix-js-sdk/src/@types/local_notifications";
|
||||||
|
import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts";
|
||||||
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
import SettingsStore from "../settings/SettingsStore";
|
import SettingsStore from "../settings/SettingsStore";
|
||||||
|
|
||||||
|
@ -56,3 +58,31 @@ export function localNotificationsAreSilenced(cli: MatrixClient): boolean {
|
||||||
const event = cli.getAccountData(eventType);
|
const event = cli.getAccountData(eventType);
|
||||||
return event?.getContent<LocalNotificationSettings>()?.is_silenced ?? false;
|
return event?.getContent<LocalNotificationSettings>()?.is_silenced ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function clearAllNotifications(client: MatrixClient): Promise<Array<{}>> {
|
||||||
|
const receiptPromises = client.getRooms().reduce((promises, room: Room) => {
|
||||||
|
if (room.getUnreadNotificationCount() > 0) {
|
||||||
|
const roomEvents = room.getLiveTimeline().getEvents();
|
||||||
|
const lastThreadEvents = room.lastThread?.events;
|
||||||
|
|
||||||
|
const lastRoomEvent = roomEvents?.[roomEvents?.length - 1];
|
||||||
|
const lastThreadLastEvent = lastThreadEvents?.[lastThreadEvents?.length - 1];
|
||||||
|
|
||||||
|
const lastEvent = (lastRoomEvent?.getTs() ?? 0) > (lastThreadLastEvent?.getTs() ?? 0)
|
||||||
|
? lastRoomEvent
|
||||||
|
: lastThreadLastEvent;
|
||||||
|
|
||||||
|
if (lastEvent) {
|
||||||
|
const receiptType = SettingsStore.getValue("sendReadReceipts", room.roomId)
|
||||||
|
? ReceiptType.Read
|
||||||
|
: ReceiptType.ReadPrivate;
|
||||||
|
const promise = client.sendReadReceipt(lastEvent, receiptType, true);
|
||||||
|
promises.push(promise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return promises;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return Promise.all(receiptPromises);
|
||||||
|
}
|
||||||
|
|
|
@ -13,8 +13,6 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
// eslint-disable-next-line deprecate/import
|
|
||||||
import { mount, ReactWrapper } from 'enzyme';
|
|
||||||
import {
|
import {
|
||||||
IPushRule,
|
IPushRule,
|
||||||
IPushRules,
|
IPushRules,
|
||||||
|
@ -22,14 +20,17 @@ import {
|
||||||
IPusher,
|
IPusher,
|
||||||
LOCAL_NOTIFICATION_SETTINGS_PREFIX,
|
LOCAL_NOTIFICATION_SETTINGS_PREFIX,
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
|
Room,
|
||||||
|
NotificationCountType,
|
||||||
} from 'matrix-js-sdk/src/matrix';
|
} from 'matrix-js-sdk/src/matrix';
|
||||||
import { IThreepid, ThreepidMedium } from 'matrix-js-sdk/src/@types/threepids';
|
import { IThreepid, ThreepidMedium } from 'matrix-js-sdk/src/@types/threepids';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { fireEvent, getByTestId, render, screen, waitFor } from '@testing-library/react';
|
||||||
|
|
||||||
import Notifications from '../../../../src/components/views/settings/Notifications';
|
import Notifications from '../../../../src/components/views/settings/Notifications';
|
||||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||||
import { StandardActions } from '../../../../src/notifications/StandardActions';
|
import { StandardActions } from '../../../../src/notifications/StandardActions';
|
||||||
import { getMockClientWithEventEmitter } from '../../../test-utils';
|
import { getMockClientWithEventEmitter, mkMessage } from '../../../test-utils';
|
||||||
|
|
||||||
// don't pollute test output with error logs from mock rejections
|
// don't pollute test output with error logs from mock rejections
|
||||||
jest.mock("matrix-js-sdk/src/logger");
|
jest.mock("matrix-js-sdk/src/logger");
|
||||||
|
@ -56,13 +57,12 @@ const pushRules: IPushRules = { "global": { "underride": [{ "conditions": [{ "ki
|
||||||
const flushPromises = async () => await new Promise(resolve => setTimeout(resolve));
|
const flushPromises = async () => await new Promise(resolve => setTimeout(resolve));
|
||||||
|
|
||||||
describe('<Notifications />', () => {
|
describe('<Notifications />', () => {
|
||||||
const getComponent = () => mount(<Notifications />);
|
const getComponent = () => render(<Notifications />);
|
||||||
|
|
||||||
// get component, wait for async data and force a render
|
// get component, wait for async data and force a render
|
||||||
const getComponentAndWait = async () => {
|
const getComponentAndWait = async () => {
|
||||||
const component = getComponent();
|
const component = getComponent();
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
component.setProps({});
|
|
||||||
return component;
|
return component;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -85,11 +85,11 @@ describe('<Notifications />', () => {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
setAccountData: jest.fn(),
|
setAccountData: jest.fn(),
|
||||||
|
sendReadReceipt: jest.fn(),
|
||||||
|
supportsExperimentalThreads: jest.fn().mockReturnValue(true),
|
||||||
});
|
});
|
||||||
mockClient.getPushRules.mockResolvedValue(pushRules);
|
mockClient.getPushRules.mockResolvedValue(pushRules);
|
||||||
|
|
||||||
const findByTestId = (component, id) => component.find(`[data-test-id="${id}"]`);
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockClient.getPushRules.mockClear().mockResolvedValue(pushRules);
|
mockClient.getPushRules.mockClear().mockResolvedValue(pushRules);
|
||||||
mockClient.getPushers.mockClear().mockResolvedValue({ pushers: [] });
|
mockClient.getPushers.mockClear().mockResolvedValue({ pushers: [] });
|
||||||
|
@ -97,25 +97,25 @@ describe('<Notifications />', () => {
|
||||||
mockClient.setPusher.mockClear().mockResolvedValue({});
|
mockClient.setPusher.mockClear().mockResolvedValue({});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders spinner while loading', () => {
|
it('renders spinner while loading', async () => {
|
||||||
const component = getComponent();
|
getComponent();
|
||||||
expect(component.find('.mx_Spinner').length).toBeTruthy();
|
expect(screen.getByTestId('spinner')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders error message when fetching push rules fails', async () => {
|
it('renders error message when fetching push rules fails', async () => {
|
||||||
mockClient.getPushRules.mockRejectedValue({});
|
mockClient.getPushRules.mockRejectedValue({});
|
||||||
const component = await getComponentAndWait();
|
await getComponentAndWait();
|
||||||
expect(findByTestId(component, 'error-message').length).toBeTruthy();
|
expect(screen.getByTestId('error-message')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
it('renders error message when fetching pushers fails', async () => {
|
it('renders error message when fetching pushers fails', async () => {
|
||||||
mockClient.getPushers.mockRejectedValue({});
|
mockClient.getPushers.mockRejectedValue({});
|
||||||
const component = await getComponentAndWait();
|
await getComponentAndWait();
|
||||||
expect(findByTestId(component, 'error-message').length).toBeTruthy();
|
expect(screen.getByTestId('error-message')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
it('renders error message when fetching threepids fails', async () => {
|
it('renders error message when fetching threepids fails', async () => {
|
||||||
mockClient.getThreePids.mockRejectedValue({});
|
mockClient.getThreePids.mockRejectedValue({});
|
||||||
const component = await getComponentAndWait();
|
await getComponentAndWait();
|
||||||
expect(findByTestId(component, 'error-message').length).toBeTruthy();
|
expect(screen.getByTestId('error-message')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('main notification switches', () => {
|
describe('main notification switches', () => {
|
||||||
|
@ -127,18 +127,18 @@ describe('<Notifications />', () => {
|
||||||
},
|
},
|
||||||
} as unknown as IPushRules;
|
} as unknown as IPushRules;
|
||||||
mockClient.getPushRules.mockClear().mockResolvedValue(disableNotificationsPushRules);
|
mockClient.getPushRules.mockClear().mockResolvedValue(disableNotificationsPushRules);
|
||||||
const component = await getComponentAndWait();
|
const { container } = await getComponentAndWait();
|
||||||
|
|
||||||
expect(component).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
it('renders switches correctly', async () => {
|
it('renders switches correctly', async () => {
|
||||||
const component = await getComponentAndWait();
|
await getComponentAndWait();
|
||||||
|
|
||||||
expect(findByTestId(component, 'notif-master-switch').length).toBeTruthy();
|
expect(screen.getByTestId('notif-master-switch')).toBeInTheDocument();
|
||||||
expect(findByTestId(component, 'notif-device-switch').length).toBeTruthy();
|
expect(screen.getByTestId('notif-device-switch')).toBeInTheDocument();
|
||||||
expect(findByTestId(component, 'notif-setting-notificationsEnabled').length).toBeTruthy();
|
expect(screen.getByTestId('notif-setting-notificationsEnabled')).toBeInTheDocument();
|
||||||
expect(findByTestId(component, 'notif-setting-notificationBodyEnabled').length).toBeTruthy();
|
expect(screen.getByTestId('notif-setting-notificationBodyEnabled')).toBeInTheDocument();
|
||||||
expect(findByTestId(component, 'notif-setting-audioNotificationsEnabled').length).toBeTruthy();
|
expect(screen.getByTestId('notif-setting-audioNotificationsEnabled')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('email switches', () => {
|
describe('email switches', () => {
|
||||||
|
@ -156,9 +156,8 @@ describe('<Notifications />', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders email switches correctly when email 3pids exist', async () => {
|
it('renders email switches correctly when email 3pids exist', async () => {
|
||||||
const component = await getComponentAndWait();
|
await getComponentAndWait();
|
||||||
|
expect(screen.getByTestId('notif-email-switch')).toBeInTheDocument();
|
||||||
expect(findByTestId(component, 'notif-email-switch')).toMatchSnapshot();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders email switches correctly when notifications are on for email', async () => {
|
it('renders email switches correctly when notifications are on for email', async () => {
|
||||||
|
@ -167,19 +166,20 @@ describe('<Notifications />', () => {
|
||||||
{ kind: 'email', pushkey: testEmail } as unknown as IPusher,
|
{ kind: 'email', pushkey: testEmail } as unknown as IPusher,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const component = await getComponentAndWait();
|
await getComponentAndWait();
|
||||||
|
|
||||||
expect(findByTestId(component, 'notif-email-switch').props().value).toEqual(true);
|
const emailSwitch = screen.getByTestId('notif-email-switch');
|
||||||
|
expect(emailSwitch.querySelector('[aria-checked="true"]')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('enables email notification when toggling on', async () => {
|
it('enables email notification when toggling on', async () => {
|
||||||
const component = await getComponentAndWait();
|
await getComponentAndWait();
|
||||||
|
|
||||||
const emailToggle = findByTestId(component, 'notif-email-switch')
|
const emailToggle = screen.getByTestId('notif-email-switch')
|
||||||
.find('div[role="switch"]');
|
.querySelector('div[role="switch"]');
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
emailToggle.simulate('click');
|
fireEvent.click(emailToggle);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockClient.setPusher).toHaveBeenCalledWith(expect.objectContaining({
|
expect(mockClient.setPusher).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
@ -194,32 +194,31 @@ describe('<Notifications />', () => {
|
||||||
|
|
||||||
it('displays error when pusher update fails', async () => {
|
it('displays error when pusher update fails', async () => {
|
||||||
mockClient.setPusher.mockRejectedValue({});
|
mockClient.setPusher.mockRejectedValue({});
|
||||||
const component = await getComponentAndWait();
|
await getComponentAndWait();
|
||||||
|
|
||||||
const emailToggle = findByTestId(component, 'notif-email-switch')
|
const emailToggle = screen.getByTestId('notif-email-switch')
|
||||||
.find('div[role="switch"]');
|
.querySelector('div[role="switch"]');
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
emailToggle.simulate('click');
|
fireEvent.click(emailToggle);
|
||||||
});
|
});
|
||||||
|
|
||||||
// force render
|
// force render
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
await component.setProps({});
|
|
||||||
|
|
||||||
expect(findByTestId(component, 'error-message').length).toBeTruthy();
|
expect(screen.getByTestId('error-message')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('enables email notification when toggling off', async () => {
|
it('enables email notification when toggling off', async () => {
|
||||||
const testPusher = { kind: 'email', pushkey: 'tester@test.com' } as unknown as IPusher;
|
const testPusher = { kind: 'email', pushkey: 'tester@test.com' } as unknown as IPusher;
|
||||||
mockClient.getPushers.mockResolvedValue({ pushers: [testPusher] });
|
mockClient.getPushers.mockResolvedValue({ pushers: [testPusher] });
|
||||||
const component = await getComponentAndWait();
|
await getComponentAndWait();
|
||||||
|
|
||||||
const emailToggle = findByTestId(component, 'notif-email-switch')
|
const emailToggle = screen.getByTestId('notif-email-switch')
|
||||||
.find('div[role="switch"]');
|
.querySelector('div[role="switch"]');
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
emailToggle.simulate('click');
|
fireEvent.click(emailToggle);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockClient.setPusher).toHaveBeenCalledWith({
|
expect(mockClient.setPusher).toHaveBeenCalledWith({
|
||||||
|
@ -229,67 +228,64 @@ describe('<Notifications />', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('toggles and sets settings correctly', async () => {
|
it('toggles and sets settings correctly', async () => {
|
||||||
const component = await getComponentAndWait();
|
await getComponentAndWait();
|
||||||
let audioNotifsToggle: ReactWrapper;
|
let audioNotifsToggle;
|
||||||
|
|
||||||
const update = () => {
|
const update = () => {
|
||||||
audioNotifsToggle = findByTestId(component, 'notif-setting-audioNotificationsEnabled')
|
audioNotifsToggle = screen.getByTestId('notif-setting-audioNotificationsEnabled')
|
||||||
.find('div[role="switch"]');
|
.querySelector('div[role="switch"]');
|
||||||
};
|
};
|
||||||
update();
|
update();
|
||||||
|
|
||||||
expect(audioNotifsToggle.getDOMNode<HTMLElement>().getAttribute("aria-checked")).toEqual("true");
|
expect(audioNotifsToggle.getAttribute("aria-checked")).toEqual("true");
|
||||||
expect(SettingsStore.getValue("audioNotificationsEnabled")).toEqual(true);
|
expect(SettingsStore.getValue("audioNotificationsEnabled")).toEqual(true);
|
||||||
|
|
||||||
act(() => { audioNotifsToggle.simulate('click'); });
|
act(() => { fireEvent.click(audioNotifsToggle); });
|
||||||
update();
|
update();
|
||||||
|
|
||||||
expect(audioNotifsToggle.getDOMNode<HTMLElement>().getAttribute("aria-checked")).toEqual("false");
|
expect(audioNotifsToggle.getAttribute("aria-checked")).toEqual("false");
|
||||||
expect(SettingsStore.getValue("audioNotificationsEnabled")).toEqual(false);
|
expect(SettingsStore.getValue("audioNotificationsEnabled")).toEqual(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('individual notification level settings', () => {
|
describe('individual notification level settings', () => {
|
||||||
const getCheckedRadioForRule = (ruleEl) =>
|
|
||||||
ruleEl.find('input[type="radio"][checked=true]').props()['aria-label'];
|
|
||||||
it('renders categories correctly', async () => {
|
it('renders categories correctly', async () => {
|
||||||
const component = await getComponentAndWait();
|
await getComponentAndWait();
|
||||||
|
|
||||||
expect(findByTestId(component, 'notif-section-vector_global').length).toBeTruthy();
|
expect(screen.getByTestId('notif-section-vector_global')).toBeInTheDocument();
|
||||||
expect(findByTestId(component, 'notif-section-vector_mentions').length).toBeTruthy();
|
expect(screen.getByTestId('notif-section-vector_mentions')).toBeInTheDocument();
|
||||||
expect(findByTestId(component, 'notif-section-vector_other').length).toBeTruthy();
|
expect(screen.getByTestId('notif-section-vector_other')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders radios correctly', async () => {
|
it('renders radios correctly', async () => {
|
||||||
const component = await getComponentAndWait();
|
await getComponentAndWait();
|
||||||
const section = 'vector_global';
|
const section = 'vector_global';
|
||||||
|
|
||||||
const globalSection = findByTestId(component, `notif-section-${section}`);
|
const globalSection = screen.getByTestId(`notif-section-${section}`);
|
||||||
// 4 notification rules with class 'global'
|
// 4 notification rules with class 'global'
|
||||||
expect(globalSection.find('fieldset').length).toEqual(4);
|
expect(globalSection.querySelectorAll('fieldset').length).toEqual(4);
|
||||||
// oneToOneRule is set to 'on'
|
// oneToOneRule is set to 'on'
|
||||||
const oneToOneRuleElement = findByTestId(component, section + oneToOneRule.rule_id);
|
const oneToOneRuleElement = screen.getByTestId(section + oneToOneRule.rule_id);
|
||||||
expect(getCheckedRadioForRule(oneToOneRuleElement)).toEqual('On');
|
expect(oneToOneRuleElement.querySelector("[aria-label='On']")).toBeInTheDocument();
|
||||||
// encryptedOneToOneRule is set to 'loud'
|
// encryptedOneToOneRule is set to 'loud'
|
||||||
const encryptedOneToOneElement = findByTestId(component, section + encryptedOneToOneRule.rule_id);
|
const encryptedOneToOneElement = screen.getByTestId(section + encryptedOneToOneRule.rule_id);
|
||||||
expect(getCheckedRadioForRule(encryptedOneToOneElement)).toEqual('Noisy');
|
expect(encryptedOneToOneElement.querySelector("[aria-label='Noisy']")).toBeInTheDocument();
|
||||||
// encryptedGroupRule is set to 'off'
|
// encryptedGroupRule is set to 'off'
|
||||||
const encryptedGroupElement = findByTestId(component, section + encryptedGroupRule.rule_id);
|
const encryptedGroupElement = screen.getByTestId(section + encryptedGroupRule.rule_id);
|
||||||
expect(getCheckedRadioForRule(encryptedGroupElement)).toEqual('Off');
|
expect(encryptedGroupElement.querySelector("[aria-label='Off']")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates notification level when changed', async () => {
|
it('updates notification level when changed', async () => {
|
||||||
const component = await getComponentAndWait();
|
await getComponentAndWait();
|
||||||
const section = 'vector_global';
|
const section = 'vector_global';
|
||||||
|
|
||||||
// oneToOneRule is set to 'on'
|
// oneToOneRule is set to 'on'
|
||||||
// and is kind: 'underride'
|
// and is kind: 'underride'
|
||||||
const oneToOneRuleElement = findByTestId(component, section + oneToOneRule.rule_id);
|
const oneToOneRuleElement = screen.getByTestId(section + oneToOneRule.rule_id);
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
// toggle at 0 is 'off'
|
const offToggle = oneToOneRuleElement.querySelector('input[type="radio"]');
|
||||||
const offToggle = oneToOneRuleElement.find('input[type="radio"]').at(0);
|
fireEvent.click(offToggle);
|
||||||
offToggle.simulate('change');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockClient.setPushRuleEnabled).toHaveBeenCalledWith(
|
expect(mockClient.setPushRuleEnabled).toHaveBeenCalledWith(
|
||||||
|
@ -300,4 +296,32 @@ describe('<Notifications />', () => {
|
||||||
'global', 'underride', oneToOneRule.rule_id, StandardActions.ACTION_DONT_NOTIFY);
|
'global', 'underride', oneToOneRule.rule_id, StandardActions.ACTION_DONT_NOTIFY);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("clear all notifications", () => {
|
||||||
|
it("clears all notifications", async () => {
|
||||||
|
const room = new Room("room123", mockClient, "@alice:example.org");
|
||||||
|
mockClient.getRooms.mockReset().mockReturnValue([room]);
|
||||||
|
|
||||||
|
const message = mkMessage({
|
||||||
|
event: true,
|
||||||
|
room: "room123",
|
||||||
|
user: "@alice:example.org",
|
||||||
|
ts: 1,
|
||||||
|
});
|
||||||
|
room.addLiveEvents([message]);
|
||||||
|
room.setUnreadNotificationCount(NotificationCountType.Total, 1);
|
||||||
|
|
||||||
|
const { container } = await getComponentAndWait();
|
||||||
|
const clearNotificationEl = getByTestId(container, "clear-notifications");
|
||||||
|
|
||||||
|
fireEvent.click(clearNotificationEl);
|
||||||
|
|
||||||
|
expect(clearNotificationEl.className).toContain("mx_AccessibleButton_disabled");
|
||||||
|
expect(mockClient.sendReadReceipt).toHaveBeenCalled();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(clearNotificationEl.className).not.toContain("mx_AccessibleButton_disabled");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,157 +1,38 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`<Notifications /> main notification switches email switches renders email switches correctly when email 3pids exist 1`] = `
|
|
||||||
<LabelledToggleSwitch
|
|
||||||
data-test-id="notif-email-switch"
|
|
||||||
disabled={false}
|
|
||||||
key="tester@test.com"
|
|
||||||
label="Enable email notifications for tester@test.com"
|
|
||||||
onChange={[Function]}
|
|
||||||
value={false}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="mx_SettingsFlag"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="mx_SettingsFlag_label"
|
|
||||||
>
|
|
||||||
Enable email notifications for tester@test.com
|
|
||||||
</span>
|
|
||||||
<_default
|
|
||||||
checked={false}
|
|
||||||
disabled={false}
|
|
||||||
onChange={[Function]}
|
|
||||||
title="Enable email notifications for tester@test.com"
|
|
||||||
>
|
|
||||||
<AccessibleTooltipButton
|
|
||||||
aria-checked={false}
|
|
||||||
aria-disabled={false}
|
|
||||||
className="mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
|
||||||
onClick={[Function]}
|
|
||||||
role="switch"
|
|
||||||
title="Enable email notifications for tester@test.com"
|
|
||||||
>
|
|
||||||
<AccessibleButton
|
|
||||||
aria-checked={false}
|
|
||||||
aria-disabled={false}
|
|
||||||
aria-label="Enable email notifications for tester@test.com"
|
|
||||||
className="mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
|
||||||
element="div"
|
|
||||||
onBlur={[Function]}
|
|
||||||
onClick={[Function]}
|
|
||||||
onFocus={[Function]}
|
|
||||||
onMouseLeave={[Function]}
|
|
||||||
onMouseOver={[Function]}
|
|
||||||
role="switch"
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-checked={false}
|
|
||||||
aria-disabled={false}
|
|
||||||
aria-label="Enable email notifications for tester@test.com"
|
|
||||||
className="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
|
||||||
onBlur={[Function]}
|
|
||||||
onClick={[Function]}
|
|
||||||
onFocus={[Function]}
|
|
||||||
onKeyDown={[Function]}
|
|
||||||
onKeyUp={[Function]}
|
|
||||||
onMouseLeave={[Function]}
|
|
||||||
onMouseOver={[Function]}
|
|
||||||
role="switch"
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="mx_ToggleSwitch_ball"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</AccessibleButton>
|
|
||||||
</AccessibleTooltipButton>
|
|
||||||
</_default>
|
|
||||||
</div>
|
|
||||||
</LabelledToggleSwitch>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`<Notifications /> main notification switches renders only enable notifications switch when notifications are disabled 1`] = `
|
exports[`<Notifications /> main notification switches renders only enable notifications switch when notifications are disabled 1`] = `
|
||||||
<Notifications>
|
<div>
|
||||||
<div
|
<div
|
||||||
className="mx_UserNotifSettings"
|
class="mx_UserNotifSettings"
|
||||||
>
|
>
|
||||||
<LabelledToggleSwitch
|
<div
|
||||||
caption="Turn off to disable notifications on all your devices and sessions"
|
class="mx_SettingsFlag"
|
||||||
data-test-id="notif-master-switch"
|
data-testid="notif-master-switch"
|
||||||
disabled={false}
|
|
||||||
label="Enable notifications for this account"
|
|
||||||
onChange={[Function]}
|
|
||||||
value={false}
|
|
||||||
>
|
>
|
||||||
<div
|
<span
|
||||||
className="mx_SettingsFlag"
|
class="mx_SettingsFlag_label"
|
||||||
>
|
>
|
||||||
|
Enable notifications for this account
|
||||||
|
<br />
|
||||||
<span
|
<span
|
||||||
className="mx_SettingsFlag_label"
|
class="mx_Caption"
|
||||||
>
|
>
|
||||||
Enable notifications for this account
|
Turn off to disable notifications on all your devices and sessions
|
||||||
<br />
|
|
||||||
<Caption>
|
|
||||||
<span
|
|
||||||
className="mx_Caption"
|
|
||||||
>
|
|
||||||
Turn off to disable notifications on all your devices and sessions
|
|
||||||
</span>
|
|
||||||
</Caption>
|
|
||||||
</span>
|
</span>
|
||||||
<_default
|
</span>
|
||||||
checked={false}
|
<div
|
||||||
disabled={false}
|
aria-checked="false"
|
||||||
onChange={[Function]}
|
aria-disabled="false"
|
||||||
title="Enable notifications for this account"
|
aria-label="Enable notifications for this account"
|
||||||
>
|
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||||
<AccessibleTooltipButton
|
role="switch"
|
||||||
aria-checked={false}
|
tabindex="0"
|
||||||
aria-disabled={false}
|
>
|
||||||
className="mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
<div
|
||||||
onClick={[Function]}
|
class="mx_ToggleSwitch_ball"
|
||||||
role="switch"
|
/>
|
||||||
title="Enable notifications for this account"
|
|
||||||
>
|
|
||||||
<AccessibleButton
|
|
||||||
aria-checked={false}
|
|
||||||
aria-disabled={false}
|
|
||||||
aria-label="Enable notifications for this account"
|
|
||||||
className="mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
|
||||||
element="div"
|
|
||||||
onBlur={[Function]}
|
|
||||||
onClick={[Function]}
|
|
||||||
onFocus={[Function]}
|
|
||||||
onMouseLeave={[Function]}
|
|
||||||
onMouseOver={[Function]}
|
|
||||||
role="switch"
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-checked={false}
|
|
||||||
aria-disabled={false}
|
|
||||||
aria-label="Enable notifications for this account"
|
|
||||||
className="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
|
||||||
onBlur={[Function]}
|
|
||||||
onClick={[Function]}
|
|
||||||
onFocus={[Function]}
|
|
||||||
onKeyDown={[Function]}
|
|
||||||
onKeyUp={[Function]}
|
|
||||||
onMouseLeave={[Function]}
|
|
||||||
onMouseOver={[Function]}
|
|
||||||
role="switch"
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="mx_ToggleSwitch_ball"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</AccessibleButton>
|
|
||||||
</AccessibleTooltipButton>
|
|
||||||
</_default>
|
|
||||||
</div>
|
</div>
|
||||||
</LabelledToggleSwitch>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Notifications>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -16,15 +16,21 @@ limitations under the License.
|
||||||
|
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { mocked } from "jest-mock";
|
import { mocked } from "jest-mock";
|
||||||
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
localNotificationsAreSilenced,
|
localNotificationsAreSilenced,
|
||||||
getLocalNotificationAccountDataEventType,
|
getLocalNotificationAccountDataEventType,
|
||||||
createLocalNotificationSettingsIfNeeded,
|
createLocalNotificationSettingsIfNeeded,
|
||||||
deviceNotificationSettingsKeys,
|
deviceNotificationSettingsKeys,
|
||||||
|
clearAllNotifications,
|
||||||
} from "../../src/utils/notifications";
|
} from "../../src/utils/notifications";
|
||||||
import SettingsStore from "../../src/settings/SettingsStore";
|
import SettingsStore from "../../src/settings/SettingsStore";
|
||||||
import { getMockClientWithEventEmitter } from "../test-utils/client";
|
import { getMockClientWithEventEmitter } from "../test-utils/client";
|
||||||
|
import { mkMessage, stubClient } from "../test-utils/test-utils";
|
||||||
|
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
|
||||||
|
|
||||||
jest.mock("../../src/settings/SettingsStore");
|
jest.mock("../../src/settings/SettingsStore");
|
||||||
|
|
||||||
|
@ -99,4 +105,61 @@ describe('notifications', () => {
|
||||||
expect(localNotificationsAreSilenced(mockClient)).toBeFalsy();
|
expect(localNotificationsAreSilenced(mockClient)).toBeFalsy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("clearAllNotifications", () => {
|
||||||
|
let client: MatrixClient;
|
||||||
|
let room: Room;
|
||||||
|
let sendReadReceiptSpy;
|
||||||
|
|
||||||
|
const ROOM_ID = "123";
|
||||||
|
const USER_ID = "@bob:example.org";
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
stubClient();
|
||||||
|
client = mocked(MatrixClientPeg.get());
|
||||||
|
room = new Room(ROOM_ID, client, USER_ID);
|
||||||
|
sendReadReceiptSpy = jest.spyOn(client, "sendReadReceipt").mockResolvedValue({});
|
||||||
|
jest.spyOn(client, "getRooms").mockReturnValue([room]);
|
||||||
|
jest.spyOn(SettingsStore, "getValue").mockImplementation((name) => {
|
||||||
|
return name === "sendReadReceipts";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not send any requests if everything has been read", () => {
|
||||||
|
clearAllNotifications(client);
|
||||||
|
expect(sendReadReceiptSpy).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sends unthreaded receipt requests", () => {
|
||||||
|
const message = mkMessage({
|
||||||
|
event: true,
|
||||||
|
room: ROOM_ID,
|
||||||
|
user: USER_ID,
|
||||||
|
ts: 1,
|
||||||
|
});
|
||||||
|
room.addLiveEvents([message]);
|
||||||
|
room.setUnreadNotificationCount(NotificationCountType.Total, 1);
|
||||||
|
|
||||||
|
clearAllNotifications(client);
|
||||||
|
|
||||||
|
expect(sendReadReceiptSpy).toBeCalledWith(message, ReceiptType.Read, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sends private read receipts", () => {
|
||||||
|
const message = mkMessage({
|
||||||
|
event: true,
|
||||||
|
room: ROOM_ID,
|
||||||
|
user: USER_ID,
|
||||||
|
ts: 1,
|
||||||
|
});
|
||||||
|
room.addLiveEvents([message]);
|
||||||
|
room.setUnreadNotificationCount(NotificationCountType.Total, 1);
|
||||||
|
|
||||||
|
jest.spyOn(SettingsStore, "getValue").mockReset().mockReturnValue(false);
|
||||||
|
|
||||||
|
clearAllNotifications(client);
|
||||||
|
|
||||||
|
expect(sendReadReceiptSpy).toBeCalledWith(message, ReceiptType.ReadPrivate, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue