Skip to content

Commit

Permalink
Add support for rendering a custom wrapper around Element (#25537)
Browse files Browse the repository at this point in the history
Co-authored-by: Mikhail Aheichyk <mikhail.aheichyk@nordeck.net>
  • Loading branch information
maheichyk and Mikhail Aheichyk committed Aug 30, 2023
1 parent 2ad80c8 commit 3fd6b62
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 29 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
27 changes: 17 additions & 10 deletions src/vector/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import { createClient } from "matrix-js-sdk/src/matrix";
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 { 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";
import VectorBasePlatform from "./platform/VectorBasePlatform";
Expand Down Expand Up @@ -109,17 +111,22 @@ export async function loadApp(fragParams: {}): Promise<ReactElement> {

const initialScreenAfterLogin = getInitialScreenAfterLogin(window.location);

const wrapperOpts: WrapperOpts = { Wrapper: React.Fragment };
ModuleRunner.instance.invoke(WrapperLifecycle.Wrapper, wrapperOpts);

return (
<MatrixChat
onNewScreen={onNewScreen}
config={config}
realQueryParams={params}
startingFragmentQueryParams={fragParams}
enableGuest={!config.disable_guests}
onTokenLoginCompleted={onTokenLoginCompleted}
initialScreenAfterLogin={initialScreenAfterLogin}
defaultDeviceDisplayName={defaultDeviceName}
/>
<wrapperOpts.Wrapper>
<MatrixChat
onNewScreen={onNewScreen}
config={config}
realQueryParams={params}
startingFragmentQueryParams={fragParams}
enableGuest={!config.disable_guests}
onTokenLoginCompleted={onTokenLoginCompleted}
initialScreenAfterLogin={initialScreenAfterLogin}
defaultDeviceDisplayName={defaultDeviceName}
/>
</wrapperOpts.Wrapper>
);
}

Expand Down
25 changes: 8 additions & 17 deletions test/app-tests/loading-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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")
Expand All @@ -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"));
Expand All @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -650,11 +650,6 @@ describe("loading:", function () {
}
});

// assert that we are on the loading page
async function assertAtLoadingSpinner(): Promise<void> {
await screen.findByRole("progressbar");
}

async function awaitLoggedIn(matrixChat: RenderResult): Promise<void> {
if (matrixChat.container.querySelector(".mx_MatrixChat_wrapper")) return; // already logged in

Expand All @@ -680,10 +675,6 @@ async function awaitLoginComponent(matrixChat?: RenderResult): Promise<void> {
await waitFor(() => matrixChat?.container.querySelector(".mx_AuthPage"));
}

async function awaitWelcomeComponent(matrixChat?: RenderResult): Promise<void> {
await waitFor(() => matrixChat?.container.querySelector(".mx_Welcome"));
}

function moveFromWelcomeToLogin(matrixChat?: RenderResult): Promise<void> {
dis.dispatch({ action: "start_login" });
return awaitLoginComponent(matrixChat);
Expand Down
87 changes: 87 additions & 0 deletions test/app-tests/wrapper-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
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 } 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;

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 chat 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 (
<>
<div data-testid="wrapper-header">Header</div>
<div data-testid="wrapper-matrix-chat">{children}</div>
<div data-testid="wrapper-footer">Footer</div>
</>
);
};
}
});

const matrixChatResult: RenderResult = render(await loadApp({}));

// at this point, we're trying to do a guest registration;
// we expect a spinner
await waitForLoadingSpinner();

await waitForWelcomeComponent(matrixChatResult);

// Are not semantic elements because Element has a footer already.
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);
});
});
11 changes: 11 additions & 0 deletions test/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -47,3 +49,12 @@ export function deleteIndexedDB(dbName: string): Promise<void> {
throw e;
});
}

// wait for loading page
export async function waitForLoadingSpinner(): Promise<void> {
await screen.findByRole("progressbar");
}

export async function waitForWelcomeComponent(matrixChat?: RenderResult): Promise<void> {
await waitFor(() => matrixChat?.container.querySelector(".mx_Welcome"));
}
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1664,7 +1664,7 @@
version "3.2.14"
resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz#acd96c00a881d0f462e1f97a56c73742c8dbc984"

"@matrix-org/react-sdk-module-api@^2.0.0", "@matrix-org/react-sdk-module-api@^2.1.0":
"@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==
Expand Down

0 comments on commit 3fd6b62

Please sign in to comment.