mirror of https://github.com/vector-im/riot-web
Add option to stop sending read receipts (delabs MSC2285: private read receipts) (#8629)
Co-authored-by: Travis Ralston <travisr@matrix.org>pull/28217/head
parent
b61cc4850b
commit
7eaed1a3f8
|
@ -23,8 +23,8 @@ import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
|||
import { Relations } from "matrix-js-sdk/src/models/relations";
|
||||
import { logger } from 'matrix-js-sdk/src/logger';
|
||||
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
|
||||
import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts";
|
||||
import { M_BEACON_INFO } from 'matrix-js-sdk/src/@types/beacon';
|
||||
import { isSupportedReceiptType } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import shouldHideEvent from '../../shouldHideEvent';
|
||||
import { wantsDateSeparator } from '../../DateUtils';
|
||||
|
@ -828,7 +828,11 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
}
|
||||
const receipts: IReadReceiptProps[] = [];
|
||||
room.getReceiptsForEvent(event).forEach((r) => {
|
||||
if (!r.userId || ![ReceiptType.Read, ReceiptType.ReadPrivate].includes(r.type) || r.userId === myUserId) {
|
||||
if (
|
||||
!r.userId ||
|
||||
!isSupportedReceiptType(r.type) ||
|
||||
r.userId === myUserId
|
||||
) {
|
||||
return; // ignore non-read receipts and receipts from self.
|
||||
}
|
||||
if (MatrixClientPeg.get().isUserIgnored(r.userId)) {
|
||||
|
|
|
@ -30,6 +30,7 @@ import { ClientEvent } from "matrix-js-sdk/src/client";
|
|||
import { Thread } from 'matrix-js-sdk/src/models/thread';
|
||||
import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts";
|
||||
import { MatrixError } from 'matrix-js-sdk/src/http-api';
|
||||
import { getPrivateReadReceiptField } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import { Layout } from "../../settings/enums/Layout";
|
||||
|
@ -965,29 +966,35 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
this.lastRMSentEventId = this.state.readMarkerEventId;
|
||||
|
||||
const roomId = this.props.timelineSet.room.roomId;
|
||||
const hiddenRR = SettingsStore.getValue("feature_hidden_read_receipts", roomId);
|
||||
const sendRRs = SettingsStore.getValue("sendReadReceipts", roomId);
|
||||
|
||||
debuglog(
|
||||
`Sending Read Markers for ${this.props.timelineSet.room.roomId}: `,
|
||||
`rm=${this.state.readMarkerEventId} `,
|
||||
`rr=${sendRRs ? lastReadEvent?.getId() : null} `,
|
||||
`prr=${lastReadEvent?.getId()}`,
|
||||
|
||||
debuglog('Sending Read Markers for ',
|
||||
this.props.timelineSet.room.roomId,
|
||||
'rm', this.state.readMarkerEventId,
|
||||
lastReadEvent ? 'rr ' + lastReadEvent.getId() : '',
|
||||
' hidden:' + hiddenRR,
|
||||
);
|
||||
MatrixClientPeg.get().setRoomReadMarkers(
|
||||
roomId,
|
||||
this.state.readMarkerEventId,
|
||||
hiddenRR ? null : lastReadEvent, // Could be null, in which case no RR is sent
|
||||
lastReadEvent, // Could be null, in which case no private RR is sent
|
||||
).catch((e) => {
|
||||
sendRRs ? lastReadEvent : null, // Public read receipt (could be null)
|
||||
lastReadEvent, // Private read receipt (could be null)
|
||||
).catch(async (e) => {
|
||||
// /read_markers API is not implemented on this HS, fallback to just RR
|
||||
if (e.errcode === 'M_UNRECOGNIZED' && lastReadEvent) {
|
||||
return MatrixClientPeg.get().sendReadReceipt(
|
||||
lastReadEvent,
|
||||
hiddenRR ? ReceiptType.ReadPrivate : ReceiptType.Read,
|
||||
).catch((e) => {
|
||||
const privateField = await getPrivateReadReceiptField(MatrixClientPeg.get());
|
||||
if (!sendRRs && !privateField) return;
|
||||
|
||||
try {
|
||||
return await MatrixClientPeg.get().sendReadReceipt(
|
||||
lastReadEvent,
|
||||
sendRRs ? ReceiptType.Read : privateField,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(e);
|
||||
this.lastRRSentEventId = undefined;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
logger.error(e);
|
||||
}
|
||||
|
@ -1575,8 +1582,10 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
const isNodeInView = (node) => {
|
||||
if (node) {
|
||||
const boundingRect = node.getBoundingClientRect();
|
||||
if ((allowPartial && boundingRect.top < wrapperRect.bottom) ||
|
||||
(!allowPartial && boundingRect.bottom < wrapperRect.bottom)) {
|
||||
if (
|
||||
(allowPartial && boundingRect.top <= wrapperRect.bottom) ||
|
||||
(!allowPartial && boundingRect.bottom <= wrapperRect.bottom)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ interface IProps {
|
|||
// XXX: once design replaces all toggles make this the default
|
||||
useCheckbox?: boolean;
|
||||
disabled?: boolean;
|
||||
disabledDescription?: string;
|
||||
hideIfCannotSet?: boolean;
|
||||
onChange?(checked: boolean): void;
|
||||
}
|
||||
|
@ -84,6 +85,13 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
|
|||
: SettingsStore.getDisplayName(this.props.name, this.props.level);
|
||||
const description = SettingsStore.getDescription(this.props.name);
|
||||
|
||||
let disabledDescription: JSX.Element;
|
||||
if (this.props.disabled && this.props.disabledDescription) {
|
||||
disabledDescription = <div className="mx_SettingsFlag_microcopy">
|
||||
{ this.props.disabledDescription }
|
||||
</div>;
|
||||
}
|
||||
|
||||
if (this.props.useCheckbox) {
|
||||
return <StyledCheckbox
|
||||
checked={this.state.value}
|
||||
|
@ -100,6 +108,7 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
|
|||
{ description && <div className="mx_SettingsFlag_microcopy">
|
||||
{ description }
|
||||
</div> }
|
||||
{ disabledDescription }
|
||||
</label>
|
||||
<ToggleSwitch
|
||||
checked={this.state.value}
|
||||
|
|
|
@ -47,7 +47,6 @@ export class LabsSettingToggle extends React.Component<ILabsSettingToggleProps>
|
|||
}
|
||||
|
||||
interface IState {
|
||||
showHiddenReadReceipts: boolean;
|
||||
showJumpToDate: boolean;
|
||||
showExploringPublicSpaces: boolean;
|
||||
}
|
||||
|
@ -58,10 +57,6 @@ export default class LabsUserSettingsTab extends React.Component<{}, IState> {
|
|||
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
||||
cli.doesServerSupportUnstableFeature("org.matrix.msc2285").then((showHiddenReadReceipts) => {
|
||||
this.setState({ showHiddenReadReceipts });
|
||||
});
|
||||
|
||||
cli.doesServerSupportUnstableFeature("org.matrix.msc3030").then((showJumpToDate) => {
|
||||
this.setState({ showJumpToDate });
|
||||
});
|
||||
|
@ -71,7 +66,6 @@ export default class LabsUserSettingsTab extends React.Component<{}, IState> {
|
|||
});
|
||||
|
||||
this.state = {
|
||||
showHiddenReadReceipts: false,
|
||||
showJumpToDate: false,
|
||||
showExploringPublicSpaces: false,
|
||||
};
|
||||
|
@ -121,16 +115,6 @@ export default class LabsUserSettingsTab extends React.Component<{}, IState> {
|
|||
/>,
|
||||
);
|
||||
|
||||
if (this.state.showHiddenReadReceipts) {
|
||||
groups.getOrCreate(LabGroup.Messaging, []).push(
|
||||
<SettingsFlag
|
||||
key="feature_hidden_read_receipts"
|
||||
name="feature_hidden_read_receipts"
|
||||
level={SettingLevel.DEVICE}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.showJumpToDate) {
|
||||
groups.getOrCreate(LabGroup.Messaging, []).push(
|
||||
<SettingsFlag
|
||||
|
|
|
@ -29,6 +29,7 @@ import { UserTab } from "../../../dialogs/UserTab";
|
|||
import { OpenToTabPayload } from "../../../../../dispatcher/payloads/OpenToTabPayload";
|
||||
import { Action } from "../../../../../dispatcher/actions";
|
||||
import SdkConfig from "../../../../../SdkConfig";
|
||||
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
|
||||
import { showUserOnboardingPage } from "../../../user-onboarding/UserOnboardingPage";
|
||||
|
||||
interface IProps {
|
||||
|
@ -36,51 +37,59 @@ interface IProps {
|
|||
}
|
||||
|
||||
interface IState {
|
||||
disablingReadReceiptsSupported: boolean;
|
||||
autocompleteDelay: string;
|
||||
readMarkerInViewThresholdMs: string;
|
||||
readMarkerOutOfViewThresholdMs: string;
|
||||
}
|
||||
|
||||
export default class PreferencesUserSettingsTab extends React.Component<IProps, IState> {
|
||||
static ROOM_LIST_SETTINGS = [
|
||||
private static ROOM_LIST_SETTINGS = [
|
||||
'breadcrumbs',
|
||||
];
|
||||
|
||||
static SPACES_SETTINGS = [
|
||||
private static SPACES_SETTINGS = [
|
||||
"Spaces.allRoomsInHome",
|
||||
];
|
||||
|
||||
static KEYBINDINGS_SETTINGS = [
|
||||
private static KEYBINDINGS_SETTINGS = [
|
||||
'ctrlFForSearch',
|
||||
];
|
||||
|
||||
static COMPOSER_SETTINGS = [
|
||||
private static PRESENCE_SETTINGS = [
|
||||
"sendTypingNotifications",
|
||||
// sendReadReceipts - handled specially due to server needing support
|
||||
];
|
||||
|
||||
private static COMPOSER_SETTINGS = [
|
||||
'MessageComposerInput.autoReplaceEmoji',
|
||||
'MessageComposerInput.useMarkdown',
|
||||
'MessageComposerInput.suggestEmoji',
|
||||
'sendTypingNotifications',
|
||||
'MessageComposerInput.ctrlEnterToSend',
|
||||
'MessageComposerInput.surroundWith',
|
||||
'MessageComposerInput.showStickersButton',
|
||||
'MessageComposerInput.insertTrailingColon',
|
||||
];
|
||||
|
||||
static TIME_SETTINGS = [
|
||||
private static TIME_SETTINGS = [
|
||||
'showTwelveHourTimestamps',
|
||||
'alwaysShowTimestamps',
|
||||
];
|
||||
static CODE_BLOCKS_SETTINGS = [
|
||||
|
||||
private static CODE_BLOCKS_SETTINGS = [
|
||||
'enableSyntaxHighlightLanguageDetection',
|
||||
'expandCodeByDefault',
|
||||
'showCodeLineNumbers',
|
||||
];
|
||||
static IMAGES_AND_VIDEOS_SETTINGS = [
|
||||
|
||||
private static IMAGES_AND_VIDEOS_SETTINGS = [
|
||||
'urlPreviewsEnabled',
|
||||
'autoplayGifs',
|
||||
'autoplayVideo',
|
||||
'showImages',
|
||||
];
|
||||
static TIMELINE_SETTINGS = [
|
||||
|
||||
private static TIMELINE_SETTINGS = [
|
||||
'showTypingNotifications',
|
||||
'showRedactions',
|
||||
'showReadReceipts',
|
||||
|
@ -93,7 +102,8 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
|
|||
'scrollToBottomOnMessageSent',
|
||||
'useOnlyCurrentProfiles',
|
||||
];
|
||||
static GENERAL_SETTINGS = [
|
||||
|
||||
private static GENERAL_SETTINGS = [
|
||||
'promptBeforeInviteUnknownUsers',
|
||||
// Start automatically after startup (electron-only)
|
||||
// Autocomplete delay (niche text box)
|
||||
|
@ -103,6 +113,7 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
|
|||
super(props);
|
||||
|
||||
this.state = {
|
||||
disablingReadReceiptsSupported: false,
|
||||
autocompleteDelay:
|
||||
SettingsStore.getValueAt(SettingLevel.DEVICE, 'autocompleteDelay').toString(10),
|
||||
readMarkerInViewThresholdMs:
|
||||
|
@ -112,6 +123,15 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
|
|||
};
|
||||
}
|
||||
|
||||
public async componentDidMount(): Promise<void> {
|
||||
this.setState({
|
||||
disablingReadReceiptsSupported: (
|
||||
await MatrixClientPeg.get().doesServerSupportUnstableFeature("org.matrix.msc2285.stable") ||
|
||||
await MatrixClientPeg.get().doesServerSupportUnstableFeature("org.matrix.msc2285")
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
private onAutocompleteDelayChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ autocompleteDelay: e.target.value });
|
||||
SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value);
|
||||
|
@ -185,6 +205,20 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
|
|||
{ this.renderGroup(PreferencesUserSettingsTab.TIME_SETTINGS) }
|
||||
</div>
|
||||
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Presence") }</span>
|
||||
<span className="mx_SettingsTab_subsectionText">
|
||||
{ _t("Share your activity and status with others.") }
|
||||
</span>
|
||||
<SettingsFlag
|
||||
disabled={!this.state.disablingReadReceiptsSupported}
|
||||
disabledDescription={_t("Your server doesn't support disabling sending read receipts.")}
|
||||
name="sendReadReceipts"
|
||||
level={SettingLevel.ACCOUNT}
|
||||
/>
|
||||
{ this.renderGroup(PreferencesUserSettingsTab.PRESENCE_SETTINGS) }
|
||||
</div>
|
||||
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Composer") }</span>
|
||||
{ this.renderGroup(PreferencesUserSettingsTab.COMPOSER_SETTINGS) }
|
||||
|
|
|
@ -304,7 +304,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
|||
<div className="mx_SettingsTab_subsectionText">
|
||||
<p>
|
||||
{ _t("Share anonymous data to help us identify issues. Nothing personal. " +
|
||||
"No third parties.") }
|
||||
"No third parties.") }
|
||||
</p>
|
||||
<AccessibleButton
|
||||
kind="link"
|
||||
|
|
|
@ -898,7 +898,7 @@
|
|||
"Use new room breadcrumbs": "Use new room breadcrumbs",
|
||||
"Right panel stays open (defaults to room member list)": "Right panel stays open (defaults to room member list)",
|
||||
"Jump to date (adds /jumptodate and jump to date headers)": "Jump to date (adds /jumptodate and jump to date headers)",
|
||||
"Don't send read receipts": "Don't send read receipts",
|
||||
"Send read receipts": "Send read receipts",
|
||||
"Right-click message context menu": "Right-click message context menu",
|
||||
"Location sharing - pin drop": "Location sharing - pin drop",
|
||||
"Live Location Sharing (temporary implementation: locations persist in room history)": "Live Location Sharing (temporary implementation: locations persist in room history)",
|
||||
|
@ -1538,6 +1538,9 @@
|
|||
"Keyboard shortcuts": "Keyboard shortcuts",
|
||||
"To view all keyboard shortcuts, <a>click here</a>.": "To view all keyboard shortcuts, <a>click here</a>.",
|
||||
"Displaying time": "Displaying time",
|
||||
"Presence": "Presence",
|
||||
"Share your activity and status with others.": "Share your activity and status with others.",
|
||||
"Your server doesn't support disabling sending read receipts.": "Your server doesn't support disabling sending read receipts.",
|
||||
"Composer": "Composer",
|
||||
"Code blocks": "Code blocks",
|
||||
"Images, GIFs and videos": "Images, GIFs and videos",
|
||||
|
|
|
@ -401,10 +401,10 @@ export const SETTINGS: {[setting: string]: ISetting} = {
|
|||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||
default: null,
|
||||
},
|
||||
"feature_hidden_read_receipts": {
|
||||
supportedLevels: LEVELS_FEATURE,
|
||||
displayName: _td("Don't send read receipts"),
|
||||
default: false,
|
||||
"sendReadReceipts": {
|
||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||
displayName: _td("Send read receipts"),
|
||||
default: true,
|
||||
},
|
||||
"feature_message_right_click_context_menu": {
|
||||
isFeature: true,
|
||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts";
|
||||
import { isSupportedReceiptType } from "matrix-js-sdk/src/utils";
|
||||
|
||||
/**
|
||||
* Determines if a read receipt update event includes the client's own user.
|
||||
|
@ -27,13 +27,10 @@ import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts";
|
|||
export function readReceiptChangeIsFor(event: MatrixEvent, client: MatrixClient): boolean {
|
||||
const myUserId = client.getUserId();
|
||||
for (const eventId of Object.keys(event.getContent())) {
|
||||
const readReceiptUsers = Object.keys(event.getContent()[eventId][ReceiptType.Read] || {});
|
||||
if (readReceiptUsers.includes(myUserId)) {
|
||||
return true;
|
||||
}
|
||||
const readPrivateReceiptUsers = Object.keys(event.getContent()[eventId][ReceiptType.ReadPrivate] || {});
|
||||
if (readPrivateReceiptUsers.includes(myUserId)) {
|
||||
return true;
|
||||
for (const [receiptType, receipt] of Object.entries(event.getContent()[eventId])) {
|
||||
if (!isSupportedReceiptType(receiptType)) continue;
|
||||
|
||||
if (Object.keys((receipt || {})).includes(myUserId)) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,28 +17,74 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
// eslint-disable-next-line deprecate/import
|
||||
import { mount, ReactWrapper } from "enzyme";
|
||||
import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline";
|
||||
import { MessageEvent } from 'matrix-events-sdk';
|
||||
import { EventTimelineSet, MatrixEvent, PendingEventOrdering, Room } from 'matrix-js-sdk/src/matrix';
|
||||
import {
|
||||
EventTimelineSet,
|
||||
EventType,
|
||||
MatrixEvent,
|
||||
PendingEventOrdering,
|
||||
Room,
|
||||
} from 'matrix-js-sdk/src/matrix';
|
||||
import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts";
|
||||
import { render, RenderResult } from "@testing-library/react";
|
||||
|
||||
import { stubClient } from "../../test-utils";
|
||||
import { mkRoom, stubClient } from "../../test-utils";
|
||||
import TimelinePanel from '../../../src/components/structures/TimelinePanel';
|
||||
import { MatrixClientPeg } from '../../../src/MatrixClientPeg';
|
||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||
|
||||
function newReceipt(eventId: string, userId: string, readTs: number, fullyReadTs: number): MatrixEvent {
|
||||
const newReceipt = (eventId: string, userId: string, readTs: number, fullyReadTs: number): MatrixEvent => {
|
||||
const receiptContent = {
|
||||
[eventId]: {
|
||||
"m.read": { [userId]: { ts: readTs } },
|
||||
"org.matrix.msc2285.read.private": { [userId]: { ts: readTs } },
|
||||
"m.fully_read": { [userId]: { ts: fullyReadTs } },
|
||||
[ReceiptType.Read]: { [userId]: { ts: readTs } },
|
||||
[ReceiptType.ReadPrivate]: { [userId]: { ts: readTs } },
|
||||
[ReceiptType.FullyRead]: { [userId]: { ts: fullyReadTs } },
|
||||
},
|
||||
};
|
||||
return new MatrixEvent({ content: receiptContent, type: "m.receipt" });
|
||||
}
|
||||
};
|
||||
|
||||
const renderPanel = (room: Room, events: MatrixEvent[]): RenderResult => {
|
||||
const timelineSet = { room: room as Room } as EventTimelineSet;
|
||||
const timeline = new EventTimeline(timelineSet);
|
||||
events.forEach((event) => timeline.addEvent(event, true));
|
||||
timelineSet.getLiveTimeline = () => timeline;
|
||||
timelineSet.getTimelineForEvent = () => timeline;
|
||||
timelineSet.getPendingEvents = () => events;
|
||||
timelineSet.room.getEventReadUpTo = () => events[1].getId();
|
||||
|
||||
return render(
|
||||
<TimelinePanel
|
||||
timelineSet={timelineSet}
|
||||
manageReadReceipts
|
||||
sendReadReceiptOnLoad
|
||||
/>,
|
||||
);
|
||||
};
|
||||
|
||||
const mockEvents = (room: Room, count = 2): MatrixEvent[] => {
|
||||
const events = [];
|
||||
for (let index = 0; index < count; index++) {
|
||||
events.push(new MatrixEvent({
|
||||
room_id: room.roomId,
|
||||
event_id: `event_${index}`,
|
||||
type: EventType.RoomMessage,
|
||||
user_id: "userId",
|
||||
content: MessageEvent.from(`Event${index}`).serialize().content,
|
||||
}));
|
||||
}
|
||||
|
||||
return events;
|
||||
};
|
||||
|
||||
describe('TimelinePanel', () => {
|
||||
describe('Read Receipts and Markers', () => {
|
||||
it('Forgets the read marker when asked to', () => {
|
||||
stubClient();
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
});
|
||||
|
||||
describe('read receipts and markers', () => {
|
||||
it('should forget the read marker when asked to', () => {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const readMarkersSent = [];
|
||||
|
||||
|
@ -95,5 +141,35 @@ describe('TimelinePanel', () => {
|
|||
// We sent off a read marker for the new event
|
||||
expect(readMarkersSent).toEqual(["ev1"]);
|
||||
});
|
||||
|
||||
it("sends public read receipt when enabled", () => {
|
||||
const client = MatrixClientPeg.get();
|
||||
const room = mkRoom(client, "roomId");
|
||||
const events = mockEvents(room);
|
||||
|
||||
const getValueCopy = SettingsStore.getValue;
|
||||
SettingsStore.getValue = jest.fn().mockImplementation((name: string) => {
|
||||
if (name === "sendReadReceipts") return true;
|
||||
return getValueCopy(name);
|
||||
});
|
||||
|
||||
renderPanel(room, events);
|
||||
expect(client.setRoomReadMarkers).toHaveBeenCalledWith(room.roomId, null, events[0], events[0]);
|
||||
});
|
||||
|
||||
it("does not send public read receipt when enabled", () => {
|
||||
const client = MatrixClientPeg.get();
|
||||
const room = mkRoom(client, "roomId");
|
||||
const events = mockEvents(room);
|
||||
|
||||
const getValueCopy = SettingsStore.getValue;
|
||||
SettingsStore.getValue = jest.fn().mockImplementation((name: string) => {
|
||||
if (name === "sendReadReceipts") return false;
|
||||
return getValueCopy(name);
|
||||
});
|
||||
|
||||
renderPanel(room, events);
|
||||
expect(client.setRoomReadMarkers).toHaveBeenCalledWith(room.roomId, null, null, events[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -127,6 +127,7 @@ export function createTestClient(): MatrixClient {
|
|||
setAccountData: jest.fn(),
|
||||
setRoomAccountData: jest.fn(),
|
||||
setRoomTopic: jest.fn(),
|
||||
setRoomReadMarkers: jest.fn().mockResolvedValue({}),
|
||||
sendTyping: jest.fn().mockResolvedValue({}),
|
||||
sendMessage: jest.fn().mockResolvedValue({}),
|
||||
sendStateEvent: jest.fn().mockResolvedValue(undefined),
|
||||
|
@ -361,6 +362,8 @@ export function mkStubRoom(roomId: string = null, name: string, client: MatrixCl
|
|||
getMembersWithMembership: jest.fn().mockReturnValue([]),
|
||||
getJoinedMembers: jest.fn().mockReturnValue([]),
|
||||
getJoinedMemberCount: jest.fn().mockReturnValue(1),
|
||||
getInvitedAndJoinedMemberCount: jest.fn().mockReturnValue(1),
|
||||
setUnreadNotificationCount: jest.fn(),
|
||||
getMembers: jest.fn().mockReturnValue([]),
|
||||
getPendingEvents: () => [],
|
||||
getLiveTimeline: jest.fn().mockReturnValue(stubTimeline),
|
||||
|
@ -407,6 +410,7 @@ export function mkStubRoom(roomId: string = null, name: string, client: MatrixCl
|
|||
myUserId: client?.getUserId(),
|
||||
canInvite: jest.fn(),
|
||||
getThreads: jest.fn().mockReturnValue([]),
|
||||
eventShouldLiveIn: jest.fn().mockReturnValue({}),
|
||||
} as unknown as Room;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue