diff --git a/src/components/views/elements/AppTile.tsx b/src/components/views/elements/AppTile.tsx index 6f7267b3d8c..05c67ab9413 100644 --- a/src/components/views/elements/AppTile.tsx +++ b/src/components/views/elements/AppTile.tsx @@ -35,7 +35,7 @@ import SettingsStore from "../../../settings/SettingsStore"; import { aboveLeftOf, ContextMenuButton } from "../../structures/ContextMenu"; import PersistedElement, { getPersistKey } from "./PersistedElement"; import { WidgetType } from "../../../widgets/WidgetType"; -import { StopGapWidget } from "../../../stores/widgets/StopGapWidget"; +import { ElementWidget, StopGapWidget } from "../../../stores/widgets/StopGapWidget"; import { ElementWidgetActions } from "../../../stores/widgets/ElementWidgetActions"; import WidgetContextMenu from "../context_menus/WidgetContextMenu"; import WidgetAvatar from "../avatars/WidgetAvatar"; @@ -50,6 +50,7 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { ActionPayload } from "../../../dispatcher/payloads"; import { Action } from '../../../dispatcher/actions'; import { ElementWidgetCapabilities } from '../../../stores/widgets/ElementWidgetCapabilities'; +import { WidgetMessagingStore } from '../../../stores/widgets/WidgetMessagingStore'; interface IProps { app: IApp; @@ -196,6 +197,24 @@ export default class AppTile extends React.Component { } }; + private determineInitialRequiresClientState(): boolean { + try { + const mockWidget = new ElementWidget(this.props.app); + const widgetApi = WidgetMessagingStore.instance.getMessaging(mockWidget, this.props.room.roomId); + if (widgetApi) { + // Load value from existing API to prevent resetting the requiresClient value on layout changes. + return widgetApi.hasCapability(ElementWidgetCapabilities.RequiresClient); + } + } catch { + // fallback to true + } + + // requiresClient is initially set to true. This avoids the broken state of the popout + // button being visible (for an instance) and then disappearing when the widget is loaded. + // requiresClient <-> hide the popout button + return true; + } + /** * Set initial component state when the App wUrl (widget URL) is being updated. * Component props *must* be passed (rather than relying on this.props). @@ -214,10 +233,7 @@ export default class AppTile extends React.Component { error: null, menuDisplayed: false, widgetPageTitle: this.props.widgetPageTitle, - // requiresClient is initially set to true. This avoids the broken state of the popout - // button being visible (for an instance) and then disappearing when the widget is loaded. - // requiresClient <-> hide the popout button - requiresClient: true, + requiresClient: this.determineInitialRequiresClientState(), }; } diff --git a/test/components/views/elements/AppTile-test.tsx b/test/components/views/elements/AppTile-test.tsx index 207623833fe..d528f52a9ee 100644 --- a/test/components/views/elements/AppTile-test.tsx +++ b/test/components/views/elements/AppTile-test.tsx @@ -18,7 +18,7 @@ import React from "react"; import TestRenderer from "react-test-renderer"; import { jest } from "@jest/globals"; import { Room } from "matrix-js-sdk/src/models/room"; -import { MatrixWidgetType } from "matrix-widget-api"; +import { ClientWidgetApi, MatrixWidgetType } from "matrix-widget-api"; import { mount, ReactWrapper } from "enzyme"; import { Optional } from "matrix-events-sdk"; @@ -39,11 +39,14 @@ import ActiveWidgetStore from "../../../../src/stores/ActiveWidgetStore"; 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 { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore"; describe("AppTile", () => { let cli; - let r1; - let r2; + let r1: Room; + let r2: Room; const resizeNotifier = new ResizeNotifier(); let app1: IApp; let app2: IApp; @@ -328,6 +331,10 @@ describe("AppTile", () => { moveToContainerSpy = jest.spyOn(WidgetLayoutStore.instance, 'moveToContainer'); }); + it("requiresClient should be true", () => { + expect(wrapper.state('requiresClient')).toBe(true); + }); + it("clicking 'minimise' should send the widget to the right", () => { const minimiseButton = wrapper.find('.mx_AppTileMenuBar_iconButton_minimise'); minimiseButton.first().simulate('click'); @@ -355,5 +362,35 @@ describe("AppTile", () => { expect(moveToContainerSpy).toHaveBeenCalledWith(r1, app1, Container.Top); }); }); + + describe("with an existing widgetApi holding requiresClient = false", () => { + let wrapper: ReactWrapper; + + beforeEach(() => { + const api = { + hasCapability: (capability: ElementWidgetCapabilities): boolean => { + return !(capability === ElementWidgetCapabilities.RequiresClient); + }, + once: () => {}, + } as unknown as ClientWidgetApi; + + const mockWidget = new ElementWidget(app1); + WidgetMessagingStore.instance.storeMessaging(mockWidget, r1.roomId, api); + + wrapper = mount(( + + + + )); + }); + + it("requiresClient should be false", () => { + expect(wrapper.state('requiresClient')).toBe(false); + }); + }); }); });