Flatten Vector-override components (#28346)

* Flatten Vector-override components

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Improve coverage

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Ie

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* delint

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2024-11-05 15:41:00 +00:00 committed by GitHub
parent a355292a7f
commit 9d79a934bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 395 additions and 409 deletions

View File

@ -1,5 +1 @@
{ {}
"src/components/views/auth/AuthFooter.tsx": "src/components/views/auth/VectorAuthFooter.tsx",
"src/components/views/auth/AuthHeaderLogo.tsx": "src/components/views/auth/VectorAuthHeaderLogo.tsx",
"src/components/views/auth/AuthPage.tsx": "src/components/views/auth/VectorAuthPage.tsx"
}

View File

@ -31,6 +31,8 @@ import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
import { IConfigOptions } from "./IConfigOptions"; import { IConfigOptions } from "./IConfigOptions";
import SdkConfig from "./SdkConfig"; import SdkConfig from "./SdkConfig";
import { buildAndEncodePickleKey, encryptPickleKey } from "./utils/tokens/pickling"; import { buildAndEncodePickleKey, encryptPickleKey } from "./utils/tokens/pickling";
import Favicon from "./favicon.ts";
import { getVectorConfig } from "./vector/getconfig.ts";
export const SSO_HOMESERVER_URL_KEY = "mx_sso_hs_url"; export const SSO_HOMESERVER_URL_KEY = "mx_sso_hs_url";
export const SSO_ID_SERVER_URL_KEY = "mx_sso_is_url"; export const SSO_ID_SERVER_URL_KEY = "mx_sso_is_url";
@ -66,14 +68,20 @@ const UPDATE_DEFER_KEY = "mx_defer_update";
export default abstract class BasePlatform { export default abstract class BasePlatform {
protected notificationCount = 0; protected notificationCount = 0;
protected errorDidOccur = false; protected errorDidOccur = false;
protected _favicon?: Favicon;
protected constructor() { protected constructor() {
dis.register(this.onAction); dis.register(this.onAction);
this.startUpdateCheck = this.startUpdateCheck.bind(this); this.startUpdateCheck = this.startUpdateCheck.bind(this);
} }
public abstract getConfig(): Promise<IConfigOptions | undefined>; public async getConfig(): Promise<IConfigOptions | undefined> {
return getVectorConfig();
}
/**
* Get a sensible default display name for the device Element is running on
*/
public abstract getDefaultDeviceDisplayName(): string; public abstract getDefaultDeviceDisplayName(): string;
protected onAction = (payload: ActionPayload): void => { protected onAction = (payload: ActionPayload): void => {
@ -89,11 +97,15 @@ export default abstract class BasePlatform {
public abstract getHumanReadableName(): string; public abstract getHumanReadableName(): string;
public setNotificationCount(count: number): void { public setNotificationCount(count: number): void {
if (this.notificationCount === count) return;
this.notificationCount = count; this.notificationCount = count;
this.updateFavicon();
} }
public setErrorStatus(errorDidOccur: boolean): void { public setErrorStatus(errorDidOccur: boolean): void {
if (this.errorDidOccur === errorDidOccur) return;
this.errorDidOccur = errorDidOccur; this.errorDidOccur = errorDidOccur;
this.updateFavicon();
} }
/** /**
@ -456,4 +468,34 @@ export default abstract class BasePlatform {
url.hash = ""; url.hash = "";
return url; return url;
} }
/**
* Delay creating the `Favicon` instance until first use (on the first notification) as
* it uses canvas, which can trigger a permission prompt in Firefox's resist fingerprinting mode.
* See https://github.com/element-hq/element-web/issues/9605.
*/
public get favicon(): Favicon {
if (this._favicon) {
return this._favicon;
}
this._favicon = new Favicon();
return this._favicon;
}
private updateFavicon(): void {
let bgColor = "#d00";
let notif: string | number = this.notificationCount;
if (this.errorDidOccur) {
notif = notif || "×";
bgColor = "#f00";
}
this.favicon.badge(notif, { bgColor });
}
/**
* Begin update polling, if applicable
*/
public startUpdater(): void {}
} }

View File

@ -7,18 +7,36 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React from "react"; import React, { ReactElement } from "react";
import SdkConfig from "../../../SdkConfig";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
export default class AuthFooter extends React.Component { const AuthFooter = (): ReactElement => {
public render(): React.ReactNode { const brandingConfig = SdkConfig.getObject("branding");
return ( const links = brandingConfig?.get("auth_footer_links") ?? [
<footer className="mx_AuthFooter" role="contentinfo"> { text: "Blog", url: "https://element.io/blog" },
<a href="https://matrix.org" target="_blank" rel="noreferrer noopener"> { text: "Twitter", url: "https://twitter.com/element_hq" },
{_t("auth|footer_powered_by_matrix")} { text: "GitHub", url: "https://github.com/element-hq/element-web" },
</a> ];
</footer>
const authFooterLinks: JSX.Element[] = [];
for (const linkEntry of links) {
authFooterLinks.push(
<a href={linkEntry.url} key={linkEntry.text} target="_blank" rel="noreferrer noopener">
{linkEntry.text}
</a>,
); );
} }
}
return (
<footer className="mx_AuthFooter" role="contentinfo">
{authFooterLinks}
<a href="https://matrix.org" target="_blank" rel="noreferrer noopener">
{_t("powered_by_matrix")}
</a>
</footer>
);
};
export default AuthFooter;

View File

@ -1,5 +1,6 @@
/* /*
Copyright 2019-2024 New Vector Ltd. Copyright 2019-2024 New Vector Ltd.
Copyright 2015, 2016 OpenMarket Ltd
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
@ -7,8 +8,17 @@ Please see LICENSE files in the repository root for full details.
import React from "react"; import React from "react";
import SdkConfig from "../../../SdkConfig";
export default class AuthHeaderLogo extends React.PureComponent { export default class AuthHeaderLogo extends React.PureComponent {
public render(): React.ReactNode { public render(): React.ReactElement {
return <aside className="mx_AuthHeaderLogo">Matrix</aside>; const brandingConfig = SdkConfig.getObject("branding");
const logoUrl = brandingConfig?.get("auth_header_logo_url") ?? "themes/element/img/logos/element-logo.svg";
return (
<aside className="mx_AuthHeaderLogo">
<img src={logoUrl} alt="Element" />
</aside>
);
} }
} }

View File

@ -7,15 +7,69 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { ReactNode } from "react"; import React from "react";
import SdkConfig from "../../../SdkConfig";
import AuthFooter from "./AuthFooter"; import AuthFooter from "./AuthFooter";
export default class AuthPage extends React.PureComponent<{ children: ReactNode }> { export default class AuthPage extends React.PureComponent<React.PropsWithChildren> {
public render(): React.ReactNode { private static welcomeBackgroundUrl?: string;
// cache the url as a static to prevent it changing without refreshing
private static getWelcomeBackgroundUrl(): string {
if (AuthPage.welcomeBackgroundUrl) return AuthPage.welcomeBackgroundUrl;
const brandingConfig = SdkConfig.getObject("branding");
AuthPage.welcomeBackgroundUrl = "themes/element/img/backgrounds/lake.jpg";
const configuredUrl = brandingConfig?.get("welcome_background_url");
if (configuredUrl) {
if (Array.isArray(configuredUrl)) {
const index = Math.floor(Math.random() * configuredUrl.length);
AuthPage.welcomeBackgroundUrl = configuredUrl[index];
} else {
AuthPage.welcomeBackgroundUrl = configuredUrl;
}
}
return AuthPage.welcomeBackgroundUrl;
}
public render(): React.ReactElement {
const pageStyle = {
background: `center/cover fixed url(${AuthPage.getWelcomeBackgroundUrl()})`,
};
const modalStyle: React.CSSProperties = {
position: "relative",
background: "initial",
};
const blurStyle: React.CSSProperties = {
position: "absolute",
top: 0,
right: 0,
bottom: 0,
left: 0,
filter: "blur(40px)",
background: pageStyle.background,
};
const modalContentStyle: React.CSSProperties = {
display: "flex",
zIndex: 1,
background: "rgba(255, 255, 255, 0.59)",
borderRadius: "8px",
};
return ( return (
<div className="mx_AuthPage"> <div className="mx_AuthPage" style={pageStyle}>
<div className="mx_AuthPage_modal">{this.props.children}</div> <div className="mx_AuthPage_modal" style={modalStyle}>
<div className="mx_AuthPage_modalBlur" style={blurStyle} />
<div className="mx_AuthPage_modalContent" style={modalContentStyle}>
{this.props.children}
</div>
</div>
<AuthFooter /> <AuthFooter />
</div> </div>
); );

View File

@ -1,41 +0,0 @@
/*
Copyright 2019-2024 New Vector Ltd.
Copyright 2015, 2016 OpenMarket Ltd
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import React, { ReactElement } from "react";
import SdkConfig from "../../../SdkConfig";
import { _t } from "../../../languageHandler";
const VectorAuthFooter = (): ReactElement => {
const brandingConfig = SdkConfig.getObject("branding");
const links = brandingConfig?.get("auth_footer_links") ?? [
{ text: "Blog", url: "https://element.io/blog" },
{ text: "Twitter", url: "https://twitter.com/element_hq" },
{ text: "GitHub", url: "https://github.com/element-hq/element-web" },
];
const authFooterLinks: JSX.Element[] = [];
for (const linkEntry of links) {
authFooterLinks.push(
<a href={linkEntry.url} key={linkEntry.text} target="_blank" rel="noreferrer noopener">
{linkEntry.text}
</a>,
);
}
return (
<footer className="mx_AuthFooter" role="contentinfo">
{authFooterLinks}
<a href="https://matrix.org" target="_blank" rel="noreferrer noopener">
{_t("powered_by_matrix")}
</a>
</footer>
);
};
export default VectorAuthFooter;

View File

@ -1,24 +0,0 @@
/*
Copyright 2019-2024 New Vector Ltd.
Copyright 2015, 2016 OpenMarket Ltd
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import * as React from "react";
import SdkConfig from "../../../SdkConfig";
export default class VectorAuthHeaderLogo extends React.PureComponent {
public render(): React.ReactElement {
const brandingConfig = SdkConfig.getObject("branding");
const logoUrl = brandingConfig?.get("auth_header_logo_url") ?? "themes/element/img/logos/element-logo.svg";
return (
<aside className="mx_AuthHeaderLogo">
<img src={logoUrl} alt="Element" />
</aside>
);
}
}

View File

@ -1,75 +0,0 @@
/*
Copyright 2019-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import * as React from "react";
import SdkConfig from "../../../SdkConfig";
import VectorAuthFooter from "./VectorAuthFooter";
export default class VectorAuthPage extends React.PureComponent<React.PropsWithChildren> {
private static welcomeBackgroundUrl?: string;
// cache the url as a static to prevent it changing without refreshing
private static getWelcomeBackgroundUrl(): string {
if (VectorAuthPage.welcomeBackgroundUrl) return VectorAuthPage.welcomeBackgroundUrl;
const brandingConfig = SdkConfig.getObject("branding");
VectorAuthPage.welcomeBackgroundUrl = "themes/element/img/backgrounds/lake.jpg";
const configuredUrl = brandingConfig?.get("welcome_background_url");
if (configuredUrl) {
if (Array.isArray(configuredUrl)) {
const index = Math.floor(Math.random() * configuredUrl.length);
VectorAuthPage.welcomeBackgroundUrl = configuredUrl[index];
} else {
VectorAuthPage.welcomeBackgroundUrl = configuredUrl;
}
}
return VectorAuthPage.welcomeBackgroundUrl;
}
public render(): React.ReactElement {
const pageStyle = {
background: `center/cover fixed url(${VectorAuthPage.getWelcomeBackgroundUrl()})`,
};
const modalStyle: React.CSSProperties = {
position: "relative",
background: "initial",
};
const blurStyle: React.CSSProperties = {
position: "absolute",
top: 0,
right: 0,
bottom: 0,
left: 0,
filter: "blur(40px)",
background: pageStyle.background,
};
const modalContentStyle: React.CSSProperties = {
display: "flex",
zIndex: 1,
background: "rgba(255, 255, 255, 0.59)",
borderRadius: "8px",
};
return (
<div className="mx_AuthPage" style={pageStyle}>
<div className="mx_AuthPage_modal" style={modalStyle}>
<div className="mx_AuthPage_modalBlur" style={blurStyle} />
<div className="mx_AuthPage_modalContent" style={modalContentStyle}>
{this.props.children}
</div>
</div>
<VectorAuthFooter />
</div>
);
}
}

View File

@ -209,7 +209,6 @@
"failed_query_registration_methods": "Unable to query for supported registration methods.", "failed_query_registration_methods": "Unable to query for supported registration methods.",
"failed_soft_logout_auth": "Failed to re-authenticate", "failed_soft_logout_auth": "Failed to re-authenticate",
"failed_soft_logout_homeserver": "Failed to re-authenticate due to a homeserver problem", "failed_soft_logout_homeserver": "Failed to re-authenticate due to a homeserver problem",
"footer_powered_by_matrix": "powered by Matrix",
"forgot_password_email_invalid": "The email address doesn't appear to be valid.", "forgot_password_email_invalid": "The email address doesn't appear to be valid.",
"forgot_password_email_required": "The email address linked to your account must be entered.", "forgot_password_email_required": "The email address linked to your account must be entered.",
"forgot_password_prompt": "Forgotten your password?", "forgot_password_prompt": "Forgotten your password?",
@ -3706,7 +3705,6 @@
"truncated_list_n_more": { "truncated_list_n_more": {
"other": "And %(count)s more..." "other": "And %(count)s more..."
}, },
"unknown_device": "Unknown device",
"unsupported_browser": { "unsupported_browser": {
"description": "If you continue, some features may stop working and there is a risk that you may lose data in the future. Update your browser to continue using %(brand)s.", "description": "If you continue, some features may stop working and there is a risk that you may lose data in the future. Update your browser to continue using %(brand)s.",
"title": "%(brand)s does not support this browser" "title": "%(brand)s does not support this browser"

View File

@ -27,7 +27,6 @@ import MatrixChat from "../components/structures/MatrixChat";
import { ValidatedServerConfig } from "../utils/ValidatedServerConfig"; import { ValidatedServerConfig } from "../utils/ValidatedServerConfig";
import { ModuleRunner } from "../modules/ModuleRunner"; import { ModuleRunner } from "../modules/ModuleRunner";
import { parseQs } from "./url_utils"; import { parseQs } from "./url_utils";
import VectorBasePlatform from "./platform/VectorBasePlatform";
import { getInitialScreenAfterLogin, getScreenFromLocation, init as initRouting, onNewScreen } from "./routing"; import { getInitialScreenAfterLogin, getScreenFromLocation, init as initRouting, onNewScreen } from "./routing";
import { UserFriendlyError } from "../languageHandler"; import { UserFriendlyError } from "../languageHandler";
@ -64,7 +63,7 @@ export async function loadApp(fragParams: {}, matrixChatRef: React.Ref<MatrixCha
const urlWithoutQuery = window.location.protocol + "//" + window.location.host + window.location.pathname; const urlWithoutQuery = window.location.protocol + "//" + window.location.host + window.location.pathname;
logger.log("Vector starting at " + urlWithoutQuery); logger.log("Vector starting at " + urlWithoutQuery);
(platform as VectorBasePlatform).startUpdater(); platform?.startUpdater();
// Don't bother loading the app until the config is verified // Don't bother loading the app until the config is verified
const config = await verifyServerConfig(); const config = await verifyServerConfig();

View File

@ -15,7 +15,7 @@ import React from "react";
import { randomString } from "matrix-js-sdk/src/randomstring"; import { randomString } from "matrix-js-sdk/src/randomstring";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { UpdateCheckStatus, UpdateStatus } from "../../BasePlatform"; import BasePlatform, { UpdateCheckStatus, UpdateStatus } from "../../BasePlatform";
import BaseEventIndexManager from "../../indexing/BaseEventIndexManager"; import BaseEventIndexManager from "../../indexing/BaseEventIndexManager";
import dis from "../../dispatcher/dispatcher"; import dis from "../../dispatcher/dispatcher";
import SdkConfig from "../../SdkConfig"; import SdkConfig from "../../SdkConfig";
@ -35,7 +35,6 @@ import { UPDATE_EVENT } from "../../stores/AsyncStore";
import { avatarUrlForRoom, getInitialLetter } from "../../Avatar"; import { avatarUrlForRoom, getInitialLetter } from "../../Avatar";
import DesktopCapturerSourcePicker from "../../components/views/elements/DesktopCapturerSourcePicker"; import DesktopCapturerSourcePicker from "../../components/views/elements/DesktopCapturerSourcePicker";
import { MatrixClientPeg } from "../../MatrixClientPeg"; import { MatrixClientPeg } from "../../MatrixClientPeg";
import VectorBasePlatform from "./VectorBasePlatform";
import { SeshatIndexManager } from "./SeshatIndexManager"; import { SeshatIndexManager } from "./SeshatIndexManager";
import { IPCManager } from "./IPCManager"; import { IPCManager } from "./IPCManager";
import { _t } from "../../languageHandler"; import { _t } from "../../languageHandler";
@ -90,7 +89,7 @@ function getUpdateCheckStatus(status: boolean | string): UpdateStatus {
} }
} }
export default class ElectronPlatform extends VectorBasePlatform { export default class ElectronPlatform extends BasePlatform {
private readonly ipc = new IPCManager("ipcCall", "ipcReply"); private readonly ipc = new IPCManager("ipcCall", "ipcReply");
private readonly eventIndexManager: BaseEventIndexManager = new SeshatIndexManager(); private readonly eventIndexManager: BaseEventIndexManager = new SeshatIndexManager();
// this is the opaque token we pass to the HS which when we get it in our callback we can resolve to a profile // this is the opaque token we pass to the HS which when we get it in our callback we can resolve to a profile

View File

@ -1,80 +0,0 @@
/*
Copyright 2018-2024 New Vector Ltd.
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2016 Aviral Dasgupta
Copyright 2016 OpenMarket Ltd
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import type { IConfigOptions } from "../../IConfigOptions";
import BasePlatform from "../../BasePlatform";
import { getVectorConfig } from "../getconfig";
import Favicon from "../../favicon";
import { _t } from "../../languageHandler";
/**
* Vector-specific extensions to the BasePlatform template
*/
export default abstract class VectorBasePlatform extends BasePlatform {
protected _favicon?: Favicon;
public async getConfig(): Promise<IConfigOptions | undefined> {
return getVectorConfig();
}
public getHumanReadableName(): string {
return "Vector Base Platform"; // no translation required: only used for analytics
}
/**
* Delay creating the `Favicon` instance until first use (on the first notification) as
* it uses canvas, which can trigger a permission prompt in Firefox's resist fingerprinting mode.
* See https://github.com/element-hq/element-web/issues/9605.
*/
public get favicon(): Favicon {
if (this._favicon) {
return this._favicon;
}
this._favicon = new Favicon();
return this._favicon;
}
private updateFavicon(): void {
let bgColor = "#d00";
let notif: string | number = this.notificationCount;
if (this.errorDidOccur) {
notif = notif || "×";
bgColor = "#f00";
}
this.favicon.badge(notif, { bgColor });
}
public setNotificationCount(count: number): void {
if (this.notificationCount === count) return;
super.setNotificationCount(count);
this.updateFavicon();
}
public setErrorStatus(errorDidOccur: boolean): void {
if (this.errorDidOccur === errorDidOccur) return;
super.setErrorStatus(errorDidOccur);
this.updateFavicon();
}
/**
* Begin update polling, if applicable
*/
public startUpdater(): void {}
/**
* Get a sensible default display name for the
* device Vector is running on
*/
public getDefaultDeviceDisplayName(): string {
return _t("unknown_device");
}
}

View File

@ -11,12 +11,11 @@ import UAParser from "ua-parser-js";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClientPeg } from "../../MatrixClientPeg"; import { MatrixClientPeg } from "../../MatrixClientPeg";
import { UpdateCheckStatus, UpdateStatus } from "../../BasePlatform"; import BasePlatform, { UpdateCheckStatus, UpdateStatus } from "../../BasePlatform";
import dis from "../../dispatcher/dispatcher"; import dis from "../../dispatcher/dispatcher";
import { hideToast as hideUpdateToast, showToast as showUpdateToast } from "../../toasts/UpdateToast"; import { hideToast as hideUpdateToast, showToast as showUpdateToast } from "../../toasts/UpdateToast";
import { Action } from "../../dispatcher/actions"; import { Action } from "../../dispatcher/actions";
import { CheckUpdatesPayload } from "../../dispatcher/payloads/CheckUpdatesPayload"; import { CheckUpdatesPayload } from "../../dispatcher/payloads/CheckUpdatesPayload";
import VectorBasePlatform from "./VectorBasePlatform";
import { parseQs } from "../url_utils"; import { parseQs } from "../url_utils";
import { _t } from "../../languageHandler"; import { _t } from "../../languageHandler";
@ -31,7 +30,7 @@ function getNormalizedAppVersion(version: string): string {
return version; return version;
} }
export default class WebPlatform extends VectorBasePlatform { export default class WebPlatform extends BasePlatform {
private static readonly VERSION = process.env.VERSION!; // baked in by Webpack private static readonly VERSION = process.env.VERSION!; // baked in by Webpack
public constructor() { public constructor() {

View File

@ -953,7 +953,7 @@ describe("<MatrixChat />", () => {
const getComponentAndWaitForReady = async (): Promise<RenderResult> => { const getComponentAndWaitForReady = async (): Promise<RenderResult> => {
const renderResult = getComponent(); const renderResult = getComponent();
// wait for welcome page chrome render // wait for welcome page chrome render
await screen.findByText("powered by Matrix"); await screen.findByText("Powered by Matrix");
// go to login page // go to login page
defaultDispatcher.dispatch({ defaultDispatcher.dispatch({
@ -1480,7 +1480,7 @@ describe("<MatrixChat />", () => {
const getComponentAndWaitForReady = async (): Promise<RenderResult> => { const getComponentAndWaitForReady = async (): Promise<RenderResult> => {
const renderResult = getComponent(); const renderResult = getComponent();
// wait for welcome page chrome render // wait for welcome page chrome render
await screen.findByText("powered by Matrix"); await screen.findByText("Powered by Matrix");
// go to mobile_register page // go to mobile_register page
defaultDispatcher.dispatch({ defaultDispatcher.dispatch({
@ -1500,7 +1500,7 @@ describe("<MatrixChat />", () => {
it("should render welcome screen if mobile registration is not enabled in settings", async () => { it("should render welcome screen if mobile registration is not enabled in settings", async () => {
await getComponentAndWaitForReady(); await getComponentAndWaitForReady();
await screen.findByText("powered by Matrix"); await screen.findByText("Powered by Matrix");
}); });
it("should render mobile registration", async () => { it("should render mobile registration", async () => {

View File

@ -114,46 +114,56 @@ exports[`<MatrixChat /> Multi-tab lockout waits for other tab to stop during sta
> >
<div <div
class="mx_AuthPage_modal" class="mx_AuthPage_modal"
style="position: relative;"
> >
<div <div
class="mx_Welcome" class="mx_AuthPage_modalBlur"
data-testid="mx_welcome_screen" style="position: absolute; top: 0px; right: 0px; bottom: 0px; left: 0px; filter: blur(40px);"
/>
<div
class="mx_AuthPage_modalContent"
style="display: flex; z-index: 1; background: rgba(255, 255, 255, 0.59); border-radius: 8px;"
> >
<div <div
class="mx_WelcomePage mx_WelcomePage_loggedIn" class="mx_Welcome"
data-testid="mx_welcome_screen"
> >
<div <div
class="mx_WelcomePage_body" class="mx_WelcomePage mx_WelcomePage_loggedIn"
>
<h1>
Hello
</h1>
</div>
</div>
<div
class="mx_Dropdown mx_LanguageDropdown mx_AuthBody_language"
>
<div
aria-describedby="mx_LanguageDropdown_value"
aria-expanded="false"
aria-haspopup="listbox"
aria-label="Language Dropdown"
aria-owns="mx_LanguageDropdown_input"
class="mx_AccessibleButton mx_Dropdown_input mx_no_textinput"
role="button"
tabindex="0"
> >
<div <div
class="mx_Dropdown_option" class="mx_WelcomePage_body"
id="mx_LanguageDropdown_value"
> >
<div> <h1>
English Hello
</div> </h1>
</div>
</div>
<div
class="mx_Dropdown mx_LanguageDropdown mx_AuthBody_language"
>
<div
aria-describedby="mx_LanguageDropdown_value"
aria-expanded="false"
aria-haspopup="listbox"
aria-label="Language Dropdown"
aria-owns="mx_LanguageDropdown_input"
class="mx_AccessibleButton mx_Dropdown_input mx_no_textinput"
role="button"
tabindex="0"
>
<div
class="mx_Dropdown_option"
id="mx_LanguageDropdown_value"
>
<div>
English
</div>
</div>
<span
class="mx_Dropdown_arrow"
/>
</div> </div>
<span
class="mx_Dropdown_arrow"
/>
</div> </div>
</div> </div>
</div> </div>
@ -162,12 +172,33 @@ exports[`<MatrixChat /> Multi-tab lockout waits for other tab to stop during sta
class="mx_AuthFooter" class="mx_AuthFooter"
role="contentinfo" role="contentinfo"
> >
<a
href="https://element.io/blog"
rel="noreferrer noopener"
target="_blank"
>
Blog
</a>
<a
href="https://twitter.com/element_hq"
rel="noreferrer noopener"
target="_blank"
>
Twitter
</a>
<a
href="https://github.com/element-hq/element-web"
rel="noreferrer noopener"
target="_blank"
>
GitHub
</a>
<a <a
href="https://matrix.org" href="https://matrix.org"
rel="noreferrer noopener" rel="noreferrer noopener"
target="_blank" target="_blank"
> >
powered by Matrix Powered by Matrix
</a> </a>
</footer> </footer>
</div> </div>
@ -201,116 +232,150 @@ exports[`<MatrixChat /> with a soft-logged-out session should show the soft-logo
> >
<div <div
class="mx_AuthPage_modal" class="mx_AuthPage_modal"
style="position: relative;"
> >
<div <div
class="mx_AuthHeader" class="mx_AuthPage_modalBlur"
style="position: absolute; top: 0px; right: 0px; bottom: 0px; left: 0px; filter: blur(40px);"
/>
<div
class="mx_AuthPage_modalContent"
style="display: flex; z-index: 1; background: rgba(255, 255, 255, 0.59); border-radius: 8px;"
> >
<aside
class="mx_AuthHeaderLogo"
>
Matrix
</aside>
<div <div
class="mx_Dropdown mx_LanguageDropdown mx_AuthBody_language" class="mx_AuthHeader"
> >
<div <aside
aria-describedby="mx_LanguageDropdown_value" class="mx_AuthHeaderLogo"
aria-expanded="false"
aria-haspopup="listbox"
aria-label="Language Dropdown"
aria-owns="mx_LanguageDropdown_input"
class="mx_AccessibleButton mx_Dropdown_input mx_no_textinput"
role="button"
tabindex="0"
> >
<div <img
class="mx_Dropdown_option" alt="Element"
id="mx_LanguageDropdown_value" src="themes/element/img/logos/element-logo.svg"
>
<div>
English
</div>
</div>
<span
class="mx_Dropdown_arrow"
/> />
</div> </aside>
</div>
</div>
<main
class="mx_AuthBody"
>
<h1>
You're signed out
</h1>
<h2>
Sign in
</h2>
<div>
<form>
<p>
Enter your password to sign in and regain access to your account.
</p>
<div
class="mx_Field mx_Field_input"
>
<input
id="mx_Field_1"
label="Password"
placeholder="Password"
type="password"
value=""
/>
<label
for="mx_Field_1"
>
Password
</label>
</div>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
role="button"
tabindex="0"
type="submit"
>
Sign in
</div>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link"
role="button"
tabindex="0"
>
Forgotten your password?
</div>
</form>
</div>
<h2>
Clear personal data
</h2>
<p>
Warning: your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.
</p>
<div>
<div <div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger" class="mx_Dropdown mx_LanguageDropdown mx_AuthBody_language"
role="button"
tabindex="0"
> >
Clear all data <div
aria-describedby="mx_LanguageDropdown_value"
aria-expanded="false"
aria-haspopup="listbox"
aria-label="Language Dropdown"
aria-owns="mx_LanguageDropdown_input"
class="mx_AccessibleButton mx_Dropdown_input mx_no_textinput"
role="button"
tabindex="0"
>
<div
class="mx_Dropdown_option"
id="mx_LanguageDropdown_value"
>
<div>
English
</div>
</div>
<span
class="mx_Dropdown_arrow"
/>
</div>
</div> </div>
</div> </div>
</main> <main
class="mx_AuthBody"
>
<h1>
You're signed out
</h1>
<h2>
Sign in
</h2>
<div>
<form>
<p>
Enter your password to sign in and regain access to your account.
</p>
<div
class="mx_Field mx_Field_input"
>
<input
id="mx_Field_1"
label="Password"
placeholder="Password"
type="password"
value=""
/>
<label
for="mx_Field_1"
>
Password
</label>
</div>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
role="button"
tabindex="0"
type="submit"
>
Sign in
</div>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link"
role="button"
tabindex="0"
>
Forgotten your password?
</div>
</form>
</div>
<h2>
Clear personal data
</h2>
<p>
Warning: your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.
</p>
<div>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger"
role="button"
tabindex="0"
>
Clear all data
</div>
</div>
</main>
</div>
</div> </div>
<footer <footer
class="mx_AuthFooter" class="mx_AuthFooter"
role="contentinfo" role="contentinfo"
> >
<a
href="https://element.io/blog"
rel="noreferrer noopener"
target="_blank"
>
Blog
</a>
<a
href="https://twitter.com/element_hq"
rel="noreferrer noopener"
target="_blank"
>
Twitter
</a>
<a
href="https://github.com/element-hq/element-web"
rel="noreferrer noopener"
target="_blank"
>
GitHub
</a>
<a <a
href="https://matrix.org" href="https://matrix.org"
rel="noreferrer noopener" rel="noreferrer noopener"
target="_blank" target="_blank"
> >
powered by Matrix Powered by Matrix
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -9,16 +9,16 @@ Please see LICENSE files in the repository root for full details.
import * as React from "react"; import * as React from "react";
import { render } from "jest-matrix-react"; import { render } from "jest-matrix-react";
import VectorAuthPage from "../../../../../src/components/views/auth/VectorAuthPage"; import AuthFooter from "../../../../../src/components/views/auth/AuthFooter";
import { setupLanguageMock } from "../../../../setup/setupLanguage"; import { setupLanguageMock } from "../../../../setup/setupLanguage";
describe("<VectorAuthPage />", () => { describe("<AuthFooter />", () => {
beforeEach(() => { beforeEach(() => {
setupLanguageMock(); setupLanguageMock();
}); });
it("should match snapshot", () => { it("should match snapshot", () => {
const { asFragment } = render(<VectorAuthPage />); const { asFragment } = render(<AuthFooter />);
expect(asFragment()).toMatchSnapshot(); expect(asFragment()).toMatchSnapshot();
}); });
}); });

View File

@ -9,11 +9,11 @@ Please see LICENSE files in the repository root for full details.
import * as React from "react"; import * as React from "react";
import { render } from "jest-matrix-react"; import { render } from "jest-matrix-react";
import VectorAuthHeaderLogo from "../../../../../src/components/views/auth/VectorAuthHeaderLogo"; import AuthHeaderLogo from "../../../../../src/components/views/auth/AuthHeaderLogo";
describe("<VectorAuthHeaderLogo />", () => { describe("<AuthHeaderLogo />", () => {
it("should match snapshot", () => { it("should match snapshot", () => {
const { asFragment } = render(<VectorAuthHeaderLogo />); const { asFragment } = render(<AuthHeaderLogo />);
expect(asFragment()).toMatchSnapshot(); expect(asFragment()).toMatchSnapshot();
}); });
}); });

View File

@ -0,0 +1,36 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import * as React from "react";
import { render } from "jest-matrix-react";
import AuthPage from "../../../../../src/components/views/auth/AuthPage";
import { setupLanguageMock } from "../../../../setup/setupLanguage";
import SdkConfig from "../../../../../src/SdkConfig.ts";
describe("<AuthPage />", () => {
beforeEach(() => {
setupLanguageMock();
SdkConfig.reset();
// @ts-ignore private access
AuthPage.welcomeBackgroundUrl = undefined;
});
it("should match snapshot", () => {
const { asFragment } = render(<AuthPage />);
expect(asFragment()).toMatchSnapshot();
});
it("should use configured background url", () => {
SdkConfig.add({ branding: { welcome_background_url: ["https://example.com/image.png"] } });
const { container } = render(<AuthPage />);
expect(container.querySelector(".mx_AuthPage")).toHaveStyle({
background: "center/cover fixed url(https://example.com/image.png)",
});
});
});

View File

@ -1,24 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import * as React from "react";
import { render } from "jest-matrix-react";
import VectorAuthFooter from "../../../../../src/components/views/auth/VectorAuthFooter";
import { setupLanguageMock } from "../../../../setup/setupLanguage";
describe("<VectorAuthFooter />", () => {
beforeEach(() => {
setupLanguageMock();
});
it("should match snapshot", () => {
const { asFragment } = render(<VectorAuthFooter />);
expect(asFragment()).toMatchSnapshot();
});
});

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<VectorAuthFooter /> should match snapshot 1`] = ` exports[`<AuthFooter /> should match snapshot 1`] = `
<DocumentFragment> <DocumentFragment>
<footer <footer
class="mx_AuthFooter" class="mx_AuthFooter"

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<VectorAuthHeaderLogo /> should match snapshot 1`] = ` exports[`<AuthHeaderLogo /> should match snapshot 1`] = `
<DocumentFragment> <DocumentFragment>
<aside <aside
class="mx_AuthHeaderLogo" class="mx_AuthHeaderLogo"

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<VectorAuthPage /> should match snapshot 1`] = ` exports[`<AuthPage /> should match snapshot 1`] = `
<DocumentFragment> <DocumentFragment>
<div <div
class="mx_AuthPage" class="mx_AuthPage"

View File

@ -229,4 +229,18 @@ describe("WebPlatform", () => {
}); });
}); });
}); });
it("should return config from config.json", async () => {
window.location.hostname = "domain.com";
fetchMock.get(/config\.json.*/, { brand: "test" });
const platform = new WebPlatform();
await expect(platform.getConfig()).resolves.toEqual(expect.objectContaining({ brand: "test" }));
});
it("should re-render favicon when setting error status", () => {
const platform = new WebPlatform();
const spy = jest.spyOn(platform.favicon, "badge");
platform.setErrorStatus(true);
expect(spy).toHaveBeenCalledWith(expect.anything(), { bgColor: "#f00" });
});
}); });