Compare commits

...

8 Commits

Author SHA1 Message Date
Timo
656e4aed1b update js sdk to matrix-js-sdk/element-call-nov-preview 2024-11-12 15:21:16 +01:00
Timo
9cde9af706 Merge remote-tracking branch 'robintown/widget-feed-state' into element-call-nov-preview 2024-11-12 15:19:27 +01:00
Robin
54765b4473 Only feed state events to widgets if we believe they update the state
As part of the effort to make room state more reliable, we're looking at changing how state is communicated to widgets. Rather than passing all state events from the timeline through (and forcing the widget to assume that they belong to the server's view of the room state), let's try passing only those state events through that we believe actually belong to the server's view of the room state. When combined with a matrix-js-sdk that supports MSC4222, this improves the reliability of state tracking for widgets.

We would like to later explore a solution that has a separate 'state update' widget action in addition to the generic 'incoming timeline event' widget action, to make the widget API better reflect the shape of the data sources that drive it (like the sync responses of Simplified Sliding Sync or MSC4222).
2024-11-11 17:46:49 -05:00
Michael Telatynski
f3d0fc4d68 Merge remote-tracking branch 'origin/dbkr/stateafter' into dbkr/stateafter 2024-11-08 16:42:05 +00:00
Michael Telatynski
0b636aec4b Improve coverage
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-08 16:41:57 +00:00
Michael Telatynski
cb0d72fc2f Merge branch 'develop' into dbkr/stateafter 2024-11-08 09:58:00 +00:00
Michael Telatynski
931edd7419 Merge branch 'develop' into dbkr/stateafter 2024-11-06 15:42:28 +00:00
Michael Telatynski
044eaf7eb5 Update calls to addEventToTimeline and addLiveEvents for new signature
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-06 14:55:21 +00:00
38 changed files with 249 additions and 145 deletions

View File

@@ -126,7 +126,7 @@
"maplibre-gl": "^2.0.0",
"matrix-encrypt-attachment": "^1.0.3",
"matrix-events-sdk": "0.0.1",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#8e65c13d5019eb215fa99c86ddef463d9c383115",
"matrix-widget-api": "^1.10.0",
"memoize-one": "^6.0.0",
"oidc-client-ts": "^3.0.1",

View File

@@ -104,7 +104,11 @@ class FilePanel extends React.Component<IProps, IState> {
}
if (!this.state.timelineSet.eventIdToTimeline(ev.getId()!)) {
this.state.timelineSet.addEventToTimeline(ev, timeline, false);
this.state.timelineSet.addEventToTimeline(ev, timeline, {
fromCache: false,
addToState: false,
toStartOfTimeline: false,
});
}
}

View File

