From 104bd556df88fa6caf12285325323a3a0fdcc161 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Wed, 26 Apr 2023 19:44:13 -0400 Subject: [PATCH] Enable pagination for overlay timelines --- package.json | 4 +- src/components/structures/TimelinePanel.tsx | 171 ++++++++++--- .../structures/TimelinePanel-test.tsx | 226 ++++++++++++++++-- test/test-utils/test-utils.ts | 2 +- yarn.lock | 10 +- 5 files changed, 349 insertions(+), 64 deletions(-) diff --git a/package.json b/package.json index bf7a093494f5..d094de34f594 100644 --- a/package.json +++ b/package.json @@ -157,7 +157,7 @@ "@types/fs-extra": "^11.0.0", "@types/geojson": "^7946.0.8", "@types/glob-to-regexp": "^0.4.1", - "@types/jest": "29.2.5", + "@types/jest": "29.2.6", "@types/katex": "^0.16.0", "@types/lodash": "^4.14.168", "@types/modernizr": "^3.5.3", @@ -223,4 +223,4 @@ "outputName": "jest-sonar-report.xml", "relativePaths": true } -} +} \ No newline at end of file diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index 6172d6228a25..560e682f2476 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -24,7 +24,7 @@ import { TimelineWindow } from "matrix-js-sdk/src/timeline-window"; import { EventType, RelationType } from "matrix-js-sdk/src/@types/event"; import { SyncState } from "matrix-js-sdk/src/sync"; import { RoomMember, RoomMemberEvent } from "matrix-js-sdk/src/models/room-member"; -import { debounce, throttle } from "lodash"; +import { debounce, findLastIndex, throttle } from "lodash"; import { logger } from "matrix-js-sdk/src/logger"; import { ClientEvent } from "matrix-js-sdk/src/client"; import { Thread, ThreadEvent } from "matrix-js-sdk/src/models/thread"; @@ -73,6 +73,12 @@ const debuglog = (...args: any[]): void => { } }; +const overlaysBefore = (overlayEvent: MatrixEvent, mainEvent: MatrixEvent): boolean => + overlayEvent.localTimestamp < mainEvent.localTimestamp; + +const overlaysAfter = (overlayEvent: MatrixEvent, mainEvent: MatrixEvent): boolean => + overlayEvent.localTimestamp >= mainEvent.localTimestamp; + interface IProps { // The js-sdk EventTimelineSet object for the timeline sequence we are // representing. This may or may not have a room, depending on what it's @@ -83,7 +89,6 @@ interface IProps { // added to support virtual rooms // events from the overlay timeline set will be added by localTimestamp // into the main timeline - // back paging not yet supported overlayTimelineSet?: EventTimelineSet; // filter events from overlay timeline overlayTimelineSetFilter?: (event: MatrixEvent) => boolean; @@ -506,16 +511,53 @@ class TimelinePanel extends React.Component { // this particular event should be the first or last to be unpaginated. const eventId = scrollToken; - const marker = this.state.events.findIndex((ev) => { - return ev.getId() === eventId; - }); + // The event in question could belong to either the main timeline or + // overlay timeline; let's check both + const mainEvents = this.timelineWindow?.getEvents() ?? []; + const overlayEvents = this.overlayTimelineWindow?.getEvents() ?? []; + + let marker = mainEvents.findIndex((ev) => ev.getId() === eventId); + let overlayMarker: number; + if (marker === -1) { + // The event must be from the overlay timeline instead + overlayMarker = overlayEvents.findIndex((ev) => ev.getId() === eventId); + marker = backwards + ? findLastIndex(mainEvents, (ev) => overlaysAfter(overlayEvents[overlayMarker], ev)) + : mainEvents.findIndex((ev) => overlaysBefore(overlayEvents[overlayMarker], ev)); + } else { + overlayMarker = backwards + ? findLastIndex(overlayEvents, (ev) => overlaysBefore(ev, mainEvents[marker])) + : overlayEvents.findIndex((ev) => overlaysAfter(ev, mainEvents[marker])); + } + + // The number of events to unpaginate from the main timeline + let count: number; + if (marker === -1) { + count = 0; + } else { + count = backwards ? marker + 1 : mainEvents.length - marker; + } - const count = backwards ? marker + 1 : this.state.events.length - marker; + // The number of events to unpaginate from the overlay timeline + let overlayCount: number; + if (overlayMarker === -1) { + overlayCount = 0; + } else { + overlayCount = backwards ? overlayMarker + 1 : overlayEvents.length - overlayMarker; + } if (count > 0) { debuglog("Unpaginating", count, "in direction", dir); this.timelineWindow?.unpaginate(count, backwards); + } + if (overlayCount > 0) { + debuglog("Unpaginating", count, "from overlay timeline in direction", dir); + this.overlayTimelineWindow?.unpaginate(overlayCount, backwards); + } + + // If either timeline window shrunk + if (count > 0 || overlayCount > 0) { const { events, liveEvents, firstVisibleEventIndex } = this.getEvents(); this.buildLegacyCallEventGroupers(events); this.setState({ @@ -572,11 +614,15 @@ class TimelinePanel extends React.Component { debuglog("Initiating paginate; backwards:" + backwards); this.setState({ [paginatingKey]: true }); - return this.onPaginationRequest(this.timelineWindow, dir, PAGINATE_SIZE).then((r) => { + return this.onPaginationRequest(this.timelineWindow, dir, PAGINATE_SIZE).then(async (r) => { if (this.unmounted) { return false; } + if (this.overlayTimelineWindow) { + await this.extendOverlayWindowToCoverMainWindow(); + } + debuglog("paginate complete backwards:" + backwards + "; success:" + r); const { events, liveEvents, firstVisibleEventIndex } = this.getEvents(); @@ -769,8 +815,12 @@ class TimelinePanel extends React.Component { }); }; + private hasTimelineSetFor(roomId: string): boolean { + return roomId === this.props.timelineSet.room?.roomId || roomId === this.props.overlayTimelineSet?.room?.roomId; + } + private onRoomTimelineReset = (room: Room, timelineSet: EventTimelineSet): void => { - if (timelineSet !== this.props.timelineSet) return; + if (timelineSet !== this.props.timelineSet && timelineSet !== this.props.overlayTimelineSet) return; if (this.canResetTimeline()) { this.loadTimeline(); @@ -783,7 +833,7 @@ class TimelinePanel extends React.Component { if (this.unmounted) return; // ignore events for other rooms - if (room !== this.props.timelineSet.room) return; + if (!this.hasTimelineSetFor(room.roomId)) return; // we could skip an update if the event isn't in our timeline, // but that's probably an early optimisation. @@ -796,10 +846,7 @@ class TimelinePanel extends React.Component { } // ignore events for other rooms - const roomId = thread.roomId; - if (roomId !== this.props.timelineSet.room?.roomId) { - return; - } + if (!this.hasTimelineSetFor(thread.roomId)) return; // we could skip an update if the event isn't in our timeline, // but that's probably an early optimisation. @@ -818,9 +865,7 @@ class TimelinePanel extends React.Component { // ignore events for other rooms const roomId = ev.getRoomId(); - if (roomId !== this.props.timelineSet.room?.roomId) { - return; - } + if (roomId === undefined || !this.hasTimelineSetFor(roomId)) return; // we could skip an update if the event isn't in our timeline, // but that's probably an early optimisation. @@ -834,7 +879,7 @@ class TimelinePanel extends React.Component { if (this.unmounted) return; // ignore events for other rooms - if (member.roomId !== this.props.timelineSet.room?.roomId) return; + if (!this.hasTimelineSetFor(member.roomId)) return; // ignore events for other users if (member.userId != MatrixClientPeg.get().credentials?.userId) return; @@ -857,7 +902,8 @@ class TimelinePanel extends React.Component { if (this.unmounted) return; // ignore events for other rooms - if (replacedEvent.getRoomId() !== this.props.timelineSet.room?.roomId) return; + const roomId = replacedEvent.getRoomId(); + if (roomId === undefined || !this.hasTimelineSetFor(roomId)) return; // we could skip an update if the event isn't in our timeline, // but that's probably an early optimisation. @@ -877,7 +923,7 @@ class TimelinePanel extends React.Component { if (this.unmounted) return; // ignore events for other rooms - if (room !== this.props.timelineSet.room) return; + if (!this.hasTimelineSetFor(room.roomId)) return; this.reloadEvents(); }; @@ -905,7 +951,8 @@ class TimelinePanel extends React.Component { // Can be null for the notification timeline, etc. if (!this.props.timelineSet.room) return; - if (ev.getRoomId() !== this.props.timelineSet.room.roomId) return; + const roomId = ev.getRoomId(); + if (roomId === undefined || !this.hasTimelineSetFor(roomId)) return; if (!this.state.events.includes(ev)) return; @@ -1380,6 +1427,48 @@ class TimelinePanel extends React.Component { }); } + private async extendOverlayWindowToCoverMainWindow(): Promise { + const mainWindow = this.timelineWindow!; + const overlayWindow = this.overlayTimelineWindow!; + const mainEvents = mainWindow.getEvents(); + + if (mainEvents.length > 0) { + let paginationRequests: Promise[]; + + // Keep paginating until the main window is covered + do { + paginationRequests = []; + const overlayEvents = overlayWindow.getEvents(); + + if ( + overlayWindow.canPaginate(EventTimeline.BACKWARDS) && + (overlayEvents.length === 0 || + overlaysAfter(overlayEvents[0], mainEvents[0]) || + !mainWindow.canPaginate(EventTimeline.BACKWARDS)) + ) { + // Paginating backwards could reveal more events to be overlaid in the main window + paginationRequests.push( + this.onPaginationRequest(overlayWindow, EventTimeline.BACKWARDS, PAGINATE_SIZE), + ); + } + + if ( + overlayWindow.canPaginate(EventTimeline.FORWARDS) && + (overlayEvents.length === 0 || + overlaysBefore(overlayEvents.at(-1)!, mainEvents.at(-1)!) || + !mainWindow.canPaginate(EventTimeline.FORWARDS)) + ) { + // Paginating forwards could reveal more events to be overlaid in the main window + paginationRequests.push( + this.onPaginationRequest(overlayWindow, EventTimeline.FORWARDS, PAGINATE_SIZE), + ); + } + + await Promise.all(paginationRequests); + } while (paginationRequests.length > 0); + } + } + /** * (re)-load the event timeline, and initialise the scroll state, centered * around the given event. @@ -1417,8 +1506,14 @@ class TimelinePanel extends React.Component { this.setState( { - canBackPaginate: !!this.timelineWindow?.canPaginate(EventTimeline.BACKWARDS), - canForwardPaginate: !!this.timelineWindow?.canPaginate(EventTimeline.FORWARDS), + canBackPaginate: + (this.timelineWindow?.canPaginate(EventTimeline.BACKWARDS) || + this.overlayTimelineWindow?.canPaginate(EventTimeline.BACKWARDS)) ?? + false, + canForwardPaginate: + (this.timelineWindow?.canPaginate(EventTimeline.FORWARDS) || + this.overlayTimelineWindow?.canPaginate(EventTimeline.FORWARDS)) ?? + false, timelineLoading: false, }, () => { @@ -1494,11 +1589,10 @@ class TimelinePanel extends React.Component { // This is a hot-path optimization by skipping a promise tick // by repeating a no-op sync branch in // TimelineSet.getTimelineForEvent & MatrixClient.getEventTimeline - if (this.props.timelineSet.getTimelineForEvent(eventId)) { + if (this.props.timelineSet.getTimelineForEvent(eventId) && !this.overlayTimelineWindow) { // if we've got an eventId, and the timeline exists, we can skip // the promise tick. this.timelineWindow.load(eventId, INITIAL_SIZE); - this.overlayTimelineWindow?.load(undefined, INITIAL_SIZE); // in this branch this method will happen in sync time onLoaded(); return; @@ -1506,9 +1600,10 @@ class TimelinePanel extends React.Component { const prom = this.timelineWindow.load(eventId, INITIAL_SIZE).then(async (): Promise => { if (this.overlayTimelineWindow) { - // @TODO(kerrya) use timestampToEvent to load the overlay timeline + // TODO: use timestampToEvent to load the overlay timeline // with more correct position when main TL eventId is truthy await this.overlayTimelineWindow.load(undefined, INITIAL_SIZE); + await this.extendOverlayWindowToCoverMainWindow(); } }); this.buildLegacyCallEventGroupers(); @@ -1541,23 +1636,31 @@ class TimelinePanel extends React.Component { this.reloadEvents(); } - // get the list of events from the timeline window and the pending event list + // get the list of events from the timeline windows and the pending event list private getEvents(): Pick { - const mainEvents: MatrixEvent[] = this.timelineWindow?.getEvents() || []; - const eventFilter = this.props.overlayTimelineSetFilter || Boolean; - const overlayEvents = this.overlayTimelineWindow?.getEvents().filter(eventFilter) || []; + const mainEvents = this.timelineWindow?.getEvents() ?? []; + let overlayEvents = this.overlayTimelineWindow?.getEvents() ?? []; + if (this.props.overlayTimelineSetFilter !== undefined) { + overlayEvents = overlayEvents.filter(this.props.overlayTimelineSetFilter); + } // maintain the main timeline event order as returned from the HS // merge overlay events at approximately the right position based on local timestamp const events = overlayEvents.reduce( (acc: MatrixEvent[], overlayEvent: MatrixEvent) => { // find the first main tl event with a later timestamp - const index = acc.findIndex((event) => event.localTimestamp > overlayEvent.localTimestamp); + const index = acc.findIndex((event) => overlaysBefore(overlayEvent, event)); // insert overlay event into timeline at approximately the right place - if (index > -1) { - acc.splice(index, 0, overlayEvent); + if (index === -1) { + if (!this.timelineWindow?.canPaginate(EventTimeline.FORWARDS)) { + acc.push(overlayEvent); + } + } else if (index === 0) { + if (!this.timelineWindow?.canPaginate(EventTimeline.BACKWARDS)) { + acc.unshift(overlayEvent); + } } else { - acc.push(overlayEvent); + acc.splice(index, 0, overlayEvent); } return acc; }, @@ -1574,7 +1677,7 @@ class TimelinePanel extends React.Component { client.decryptEventIfNeeded(event); }); - const firstVisibleEventIndex = this.checkForPreJoinUISI(mainEvents); + const firstVisibleEventIndex = this.checkForPreJoinUISI(events); // Hold onto the live events separately. The read receipt and read marker // should use this list, so that they don't advance into pending events. diff --git a/test/components/structures/TimelinePanel-test.tsx b/test/components/structures/TimelinePanel-test.tsx index 1b8a0c4a9a75..0b7cad331f1a 100644 --- a/test/components/structures/TimelinePanel-test.tsx +++ b/test/components/structures/TimelinePanel-test.tsx @@ -37,6 +37,8 @@ import { ThreadFilterType, } from "matrix-js-sdk/src/models/thread"; import React, { createRef } from "react"; +import { mocked } from "jest-mock"; +import { forEachRight } from "lodash"; import TimelinePanel from "../../../src/components/structures/TimelinePanel"; import MatrixClientContext from "../../../src/contexts/MatrixClientContext"; @@ -45,6 +47,7 @@ import { isCallEvent } from "../../../src/components/structures/LegacyCallEventG import { flushPromises, mkMembership, mkRoom, stubClient } from "../../test-utils"; import { mkThread } from "../../test-utils/threads"; import { createMessageEventContent } from "../../test-utils/events"; +import ScrollPanel from "../../../src/components/structures/ScrollPanel"; const newReceipt = (eventId: string, userId: string, readTs: number, fullyReadTs: number): MatrixEvent => { const receiptContent = { @@ -57,14 +60,21 @@ const newReceipt = (eventId: string, userId: string, readTs: number, fullyReadTs return new MatrixEvent({ content: receiptContent, type: EventType.Receipt }); }; -const getProps = (room: Room, events: MatrixEvent[]): TimelinePanel["props"] => { - const timelineSet = { room: room as Room } as EventTimelineSet; +const mkTimeline = (room: Room, events: MatrixEvent[]): [EventTimeline, EventTimelineSet] => { + const timelineSet = { + room: room as Room, + getLiveTimeline: () => timeline, + getTimelineForEvent: () => timeline, + getPendingEvents: () => [], + } as EventTimelineSet; const timeline = new EventTimeline(timelineSet); - events.forEach((event) => timeline.addEvent(event, { toStartOfTimeline: true })); - timelineSet.getLiveTimeline = () => timeline; - timelineSet.getTimelineForEvent = () => timeline; - timelineSet.getPendingEvents = () => events; - timelineSet.room!.getEventReadUpTo = () => events[1].getId() ?? null; + events.forEach((event) => timeline.addEvent(event, { toStartOfTimeline: false })); + + return [timeline, timelineSet] +} + +const getProps = (room: Room, events: MatrixEvent[]): TimelinePanel["props"] => { + const [, timelineSet] = mkTimeline(room, events) return { timelineSet, @@ -97,6 +107,37 @@ const setupTestData = (): [MatrixClient, Room, MatrixEvent[]] => { return [client, room, events]; }; +const setupOverlayTestData = (client: MatrixClient, mainEvents: MatrixEvent[]): [Room, MatrixEvent[]] => { + const virtualRoom = mkRoom(client, "virtualRoomId"); + const overlayEvents = mockEvents(virtualRoom, 5); + + // Set the event order that we'll be looking for in the timeline + overlayEvents[0].localTimestamp = 1000; + mainEvents[0].localTimestamp = 2000; + overlayEvents[1].localTimestamp = 3000; + overlayEvents[2].localTimestamp = 4000; + overlayEvents[3].localTimestamp = 5000; + mainEvents[1].localTimestamp = 6000; + overlayEvents[4].localTimestamp = 7000; + + return [virtualRoom, overlayEvents] +} + +const expectEvents = (container: HTMLElement, events: MatrixEvent[]): void => { + const eventTiles = container.querySelectorAll(".mx_EventTile"); + const eventTileIds = [...eventTiles].map((tileElement) => tileElement.getAttribute("data-event-id")); + expect(eventTileIds).toEqual(events.map((ev) => ev.getId())); +}; + +const withScrollPanelMountSpy = async (continuation: (mountSpy: jest.SpyInstance) => Promise): Promise => { + const mountSpy = jest.spyOn(ScrollPanel.prototype, 'componentDidMount') + try { + await continuation(mountSpy) + } finally { + mountSpy.mockRestore() + } +} + describe("TimelinePanel", () => { beforeEach(() => { stubClient(); @@ -268,6 +309,8 @@ describe("TimelinePanel", () => { render(); + await flushPromises(); + const event = new MatrixEvent({ type: RoomEvent.Timeline }); const data = { timeline: props.timelineSet.getLiveTimeline(), liveEvent: true }; client.emit(RoomEvent.Timeline, event, room, false, false, data); @@ -279,8 +322,7 @@ describe("TimelinePanel", () => { }); describe("with overlayTimeline", () => { - // Trying to understand why this is not passing anymore - it.skip("renders merged timeline", () => { + it("renders merged timeline", async () => { const [client, room, events] = setupTestData(); const virtualRoom = mkRoom(client, "virtualRoomId"); const virtualCallInvite = new MatrixEvent({ @@ -296,25 +338,157 @@ describe("TimelinePanel", () => { const virtualEvents = [virtualCallInvite, ...mockEvents(virtualRoom), virtualCallMetaEvent]; const { timelineSet: overlayTimelineSet } = getProps(virtualRoom, virtualEvents); - const props = { - ...getProps(room, events), - overlayTimelineSet, - overlayTimelineSetFilter: isCallEvent, - }; + const { container } = render( + , + ); + + await waitFor(() => + expectEvents(container, [ + // main timeline events are included + events[0], + events[1], + // virtual timeline call event is included + virtualCallInvite, + // virtual call event has no tile renderer => not rendered + ]), + ); + }); + + it("paginates to get enough overlay events", async () => { + const [client, room, events] = setupTestData(); + const [virtualRoom, overlayEvents] = setupOverlayTestData(client, events) + const overlayEventsPage1 = overlayEvents.slice(0, 2); + const overlayEventsPage2 = overlayEvents.slice(2, 3); + const overlayEventsPage3 = overlayEvents.slice(3, 5); + + // Set the event order that we'll be looking for in the timeline + overlayEventsPage1[0].localTimestamp = 1000; + events[0].localTimestamp = 2000; + overlayEventsPage1[1].localTimestamp = 3000; + overlayEventsPage2[0].localTimestamp = 4000; + overlayEventsPage3[0].localTimestamp = 5000; + events[1].localTimestamp = 6000; + overlayEventsPage3[1].localTimestamp = 7000; + + // Start with only page 2 of the overlay events in the window + const [overlayTimeline, overlayTimelineSet] = mkTimeline(virtualRoom, overlayEventsPage2) + + // Enable the overlay timeline to be paginated + overlayTimeline.setPaginationToken("page2-start", EventTimeline.BACKWARDS); + overlayTimeline.setPaginationToken("page2-end", EventTimeline.FORWARDS); + mocked(client).paginateEventTimeline.mockImplementation(async (timeline, { backwards }) => { + if (timeline === overlayTimeline) { + if (backwards) { + forEachRight(overlayEventsPage1, (event) => + timeline.addEvent(event, { toStartOfTimeline: true }), + ); + } else { + overlayEventsPage3.forEach((event) => timeline.addEvent(event, { toStartOfTimeline: false })); + } + // Prevent any further pagination attempts in this direction + timeline.setPaginationToken(null, backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS); + return true; + } else { + return false; + } + }); + + const { container } = render( + , + ); - const { container } = render(); - - const eventTiles = container.querySelectorAll(".mx_EventTile"); - const eventTileIds = [...eventTiles].map((tileElement) => tileElement.getAttribute("data-event-id")); - expect(eventTileIds).toEqual([ - // main timeline events are included - events[1].getId(), - events[0].getId(), - // virtual timeline call event is included - virtualCallInvite.getId(), - // virtual call event has no tile renderer => not rendered - ]); + await waitFor(() => + expectEvents(container, [ + overlayEventsPage1[0], + events[0], + overlayEventsPage1[1], + overlayEventsPage2[0], + overlayEventsPage3[0], + events[1], + overlayEventsPage3[1], + ]), + ); }); + + it('unpaginates up to an event from the main timeline', async () => { + const [client, room, events] = setupTestData(); + const [virtualRoom, overlayEvents] = setupOverlayTestData(client, events) + const [, overlayTimelineSet] = mkTimeline(virtualRoom, overlayEvents) + + await withScrollPanelMountSpy(async mountSpy => { + const { container } = render( + , + ); + + await waitFor(() => + expectEvents(container, [ + overlayEvents[0], + events[0], + overlayEvents[1], + overlayEvents[2], + overlayEvents[3], + events[1], + overlayEvents[4], + ]), + ); + + // ScrollPanel has no chance of working in jsdom, so we've no choice + // but to do some shady stuff to trigger the unfill callback by hand + const scrollPanel = mountSpy.mock.contexts[0] as ScrollPanel + scrollPanel.props.onUnfillRequest(true, events[0].getId()) + + // Overlay event 0 and event 0 should be unpaginated + // Overlay events 1−3 should be hidden since they're at the edge of the window + await waitFor(() => + expectEvents(container, [ + events[1], + overlayEvents[4], + ]), + ); + }) + }) + + it('unpaginates up to an event from the overlay timeline', async () => { + const [client, room, events] = setupTestData(); + const [virtualRoom, overlayEvents] = setupOverlayTestData(client, events) + const [, overlayTimelineSet] = mkTimeline(virtualRoom, overlayEvents) + + await withScrollPanelMountSpy(async mountSpy => { + const { container } = render( + , + ); + + await waitFor(() => + expectEvents(container, [ + overlayEvents[0], + events[0], + overlayEvents[1], + overlayEvents[2], + overlayEvents[3], + events[1], + overlayEvents[4], + ]), + ); + + // ScrollPanel has no chance of working in jsdom, so we've no choice + // but to do some shady stuff to trigger the unfill callback by hand + const scrollPanel = mountSpy.mock.contexts[0] as ScrollPanel + scrollPanel.props.onUnfillRequest(false, overlayEvents[3].getId()) + + // Overlay events 3−4 and event 1 should be unpaginated + // Overlay events 1−2 should be hidden since they're at the edge of the window + await waitFor(() => + expectEvents(container, [ + overlayEvents[0], + events[0], + ]), + ); + }) + }) }); describe("when a thread updates", () => { diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index e414419359c7..b13a327853d9 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -537,7 +537,7 @@ export function mkStubRoom( on: jest.fn(), off: jest.fn(), } as unknown as RoomState, - eventShouldLiveIn: jest.fn().mockReturnValue({}), + eventShouldLiveIn: jest.fn().mockReturnValue({ shouldLiveInRoom: true, shouldLiveInThread: false }), fetchRoomThreads: jest.fn().mockReturnValue(Promise.resolve()), findEventById: jest.fn().mockReturnValue(undefined), findPredecessor: jest.fn().mockReturnValue({ roomId: "", eventId: null }), diff --git a/yarn.lock b/yarn.lock index c5950a312383..f35f469739d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2173,7 +2173,7 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@*", "@types/jest@29.2.5": +"@types/jest@*": version "29.2.5" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.2.5.tgz#c27f41a9d6253f288d1910d3c5f09484a56b73c0" integrity sha512-H2cSxkKgVmqNHXP7TC2L/WUorrZu8ZigyRywfVzv6EyBlxj39n4C00hjXYQWsbwqgElaj/CiAeSRmk5GoaKTgw== @@ -2181,6 +2181,14 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/jest@29.2.6": + version "29.2.6" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.2.6.tgz#1d43c8e533463d0437edef30b2d45d5aa3d95b0a" + integrity sha512-XEUC/Tgw3uMh6Ho8GkUtQ2lPhY5Fmgyp3TdlkTJs1W9VgNxs+Ow/x3Elh8lHQKqCbZL0AubQuqWjHVT033Hhrw== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + "@types/jsdom@^20.0.0": version "20.0.1" resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-20.0.1.tgz#07c14bc19bd2f918c1929541cdaacae894744808"