Fix widgets getting stuck in loading states (#31314)

* Refer to ClientWidgetApi as "widget API" rather than "messaging"

* Rename StopGapWidgetDriver to ElementWidgetDriver

* Rename StopGapWidget to WidgetMessaging

* Fix WidgetMessaging's lifetime by storing it in WidgetMessagingStore

(Rather than storing just the raw ClientWidgetApi objects.)

* Unfail test

* use an error

* cleanup start

* Add docs

* Prettier

* link to store

* remove a let

* More logging, split up loop

* Add a test demonstrating a regression in Call.start

* Restore Call.start to a single, robust event loop

* Fix test failure by resetting the messaging store

* Expand on the WidgetMessaging doc comment

* Add additional tests to buff up coverage

* Add a test for the sticker picker opening the IM.

* reduce copy paste

---------

Co-authored-by: Half-Shot <will@half-shot.uk>
Co-authored-by: Timo K <toger5@hotmail.de>
This commit is contained in:
Robin
2025-12-05 04:19:06 -05:00
committed by GitHub
parent f4e74c8dd2
commit 71895a3891
26 changed files with 1242 additions and 806 deletions

View File

@@ -14,7 +14,7 @@ import {
type RoomMember,
RoomStateEvent,
} from "matrix-js-sdk/src/matrix";
import { type ClientWidgetApi, Widget } from "matrix-widget-api";
import { Widget } from "matrix-widget-api";
import { act, cleanup, render, screen } from "jest-matrix-react";
import { mocked, type Mocked } from "jest-mock";
@@ -32,6 +32,7 @@ import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
import { ConnectionState } from "../../../../../src/models/Call";
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext";
import RoomContext, { type RoomContextType } from "../../../../../src/contexts/RoomContext";
import { type WidgetMessaging } from "../../../../../src/stores/widgets/WidgetMessaging";
describe("<RoomCallBanner />", () => {
let client: Mocked<MatrixClient>;
@@ -115,7 +116,7 @@ describe("<RoomCallBanner />", () => {
widget = new Widget(call.widget);
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
stop: () => {},
} as unknown as ClientWidgetApi);
} as unknown as WidgetMessaging);
});
afterEach(() => {
cleanup(); // Unmount before we do any cleanup that might update the component

View File

@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import { Room, type MatrixClient } from "matrix-js-sdk/src/matrix";
import { type ClientWidgetApi, type IWidget, MatrixWidgetType } from "matrix-widget-api";
import { type IWidget, MatrixWidgetType } from "matrix-widget-api";
import { act, render, type RenderResult, waitForElementToBeRemoved, waitFor } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import {
@@ -34,7 +34,7 @@ import AppTile from "../../../../../src/components/views/elements/AppTile";
import { Container, WidgetLayoutStore } from "../../../../../src/stores/widgets/WidgetLayoutStore";
import AppsDrawer from "../../../../../src/components/views/rooms/AppsDrawer";
import { ElementWidgetCapabilities } from "../../../../../src/stores/widgets/ElementWidgetCapabilities";
import { ElementWidget } from "../../../../../src/stores/widgets/StopGapWidget";
import { ElementWidget, type WidgetMessaging } from "../../../../../src/stores/widgets/WidgetMessaging";
import { WidgetMessagingStore } from "../../../../../src/stores/widgets/WidgetMessagingStore";
import { ModuleRunner } from "../../../../../src/modules/ModuleRunner";
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
@@ -116,9 +116,11 @@ describe("AppTile", () => {
await RightPanelStore.instance.onReady();
});
beforeEach(() => {
beforeEach(async () => {
sdkContext = new SdkContextClass();
jest.spyOn(SettingsStore, "getValue").mockRestore();
// @ts-ignore
await WidgetMessagingStore.instance.onReady();
});
it("destroys non-persisted right panel widget on room change", async () => {
@@ -424,16 +426,20 @@ describe("AppTile", () => {
describe("with an existing widgetApi with requiresClient = false", () => {
beforeEach(() => {
const api = {
hasCapability: (capability: ElementWidgetCapabilities): boolean => {
return !(capability === ElementWidgetCapabilities.RequiresClient);
},
once: () => {},
const messaging = {
on: () => {},
off: () => {},
prepare: async () => {},
stop: () => {},
} as unknown as ClientWidgetApi;
widgetApi: {
hasCapability: (capability: ElementWidgetCapabilities): boolean => {
return !(capability === ElementWidgetCapabilities.RequiresClient);
},
},
} as unknown as WidgetMessaging;
const mockWidget = new ElementWidget(app1);
WidgetMessagingStore.instance.storeMessaging(mockWidget, r1.roomId, api);
WidgetMessagingStore.instance.storeMessaging(mockWidget, r1.roomId, messaging);
renderResult = render(
<MatrixClientContext.Provider value={cli}>

View File

@@ -16,7 +16,7 @@ import {
PendingEventOrdering,
type RoomMember,
} from "matrix-js-sdk/src/matrix";
import { type ClientWidgetApi, Widget } from "matrix-widget-api";
import { Widget } from "matrix-widget-api";
import {
useMockedCalls,
@@ -35,6 +35,7 @@ import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
import { CallStore } from "../../../../../src/stores/CallStore";
import { WidgetMessagingStore } from "../../../../../src/stores/widgets/WidgetMessagingStore";
import { ConnectionState } from "../../../../../src/models/Call";
import { type WidgetMessaging } from "../../../../../src/stores/widgets/WidgetMessaging";
const CallEvent = wrapInMatrixClientContext(UnwrappedCallEvent);
@@ -86,7 +87,7 @@ describe("CallEvent", () => {
widget = new Widget(call.widget);
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
stop: () => {},
} as unknown as ClientWidgetApi);
} as unknown as WidgetMessaging);
});
afterEach(async () => {

View File

@@ -20,7 +20,6 @@ import {
import { KnownMembership } from "matrix-js-sdk/src/types";
import { Widget } from "matrix-widget-api";
import type { ClientWidgetApi } from "matrix-widget-api";
import {
stubClient,
mkRoomMember,
@@ -47,6 +46,7 @@ import { MessagePreviewStore } from "../../../../../src/stores/room-list/Message
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
import SettingsStore from "../../../../../src/settings/SettingsStore";
import { ConnectionState } from "../../../../../src/models/Call";
import { type WidgetMessaging } from "../../../../../src/stores/widgets/WidgetMessaging";
jest.mock("../../../../../src/customisations/helpers/UIComponents", () => ({
shouldShowComponent: jest.fn(),
@@ -204,7 +204,7 @@ describe("RoomTile", () => {
widget = new Widget(call.widget);
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
stop: () => {},
} as unknown as ClientWidgetApi);
} as unknown as WidgetMessaging);
});
afterEach(() => {

View File

@@ -18,7 +18,6 @@ import {
} from "matrix-js-sdk/src/matrix";
import { Widget } from "matrix-widget-api";
import type { ClientWidgetApi } from "matrix-widget-api";
import {
stubClient,
mkRoomMember,
@@ -33,6 +32,7 @@ import { CallView as _CallView } from "../../../../../src/components/views/voip/
import { WidgetMessagingStore } from "../../../../../src/stores/widgets/WidgetMessagingStore";
import { CallStore } from "../../../../../src/stores/CallStore";
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
import { type WidgetMessaging } from "../../../../../src/stores/widgets/WidgetMessaging";
const CallView = wrapInMatrixClientContext(_CallView);
@@ -73,8 +73,11 @@ describe("CallView", () => {
widget = new Widget(call.widget);
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
on: () => {},
off: () => {},
stop: () => {},
} as unknown as ClientWidgetApi);
embedUrl: "https://example.org",
} as unknown as WidgetMessaging);
});
afterEach(() => {