@@ -820,7 +820,11 @@ export default class EventIndex extends EventEmitter {
// Add the events to the timeline of the file panel.
matrixEvents.forEach((e) => {
if (!timelineSet.eventIdToTimeline(e.getId()!)) {
timelineSet.addEventToTimeline(e, timeline, direction == EventTimeline.BACKWARDS);
timelineSet.addEventToTimeline(e, timeline, {
toStartOfTimeline: direction == EventTimeline.BACKWARDS,
fromCache: false,
addToState: false,
});
}
});

View File

@@ -6,7 +6,14 @@
* Please see LICENSE files in the repository root for full details.
*/
import { Room, MatrixEvent, MatrixEventEvent, MatrixClient, ClientEvent } from "matrix-js-sdk/src/matrix";
import {
Room,
MatrixEvent,
MatrixEventEvent,
MatrixClient,
ClientEvent,
RoomStateEvent,
} from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import {
ClientWidgetApi,
@@ -333,6 +340,7 @@ export class StopGapWidget extends EventEmitter {
// Attach listeners for feeding events - the underlying widget classes handle permissions for us
this.client.on(ClientEvent.Event, this.onEvent);
this.client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
this.client.on(RoomStateEvent.Events, this.onStateEvent);
this.client.on(ClientEvent.ToDeviceEvent, this.onToDeviceEvent);
this.messaging.on(
@@ -463,15 +471,33 @@ export class StopGapWidget extends EventEmitter {
this.client.off(ClientEvent.Event, this.onEvent);
this.client.off(MatrixEventEvent.Decrypted, this.onEventDecrypted);
this.client.off(RoomStateEvent.Events, this.onStateEvent);
this.client.off(ClientEvent.ToDeviceEvent, this.onToDeviceEvent);
}
private onEvent = (ev: MatrixEvent): void => {
this.client.decryptEventIfNeeded(ev);
this.feedEvent(ev);
// Only process non-state events here; we don't want to confuse the
// widget with a state event from the timeline that might not have
// actually updated the room's state
if (!ev.isState()) this.handleEvent(ev);
};
private onEventDecrypted = (ev: MatrixEvent): void => {
this.handleEvent(ev);
};
private onStateEvent = (ev: MatrixEvent): void => {
// State events get to skip all the checks of handleEvent and be fed
// directly to the widget. When it comes to state events, we don't care
// so much about getting the order and contents of the timeline right as
// we care about state updates reliably getting through to the widget so
// that it sees the same state as what the server calculated.
// TODO: We can provide widgets with a more complete timeline stream
// while also getting state updates right if we create a separate widget
// action for communicating state deltas, similar to how the 'state'
// sections of sync responses in Simplified Sliding Sync and MSC4222
// work.
this.feedEvent(ev);
};
@@ -544,8 +570,7 @@ export class StopGapWidget extends EventEmitter {
return false;
}
private feedEvent(ev: MatrixEvent): void {
if (this.messaging === null) return;
private handleEvent(ev: MatrixEvent): void {
if (
// If we had decided earlier to feed this event to the widget, but
// it just wasn't ready, give it another try
@@ -573,11 +598,16 @@ export class StopGapWidget extends EventEmitter {
if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) {
this.eventsToFeed.add(ev);
} else {
const raw = ev.getEffectiveEvent();
this.messaging.feedEvent(raw as IRoomEvent, this.eventListenerRoomId!).catch((e) => {
logger.error("Error sending event to widget: ", e);
});
this.feedEvent(ev);
}
}
}
private feedEvent(ev: MatrixEvent): void {
if (this.messaging === null) return;
const raw = ev.getEffectiveEvent();
this.messaging.feedEvent(raw as IRoomEvent, this.eventListenerRoomId!).catch((e) => {
logger.error("Error sending event to widget: ", e);
});
}
}

View File

@@ -109,7 +109,7 @@ export async function createDmLocalRoom(client: MatrixClient, targets: Member[])
localRoom.targets = targets;
localRoom.updateMyMembership(KnownMembership.Join);
localRoom.addLiveEvents(events);
localRoom.addLiveEvents(events, { addToState: true });
localRoom.currentState.setStateEvents(events);
localRoom.name = localRoom.getDefaultRoomName(client.getUserId()!);
client.store.storeRoom(localRoom);

View File

@@ -44,17 +44,20 @@ export class MockedCall extends Call {
}
public static create(room: Room, id: string) {
room.addLiveEvents([
mkEvent({
event: true,
type: this.EVENT_TYPE,
room: room.roomId,
user: "@alice:example.org",
content: { "m.type": "m.video", "m.intent": "m.prompt" },
skey: id,
ts: Date.now(),
}),
]);
room.addLiveEvents(
[
mkEvent({
event: true,
type: this.EVENT_TYPE,
room: room.roomId,
user: "@alice:example.org",
content: { "m.type": "m.video", "m.intent": "m.prompt" },
skey: id,
ts: Date.now(),
}),
],
{ addToState: true },
);
// @ts-ignore deliberately calling a private method
// Let CallStore know that a call might now exist
CallStore.instance.updateRoom(room);
@@ -81,17 +84,20 @@ export class MockedCall extends Call {
public destroy() {
// Terminate the call for good measure
this.room.addLiveEvents([
mkEvent({
event: true,
type: MockedCall.EVENT_TYPE,
room: this.room.roomId,
user: "@alice:example.org",
content: { ...this.event.getContent(), "m.terminated": "Call ended" },
skey: this.widget.id,
ts: Date.now(),
}),
]);
this.room.addLiveEvents(
[
mkEvent({
event: true,
type: MockedCall.EVENT_TYPE,
room: this.room.roomId,
user: "@alice:example.org",
content: { ...this.event.getContent(), "m.terminated": "Call ended" },
skey: this.widget.id,
ts: Date.now(),
}),
],
{ addToState: true },
);
super.destroy();
}

View File

@@ -157,6 +157,6 @@ export const populateThread = async ({
// that it is already loaded, and send the events again to the room
// so they are added to the thread timeline.
ret.thread.initialEventsFetched = true;
await room.addLiveEvents(ret.events);
await room.addLiveEvents(ret.events, { addToState: false });
return ret;
};

View File

@@ -624,8 +624,7 @@ describe("Notifier", () => {
content: { body: "this is a thread root" },
}),
testRoom.threadsTimelineSets[0]!.getLiveTimeline(),
false,
false,
{ toStartOfTimeline: false, fromCache: false, addToState: true },
);
expect(fn).not.toHaveBeenCalled();

View File

@@ -147,7 +147,7 @@ describe("RoomNotifs test", () => {
const itShouldCountPredecessorHighlightWhenThereIsAPredecessorInTheCreateEvent = (): void => {
it("and there is a predecessor in the create event, it should count predecessor highlight", () => {
room.addLiveEvents([mkCreateEvent(OLD_ROOM_ID)]);
room.addLiveEvents([mkCreateEvent(OLD_ROOM_ID)], { addToState: true });
expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(8);
expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, false)).toBe(7);
@@ -157,7 +157,7 @@ describe("RoomNotifs test", () => {
const itShouldCountPredecessorHighlightWhenThereIsAPredecessorEvent = (): void => {
it("and there is a predecessor event, it should count predecessor highlight", () => {
client.getVisibleRooms();
room.addLiveEvents([mkCreateEvent(OLD_ROOM_ID)]);
room.addLiveEvents([mkCreateEvent(OLD_ROOM_ID)], { addToState: true });
upsertRoomStateEvents(room, [mkPredecessorEvent(OLD_ROOM_ID)]);
expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(8);
@@ -185,7 +185,7 @@ describe("RoomNotifs test", () => {
itShouldCountPredecessorHighlightWhenThereIsAPredecessorEvent();
it("and there is only a predecessor event, it should not count predecessor highlight", () => {
room.addLiveEvents([mkCreateEvent()]);
room.addLiveEvents([mkCreateEvent()], { addToState: true });
upsertRoomStateEvents(room, [mkPredecessorEvent(OLD_ROOM_ID)]);
expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(2);
@@ -204,7 +204,7 @@ describe("RoomNotifs test", () => {
itShouldCountPredecessorHighlightWhenThereIsAPredecessorEvent();
it("and there is only a predecessor event, it should count predecessor highlight", () => {
room.addLiveEvents([mkCreateEvent()]);
room.addLiveEvents([mkCreateEvent()], { addToState: true });
upsertRoomStateEvents(room, [mkPredecessorEvent(OLD_ROOM_ID)]);
expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(8);
@@ -212,7 +212,7 @@ describe("RoomNotifs test", () => {
});
it("and there is an unknown room in the predecessor event, it should not count predecessor highlight", () => {
room.addLiveEvents([mkCreateEvent()]);
room.addLiveEvents([mkCreateEvent()], { addToState: true });
upsertRoomStateEvents(room, [mkPredecessorEvent("!unknon:example.com")]);
expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(2);

View File

@@ -138,7 +138,7 @@ describe("Unread", () => {
room: roomId,
content: {},
});
room.addLiveEvents([event]);
room.addLiveEvents([event], { addToState: true });
// Don't care about the code path of hidden events.
mocked(haveRendererForEvent).mockClear().mockReturnValue(true);
@@ -157,7 +157,7 @@ describe("Unread", () => {
content: {},
});
// Only for timeline events.
room.addLiveEvents([event]);
room.addLiveEvents([event], { addToState: true });
expect(doesRoomHaveUnreadMessages(room, false)).toBe(false);
});
@@ -201,7 +201,7 @@ describe("Unread", () => {
content: {},
});
// Only for timeline events.
room.addLiveEvents([event2]);
room.addLiveEvents([event2], { addToState: true });
expect(doesRoomHaveUnreadMessages(room, false)).toBe(true);
});
@@ -403,7 +403,7 @@ describe("Unread", () => {
redactedEvent.makeRedacted(redactedEvent, room);
console.log("Event Id", redactedEvent.getId());
// Only for timeline events.
room.addLiveEvents([redactedEvent]);
room.addLiveEvents([redactedEvent], { addToState: true });
expect(doesRoomHaveUnreadMessages(room, true)).toBe(true);
expect(logger.warn).toHaveBeenCalledWith(
@@ -448,7 +448,7 @@ describe("Unread", () => {
room: roomId,
content: {},
});
room.addLiveEvents([event]);
room.addLiveEvents([event], { addToState: true });
});
it("an unthreaded receipt for the event makes the room read", () => {
@@ -502,7 +502,7 @@ describe("Unread", () => {
ts: 100,
currentUserId: myId,
});
room.addLiveEvents(events);
room.addLiveEvents(events, { addToState: true });
threadEvent = events[1];
});
@@ -555,7 +555,7 @@ describe("Unread", () => {
room: roomId,
content: {},
});
room.addLiveEvents([event]);
room.addLiveEvents([event], { addToState: true });
// It still returns false
expect(doesRoomHaveUnreadThreads(room)).toBe(false);

View File

@@ -7,13 +7,13 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { EventTimelineSet, PendingEventOrdering, Room } from "matrix-js-sdk/src/matrix";
import { EventTimelineSet, PendingEventOrdering, Room, RoomEvent } from "matrix-js-sdk/src/matrix";
import { screen, render, waitFor } from "jest-matrix-react";
import { mocked } from "jest-mock";
import FilePanel from "../../../../src/components/structures/FilePanel";
import ResizeNotifier from "../../../../src/utils/ResizeNotifier";
import { stubClient } from "../../../test-utils";
import { mkEvent, stubClient } from "../../../test-utils";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
jest.mock("matrix-js-sdk/src/matrix", () => ({
@@ -47,4 +47,43 @@ describe("FilePanel", () => {
});
expect(asFragment()).toMatchSnapshot();
});
describe("addEncryptedLiveEvent", () => {
it("should add file msgtype event to filtered timelineSet", async () => {
const cli = MatrixClientPeg.safeGet();
const room = new Room("!room:server", cli, cli.getSafeUserId(), {
pendingEventOrdering: PendingEventOrdering.Detached,
});
cli.reEmitter.reEmit(room, [RoomEvent.Timeline]);
const timelineSet = new EventTimelineSet(room);
room.getOrCreateFilteredTimelineSet = jest.fn().mockReturnValue(timelineSet);
mocked(cli.getRoom).mockReturnValue(room);
let filePanel: FilePanel | null;
render(
<FilePanel
roomId={room.roomId}
onClose={jest.fn()}
resizeNotifier={new ResizeNotifier()}
ref={(ref) => (filePanel = ref)}
/>,
);
await screen.findByText("No files visible in this room");
const event = mkEvent({
type: "m.room.message",
user: cli.getSafeUserId(),
room: room.roomId,
content: {
body: "hello",
url: "mxc://matrix.org/1234",
msgtype: "m.file",
},
event: true,
});
filePanel!.addEncryptedLiveEvent(event);
expect(timelineSet.getLiveTimeline().getEvents()).toContain(event);
});
});
});

View File

@@ -249,15 +249,19 @@ describe("RoomView", () => {
cli.isRoomEncrypted.mockReturnValue(true);
// and fake an encryption event into the room to prompt it to re-check
room.addLiveEvents([
new MatrixEvent({
type: "m.room.encryption",
sender: cli.getUserId()!,
content: {},
event_id: "someid",
room_id: room.roomId,
}),
]);
room.addLiveEvents(
[
new MatrixEvent({
type: "m.room.encryption",
sender: cli.getUserId()!,
content: {},
event_id: "someid",
room_id: room.roomId,
state_key: "",
}),
],
{ addToState: true },
);
// URL previews should now be disabled
expect(roomViewInstance.state.showUrlPreview).toBe(false);
@@ -440,7 +444,7 @@ describe("RoomView", () => {
skey: id,
ts,
});
room.addLiveEvents([widgetEvent]);
room.addLiveEvents([widgetEvent], { addToState: true });
room.currentState.setStateEvents([widgetEvent]);
cli.emit(RoomStateEvent.Events, widgetEvent, room.currentState, null);
await flushPromises();

View File

@@ -209,11 +209,11 @@ describe("ThreadPanel", () => {
return event ? Promise.resolve(event) : Promise.reject();
});
const [allThreads, myThreads] = room.threadsTimelineSets;
allThreads!.addLiveEvent(otherThread.rootEvent);
allThreads!.addLiveEvent(mixedThread.rootEvent);
allThreads!.addLiveEvent(ownThread.rootEvent);
myThreads!.addLiveEvent(mixedThread.rootEvent);
myThreads!.addLiveEvent(ownThread.rootEvent);
allThreads!.addLiveEvent(otherThread.rootEvent, { addToState: true });
allThreads!.addLiveEvent(mixedThread.rootEvent, { addToState: true });
allThreads!.addLiveEvent(ownThread.rootEvent, { addToState: true });
myThreads!.addLiveEvent(mixedThread.rootEvent, { addToState: true });
myThreads!.addLiveEvent(ownThread.rootEvent, { addToState: true });
let events: EventData[] = [];
const renderResult = render(<TestThreadPanel />);
@@ -259,7 +259,7 @@ describe("ThreadPanel", () => {
return event ? Promise.resolve(event) : Promise.reject();
});
const [allThreads] = room.threadsTimelineSets;
allThreads!.addLiveEvent(otherThread.rootEvent);
allThreads!.addLiveEvent(otherThread.rootEvent, { addToState: true });
let events: EventData[] = [];
const renderResult = render(<TestThreadPanel />);

View File

@@ -66,7 +66,7 @@ const mkTimeline = (room: Room, events: MatrixEvent[]): [EventTimeline, EventTim
getPendingEvents: () => [] as MatrixEvent[],
} as unknown as EventTimelineSet;
const timeline = new EventTimeline(timelineSet);
events.forEach((event) => timeline.addEvent(event, { toStartOfTimeline: false }));
events.forEach((event) => timeline.addEvent(event, { toStartOfTimeline: false, addToState: true }));
return [timeline, timelineSet];
};
@@ -150,9 +150,11 @@ const setupPagination = (
mocked(client).paginateEventTimeline.mockImplementation(async (tl, { backwards }) => {
if (tl === timeline) {
if (backwards) {
forEachRight(previousPage ?? [], (event) => tl.addEvent(event, { toStartOfTimeline: true }));
forEachRight(previousPage ?? [], (event) =>
tl.addEvent(event, { toStartOfTimeline: true, addToState: true }),
);
} else {
(nextPage ?? []).forEach((event) => tl.addEvent(event, { toStartOfTimeline: false }));
(nextPage ?? []).forEach((event) => tl.addEvent(event, { toStartOfTimeline: false, addToState: true }));
}
// Prevent any further pagination attempts in this direction
tl.setPaginationToken(null, backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS);
@@ -256,7 +258,7 @@ describe("TimelinePanel", () => {
describe("and reading the timeline", () => {
beforeEach(async () => {
await renderTimelinePanel();
timelineSet.addLiveEvent(ev1, {});
timelineSet.addLiveEvent(ev1, { addToState: true });
await flushPromises();
// @ts-ignore
@@ -285,11 +287,11 @@ describe("TimelinePanel", () => {
});
it("and forgetting the read markers, should send the stored marker again", async () => {
timelineSet.addLiveEvent(ev2, {});
timelineSet.addLiveEvent(ev2, { addToState: true });
// Add the event to the room as well as the timeline, so we can find it when we
// call findEventById in getEventReadUpTo. This is odd because in our test
// setup, timelineSet is not actually the timelineSet of the room.
await room.addLiveEvents([ev2], {});
await room.addLiveEvents([ev2], { addToState: true });
room.addEphemeralEvents([newReceipt(ev2.getId()!, userId, 222, 200)]);
await timelinePanel.forgetReadMarker();
expect(client.setRoomReadMarkers).toHaveBeenCalledWith(roomId, ev2.getId());
@@ -315,7 +317,7 @@ describe("TimelinePanel", () => {
it("should send a fully read marker and a private receipt", async () => {
await renderTimelinePanel();
timelineSet.addLiveEvent(ev1, {});
timelineSet.addLiveEvent(ev1, { addToState: true });
await flushPromises();
// @ts-ignore
@@ -361,7 +363,7 @@ describe("TimelinePanel", () => {
it("should send receipts but no fully_read when reading the thread timeline", async () => {
await renderTimelinePanel();
timelineSet.addLiveEvent(threadEv1, {});
timelineSet.addLiveEvent(threadEv1, { addToState: true });
await flushPromises();
// @ts-ignore
@@ -871,7 +873,9 @@ describe("TimelinePanel", () => {
// @ts-ignore
thread.fetchEditsWhereNeeded = () => Promise.resolve();
await thread.addEvent(reply1, false, true);
await allThreads.getLiveTimeline().addEvent(thread.rootEvent!, { toStartOfTimeline: true });
await allThreads
.getLiveTimeline()
.addEvent(thread.rootEvent!, { toStartOfTimeline: true, addToState: true });
const replyToEvent = jest.spyOn(thread, "replyToEvent", "get");
const dom = render(
@@ -907,7 +911,9 @@ describe("TimelinePanel", () => {
// @ts-ignore
realThread.fetchEditsWhereNeeded = () => Promise.resolve();
await realThread.addEvent(reply1, true);
await allThreads.getLiveTimeline().addEvent(realThread.rootEvent!, { toStartOfTimeline: true });
await allThreads
.getLiveTimeline()
.addEvent(realThread.rootEvent!, { toStartOfTimeline: true, addToState: true });
const replyToEvent = jest.spyOn(realThread, "replyToEvent", "get");
// @ts-ignore
@@ -968,7 +974,9 @@ describe("TimelinePanel", () => {
events.push(rootEvent);
events.forEach((event) => timelineSet.getLiveTimeline().addEvent(event, { toStartOfTimeline: true }));
events.forEach((event) =>
timelineSet.getLiveTimeline().addEvent(event, { toStartOfTimeline: true, addToState: true }),
);
const roomMembership = mkMembership({
mship: KnownMembership.Join,
@@ -988,7 +996,10 @@ describe("TimelinePanel", () => {
jest.spyOn(roomState, "getMember").mockReturnValue(member);
jest.spyOn(timelineSet.getLiveTimeline(), "getState").mockReturnValue(roomState);
timelineSet.addEventToTimeline(roomMembership, timelineSet.getLiveTimeline(), { toStartOfTimeline: false });
timelineSet.addEventToTimeline(roomMembership, timelineSet.getLiveTimeline(), {
toStartOfTimeline: false,
addToState: true,
});
for (const event of events) {
jest.spyOn(event, "isDecryptionFailure").mockReturnValue(true);

View File

@@ -127,7 +127,7 @@ describe("RoomGeneralContextMenu", () => {
user: "@user:id",
ts: 1000,
});
room.addLiveEvents([event], {});
room.addLiveEvents([event], { addToState: true });
const { container } = getComponent({});

View File

@@ -141,16 +141,19 @@ describe("InviteDialog", () => {
jest.clearAllMocks();
room = new Room(roomId, mockClient, mockClient.getSafeUserId());
room.addLiveEvents([
mkMessage({
msg: "Hello",
relatesTo: undefined,
event: true,
room: roomId,
user: mockClient.getSafeUserId(),
ts: Date.now(),
}),
]);
room.addLiveEvents(
[
mkMessage({
msg: "Hello",
relatesTo: undefined,
event: true,
room: roomId,
user: mockClient.getSafeUserId(),
ts: Date.now(),
}),
],
{ addToState: true },
);
room.currentState.setStateEvents([
mkRoomCreateEvent(bobId, roomId),
mkMembership({

View File

@@ -86,7 +86,7 @@ describe("<Pill>", () => {
room: room1Id,
msg: "Room 1 Message",
});
room1.addLiveEvents([room1Message]);
room1.addLiveEvents([room1Message], { addToState: true });
room2 = new Room(room2Id, client, user1Id);
room2.currentState.setStateEvents([mkRoomMemberJoinEvent(user2Id, room2Id)]);

View File

@@ -41,7 +41,7 @@ describe("<RoomTopic/>", () => {
ts: 123,
event: true,
});
room.addLiveEvents([topicEvent]);
room.addLiveEvents([topicEvent], { addToState: true });
return room;
}

View File

@@ -165,7 +165,7 @@ describe("UnreadNotificationBadge", () => {
},
ts: 5,
});
room.addLiveEvents([event]);
room.addLiveEvents([event], { addToState: true });
const { container } = render(getComponent(THREAD_ID));
expect(container.querySelector(".mx_NotificationBadge_dot")).toBeTruthy();

View File

@@ -579,7 +579,7 @@ describe("RoomHeader", () => {
state_key: "",
room_id: room.roomId,
});
room.addLiveEvents([joinRuleEvent]);
room.addLiveEvents([joinRuleEvent], { addToState: true });
render(<RoomHeader room={room} />, getWrapper());

View File

@@ -915,7 +915,7 @@ describe("<Notifications />", () => {
user: "@alice:example.org",
ts: 1,
});
await room.addLiveEvents([message]);
await room.addLiveEvents([message], { addToState: true });
const { container } = await getComponentAndWait();
const clearNotificationEl = getByTestId(container, "clear-notifications");

View File

@@ -716,7 +716,7 @@ describe("<Notifications />", () => {
user: "@alice:example.org",
ts: 1,
});
room.addLiveEvents([message]);
room.addLiveEvents([message], { addToState: true });
room.setUnreadNotificationCount(NotificationCountType.Total, 1);
const user = userEvent.setup();

View File

@@ -88,7 +88,7 @@ describe("pickFactory", () => {
client.getUserId()!,
client.deviceId!,
);
room.addLiveEvents([voiceBroadcastStartedEvent]);
room.addLiveEvents([voiceBroadcastStartedEvent], { addToState: true });
voiceBroadcastStoppedEvent = mkVoiceBroadcastInfoStateEvent(
roomId,
VoiceBroadcastInfoState.Stopped,

View File

@@ -119,7 +119,7 @@ const setUpClientRoomAndStores = (): {
skey: stateKey,
content: content as IContent,
});
room.addLiveEvents([event]);
room.addLiveEvents([event], { addToState: true });
return { event_id: event.getId()! };
});

View File

@@ -202,6 +202,7 @@ describe("MemberListStore", () => {
function addEventToRoom(room: Room, ev: MatrixEvent) {
room.getLiveTimeline().addEvent(ev, {
toStartOfTimeline: false,
addToState: true,
});
}

View File

@@ -397,7 +397,7 @@ describe("RoomViewStore", function () {
mockClient.getSafeUserId(),
"ABC123",
);
room2.addLiveEvents([broadcastEvent]);
room2.addLiveEvents([broadcastEvent], { addToState: true });
stores.voiceBroadcastPlaybacksStore.getByInfoEvent(broadcastEvent, mockClient);
dis.dispatch({ action: Action.ViewRoom, room_id: roomId2 });

View File

@@ -35,7 +35,7 @@ describe("MessagePreviewStore", () => {
event: MatrixEvent,
fireAction = true,
): Promise<void> {
room.addLiveEvents([event]);
room.addLiveEvents([event], { addToState: true });
if (fireAction) {
// @ts-ignore private access
await store.onAction({

View File

@@ -47,11 +47,11 @@ describe("RecentAlgorithm", () => {
room.getMyMembership = () => KnownMembership.Join;
room.addLiveEvents([event1]);
room.addLiveEvents([event1], { addToState: true });
expect(algorithm.getLastTs(room, "@jane:matrix.org")).toBe(5);
expect(algorithm.getLastTs(room, "@john:matrix.org")).toBe(5);
room.addLiveEvents([event2]);
room.addLiveEvents([event2], { addToState: true });
expect(algorithm.getLastTs(room, "@jane:matrix.org")).toBe(10);
expect(algorithm.getLastTs(room, "@john:matrix.org")).toBe(10);
@@ -94,8 +94,8 @@ describe("RecentAlgorithm", () => {
event: true,
});
room1.addLiveEvents([evt]);
room2.addLiveEvents([evt2]);
room1.addLiveEvents([evt], { addToState: true });
room2.addLiveEvents([evt2], { addToState: true });
expect(algorithm.sortRooms([room2, room1], DefaultTagID.Untagged)).toEqual([room1, room2]);
});
@@ -115,7 +115,7 @@ describe("RecentAlgorithm", () => {
event: true,
});
room1.addLiveEvents([evt]);
room1.addLiveEvents([evt], { addToState: true });
expect(algorithm.sortRooms([room2, room1], DefaultTagID.Untagged)).toEqual([room2, room1]);
@@ -127,7 +127,7 @@ describe("RecentAlgorithm", () => {
ts: 12,
});
room1.addLiveEvents(events);
room1.addLiveEvents(events, { addToState: true });
});
it("orders rooms based on thread replies too", () => {
@@ -145,7 +145,7 @@ describe("RecentAlgorithm", () => {
ts: 12,
length: 5,
});
room1.addLiveEvents(events1);
room1.addLiveEvents(events1, { addToState: true });
const { events: events2 } = mkThread({
room: room2,
@@ -155,7 +155,7 @@ describe("RecentAlgorithm", () => {
ts: 14,
length: 10,
});
room2.addLiveEvents(events2);
room2.addLiveEvents(events2, { addToState: true });
expect(algorithm.sortRooms([room1, room2], DefaultTagID.Untagged)).toEqual([room2, room1]);
@@ -169,7 +169,7 @@ describe("RecentAlgorithm", () => {
// replies are 1ms after each other
ts: 50,
});
room1.addLiveEvents([threadReply]);
room1.addLiveEvents([threadReply], { addToState: true });
expect(algorithm.sortRooms([room1, room2], DefaultTagID.Untagged)).toEqual([room1, room2]);
});

View File

@@ -70,7 +70,7 @@ describe("ReactionEventPreview", () => {
room: roomId,
});
room.getUnfilteredTimelineSet().addLiveEvent(message, {});
room.getUnfilteredTimelineSet().addLiveEvent(message, { addToState: true });
const event = mkEvent({
event: true,
@@ -107,7 +107,7 @@ describe("ReactionEventPreview", () => {
room: roomId,
});
room.getUnfilteredTimelineSet().addLiveEvent(message, {});
room.getUnfilteredTimelineSet().addLiveEvent(message, { addToState: true });
const event = mkEvent({
event: true,

View File

@@ -29,7 +29,7 @@ describe("useTopic", () => {
event: true,
});
room.addLiveEvents([topic]);
room.addLiveEvents([topic], { addToState: true });
function RoomTopic() {
const topic = useTopic(room);
@@ -52,7 +52,7 @@ describe("useTopic", () => {
});
act(() => {
room.addLiveEvents([updatedTopic]);
room.addLiveEvents([updatedTopic], { addToState: true });
});
expect(screen.queryByText("New topic")).toBeInTheDocument();

View File

@@ -593,18 +593,21 @@ describe("HTMLExport", () => {
it("should not make /messages requests when exporting 'Current Timeline'", async () => {
client.createMessagesRequest.mockRejectedValue(new Error("Should never be called"));
room.addLiveEvents([
new MatrixEvent({
event_id: `$eventId`,
type: EventType.RoomMessage,
sender: client.getSafeUserId(),
origin_server_ts: 123456789,
content: {
msgtype: "m.text",
body: `testing testing`,
},
}),
]);
room.addLiveEvents(
[
new MatrixEvent({
event_id: `$eventId`,
type: EventType.RoomMessage,
sender: client.getSafeUserId(),
origin_server_ts: 123456789,
content: {
msgtype: "m.text",
body: `testing testing`,
},
}),
],
{ addToState: true },
);
const exporter = new HTMLExporter(
room,

View File

@@ -121,7 +121,7 @@ describe("notifications", () => {
user: USER_ID,
msg: "Hello",
});
room.addLiveEvents([message]);
room.addLiveEvents([message], { addToState: true });
sendReadReceiptSpy = jest.spyOn(client, "sendReadReceipt").mockResolvedValue({});
jest.spyOn(client, "getRooms").mockReturnValue([room]);
jest.spyOn(SettingsStore, "getValue").mockImplementation((name) => {
@@ -187,7 +187,7 @@ describe("notifications", () => {
user: USER_ID,
ts: 1,
});
room.addLiveEvents([message]);
room.addLiveEvents([message], { addToState: true });
room.setUnreadNotificationCount(NotificationCountType.Total, 1);
await clearAllNotifications(client);
@@ -202,7 +202,7 @@ describe("notifications", () => {
user: USER_ID,
ts: 1,
});
room.addLiveEvents([message]);
room.addLiveEvents([message], { addToState: true });
room.setUnreadNotificationCount(NotificationCountType.Total, 1);
jest.spyOn(SettingsStore, "getValue").mockReset().mockReturnValue(false);

View File

@@ -85,7 +85,7 @@ describe("VoiceBroadcastBody", () => {
deviceId,
infoEvent,
);
room.addEventsToTimeline([infoEvent], true, room.getLiveTimeline());
room.addEventsToTimeline([infoEvent], true, true, room.getLiveTimeline());
testRecording = new VoiceBroadcastRecording(infoEvent, client);
testPlayback = new VoiceBroadcastPlayback(infoEvent, client, new VoiceBroadcastRecordingsStore());
mocked(VoiceBroadcastRecordingBody).mockImplementation(({ recording }): ReactElement | null => {
@@ -127,7 +127,7 @@ describe("VoiceBroadcastBody", () => {
describe("when there is a stopped voice broadcast", () => {
beforeEach(() => {
room.addEventsToTimeline([stoppedEvent], true, room.getLiveTimeline());
room.addEventsToTimeline([stoppedEvent], true, true, room.getLiveTimeline());
renderVoiceBroadcast();
});
@@ -148,7 +148,7 @@ describe("VoiceBroadcastBody", () => {
describe("and the recordings ends", () => {
beforeEach(() => {
act(() => {
room.addEventsToTimeline([stoppedEvent], true, room.getLiveTimeline());
room.addEventsToTimeline([stoppedEvent], true, true, room.getLiveTimeline());
});
});

View File

@@ -243,7 +243,7 @@ describe("VoiceBroadcastPlayback", () => {
beforeEach(async () => {
infoEvent = mkInfoEvent(VoiceBroadcastInfoState.Resumed);
createChunkEvents();
room.addLiveEvents([infoEvent]);
room.addLiveEvents([infoEvent], { addToState: true });
playback = await mkPlayback();
});
@@ -331,7 +331,7 @@ describe("VoiceBroadcastPlayback", () => {
infoEvent = mkInfoEvent(VoiceBroadcastInfoState.Resumed);
createChunkEvents();
setUpChunkEvents([chunk2Event, chunk1Event]);
room.addLiveEvents([infoEvent, chunk1Event, chunk2Event]);
room.addLiveEvents([infoEvent, chunk1Event, chunk2Event], { addToState: true });
room.relations.aggregateChildEvent(chunk2Event);
room.relations.aggregateChildEvent(chunk1Event);
playback = await mkPlayback();
@@ -372,7 +372,7 @@ describe("VoiceBroadcastPlayback", () => {
describe("and an event with the same transaction Id occurs", () => {
beforeEach(() => {
room.addLiveEvents([chunk2BEvent]);
room.addLiveEvents([chunk2BEvent], { addToState: true });
room.relations.aggregateChildEvent(chunk2BEvent);
});
@@ -404,7 +404,7 @@ describe("VoiceBroadcastPlayback", () => {
infoEvent,
2,
);
room.addLiveEvents([stoppedEvent]);
room.addLiveEvents([stoppedEvent], { addToState: true });
room.relations.aggregateChildEvent(stoppedEvent);
chunk2Playback.emit(PlaybackState.Stopped);
});
@@ -426,7 +426,7 @@ describe("VoiceBroadcastPlayback", () => {
infoEvent,
3,
);
room.addLiveEvents([stoppedEvent]);
room.addLiveEvents([stoppedEvent], { addToState: true });
room.relations.aggregateChildEvent(stoppedEvent);
chunk2Playback.emit(PlaybackState.Stopped);
});
@@ -435,7 +435,7 @@ describe("VoiceBroadcastPlayback", () => {
describe("and the next chunk arrives", () => {
beforeEach(() => {
room.addLiveEvents([chunk3Event]);
room.addLiveEvents([chunk3Event], { addToState: true });
room.relations.aggregateChildEvent(chunk3Event);
});
@@ -521,7 +521,7 @@ describe("VoiceBroadcastPlayback", () => {
createChunkEvents();
// use delayed first chunk here to simulate loading time
setUpChunkEvents([chunk2Event, deplayedChunk1Event, chunk3Event]);
room.addLiveEvents([infoEvent, deplayedChunk1Event, chunk2Event, chunk3Event]);
room.addLiveEvents([infoEvent, deplayedChunk1Event, chunk2Event, chunk3Event], { addToState: true });
playback = await mkPlayback(true);
});

View File

@@ -32,7 +32,7 @@ describe("hasRoomLiveVoiceBroadcast", () => {
startedEvent?: MatrixEvent,
): MatrixEvent => {
const infoEvent = mkVoiceBroadcastInfoStateEvent(room.roomId, state, userId, deviceId, startedEvent);
room.addLiveEvents([infoEvent]);
room.addLiveEvents([infoEvent], { addToState: true });
room.currentState.setStateEvents([infoEvent]);
room.relations.aggregateChildEvent(infoEvent);
return infoEvent;

View File

@@ -31,7 +31,7 @@ const mkRelatedEvent = (
},
user: client.getSafeUserId(),
});
room.addLiveEvents([event]);
room.addLiveEvents([event], { addToState: true });
return event;
};
@@ -65,7 +65,7 @@ describe("isRelatedToVoiceBroadcast", () => {
user: client.getSafeUserId(),
});
room.addLiveEvents([broadcastEvent, nonBroadcastEvent]);
room.addLiveEvents([broadcastEvent, nonBroadcastEvent], { addToState: true });
});
it("should return true if related (reference) to a broadcast event", () => {

View File

@@ -67,7 +67,7 @@ describe("retrieveStartedInfoEvent", () => {
it("when the room contains the event, it should return it", async () => {
const startEvent = mkStartEvent();
const stopEvent = mkStopEvent(startEvent);
room.addLiveEvents([startEvent]);
room.addLiveEvents([startEvent], { addToState: true });
expect(await retrieveStartedInfoEvent(stopEvent, client)).toBe(startEvent);
});

View File

@@ -8307,9 +8307,9 @@ matrix-events-sdk@0.0.1:
resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd"
integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
version "34.10.0"
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/6855ace6422082d173438cb23368d2fabc6a1086"
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#8e65c13d5019eb215fa99c86ddef463d9c383115":
version "34.11.1"
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8e65c13d5019eb215fa99c86ddef463d9c383115"
dependencies:
"@babel/runtime" "^7.12.5"
"@matrix-org/matrix-sdk-crypto-wasm" "^9.0.0"