From 0a952287cb74104b6b8e1223381b5d935d68aa3b Mon Sep 17 00:00:00 2001 From: Mikhail Aheichyk Date: Fri, 23 Feb 2024 17:27:14 +0300 Subject: [PATCH] Feeds event with relation to unknown to the widget Signed-off-by: Mikhail Aheichyk --- src/stores/widgets/StopGapWidget.ts | 11 ++- test/stores/widgets/StopGapWidget-test.ts | 101 +++++++++++++++++++++- 2 files changed, 108 insertions(+), 4 deletions(-) diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index c131a7e76669..9cfb3d47ce16 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -495,6 +495,7 @@ export class StopGapWidget extends EventEmitter { // // This approach of "read up to" prevents widgets receiving decryption spam from startup or // receiving out-of-order events from backfill and such. + let isRelationToUnknown: boolean | undefined = undefined; const upToEventId = this.readUpToMap[ev.getRoomId()!]; if (upToEventId) { // Small optimization for exact match (prevent search) @@ -521,8 +522,12 @@ export class StopGapWidget extends EventEmitter { } if (isBeforeMark) { - // Ignore the event: it is before our interest. - return; + isRelationToUnknown = + !ev.replyEventId && !!ev.relationEventId && !room.findEventById(ev.relationEventId); + if (!isRelationToUnknown) { + // Ignore the event: it is before our interest. + return; + } } } @@ -533,7 +538,7 @@ export class StopGapWidget extends EventEmitter { const evId = ev.getId(); if (evRoomId && evId) { const room = this.client.getRoom(evRoomId); - if (room && room.getMyMembership() === "join") { + if (room && room.getMyMembership() === "join" && !isRelationToUnknown) { this.readUpToMap[evRoomId] = evId; } } diff --git a/test/stores/widgets/StopGapWidget-test.ts b/test/stores/widgets/StopGapWidget-test.ts index 59ea954b9cfd..6d4c0e63232a 100644 --- a/test/stores/widgets/StopGapWidget-test.ts +++ b/test/stores/widgets/StopGapWidget-test.ts @@ -16,7 +16,7 @@ limitations under the License. import { mocked, MockedObject } from "jest-mock"; import { last } from "lodash"; -import { MatrixEvent, MatrixClient, ClientEvent } from "matrix-js-sdk/src/matrix"; +import { MatrixEvent, MatrixClient, ClientEvent, EventTimeline } from "matrix-js-sdk/src/matrix"; import { ClientWidgetApi, WidgetApiFromWidgetAction } from "matrix-widget-api"; import { waitFor } from "@testing-library/react"; @@ -81,6 +81,105 @@ describe("StopGapWidget", () => { expect(messaging.feedToDevice).toHaveBeenCalledWith(event.getEffectiveEvent(), false); }); + describe("feed event", () => { + let event1: MatrixEvent; + let event2: MatrixEvent; + + beforeEach(() => { + event1 = mkEvent({ + event: true, + id: "$event-id1", + type: "org.example.foo", + user: "@alice:example.org", + content: { hello: "world" }, + room: "!1:example.org", + }); + + event2 = mkEvent({ + event: true, + id: "$event-id2", + type: "org.example.foo", + user: "@alice:example.org", + content: { hello: "world" }, + room: "!1:example.org", + }); + + const room = mkRoom(client, "!1:example.org"); + client.getRoom.mockImplementation((roomId) => (roomId === "!1:example.org" ? room : null)); + room.getLiveTimeline.mockReturnValue({ + getEvents: (): MatrixEvent[] => [event1, event2], + } as unknown as EventTimeline); + + messaging.feedEvent.mockResolvedValue(); + }); + + it("feeds incoming event to the widget", async () => { + client.emit(ClientEvent.Event, event1); + expect(messaging.feedEvent).toHaveBeenCalledWith(event1.getEffectiveEvent(), "!1:example.org"); + + client.emit(ClientEvent.Event, event2); + expect(messaging.feedEvent).toHaveBeenCalledTimes(2); + expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2.getEffectiveEvent(), "!1:example.org"); + }); + + it("should not feed incoming event to the widget if seen already", async () => { + client.emit(ClientEvent.Event, event1); + expect(messaging.feedEvent).toHaveBeenCalledWith(event1.getEffectiveEvent(), "!1:example.org"); + + client.emit(ClientEvent.Event, event2); + expect(messaging.feedEvent).toHaveBeenCalledTimes(2); + expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2.getEffectiveEvent(), "!1:example.org"); + + client.emit(ClientEvent.Event, event1); + expect(messaging.feedEvent).toHaveBeenCalledTimes(2); + expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2.getEffectiveEvent(), "!1:example.org"); + }); + + it("should not feed incoming event if not in timeline", () => { + const event = mkEvent({ + event: true, + id: "$event-id", + type: "org.example.foo", + user: "@alice:example.org", + content: { + hello: "world", + }, + room: "!1:example.org", + }); + + client.emit(ClientEvent.Event, event); + expect(messaging.feedEvent).toHaveBeenCalledWith(event.getEffectiveEvent(), "!1:example.org"); + }); + + it("feeds incoming event that is not in timeline but relates to unknown parent to the widget", async () => { + const event = mkEvent({ + event: true, + id: "$event-idRelation", + type: "org.example.foo", + user: "@alice:example.org", + content: { + "hello": "world", + "m.relates_to": { + event_id: "$unknown-parent", + rel_type: "m.reference", + }, + }, + room: "!1:example.org", + }); + + client.emit(ClientEvent.Event, event1); + expect(messaging.feedEvent).toHaveBeenCalledWith(event1.getEffectiveEvent(), "!1:example.org"); + + client.emit(ClientEvent.Event, event); + expect(messaging.feedEvent).toHaveBeenCalledTimes(2); + expect(messaging.feedEvent).toHaveBeenLastCalledWith(event.getEffectiveEvent(), "!1:example.org"); + + client.emit(ClientEvent.Event, event1); + expect(messaging.feedEvent).toHaveBeenCalledTimes(2); + expect(messaging.feedEvent).toHaveBeenLastCalledWith(event.getEffectiveEvent(), "!1:example.org"); + }); + }); + describe("when there is a voice broadcast recording", () => { let voiceBroadcastInfoEvent: MatrixEvent; let voiceBroadcastRecording: VoiceBroadcastRecording;