diff --git a/package.json b/package.json index b39a6ccfe4d..6d647881d80 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "matrix-encrypt-attachment": "^1.0.3", "matrix-events-sdk": "0.0.1", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", - "matrix-widget-api": "^1.10.0", + "matrix-widget-api": "1.11.0", "memoize-one": "^6.0.0", "mime": "^4.0.4", "oidc-client-ts": "^3.0.1", diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index c17aa81aab3..40e36473e31 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -6,14 +6,7 @@ * Please see LICENSE files in the repository root for full details. */ -import { - Room, - MatrixEvent, - MatrixEventEvent, - MatrixClient, - ClientEvent, - RoomStateEvent, -} from "matrix-js-sdk/src/matrix"; +import { Room, MatrixEvent, MatrixEventEvent, MatrixClient, ClientEvent } from "matrix-js-sdk/src/matrix"; import { KnownMembership } from "matrix-js-sdk/src/types"; import { ClientWidgetApi, @@ -33,6 +26,7 @@ import { WidgetApiFromWidgetAction, WidgetKind, } from "matrix-widget-api"; +import { Optional } from "matrix-events-sdk"; import { EventEmitter } from "events"; import { logger } from "matrix-js-sdk/src/logger"; @@ -62,7 +56,6 @@ import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload"; import Modal from "../../Modal"; import ErrorDialog from "../../components/views/dialogs/ErrorDialog"; import { SdkContextClass } from "../../contexts/SDKContext"; -import { UPDATE_EVENT } from "../AsyncStore"; // TODO: Destroy all of this code @@ -158,9 +151,6 @@ export class StopGapWidget extends EventEmitter { private mockWidget: ElementWidget; private scalarToken?: string; private roomId?: string; - // The room that we're currently allowing the widget to interact with. Only - // used for account widgets, which may follow the user to different rooms. - private viewedRoomId: string | null = null; private kind: WidgetKind; private readonly virtual: boolean; private readUpToMap: { [roomId: string]: string } = {}; // room ID to event ID @@ -187,6 +177,17 @@ export class StopGapWidget extends EventEmitter { this.stickyPromise = appTileProps.stickyPromise; } + private get eventListenerRoomId(): Optional { + // When widgets are listening to events, we need to make sure they're only + // receiving events for the right room. In particular, room widgets get locked + // to the room they were added in while account widgets listen to the currently + // active room. + + if (this.roomId) return this.roomId; + + return SdkContextClass.instance.roomViewStore.getRoomId(); + } + public get widgetApi(): ClientWidgetApi | null { return this.messaging; } @@ -258,17 +259,6 @@ export class StopGapWidget extends EventEmitter { }); } }; - - // This listener is only active for account widgets, which may follow the - // user to different rooms - private onRoomViewStoreUpdate = (): void => { - const roomId = SdkContextClass.instance.roomViewStore.getRoomId() ?? null; - if (roomId !== this.viewedRoomId) { - this.messaging!.setViewedRoomId(roomId); - this.viewedRoomId = roomId; - } - }; - /** * This starts the messaging for the widget if it is not in the state `started` yet. * @param iframe the iframe the widget should use @@ -295,17 +285,6 @@ export class StopGapWidget extends EventEmitter { this.messaging.on("capabilitiesNotified", () => this.emit("capabilitiesNotified")); this.messaging.on(`action:${WidgetApiFromWidgetAction.OpenModalWidget}`, this.onOpenModal); - // When widgets are listening to events, we need to make sure they're only - // receiving events for the right room - if (this.roomId === undefined) { - // Account widgets listen to the currently active room - this.messaging.setViewedRoomId(SdkContextClass.instance.roomViewStore.getRoomId() ?? null); - SdkContextClass.instance.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate); - } else { - // Room widgets get locked to the room they were added in - this.messaging.setViewedRoomId(this.roomId); - } - // Always attach a handler for ViewRoom, but permission check it internally this.messaging.on(`action:${ElementWidgetActions.ViewRoom}`, (ev: CustomEvent) => { ev.preventDefault(); // stop the widget API from auto-rejecting this @@ -350,7 +329,6 @@ 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.onStateUpdate); this.client.on(ClientEvent.ToDeviceEvent, this.onToDeviceEvent); this.messaging.on( @@ -479,11 +457,8 @@ export class StopGapWidget extends EventEmitter { WidgetMessagingStore.instance.stopMessaging(this.mockWidget, this.roomId); this.messaging = null; - SdkContextClass.instance.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate); - this.client.off(ClientEvent.Event, this.onEvent); this.client.off(MatrixEventEvent.Decrypted, this.onEventDecrypted); - this.client.off(RoomStateEvent.Events, this.onStateUpdate); this.client.off(ClientEvent.ToDeviceEvent, this.onToDeviceEvent); } @@ -496,14 +471,6 @@ export class StopGapWidget extends EventEmitter { this.feedEvent(ev); }; - private onStateUpdate = (ev: MatrixEvent): void => { - if (this.messaging === null) return; - const raw = ev.getEffectiveEvent(); - this.messaging.feedStateUpdate(raw as IRoomEvent).catch((e) => { - logger.error("Error sending state update to widget: ", e); - }); - }; - private onToDeviceEvent = async (ev: MatrixEvent): Promise => { await this.client.decryptEventIfNeeded(ev); if (ev.isDecryptionFailure()) return; @@ -603,7 +570,7 @@ export class StopGapWidget extends EventEmitter { this.eventsToFeed.add(ev); } else { const raw = ev.getEffectiveEvent(); - this.messaging.feedEvent(raw as IRoomEvent).catch((e) => { + this.messaging.feedEvent(raw as IRoomEvent, this.eventListenerRoomId!).catch((e) => { logger.error("Error sending event to widget: ", e); }); } diff --git a/src/stores/widgets/StopGapWidgetDriver.ts b/src/stores/widgets/StopGapWidgetDriver.ts index 7f5affab0da..fa5a43f248c 100644 --- a/src/stores/widgets/StopGapWidgetDriver.ts +++ b/src/stores/widgets/StopGapWidgetDriver.ts @@ -19,6 +19,7 @@ import { MatrixCapabilities, OpenIDRequestState, SimpleObservable, + Symbols, Widget, WidgetDriver, WidgetEventCapability, @@ -35,6 +36,7 @@ import { IContent, MatrixError, MatrixEvent, + Room, Direction, THREAD_RELATION_TYPE, SendDelayedEventResponse, @@ -467,69 +469,70 @@ export class StopGapWidgetDriver extends WidgetDriver { } } - /** - * Reads all events of the given type, and optionally `msgtype` (if applicable/defined), - * the user has access to. The widget API will have already verified that the widget is - * capable of receiving the events. Less events than the limit are allowed to be returned, - * but not more. - * @param roomId The ID of the room to look within. - * @param eventType The event type to be read. - * @param msgtype The msgtype of the events to be read, if applicable/defined. - * @param stateKey The state key of the events to be read, if applicable/defined. - * @param limit The maximum number of events to retrieve. Will be zero to denote "as many as - * possible". - * @param since When null, retrieves the number of events specified by the "limit" parameter. - * Otherwise, the event ID at which only subsequent events will be returned, as many as specified - * in "limit". - * @returns {Promise} Resolves to the room events, or an empty array. - */ - public async readRoomTimeline( - roomId: string, + private pickRooms(roomIds?: (string | Symbols.AnyRoom)[]): Room[] { + const client = MatrixClientPeg.get(); + if (!client) throw new Error("Not attached to a client"); + + const targetRooms = roomIds + ? roomIds.includes(Symbols.AnyRoom) + ? client.getVisibleRooms(SettingsStore.getValue("feature_dynamic_room_predecessors")) + : roomIds.map((r) => client.getRoom(r)) + : [client.getRoom(SdkContextClass.instance.roomViewStore.getRoomId()!)]; + return targetRooms.filter((r) => !!r) as Room[]; + } + + public async readRoomEvents( eventType: string, msgtype: string | undefined, - stateKey: string | undefined, - limit: number, - since: string | undefined, + limitPerRoom: number, + roomIds?: (string | Symbols.AnyRoom)[], ): Promise { - limit = limit > 0 ? Math.min(limit, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary - - const room = MatrixClientPeg.safeGet().getRoom(roomId); - if (room === null) return []; - const results: MatrixEvent[] = []; - const events = room.getLiveTimeline().getEvents(); // timelines are most recent last - for (let i = events.length - 1; i >= 0; i--) { - const ev = events[i]; - if (results.length >= limit) break; - if (since !== undefined && ev.getId() === since) break; - - if (ev.getType() !== eventType || ev.isState()) continue; - if (eventType === EventType.RoomMessage && msgtype && msgtype !== ev.getContent()["msgtype"]) continue; - if (ev.getStateKey() !== undefined && stateKey !== undefined && ev.getStateKey() !== stateKey) continue; - results.push(ev); - } + limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary + + const rooms = this.pickRooms(roomIds); + const allResults: IRoomEvent[] = []; + for (const room of rooms) { + const results: MatrixEvent[] = []; + const events = room.getLiveTimeline().getEvents(); // timelines are most recent last + for (let i = events.length - 1; i > 0; i--) { + if (results.length >= limitPerRoom) break; + + const ev = events[i]; + if (ev.getType() !== eventType || ev.isState()) continue; + if (eventType === EventType.RoomMessage && msgtype && msgtype !== ev.getContent()["msgtype"]) continue; + results.push(ev); + } - return results.map((e) => e.getEffectiveEvent() as IRoomEvent); + results.forEach((e) => allResults.push(e.getEffectiveEvent() as IRoomEvent)); + } + return allResults; } - /** - * Reads the current values of all matching room state entries. - * @param roomId The ID of the room. - * @param eventType The event type of the entries to be read. - * @param stateKey The state key of the entry to be read. If undefined, - * all room state entries with a matching event type should be returned. - * @returns {Promise} Resolves to the events representing the - * current values of the room state entries. - */ - public async readRoomState(roomId: string, eventType: string, stateKey: string | undefined): Promise { - const room = MatrixClientPeg.safeGet().getRoom(roomId); - if (room === null) return []; - const state = room.getLiveTimeline().getState(Direction.Forward); - if (state === undefined) return []; - - if (stateKey === undefined) - return state.getStateEvents(eventType).map((e) => e.getEffectiveEvent() as IRoomEvent); - const event = state.getStateEvents(eventType, stateKey); - return event === null ? [] : [event.getEffectiveEvent() as IRoomEvent]; + public async readStateEvents( + eventType: string, + stateKey: string | undefined, + limitPerRoom: number, + roomIds?: (string | Symbols.AnyRoom)[], + ): Promise { + limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary + + const rooms = this.pickRooms(roomIds); + const allResults: IRoomEvent[] = []; + for (const room of rooms) { + const results: MatrixEvent[] = []; + const state = room.currentState.events.get(eventType); + if (state) { + if (stateKey === "" || !!stateKey) { + const forKey = state.get(stateKey); + if (forKey) results.push(forKey); + } else { + results.push(...Array.from(state.values())); + } + } + + results.slice(0, limitPerRoom).forEach((e) => allResults.push(e.getEffectiveEvent() as IRoomEvent)); + } + return allResults; } public async askOpenID(observer: SimpleObservable): Promise { @@ -690,17 +693,6 @@ export class StopGapWidgetDriver extends WidgetDriver { return { file: blob }; } - /** - * Gets the IDs of all joined or invited rooms currently known to the - * client. - * @returns The room IDs. - */ - public getKnownRooms(): string[] { - return MatrixClientPeg.safeGet() - .getVisibleRooms(SettingsStore.getValue("feature_dynamic_room_predecessors")) - .map((r) => r.roomId); - } - /** * Expresses a {@link MatrixError} as a JSON payload * for use by Widget API error responses. diff --git a/test/unit-tests/stores/widgets/StopGapWidget-test.ts b/test/unit-tests/stores/widgets/StopGapWidget-test.ts index 61e96886b90..f767c96a028 100644 --- a/test/unit-tests/stores/widgets/StopGapWidget-test.ts +++ b/test/unit-tests/stores/widgets/StopGapWidget-test.ts @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import { mocked, MockedFunction, MockedObject } from "jest-mock"; +import { mocked, MockedObject } from "jest-mock"; import { last } from "lodash"; import { MatrixEvent, @@ -15,20 +15,15 @@ import { EventTimeline, EventType, MatrixEventEvent, - RoomStateEvent, - RoomState, } from "matrix-js-sdk/src/matrix"; import { ClientWidgetApi, WidgetApiFromWidgetAction } from "matrix-widget-api"; import { waitFor } from "jest-matrix-react"; -import { Optional } from "matrix-events-sdk"; import { stubClient, mkRoom, mkEvent } from "../../../test-utils"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import { StopGapWidget } from "../../../../src/stores/widgets/StopGapWidget"; import ActiveWidgetStore from "../../../../src/stores/ActiveWidgetStore"; import SettingsStore from "../../../../src/settings/SettingsStore"; -import { SdkContextClass } from "../../../../src/contexts/SDKContext"; -import { UPDATE_EVENT } from "../../../../src/stores/AsyncStore"; jest.mock("matrix-widget-api/lib/ClientWidgetApi"); @@ -58,7 +53,6 @@ describe("StopGapWidget", () => { // Start messaging without an iframe, since ClientWidgetApi is mocked widget.startMessaging(null as unknown as HTMLIFrameElement); messaging = mocked(last(mocked(ClientWidgetApi).mock.instances)!); - messaging.feedStateUpdate.mockResolvedValue(); }); afterEach(() => { @@ -90,20 +84,6 @@ describe("StopGapWidget", () => { expect(messaging.feedToDevice).toHaveBeenCalledWith(event.getEffectiveEvent(), false); }); - it("feeds incoming state updates to the widget", () => { - const event = mkEvent({ - event: true, - type: "org.example.foo", - skey: "", - user: "@alice:example.org", - content: { hello: "world" }, - room: "!1:example.org", - }); - - client.emit(RoomStateEvent.Events, event, {} as unknown as RoomState, null); - expect(messaging.feedStateUpdate).toHaveBeenCalledWith(event.getEffectiveEvent()); - }); - describe("feed event", () => { let event1: MatrixEvent; let event2: MatrixEvent; @@ -138,24 +118,24 @@ describe("StopGapWidget", () => { it("feeds incoming event to the widget", async () => { client.emit(ClientEvent.Event, event1); - expect(messaging.feedEvent).toHaveBeenCalledWith(event1.getEffectiveEvent()); + 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()); + 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()); + 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()); + 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()); + expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2.getEffectiveEvent(), "!1:example.org"); }); it("feeds decrypted events asynchronously", async () => { @@ -185,7 +165,7 @@ describe("StopGapWidget", () => { decryptingSpy2.mockReturnValue(false); client.emit(MatrixEventEvent.Decrypted, event2Encrypted); expect(messaging.feedEvent).toHaveBeenCalledTimes(1); - expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2Encrypted.getEffectiveEvent()); + expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2Encrypted.getEffectiveEvent(), "!1:example.org"); // …then event 1 event1Encrypted.event.type = event1.getType(); event1Encrypted.event.content = event1.getContent(); @@ -195,7 +175,7 @@ describe("StopGapWidget", () => { // doesn't have to be blocked on the decryption of event 1 (or // worse, dropped) expect(messaging.feedEvent).toHaveBeenCalledTimes(2); - expect(messaging.feedEvent).toHaveBeenLastCalledWith(event1Encrypted.getEffectiveEvent()); + expect(messaging.feedEvent).toHaveBeenLastCalledWith(event1Encrypted.getEffectiveEvent(), "!1:example.org"); }); it("should not feed incoming event if not in timeline", () => { @@ -211,7 +191,7 @@ describe("StopGapWidget", () => { }); client.emit(ClientEvent.Event, event); - expect(messaging.feedEvent).toHaveBeenCalledWith(event.getEffectiveEvent()); + 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 () => { @@ -231,19 +211,18 @@ describe("StopGapWidget", () => { }); client.emit(ClientEvent.Event, event1); - expect(messaging.feedEvent).toHaveBeenCalledWith(event1.getEffectiveEvent()); + 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()); + 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()); + expect(messaging.feedEvent).toHaveBeenLastCalledWith(event.getEffectiveEvent(), "!1:example.org"); }); }); }); - describe("StopGapWidget with stickyPromise", () => { let client: MockedObject; let widget: StopGapWidget; @@ -309,49 +288,3 @@ describe("StopGapWidget with stickyPromise", () => { waitFor(() => expect(setPersistenceSpy).toHaveBeenCalled(), { interval: 5 }); }); }); - -describe("StopGapWidget as an account widget", () => { - let widget: StopGapWidget; - let messaging: MockedObject; - let getRoomId: MockedFunction<() => Optional>; - - beforeEach(() => { - stubClient(); - // I give up, getting the return type of spyOn right is hopeless - getRoomId = jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId") as unknown as MockedFunction< - () => Optional - >; - getRoomId.mockReturnValue("!1:example.org"); - - widget = new StopGapWidget({ - app: { - id: "test", - creatorUserId: "@alice:example.org", - type: "example", - url: "https://example.org?user-id=$matrix_user_id&device-id=$org.matrix.msc3819.matrix_device_id&base-url=$org.matrix.msc4039.matrix_base_url&theme=$org.matrix.msc2873.client_theme", - roomId: "!1:example.org", - }, - userId: "@alice:example.org", - creatorUserId: "@alice:example.org", - waitForIframeLoad: true, - userWidget: false, - }); - // Start messaging without an iframe, since ClientWidgetApi is mocked - widget.startMessaging(null as unknown as HTMLIFrameElement); - messaging = mocked(last(mocked(ClientWidgetApi).mock.instances)!); - }); - - afterEach(() => { - widget.stopMessaging(); - getRoomId.mockRestore(); - }); - - it("updates viewed room", () => { - expect(messaging.setViewedRoomId).toHaveBeenCalledTimes(1); - expect(messaging.setViewedRoomId).toHaveBeenLastCalledWith("!1:example.org"); - getRoomId.mockReturnValue("!2:example.org"); - SdkContextClass.instance.roomViewStore.emit(UPDATE_EVENT); - expect(messaging.setViewedRoomId).toHaveBeenCalledTimes(2); - expect(messaging.setViewedRoomId).toHaveBeenLastCalledWith("!2:example.org"); - }); -}); diff --git a/test/unit-tests/stores/widgets/StopGapWidgetDriver-test.ts b/test/unit-tests/stores/widgets/StopGapWidgetDriver-test.ts index ccf2638d506..e484d0cc33f 100644 --- a/test/unit-tests/stores/widgets/StopGapWidgetDriver-test.ts +++ b/test/unit-tests/stores/widgets/StopGapWidgetDriver-test.ts @@ -17,7 +17,6 @@ import { MatrixEvent, MsgType, RelationType, - Room, } from "matrix-js-sdk/src/matrix"; import { Widget, @@ -39,7 +38,7 @@ import { import { SdkContextClass } from "../../../../src/contexts/SDKContext"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import { StopGapWidgetDriver } from "../../../../src/stores/widgets/StopGapWidgetDriver"; -import { mkEvent, stubClient } from "../../../test-utils"; +import { stubClient } from "../../../test-utils"; import { ModuleRunner } from "../../../../src/modules/ModuleRunner"; import dis from "../../../../src/dispatcher/dispatcher"; import Modal from "../../../../src/Modal"; @@ -570,7 +569,7 @@ describe("StopGapWidgetDriver", () => { it("passes the flag through to getVisibleRooms", () => { const driver = mkDefaultDriver(); - driver.getKnownRooms(); + driver.readRoomEvents(EventType.CallAnswer, "", 0, ["*"]); expect(client.getVisibleRooms).toHaveBeenCalledWith(false); }); }); @@ -585,7 +584,7 @@ describe("StopGapWidgetDriver", () => { it("passes the flag through to getVisibleRooms", () => { const driver = mkDefaultDriver(); - driver.getKnownRooms(); + driver.readRoomEvents(EventType.CallAnswer, "", 0, ["*"]); expect(client.getVisibleRooms).toHaveBeenCalledWith(true); }); }); @@ -693,107 +692,4 @@ describe("StopGapWidgetDriver", () => { await expect(file.text()).resolves.toEqual("test contents"); }); }); - - describe("readRoomTimeline", () => { - const event1 = mkEvent({ - event: true, - id: "$event-id1", - type: "org.example.foo", - user: "@alice:example.org", - content: { hello: "world" }, - room: "!1:example.org", - }); - const event2 = mkEvent({ - event: true, - id: "$event-id2", - type: "org.example.foo", - user: "@alice:example.org", - content: { hello: "world" }, - room: "!1:example.org", - }); - let driver: WidgetDriver; - - beforeEach(() => { - driver = mkDefaultDriver(); - client.getRoom.mockReturnValue({ - getLiveTimeline: () => ({ getEvents: () => [event1, event2] }), - } as unknown as Room); - }); - - it("reads all events", async () => { - expect( - await driver.readRoomTimeline("!1:example.org", "org.example.foo", undefined, undefined, 10, undefined), - ).toEqual([event2, event1].map((e) => e.getEffectiveEvent())); - }); - - it("reads up to a limit", async () => { - expect( - await driver.readRoomTimeline("!1:example.org", "org.example.foo", undefined, undefined, 1, undefined), - ).toEqual([event2.getEffectiveEvent()]); - }); - - it("reads up to a specific event", async () => { - expect( - await driver.readRoomTimeline( - "!1:example.org", - "org.example.foo", - undefined, - undefined, - 10, - event1.getId(), - ), - ).toEqual([event2.getEffectiveEvent()]); - }); - }); - - describe("readRoomState", () => { - const event1 = mkEvent({ - event: true, - id: "$event-id1", - type: "org.example.foo", - user: "@alice:example.org", - content: { hello: "world" }, - skey: "1", - room: "!1:example.org", - }); - const event2 = mkEvent({ - event: true, - id: "$event-id2", - type: "org.example.foo", - user: "@alice:example.org", - content: { hello: "world" }, - skey: "2", - room: "!1:example.org", - }); - let driver: WidgetDriver; - let getStateEvents: jest.Mock; - - beforeEach(() => { - driver = mkDefaultDriver(); - getStateEvents = jest.fn(); - client.getRoom.mockReturnValue({ - getLiveTimeline: () => ({ getState: () => ({ getStateEvents }) }), - } as unknown as Room); - }); - - it("reads a specific state key", async () => { - getStateEvents.mockImplementation((eventType, stateKey) => { - if (eventType === "org.example.foo" && stateKey === "1") return event1; - return undefined; - }); - expect(await driver.readRoomState("!1:example.org", "org.example.foo", "1")).toEqual([ - event1.getEffectiveEvent(), - ]); - }); - - it("reads all state keys", async () => { - getStateEvents.mockImplementation((eventType, stateKey) => { - if (eventType === "org.example.foo" && stateKey === undefined) return [event1, event2]; - return []; - }); - expect(await driver.readRoomState("!1:example.org", "org.example.foo", undefined)).toEqual( - [event1, event2].map((e) => e.getEffectiveEvent()), - ); - }); - }); }); diff --git a/yarn.lock b/yarn.lock index 74185f7e311..a3ee0454d1a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8693,10 +8693,10 @@ matrix-web-i18n@^3.2.1: minimist "^1.2.8" walk "^2.3.15" -matrix-widget-api@^1.10.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.12.0.tgz#b3d22bab1670051c8eeee66bb96d08b33148bc99" - integrity sha512-6JRd9fJGGvuBRhcTg9wX+Skn/Q1wox3jdp5yYQKJ6pPw4urW9bkTR90APBKVDB1vorJKT44jml+lCzkDMRBjww== +matrix-widget-api@1.11.0, matrix-widget-api@^1.10.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.11.0.tgz#2f548b11a7c0df789d5d4fdb5cc9ef7af8aef3da" + integrity sha512-ED/9hrJqDWVLeED0g1uJnYRhINh3ZTquwurdM+Hc8wLVJIQ8G/r7A7z74NC+8bBIHQ1Jo7i1Uq5CoJp/TzFYrA== dependencies: "@types/events" "^3.0.0" events "^3.2.0"