From 11096b207a1510569f5c54182e328f6148a6475c Mon Sep 17 00:00:00 2001 From: Charly Nguyen <1422657+charlynguyen@users.noreply.github.com> Date: Mon, 22 Jan 2024 11:53:27 +0100 Subject: [PATCH] Expose apps/widgets (#12071) * Expose apps/widgets Signed-off-by: Charly Nguyen * Bump @matrix-org/react-sdk-module-api from 2.2.1 to 2.3.0 Signed-off-by: Charly Nguyen --------- Signed-off-by: Charly Nguyen --- package.json | 2 +- src/modules/ProxiedModuleApi.ts | 36 ++++++ .../utils/autocomplete-test.ts | 2 + .../settings/AddPrivilegedUsers-test.tsx | 2 + test/modules/ProxiedModuleApi-test.tsx | 105 +++++++++++++++++- test/stores/AutoRageshakeStore-test.ts | 2 + test/stores/RoomViewStore-test.ts | 3 + test/utils/SearchInput-test.ts | 2 + .../components/VoiceBroadcastBody-test.tsx | 2 + yarn.lock | 8 +- 10 files changed, 158 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 71ffc3af4a..f0acbba77d 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "@matrix-org/emojibase-bindings": "^1.1.2", "@matrix-org/matrix-wysiwyg": "2.17.0", "@matrix-org/olm": "3.2.15", - "@matrix-org/react-sdk-module-api": "^2.2.1", + "@matrix-org/react-sdk-module-api": "^2.3.0", "@matrix-org/spec": "^1.7.0", "@sentry/browser": "^7.0.0", "@testing-library/react-hooks": "^8.0.1", diff --git a/src/modules/ProxiedModuleApi.ts b/src/modules/ProxiedModuleApi.ts index 67d9bf5e49..0f85992d24 100644 --- a/src/modules/ProxiedModuleApi.ts +++ b/src/modules/ProxiedModuleApi.ts @@ -38,6 +38,8 @@ import { Action } from "../dispatcher/actions"; import { OverwriteLoginPayload } from "../dispatcher/payloads/OverwriteLoginPayload"; import { ActionPayload } from "../dispatcher/payloads"; import SettingsStore from "../settings/SettingsStore"; +import WidgetStore, { IApp } from "../stores/WidgetStore"; +import { Container, WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore"; /** * Glue between the `ModuleApi` interface and the react-sdk. Anticipates one instance @@ -218,4 +220,38 @@ export class ProxiedModuleApi implements ModuleApi { if (!maybeObj || !(typeof maybeObj === "object")) return undefined; return maybeObj[key]; } + + /** + * @override + */ + public getApps(roomId: string): IApp[] { + return WidgetStore.instance.getApps(roomId); + } + + /** + * @override + */ + public getAppAvatarUrl(app: IApp, width?: number, height?: number, resizeMethod?: string): string | null { + if (!app.avatar_url) return null; + // eslint-disable-next-line no-restricted-properties + return MatrixClientPeg.safeGet().mxcUrlToHttp(app.avatar_url, width, height, resizeMethod); + } + + /** + * @override + */ + public isAppInContainer(app: IApp, container: Container, roomId: string): boolean { + const room = MatrixClientPeg.safeGet().getRoom(roomId); + if (!room) return false; + return WidgetLayoutStore.instance.isInContainer(room, app, container); + } + + /** + * @override + */ + public moveAppToContainer(app: IApp, container: Container, roomId: string): void { + const room = MatrixClientPeg.safeGet().getRoom(roomId); + if (!room) return; + WidgetLayoutStore.instance.moveToContainer(room, app, container); + } } diff --git a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts index 2cb049932d..a35382b6ff 100644 --- a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts +++ b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts @@ -41,6 +41,8 @@ const createMockCompletion = (props: Partial): ICompletion => { }; jest.mock("../../../../../../src/Avatar"); +jest.mock("../../../../../../src/stores/WidgetStore"); +jest.mock("../../../../../../src/stores/widgets/WidgetLayoutStore"); beforeEach(() => jest.clearAllMocks()); afterAll(() => jest.restoreAllMocks()); diff --git a/test/components/views/settings/AddPrivilegedUsers-test.tsx b/test/components/views/settings/AddPrivilegedUsers-test.tsx index 118529349c..c3e3d1bef5 100644 --- a/test/components/views/settings/AddPrivilegedUsers-test.tsx +++ b/test/components/views/settings/AddPrivilegedUsers-test.tsx @@ -30,6 +30,8 @@ import UserProvider from "../../../../src/autocomplete/UserProvider"; import { ICompletion } from "../../../../src/autocomplete/Autocompleter"; jest.mock("../../../../src/autocomplete/UserProvider"); +jest.mock("../../../../src/stores/WidgetStore"); +jest.mock("../../../../src/stores/widgets/WidgetLayoutStore"); const completions: ICompletion[] = [ { diff --git a/test/modules/ProxiedModuleApi-test.tsx b/test/modules/ProxiedModuleApi-test.tsx index de796048a1..bec6215232 100644 --- a/test/modules/ProxiedModuleApi-test.tsx +++ b/test/modules/ProxiedModuleApi-test.tsx @@ -20,14 +20,18 @@ import { AccountAuthInfo } from "@matrix-org/react-sdk-module-api/lib/types/Acco import { DialogContent, DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent"; import { screen, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; +import { MatrixClient } from "matrix-js-sdk/src/matrix"; +import { Mocked } from "jest-mock"; import { ProxiedModuleApi } from "../../src/modules/ProxiedModuleApi"; -import { stubClient } from "../test-utils"; +import { getMockClientWithEventEmitter, mkRoom, stubClient } from "../test-utils"; import { setLanguage } from "../../src/languageHandler"; import { ModuleRunner } from "../../src/modules/ModuleRunner"; import { registerMockModule } from "./MockModule"; import defaultDispatcher from "../../src/dispatcher/dispatcher"; import { Action } from "../../src/dispatcher/actions"; +import WidgetStore, { IApp } from "../../src/stores/WidgetStore"; +import { Container, WidgetLayoutStore } from "../../src/stores/widgets/WidgetLayoutStore"; describe("ProxiedApiModule", () => { afterEach(() => { @@ -254,4 +258,103 @@ describe("ProxiedApiModule", () => { expect(dialog).not.toBeInTheDocument(); }); }); + + describe("getApps", () => { + it("should return apps from the widget store", () => { + const api = new ProxiedModuleApi(); + const app = {} as unknown as IApp; + const apps: IApp[] = [app]; + + jest.spyOn(WidgetStore.instance, "getApps").mockReturnValue(apps); + expect(api.getApps("!room:example.com")).toEqual(apps); + }); + }); + + describe("getAppAvatarUrl", () => { + const app = {} as unknown as IApp; + const avatarUrl = "https://example.com/avatar.png"; + + let api: ProxiedModuleApi; + let client: Mocked; + + beforeEach(() => { + api = new ProxiedModuleApi(); + client = getMockClientWithEventEmitter({ mxcUrlToHttp: jest.fn().mockReturnValue(avatarUrl) }); + }); + + it("should return null if the app has no avatar URL", () => { + expect(api.getAppAvatarUrl(app)).toBeNull(); + }); + + it("should return the app avatar URL", () => { + expect(api.getAppAvatarUrl({ ...app, avatar_url: avatarUrl })).toBe(avatarUrl); + }); + + it("should support optional thumbnail params", () => { + api.getAppAvatarUrl({ ...app, avatar_url: avatarUrl }, 1, 2, "3"); + // eslint-disable-next-line no-restricted-properties + expect(client.mxcUrlToHttp).toHaveBeenCalledWith(avatarUrl, 1, 2, "3"); + }); + }); + + describe("isAppInContainer", () => { + const app = {} as unknown as IApp; + const roomId = "!room:example.com"; + + let api: ProxiedModuleApi; + let client: MatrixClient; + + beforeEach(() => { + api = new ProxiedModuleApi(); + client = stubClient(); + + jest.spyOn(WidgetLayoutStore.instance, "isInContainer"); + }); + + it("should return false if there is no room", () => { + client.getRoom = jest.fn().mockReturnValue(null); + + expect(api.isAppInContainer(app, Container.Top, roomId)).toBe(false); + expect(WidgetLayoutStore.instance.isInContainer).not.toHaveBeenCalled(); + }); + + it("should return false if the app is not in the container", () => { + jest.spyOn(WidgetLayoutStore.instance, "isInContainer").mockReturnValue(false); + expect(api.isAppInContainer(app, Container.Top, roomId)).toBe(false); + }); + + it("should return true if the app is in the container", () => { + jest.spyOn(WidgetLayoutStore.instance, "isInContainer").mockReturnValue(true); + expect(api.isAppInContainer(app, Container.Top, roomId)).toBe(true); + }); + }); + + describe("moveAppToContainer", () => { + const app = {} as unknown as IApp; + const roomId = "!room:example.com"; + + let api: ProxiedModuleApi; + let client: MatrixClient; + + beforeEach(() => { + api = new ProxiedModuleApi(); + client = stubClient(); + + jest.spyOn(WidgetLayoutStore.instance, "moveToContainer"); + }); + + it("should not move if there is no room", () => { + client.getRoom = jest.fn().mockReturnValue(null); + api.moveAppToContainer(app, Container.Top, roomId); + expect(WidgetLayoutStore.instance.moveToContainer).not.toHaveBeenCalled(); + }); + + it("should move if there is a room", () => { + const room = mkRoom(client, roomId); + client.getRoom = jest.fn().mockReturnValue(room); + + api.moveAppToContainer(app, Container.Top, roomId); + expect(WidgetLayoutStore.instance.moveToContainer).toHaveBeenCalledWith(room, app, Container.Top); + }); + }); }); diff --git a/test/stores/AutoRageshakeStore-test.ts b/test/stores/AutoRageshakeStore-test.ts index 7a481441fb..c4709dfbd0 100644 --- a/test/stores/AutoRageshakeStore-test.ts +++ b/test/stores/AutoRageshakeStore-test.ts @@ -29,6 +29,8 @@ import AutoRageshakeStore from "../../src/stores/AutoRageshakeStore"; import { mkEvent, stubClient } from "../test-utils"; jest.mock("../../src/rageshake/submit-rageshake"); +jest.mock("../../src/stores/WidgetStore"); +jest.mock("../../src/stores/widgets/WidgetLayoutStore"); describe("AutoRageshakeStore", () => { const roomId = "!room:example.com"; diff --git a/test/stores/RoomViewStore-test.ts b/test/stores/RoomViewStore-test.ts index 8ad55e6fc1..c1cf7c0478 100644 --- a/test/stores/RoomViewStore-test.ts +++ b/test/stores/RoomViewStore-test.ts @@ -84,6 +84,9 @@ jest.mock("../../src/utils/DMRoomMap", () => { }; }); +jest.mock("../../src/stores/WidgetStore"); +jest.mock("../../src/stores/widgets/WidgetLayoutStore"); + describe("RoomViewStore", function () { const userId = "@alice:server"; const roomId = "!randomcharacters:aser.ver"; diff --git a/test/utils/SearchInput-test.ts b/test/utils/SearchInput-test.ts index a2a9f73ee2..344fc0f16d 100644 --- a/test/utils/SearchInput-test.ts +++ b/test/utils/SearchInput-test.ts @@ -19,6 +19,8 @@ import { parsePermalink } from "../../src/utils/permalinks/Permalinks"; import { transformSearchTerm } from "../../src/utils/SearchInput"; jest.mock("../../src/utils/permalinks/Permalinks"); +jest.mock("../../src/stores/WidgetStore"); +jest.mock("../../src/stores/widgets/WidgetLayoutStore"); describe("transforming search term", () => { it("should return the primaryEntityId if the search term was a permalink", () => { diff --git a/test/voice-broadcast/components/VoiceBroadcastBody-test.tsx b/test/voice-broadcast/components/VoiceBroadcastBody-test.tsx index 70e5378894..be880d1f2c 100644 --- a/test/voice-broadcast/components/VoiceBroadcastBody-test.tsx +++ b/test/voice-broadcast/components/VoiceBroadcastBody-test.tsx @@ -44,6 +44,8 @@ jest.mock("../../../src/voice-broadcast/components/molecules/VoiceBroadcastPlayb jest.mock("../../../src/utils/permalinks/Permalinks"); jest.mock("../../../src/utils/MediaEventHelper"); +jest.mock("../../../src/stores/WidgetStore"); +jest.mock("../../../src/stores/widgets/WidgetLayoutStore"); describe("VoiceBroadcastBody", () => { const roomId = "!room:example.com"; diff --git a/yarn.lock b/yarn.lock index dd45e33a2c..affb509dfa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1847,10 +1847,10 @@ resolved "https://registry.yarnpkg.com/@matrix-org/olm/-/olm-3.2.15.tgz#55f3c1b70a21bbee3f9195cecd6846b1083451ec" integrity sha512-S7lOrndAK9/8qOtaTq/WhttJC/o4GAzdfK0MUPpo8ApzsJEC0QjtwrkC3KBXdFP1cD1MXi/mlKR7aaoVMKgs6Q== -"@matrix-org/react-sdk-module-api@^2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@matrix-org/react-sdk-module-api/-/react-sdk-module-api-2.2.1.tgz#308bcb42a780200d3e7994235376784b51819379" - integrity sha512-+MXTMEapzGmhArUt86GYDQirOvm19+wvQLDApmHpUQvSZvYm7wOo1EwR9FFvSKve53fu+v6gI1grnj7YLzGQ9Q== +"@matrix-org/react-sdk-module-api@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@matrix-org/react-sdk-module-api/-/react-sdk-module-api-2.3.0.tgz#85be5cfc73be0494c13d4dc9050cb70c58d7a08b" + integrity sha512-x/ie44yaXNtE5AKcmQiW5yINVEIJ7IjjEc35vj6j52fM8tZ9XbJx9PANKSWsdd0NJp3OqyaeHftmN6ESfx4YoQ== dependencies: "@babel/runtime" "^7.17.9"