From 4dcbe3e3cf26b52b0f648c08846bc94212f80148 Mon Sep 17 00:00:00 2001 From: Mikhail Aheichyk Date: Thu, 8 Jun 2023 13:13:10 +0300 Subject: [PATCH 1/6] Add banner configurable via module API Signed-off-by: Mikhail Aheichyk --- src/vector/app.tsx | 30 +++++++++++++++++++----------- src/vector/index.html | 2 +- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/vector/app.tsx b/src/vector/app.tsx index 463ef1346a4..53ea8a255a8 100644 --- a/src/vector/app.tsx +++ b/src/vector/app.tsx @@ -35,6 +35,8 @@ import { SnakedObject } from "matrix-react-sdk/src/utils/SnakedObject"; import MatrixChat from "matrix-react-sdk/src/components/structures/MatrixChat"; import { ValidatedServerConfig } from "matrix-react-sdk/src/utils/ValidatedServerConfig"; import { QueryDict, encodeParams } from "matrix-js-sdk/src/utils"; +import { BannerLifecycle, BannerOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/BannerLifecycle"; +import { ModuleRunner } from "matrix-react-sdk/src/modules/ModuleRunner"; import { parseQs } from "./url_utils"; import VectorBasePlatform from "./platform/VectorBasePlatform"; @@ -129,18 +131,24 @@ export async function loadApp(fragParams: {}): Promise { const defaultDeviceName = snakedConfig.get("default_device_display_name") ?? platform?.getDefaultDeviceDisplayName(); + const opts: BannerOpts = { banner: undefined }; + ModuleRunner.instance.invoke(BannerLifecycle.Banner, opts); + return ( - + <> + {opts.banner} + + ); } diff --git a/src/vector/index.html b/src/vector/index.html index 9f41e6c2161..3f6fd9d69ec 100644 --- a/src/vector/index.html +++ b/src/vector/index.html @@ -60,7 +60,7 @@ -
+
From 3d21dd0604d0e0458b80436d8116396ba0412b60 Mon Sep 17 00:00:00 2001 From: Mikhail Aheichyk Date: Fri, 18 Aug 2023 12:59:02 +0300 Subject: [PATCH 2/6] Use wrapper from module API to add possibility to configure header, footer around MatrixChat. Signed-off-by: Mikhail Aheichyk --- src/vector/app.tsx | 11 +++++------ src/vector/index.html | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/vector/app.tsx b/src/vector/app.tsx index ec9be75557a..2db717418fa 100644 --- a/src/vector/app.tsx +++ b/src/vector/app.tsx @@ -35,7 +35,7 @@ import { SnakedObject } from "matrix-react-sdk/src/utils/SnakedObject"; import MatrixChat from "matrix-react-sdk/src/components/structures/MatrixChat"; import { ValidatedServerConfig } from "matrix-react-sdk/src/utils/ValidatedServerConfig"; import { QueryDict, encodeParams } from "matrix-js-sdk/src/utils"; -import { BannerLifecycle, BannerOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/BannerLifecycle"; +import { WrapperLifecycle, WrapperOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WrapperLifecycle"; import { ModuleRunner } from "matrix-react-sdk/src/modules/ModuleRunner"; import { parseQs } from "./url_utils"; @@ -137,12 +137,11 @@ export async function loadApp(fragParams: {}): Promise { const initialScreenAfterLogin = getInitialScreenAfterLogin(window.location); - const opts: BannerOpts = { banner: undefined }; - ModuleRunner.instance.invoke(BannerLifecycle.Banner, opts); + const wrapperOpts: WrapperOpts = { Wrapper: React.Fragment }; + ModuleRunner.instance.invoke(WrapperLifecycle.Wrapper, wrapperOpts); return ( - <> - {opts.banner} + { initialScreenAfterLogin={initialScreenAfterLogin} defaultDeviceDisplayName={defaultDeviceName} /> - + ); } diff --git a/src/vector/index.html b/src/vector/index.html index 3f6fd9d69ec..9f41e6c2161 100644 --- a/src/vector/index.html +++ b/src/vector/index.html @@ -60,7 +60,7 @@ -
+
From 761d08e5d3424f20cccf0b0548180587f08a7219 Mon Sep 17 00:00:00 2001 From: Mikhail Aheichyk Date: Mon, 21 Aug 2023 20:49:53 +0300 Subject: [PATCH 3/6] Bump react-sdk-module-api from 2.0.0 to 2.1.0 Signed-off-by: Mikhail Aheichyk --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 897a6b33ce2..aef82758fd5 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ }, "dependencies": { "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz", - "@matrix-org/react-sdk-module-api": "^2.0.0", + "@matrix-org/react-sdk-module-api": "^2.1.0", "gfm.css": "^1.1.2", "jsrsasign": "^10.5.25", "katex": "^0.16.0", diff --git a/yarn.lock b/yarn.lock index ee90db70cac..dfdf678b429 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1636,10 +1636,10 @@ dependencies: "@babel/runtime" "^7.17.9" -"@matrix-org/react-sdk-module-api@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@matrix-org/react-sdk-module-api/-/react-sdk-module-api-2.0.0.tgz#f894af429ad352d5151dc7240cc2f987d9dab780" - integrity sha512-o/M+IfB3bu4S3yTO10zMRiEtTQagV9AJ9cNmq8a/ksniCx3QLShtzWeL5FkTa8co0ab/VdxdqTlEux0aStT/dg== +"@matrix-org/react-sdk-module-api@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@matrix-org/react-sdk-module-api/-/react-sdk-module-api-2.1.0.tgz#ca9d67853512fda1df2786810b90be31dd8dc7b1" + integrity sha512-SARD5BsmZYv1hvuezLfBUafJ9+rPLbk5WO0S3vZgkLH3jJQrk7f/65qBB5fLKF2ljprfZ1GTpuBeq04wn7Tnmg== dependencies: "@babel/runtime" "^7.17.9" From 97074c8a80595dbe494cb4877735f78578b2be8d Mon Sep 17 00:00:00 2001 From: Mikhail Aheichyk Date: Tue, 22 Aug 2023 10:12:55 +0300 Subject: [PATCH 4/6] Test is added for wrapper. Signed-off-by: Mikhail Aheichyk --- test/app-tests/wrapper-test.tsx | 90 +++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 test/app-tests/wrapper-test.tsx diff --git a/test/app-tests/wrapper-test.tsx b/test/app-tests/wrapper-test.tsx new file mode 100644 index 00000000000..c4747098431 --- /dev/null +++ b/test/app-tests/wrapper-test.tsx @@ -0,0 +1,90 @@ +/* +Copyright 2023 Mikhail Aheichyk +Copyright 2023 Nordeck IT + Consulting GmbH. + +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 SdkConfig from "matrix-react-sdk/src/SdkConfig"; +import PlatformPeg from "matrix-react-sdk/src/PlatformPeg"; +import fetchMock from "fetch-mock-jest"; +import { render, RenderResult, screen, waitFor } from "@testing-library/react"; +import { ModuleRunner } from "matrix-react-sdk/src/modules/ModuleRunner"; +import { WrapperLifecycle, WrapperOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WrapperLifecycle"; + +import WebPlatform from "../../src/vector/platform/WebPlatform"; +import { loadApp } from "../../src/vector/app"; + +fetchMock.config.overwriteRoutes = true; + +describe("Wrapper", () => { + beforeEach(async () => { + SdkConfig.reset(); + PlatformPeg.set(new WebPlatform()); + fetchMock.get("https://matrix-client.matrix.org/_matrix/client/versions", { + unstable_features: {}, + versions: ["v1.1"], + }); + fetchMock.get("https://matrix.org/.well-known/matrix/client", { + "m.homeserver": { + base_url: "https://matrix-client.matrix.org", + }, + }); + fetchMock.get("/version", "1.10.13"); + }); + + it("wrap a matrix client with header and footer", async () => { + SdkConfig.put({ + default_server_config: { + "m.homeserver": { + base_url: "https://matrix-client.matrix.org", + }, + }, + }); + + jest.spyOn(ModuleRunner.instance, "invoke").mockImplementation((lifecycleEvent, opts) => { + if (lifecycleEvent === WrapperLifecycle.Wrapper) { + (opts as WrapperOpts).Wrapper = ({ children }) => { + return ( + <> +
Header
+ {children} +
Footer
+ + ); + }; + } + }); + + const matrixChat: RenderResult = render(await loadApp({})); + + // at this point, we're trying to do a guest registration; + // we expect a spinner + await assertAtLoadingSpinner(); + + await awaitWelcomeComponent(matrixChat); + + // Are not semantic elements because Element has a footer already. + screen.getByText(/Header/i); + screen.getByText(/Footer/i); + }); +}); + +async function assertAtLoadingSpinner(): Promise { + await screen.findByRole("progressbar"); +} + +async function awaitWelcomeComponent(matrixChat?: RenderResult): Promise { + await waitFor(() => matrixChat?.container.querySelector(".mx_Welcome")); +} From 46426f70efe59c88ed9d5aa793b968e9ea1afbd5 Mon Sep 17 00:00:00 2001 From: Mikhail Aheichyk Date: Tue, 22 Aug 2023 14:33:26 +0300 Subject: [PATCH 5/6] Test is updated. Signed-off-by: Mikhail Aheichyk --- test/app-tests/wrapper-test.tsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/test/app-tests/wrapper-test.tsx b/test/app-tests/wrapper-test.tsx index c4747098431..04d79978326 100644 --- a/test/app-tests/wrapper-test.tsx +++ b/test/app-tests/wrapper-test.tsx @@ -58,26 +58,30 @@ describe("Wrapper", () => { (opts as WrapperOpts).Wrapper = ({ children }) => { return ( <> -
Header
- {children} -
Footer
+
Header
+
{children}
+
Footer
); }; } }); - const matrixChat: RenderResult = render(await loadApp({})); + const matrixChatResult: RenderResult = render(await loadApp({})); // at this point, we're trying to do a guest registration; // we expect a spinner await assertAtLoadingSpinner(); - await awaitWelcomeComponent(matrixChat); + await awaitWelcomeComponent(matrixChatResult); // Are not semantic elements because Element has a footer already. - screen.getByText(/Header/i); - screen.getByText(/Footer/i); + const header = screen.getByTestId("wrapper-header"); + const matrixChat = screen.getByTestId("wrapper-matrix-chat"); + const footer = screen.getByTestId("wrapper-footer"); + + expect(header.nextSibling).toBe(matrixChat); + expect(matrixChat.nextSibling).toBe(footer); }); }); From b2bcd9d4a943b18912a4a0c45840ab9e479a7ede Mon Sep 17 00:00:00 2001 From: Mikhail Aheichyk Date: Mon, 28 Aug 2023 10:31:10 +0300 Subject: [PATCH 6/6] Matrix chat wrapper test updates. Signed-off-by: Mikhail Aheichyk --- test/app-tests/loading-test.tsx | 25 ++++++++----------------- test/app-tests/wrapper-test.tsx | 17 +++++------------ test/test-utils.ts | 11 +++++++++++ 3 files changed, 24 insertions(+), 29 deletions(-) diff --git a/test/app-tests/loading-test.tsx b/test/app-tests/loading-test.tsx index c1f7cbf268c..af53a29ffd4 100644 --- a/test/app-tests/loading-test.tsx +++ b/test/app-tests/loading-test.tsx @@ -34,7 +34,7 @@ import { ActionPayload } from "matrix-react-sdk/src/dispatcher/payloads"; import "../jest-mocks"; import WebPlatform from "../../src/vector/platform/WebPlatform"; import { parseQs, parseQsFromFragment } from "../../src/vector/url_utils"; -import { cleanLocalstorage, deleteIndexedDB } from "../test-utils"; +import { cleanLocalstorage, deleteIndexedDB, waitForLoadingSpinner, waitForWelcomeComponent } from "../test-utils"; const DEFAULT_HS_URL = "http://my_server"; const DEFAULT_IS_URL = "http://my_is"; @@ -189,7 +189,7 @@ describe("loading:", function () { .then(async () => { // at this point, we're trying to do a guest registration; // we expect a spinner - await assertAtLoadingSpinner(); + await waitForLoadingSpinner(); httpBackend .when("POST", "/register") @@ -202,7 +202,7 @@ describe("loading:", function () { }) .then(() => { // Wait for another trip around the event loop for the UI to update - return awaitWelcomeComponent(matrixChat); + return waitForWelcomeComponent(matrixChat); }) .then(() => { return waitFor(() => expect(windowLocation?.hash).toEqual("#/welcome")); @@ -222,7 +222,7 @@ describe("loading:", function () { .then(async () => { // at this point, we're trying to do a guest registration; // we expect a spinner - await assertAtLoadingSpinner(); + await waitForLoadingSpinner(); httpBackend .when("POST", "/register") @@ -391,7 +391,7 @@ describe("loading:", function () { .then(async () => { // at this point, we're trying to do a guest registration; // we expect a spinner - await assertAtLoadingSpinner(); + await waitForLoadingSpinner(); httpBackend .when("POST", "/register") @@ -427,7 +427,7 @@ describe("loading:", function () { .then(async () => { // at this point, we're trying to do a guest registration; // we expect a spinner - await assertAtLoadingSpinner(); + await waitForLoadingSpinner(); httpBackend .when("POST", "/register") @@ -468,7 +468,7 @@ describe("loading:", function () { .then(async () => { // at this point, we're trying to do a guest registration; // we expect a spinner - await assertAtLoadingSpinner(); + await waitForLoadingSpinner(); httpBackend .when("POST", "/register") @@ -562,7 +562,7 @@ describe("loading:", function () { return sleep(1) .then(async () => { // we expect a spinner while we're logging in - await assertAtLoadingSpinner(); + await waitForLoadingSpinner(); httpBackend .when("POST", "/login") @@ -650,11 +650,6 @@ describe("loading:", function () { } }); -// assert that we are on the loading page -async function assertAtLoadingSpinner(): Promise { - await screen.findByRole("progressbar"); -} - async function awaitLoggedIn(matrixChat: RenderResult): Promise { if (matrixChat.container.querySelector(".mx_MatrixChat_wrapper")) return; // already logged in @@ -680,10 +675,6 @@ async function awaitLoginComponent(matrixChat?: RenderResult): Promise { await waitFor(() => matrixChat?.container.querySelector(".mx_AuthPage")); } -async function awaitWelcomeComponent(matrixChat?: RenderResult): Promise { - await waitFor(() => matrixChat?.container.querySelector(".mx_Welcome")); -} - function moveFromWelcomeToLogin(matrixChat?: RenderResult): Promise { dis.dispatch({ action: "start_login" }); return awaitLoginComponent(matrixChat); diff --git a/test/app-tests/wrapper-test.tsx b/test/app-tests/wrapper-test.tsx index 04d79978326..6f72dbb6066 100644 --- a/test/app-tests/wrapper-test.tsx +++ b/test/app-tests/wrapper-test.tsx @@ -19,12 +19,13 @@ import React from "react"; import SdkConfig from "matrix-react-sdk/src/SdkConfig"; import PlatformPeg from "matrix-react-sdk/src/PlatformPeg"; import fetchMock from "fetch-mock-jest"; -import { render, RenderResult, screen, waitFor } from "@testing-library/react"; +import { render, RenderResult, screen } from "@testing-library/react"; import { ModuleRunner } from "matrix-react-sdk/src/modules/ModuleRunner"; import { WrapperLifecycle, WrapperOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WrapperLifecycle"; import WebPlatform from "../../src/vector/platform/WebPlatform"; import { loadApp } from "../../src/vector/app"; +import { waitForLoadingSpinner, waitForWelcomeComponent } from "../test-utils"; fetchMock.config.overwriteRoutes = true; @@ -44,7 +45,7 @@ describe("Wrapper", () => { fetchMock.get("/version", "1.10.13"); }); - it("wrap a matrix client with header and footer", async () => { + it("wrap a matrix chat with header and footer", async () => { SdkConfig.put({ default_server_config: { "m.homeserver": { @@ -71,9 +72,9 @@ describe("Wrapper", () => { // at this point, we're trying to do a guest registration; // we expect a spinner - await assertAtLoadingSpinner(); + await waitForLoadingSpinner(); - await awaitWelcomeComponent(matrixChatResult); + await waitForWelcomeComponent(matrixChatResult); // Are not semantic elements because Element has a footer already. const header = screen.getByTestId("wrapper-header"); @@ -84,11 +85,3 @@ describe("Wrapper", () => { expect(matrixChat.nextSibling).toBe(footer); }); }); - -async function assertAtLoadingSpinner(): Promise { - await screen.findByRole("progressbar"); -} - -async function awaitWelcomeComponent(matrixChat?: RenderResult): Promise { - await waitFor(() => matrixChat?.container.querySelector(".mx_Welcome")); -} diff --git a/test/test-utils.ts b/test/test-utils.ts index af9502ea52d..6367f7092bf 100644 --- a/test/test-utils.ts +++ b/test/test-utils.ts @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { RenderResult, screen, waitFor } from "@testing-library/react"; + export function cleanLocalstorage(): void { window.localStorage.clear(); } @@ -47,3 +49,12 @@ export function deleteIndexedDB(dbName: string): Promise { throw e; }); } + +// wait for loading page +export async function waitForLoadingSpinner(): Promise { + await screen.findByRole("progressbar"); +} + +export async function waitForWelcomeComponent(matrixChat?: RenderResult): Promise { + await waitFor(() => matrixChat?.container.querySelector(".mx_Welcome")); +}