diff --git a/package.json b/package.json index 1d5f9a7fb3..0bbd4dffdb 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "@babel/runtime": "^7.12.5", "@matrix-org/analytics-events": "^0.6.0", "@matrix-org/matrix-wysiwyg": "^2.4.1", - "@matrix-org/react-sdk-module-api": "^1.0.0", + "@matrix-org/react-sdk-module-api": "^2.0.0", "@matrix-org/spec": "^1.7.0", "@sentry/browser": "^7.0.0", "@sentry/tracing": "^7.0.0", diff --git a/src/components/views/dialogs/ModuleUiDialog.tsx b/src/components/views/dialogs/ModuleUiDialog.tsx index acf9d1eda4..b43356666a 100644 --- a/src/components/views/dialogs/ModuleUiDialog.tsx +++ b/src/components/views/dialogs/ModuleUiDialog.tsx @@ -17,15 +17,18 @@ limitations under the License. import React, { createRef } from "react"; import { DialogContent, DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent"; import { logger } from "matrix-js-sdk/src/logger"; +import { ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi"; +import { ModuleUiDialogOptions } from "@matrix-org/react-sdk-module-api/lib/types/ModuleUiDialogOptions"; import ScrollableBaseModal, { IScrollableBaseState } from "./ScrollableBaseModal"; import { _t } from "../../../languageHandler"; interface IProps
> {
contentFactory: (props: P, ref: React.RefObject | undefined;
+ initialOptions: ModuleUiDialogOptions;
+ moduleApi: ModuleApi;
+ onFinished(ok?: boolean, model?: Awaited > e
super(props);
this.state = {
- title: this.props.title,
- canSubmit: true,
- actionLabel: _t("OK"),
+ title: this.props.initialOptions.title,
+ actionLabel: this.props.initialOptions.actionLabel ?? _t("OK"),
+ cancelLabel: this.props.initialOptions.cancelLabel,
+ canSubmit: this.props.initialOptions.canSubmit ?? true,
};
}
@@ -61,11 +65,23 @@ export class ModuleUiDialog > e
this.props.onFinished(false);
}
+ private setOptions(options: ModuleUiDialogOptions): void {
+ this.setState((state) => ({ ...state, ...options }));
+ }
+
protected renderContent(): React.ReactNode {
- return (
- ,
): Promise<{ didOkOrSubmit: boolean; model: M }> {
+ const initialOptions: ModuleUiDialogOptions =
+ typeof initialTitleOrOptions === "string" ? { title: initialTitleOrOptions } : initialTitleOrOptions;
+
return new Promise<{ didOkOrSubmit: boolean; model: M }>((resolve) => {
Modal.createDialog(
ModuleUiDialog ,
{
- title: title,
+ initialOptions,
contentFactory: body,
- // Typescript isn't very happy understanding that `props` satisfies `Omit `
- contentProps: {
- ...props,
- moduleApi: this,
- } as unknown as P,
+ moduleApi: this,
+ additionalContentProps: props,
},
"mx_CompoundDialog",
).finished.then(([didOkOrSubmit, model]) => {
diff --git a/test/modules/ProxiedModuleApi-test.ts b/test/modules/ProxiedModuleApi-test.ts
deleted file mode 100644
index 881e98c9df..0000000000
--- a/test/modules/ProxiedModuleApi-test.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
-Copyright 2022 The Matrix.org Foundation C.I.C.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations";
-import { AccountAuthInfo } from "@matrix-org/react-sdk-module-api/lib/types/AccountAuthInfo";
-
-import { ProxiedModuleApi } from "../../src/modules/ProxiedModuleApi";
-import { 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";
-
-describe("ProxiedApiModule", () => {
- afterEach(() => {
- ModuleRunner.instance.reset();
- });
-
- // Note: Remainder is implicitly tested from end-to-end tests of modules.
-
- describe("translations", () => {
- it("should cache translations", () => {
- const api = new ProxiedModuleApi();
- expect(api.translations).toBeFalsy();
-
- const translations: TranslationStringsObject = {
- ["custom string"]: {
- en: "custom string",
- fr: "custom french string",
- },
- };
- api.registerTranslations(translations);
- expect(api.translations).toBe(translations);
- });
-
- it("should overwriteAccountAuth", async () => {
- const dispatchSpy = jest.spyOn(defaultDispatcher, "dispatch");
-
- const api = new ProxiedModuleApi();
- const accountInfo = {} as unknown as AccountAuthInfo;
- const promise = api.overwriteAccountAuth(accountInfo);
-
- expect(dispatchSpy).toHaveBeenCalledWith(
- expect.objectContaining({
- action: Action.OverwriteLogin,
- credentials: {
- ...accountInfo,
- guest: false,
- },
- }),
- expect.anything(),
- );
-
- defaultDispatcher.fire(Action.OnLoggedIn);
-
- await expect(promise).resolves.toBeUndefined();
- });
-
- describe("integration", () => {
- it("should translate strings using translation system", async () => {
- // Test setup
- stubClient();
-
- // Set up a module to pull translations through
- const module = registerMockModule();
- const en = "custom string";
- const de = "custom german string";
- const enVars = "custom variable %(var)s";
- const varVal = "string";
- const deVars = "custom german variable %(var)s";
- const deFull = `custom german variable ${varVal}`;
- expect(module.apiInstance).toBeInstanceOf(ProxiedModuleApi);
- module.apiInstance.registerTranslations({
- [en]: {
- en: en,
- de: de,
- },
- [enVars]: {
- en: enVars,
- de: deVars,
- },
- });
- await setLanguage("de"); // calls `registerCustomTranslations()` for us
-
- // See if we can pull the German string out
- expect(module.apiInstance.translateString(en)).toEqual(de);
- expect(module.apiInstance.translateString(enVars, { var: varVal })).toEqual(deFull);
- });
- });
- });
-});
diff --git a/test/modules/ProxiedModuleApi-test.tsx b/test/modules/ProxiedModuleApi-test.tsx
new file mode 100644
index 0000000000..de796048a1
--- /dev/null
+++ b/test/modules/ProxiedModuleApi-test.tsx
@@ -0,0 +1,257 @@
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from "react";
+import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations";
+import { AccountAuthInfo } from "@matrix-org/react-sdk-module-api/lib/types/AccountAuthInfo";
+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 { ProxiedModuleApi } from "../../src/modules/ProxiedModuleApi";
+import { 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";
+
+describe("ProxiedApiModule", () => {
+ afterEach(() => {
+ ModuleRunner.instance.reset();
+ });
+
+ // Note: Remainder is implicitly tested from end-to-end tests of modules.
+
+ describe("translations", () => {
+ it("should cache translations", () => {
+ const api = new ProxiedModuleApi();
+ expect(api.translations).toBeFalsy();
+
+ const translations: TranslationStringsObject = {
+ ["custom string"]: {
+ en: "custom string",
+ fr: "custom french string",
+ },
+ };
+ api.registerTranslations(translations);
+ expect(api.translations).toBe(translations);
+ });
+
+ it("should overwriteAccountAuth", async () => {
+ const dispatchSpy = jest.spyOn(defaultDispatcher, "dispatch");
+
+ const api = new ProxiedModuleApi();
+ const accountInfo = {} as unknown as AccountAuthInfo;
+ const promise = api.overwriteAccountAuth(accountInfo);
+
+ expect(dispatchSpy).toHaveBeenCalledWith(
+ expect.objectContaining({
+ action: Action.OverwriteLogin,
+ credentials: {
+ ...accountInfo,
+ guest: false,
+ },
+ }),
+ expect.anything(),
+ );
+
+ defaultDispatcher.fire(Action.OnLoggedIn);
+
+ await expect(promise).resolves.toBeUndefined();
+ });
+
+ describe("integration", () => {
+ it("should translate strings using translation system", async () => {
+ // Test setup
+ stubClient();
+
+ // Set up a module to pull translations through
+ const module = registerMockModule();
+ const en = "custom string";
+ const de = "custom german string";
+ const enVars = "custom variable %(var)s";
+ const varVal = "string";
+ const deVars = "custom german variable %(var)s";
+ const deFull = `custom german variable ${varVal}`;
+ expect(module.apiInstance).toBeInstanceOf(ProxiedModuleApi);
+ module.apiInstance.registerTranslations({
+ [en]: {
+ en: en,
+ de: de,
+ },
+ [enVars]: {
+ en: enVars,
+ de: deVars,
+ },
+ });
+ await setLanguage("de"); // calls `registerCustomTranslations()` for us
+
+ // See if we can pull the German string out
+ expect(module.apiInstance.translateString(en)).toEqual(de);
+ expect(module.apiInstance.translateString(enVars, { var: varVal })).toEqual(deFull);
+ });
+
+ afterEach(async () => {
+ await setLanguage("en"); // reset the language
+ });
+ });
+ });
+
+ describe("openDialog", () => {
+ it("should open dialog with a custom title and default options", async () => {
+ class MyDialogContent extends DialogContent {
+ trySubmit = async () => ({ result: true });
+ render = () => This is my example content. This is my example content.