Merge remote-tracking branch 'origin/develop' into poll-votes

This commit is contained in:
Tim Vahlbrock 2024-11-08 09:04:41 +01:00
commit e55c9a1a5b
No known key found for this signature in database
229 changed files with 1745 additions and 1578 deletions

View file

@ -39,6 +39,7 @@ jest.mock("matrix-js-sdk/src/logger");
jest.mock("../../src/dispatcher/dispatcher", () => ({
dispatch: jest.fn(),
register: jest.fn(),
unregister: jest.fn(),
}));
jest.mock("../../src/SecurityManager", () => ({

View file

@ -62,6 +62,7 @@ import { DRAFT_LAST_CLEANUP_KEY } from "../../../../src/DraftCleaner";
import { UIFeature } from "../../../../src/settings/UIFeature";
import AutoDiscoveryUtils from "../../../../src/utils/AutoDiscoveryUtils";
import { ValidatedServerConfig } from "../../../../src/utils/ValidatedServerConfig";
import Modal from "../../../../src/Modal.tsx";
jest.mock("matrix-js-sdk/src/oidc/authorize", () => ({
completeAuthorizationCodeGrant: jest.fn(),
@ -952,7 +953,7 @@ describe("<MatrixChat />", () => {
const getComponentAndWaitForReady = async (): Promise<RenderResult> => {
const renderResult = getComponent();
// wait for welcome page chrome render
await screen.findByText("powered by Matrix");
await screen.findByText("Powered by Matrix");
// go to login page
defaultDispatcher.dispatch({
@ -1479,7 +1480,7 @@ describe("<MatrixChat />", () => {
const getComponentAndWaitForReady = async (): Promise<RenderResult> => {
const renderResult = getComponent();
// wait for welcome page chrome render
await screen.findByText("powered by Matrix");
await screen.findByText("Powered by Matrix");
// go to mobile_register page
defaultDispatcher.dispatch({
@ -1499,7 +1500,7 @@ describe("<MatrixChat />", () => {
it("should render welcome screen if mobile registration is not enabled in settings", async () => {
await getComponentAndWaitForReady();
await screen.findByText("powered by Matrix");
await screen.findByText("Powered by Matrix");
});
it("should render mobile registration", async () => {
@ -1514,7 +1515,9 @@ describe("<MatrixChat />", () => {
describe("when key backup failed", () => {
it("should show the new recovery method dialog", async () => {
const spy = jest.spyOn(Modal, "createDialogAsync");
jest.mock("../../../../src/async-components/views/dialogs/security/NewRecoveryMethodDialog", () => ({
__test: true,
__esModule: true,
default: () => <span>mocked dialog</span>,
}));
@ -1526,7 +1529,8 @@ describe("<MatrixChat />", () => {
});
await flushPromises();
mockClient.emit(CryptoEvent.KeyBackupFailed, "error code");
await waitFor(() => expect(screen.getByText("mocked dialog")).toBeInTheDocument());
await waitFor(() => expect(spy).toHaveBeenCalledTimes(1));
expect(await spy.mock.lastCall![0]).toEqual(expect.objectContaining({ __test: true }));
});
});
});

View file

@ -114,46 +114,56 @@ exports[`<MatrixChat /> Multi-tab lockout waits for other tab to stop during sta
>
<div
class="mx_AuthPage_modal"
style="position: relative;"
>
<div
class="mx_Welcome"
data-testid="mx_welcome_screen"
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;"
>
<div
class="mx_WelcomePage mx_WelcomePage_loggedIn"
class="mx_Welcome"
data-testid="mx_welcome_screen"
>
<div
class="mx_WelcomePage_body"
>
<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"
class="mx_WelcomePage mx_WelcomePage_loggedIn"
>
<div
class="mx_Dropdown_option"
id="mx_LanguageDropdown_value"
class="mx_WelcomePage_body"
>
<div>
English
</div>
<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
class="mx_Dropdown_option"
id="mx_LanguageDropdown_value"
>
<div>
English
</div>
</div>
<span
class="mx_Dropdown_arrow"
/>
</div>
<span
class="mx_Dropdown_arrow"
/>
</div>
</div>
</div>
@ -162,12 +172,33 @@ exports[`<MatrixChat /> Multi-tab lockout waits for other tab to stop during sta
class="mx_AuthFooter"
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
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
powered by Matrix
Powered by Matrix
</a>
</footer>
</div>
@ -201,116 +232,150 @@ exports[`<MatrixChat /> with a soft-logged-out session should show the soft-logo
>
<div
class="mx_AuthPage_modal"
style="position: relative;"
>
<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
class="mx_Dropdown mx_LanguageDropdown mx_AuthBody_language"
class="mx_AuthHeader"
>
<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"
<aside
class="mx_AuthHeaderLogo"
>
<div
class="mx_Dropdown_option"
id="mx_LanguageDropdown_value"
>
<div>
English
</div>
</div>
<span
class="mx_Dropdown_arrow"
<img
alt="Element"
src="themes/element/img/logos/element-logo.svg"
/>
</div>
</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>
</aside>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger"
role="button"
tabindex="0"
class="mx_Dropdown mx_LanguageDropdown mx_AuthBody_language"
>
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>
</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>
<footer
class="mx_AuthFooter"
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
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
powered by Matrix
Powered by Matrix
</a>
</footer>
</div>

View file

@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import { mocked } from "jest-mock";
import { act, render, RenderResult, screen } from "jest-matrix-react";
import { act, render, RenderResult, screen, waitFor } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import { MatrixClient, createClient } from "matrix-js-sdk/src/matrix";
@ -47,14 +47,12 @@ describe("<ForgotPassword>", () => {
};
const click = async (element: Element): Promise<void> => {
await act(async () => {
await userEvent.click(element, { delay: null });
});
await userEvent.click(element, { delay: null });
};
const itShouldCloseTheDialogAndShowThePasswordInput = (): void => {
it("should close the dialog and show the password input", () => {
expect(screen.queryByText("Verify your email to continue")).not.toBeInTheDocument();
it("should close the dialog and show the password input", async () => {
await waitFor(() => expect(screen.queryByText("Verify your email to continue")).not.toBeInTheDocument());
expect(screen.getByText("Reset your password")).toBeInTheDocument();
});
};
@ -314,7 +312,7 @@ describe("<ForgotPassword>", () => {
});
});
it("should send the new password and show the click validation link dialog", () => {
it("should send the new password and show the click validation link dialog", async () => {
expect(client.setPassword).toHaveBeenCalledWith(
{
type: "m.login.email.identity",
@ -326,15 +324,15 @@ describe("<ForgotPassword>", () => {
testPassword,
false,
);
expect(screen.getByText("Verify your email to continue")).toBeInTheDocument();
await expect(
screen.findByText("Verify your email to continue"),
).resolves.toBeInTheDocument();
expect(screen.getByText(testEmail)).toBeInTheDocument();
});
describe("and dismissing the dialog by clicking the background", () => {
beforeEach(async () => {
await act(async () => {
await userEvent.click(screen.getByTestId("dialog-background"), { delay: null });
});
await userEvent.click(await screen.findByTestId("dialog-background"), { delay: null });
await waitEnoughCyclesForModal({
useFakeTimers: true,
});
@ -345,7 +343,7 @@ describe("<ForgotPassword>", () => {
describe("and dismissing the dialog", () => {
beforeEach(async () => {
await click(screen.getByLabelText("Close dialog"));
await click(await screen.findByLabelText("Close dialog"));
await waitEnoughCyclesForModal({
useFakeTimers: true,
});
@ -356,14 +354,16 @@ describe("<ForgotPassword>", () => {
describe("and clicking »Re-enter email address«", () => {
beforeEach(async () => {
await click(screen.getByText("Re-enter email address"));
await click(await screen.findByText("Re-enter email address"));
await waitEnoughCyclesForModal({
useFakeTimers: true,
});
});
it("should close the dialog and go back to the email input", () => {
expect(screen.queryByText("Verify your email to continue")).not.toBeInTheDocument();
it("should close the dialog and go back to the email input", async () => {
await waitFor(() =>
expect(screen.queryByText("Verify your email to continue")).not.toBeInTheDocument(),
);
expect(screen.queryByText("Enter your email to reset password")).toBeInTheDocument();
});
});
@ -397,11 +397,11 @@ describe("<ForgotPassword>", () => {
});
it("should show the sign out warning dialog", async () => {
expect(
screen.getByText(
await expect(
screen.findByText(
"Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable.",
),
).toBeInTheDocument();
).resolves.toBeInTheDocument();
// confirm dialog
await click(screen.getByText("Continue"));

View file

@ -9,16 +9,16 @@ Please see LICENSE files in the repository root for full details.
import * as React from "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";
describe("<VectorAuthPage />", () => {
describe("<AuthFooter />", () => {
beforeEach(() => {
setupLanguageMock();
});
it("should match snapshot", () => {
const { asFragment } = render(<VectorAuthPage />);
const { asFragment } = render(<AuthFooter />);
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 { 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", () => {
const { asFragment } = render(<VectorAuthHeaderLogo />);
const { asFragment } = render(<AuthHeaderLogo />);
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
exports[`<VectorAuthFooter /> should match snapshot 1`] = `
exports[`<AuthFooter /> should match snapshot 1`] = `
<DocumentFragment>
<footer
class="mx_AuthFooter"

View file

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

View file

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

View file

@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import { Feature, ServerSupport } from "matrix-js-sdk/src/feature";
import { MatrixClient, MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix";
import { screen } from "jest-matrix-react";
import { screen, act } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import { flushPromises, mkEvent, stubClient } from "../../../../test-utils";
@ -31,12 +31,12 @@ describe("ConfirmRedactDialog", () => {
};
const confirmDeleteVoiceBroadcastStartedEvent = async () => {
createRedactEventDialog({ mxEvent });
act(() => createRedactEventDialog({ mxEvent }));
// double-flush promises required for the dialog to show up
await flushPromises();
await flushPromises();
await userEvent.click(screen.getByTestId("dialog-primary-button"));
await userEvent.click(await screen.findByTestId("dialog-primary-button"));
};
beforeEach(() => {

View file

@ -7,13 +7,57 @@
*/
import React from "react";
import { render } from "jest-matrix-react";
import { mocked } from "jest-mock";
import { render, fireEvent, waitFor } from "jest-matrix-react";
import fetchMock from "fetch-mock-jest";
import ImageView from "../../../../../src/components/views/elements/ImageView";
import { FileDownloader } from "../../../../../src/utils/FileDownloader";
import Modal from "../../../../../src/Modal";
import ErrorDialog from "../../../../../src/components/views/dialogs/ErrorDialog";
jest.mock("../../../../../src/utils/FileDownloader");
describe("<ImageView />", () => {
beforeEach(() => {
jest.resetAllMocks();
fetchMock.reset();
});
it("renders correctly", () => {
const { container } = render(<ImageView src="https://example.com/image.png" onFinished={jest.fn()} />);
expect(container).toMatchSnapshot();
});
it("should download on click", async () => {
fetchMock.get("https://example.com/image.png", "TESTFILE");
const { getByRole } = render(
<ImageView src="https://example.com/image.png" name="filename.png" onFinished={jest.fn()} />,
);
fireEvent.click(getByRole("button", { name: "Download" }));
await waitFor(() =>
expect(mocked(FileDownloader).mock.instances[0].download).toHaveBeenCalledWith({
blob: expect.anything(),
name: "filename.png",
}),
);
expect(fetchMock).toHaveFetched("https://example.com/image.png");
});
it("should handle download errors", async () => {
const modalSpy = jest.spyOn(Modal, "createDialog");
fetchMock.get("https://example.com/image.png", { status: 500 });
const { getByRole } = render(
<ImageView src="https://example.com/image.png" name="filename.png" onFinished={jest.fn()} />,
);
fireEvent.click(getByRole("button", { name: "Download" }));
await waitFor(() =>
expect(modalSpy).toHaveBeenCalledWith(
ErrorDialog,
expect.objectContaining({
title: "Download failed",
}),
),
);
});
});

View file

@ -6,9 +6,18 @@ exports[`<MapError /> applies class when isMinimised is truthy 1`] = `
class="mx_MapError test mx_MapError_isMinimised"
data-testid="map-rendering-error"
>
<div
<svg
class="mx_MapError_icon"
/>
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
/>
</svg>
<h3
class="mx_Heading_h3 mx_MapError_heading"
>
@ -36,9 +45,18 @@ exports[`<MapError /> renders correctly for MapStyleUrlNotConfigured 1`] = `
class="mx_MapError test"
data-testid="map-rendering-error"
>
<div
<svg
class="mx_MapError_icon"
/>
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
/>
</svg>
<h3
class="mx_Heading_h3 mx_MapError_heading"
>
@ -66,9 +84,18 @@ exports[`<MapError /> renders correctly for MapStyleUrlNotReachable 1`] = `
class="mx_MapError test"
data-testid="map-rendering-error"
>
<div
<svg
class="mx_MapError_icon"
/>
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
/>
</svg>
<h3
class="mx_Heading_h3 mx_MapError_heading"
>

View file

@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import { mocked } from "jest-mock";
import { fireEvent, render, screen } from "jest-matrix-react";
import { fireEvent, render, screen, waitFor } from "jest-matrix-react";
import { TimestampToEventResponse, ConnectionError, HTTPError, MatrixError } from "matrix-js-sdk/src/matrix";
import dispatcher from "../../../../../src/dispatcher/dispatcher";
@ -291,7 +291,9 @@ describe("DateSeparator", () => {
// The submit debug logs option should *NOT* be shown for network errors.
//
// We have to use `queryBy` so that it can return `null` for something that does not exist.
expect(screen.queryByTestId("jump-to-date-error-submit-debug-logs-button")).not.toBeInTheDocument();
await waitFor(() =>
expect(screen.queryByTestId("jump-to-date-error-submit-debug-logs-button")).not.toBeInTheDocument(),
);
});
});
});

View file

@ -129,6 +129,6 @@ describe("DecryptionFailureBody", () => {
const { container } = customRender(event);
// Then
expect(container).toHaveTextContent("Encrypted by a device not verified by its owner");
expect(container).toHaveTextContent("Sent from an insecure device");
});
});

View file

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import { fireEvent, render, RenderResult } from "jest-matrix-react";
import { fireEvent, render, RenderResult, waitFor } from "jest-matrix-react";
import {
M_POLL_KIND_DISCLOSED,
M_POLL_KIND_UNDISCLOSED,
@ -85,7 +85,7 @@ describe("MPollBody", () => {
expect(votesCount(renderResult, "poutine")).toBe("");
expect(votesCount(renderResult, "italian")).toBe("");
expect(votesCount(renderResult, "wings")).toBe("");
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("No votes cast");
await waitFor(() => expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("No votes cast"));
expect(renderResult.getByText("What should we order for the party?")).toBeTruthy();
});

View file

@ -23,7 +23,7 @@ exports[`DecryptionFailureBody Should display "Unable to decrypt message" 1`] =
exports[`DecryptionFailureBody should handle messages from users who change identities after verification 1`] = `
<div>
<div
class="mx_DecryptionFailureBody mx_EventTile_content mx_DecryptionFailureVerifiedIdentityChanged"
class="mx_DecryptionFailureBody mx_EventTile_content mx_DecryptionFailureSenderTrustRequirement"
>
<span>
<svg
@ -35,15 +35,10 @@ exports[`DecryptionFailureBody should handle messages from users who change iden
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12.713 17.713A.968.968 0 0 1 12 18a.968.968 0 0 1-.713-.287A.967.967 0 0 1 11 17a.97.97 0 0 1 .287-.712A.968.968 0 0 1 12 16a.97.97 0 0 1 .713.288A.968.968 0 0 1 13 17a.97.97 0 0 1-.287.713Zm0-4A.968.968 0 0 1 12 14a.968.968 0 0 1-.713-.287A.967.967 0 0 1 11 13V9a.97.97 0 0 1 .287-.712A.968.968 0 0 1 12 8a.97.97 0 0 1 .713.288A.968.968 0 0 1 13 9v4a.97.97 0 0 1-.287.713Z"
/>
<path
clip-rule="evenodd"
d="M10.264 3.039c.767-1.344 2.705-1.344 3.472 0l8.554 14.969c.762 1.333-.2 2.992-1.736 2.992H3.446c-1.535 0-2.498-1.659-1.736-2.992l8.553-14.969ZM3.446 19 12 4.031l8.554 14.97H3.446Z"
fill-rule="evenodd"
d="M12 22a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Zm0-2c2.233 0 4.125-.775 5.675-2.325C19.225 16.125 20 14.233 20 12c0-.9-.146-1.767-.438-2.6A7.951 7.951 0 0 0 18.3 7.1L7.1 18.3c.7.55 1.467.97 2.3 1.262.833.292 1.7.438 2.6.438Zm-6.3-3.1L16.9 5.7a7.95 7.95 0 0 0-2.3-1.263A7.813 7.813 0 0 0 12 4c-2.233 0-4.125.775-5.675 2.325C4.775 7.875 4 9.767 4 12c0 .9.146 1.767.438 2.6A7.95 7.95 0 0 0 5.7 16.9Z"
/>
</svg>
Verified identity has changed
Sender's verified identity has changed
</span>
</div>
</div>

View file

@ -5,9 +5,18 @@ exports[`<MBeaconBody /> when map display is not configured renders maps unavail
class="mx_MapError mx_MBeaconBody_mapError mx_MBeaconBody_mapErrorInteractive mx_MapError_isMinimised"
data-testid="map-rendering-error"
>
<div
<svg
class="mx_MapError_icon"
/>
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
/>
</svg>
<h3
class="mx_Heading_h3 mx_MapError_heading"
>

View file

@ -19,7 +19,13 @@ import {
Room,
TweakName,
} from "matrix-js-sdk/src/matrix";
import { CryptoApi, EventEncryptionInfo, EventShieldColour, EventShieldReason } from "matrix-js-sdk/src/crypto-api";
import {
CryptoApi,
DecryptionFailureCode,
EventEncryptionInfo,
EventShieldColour,
EventShieldReason,
} from "matrix-js-sdk/src/crypto-api";
import { mkEncryptedMatrixEvent } from "matrix-js-sdk/src/testing";
import EventTile, { EventTileProps } from "../../../../../src/components/views/rooms/EventTile";
@ -350,6 +356,32 @@ describe("EventTile", () => {
"mx_EventTile_e2eIcon_decryption_failure",
);
});
it("should not show a shield for previously-verified users", async () => {
mxEvent = mkEvent({
type: "m.room.encrypted",
room: room.roomId,
user: "@alice:example.org",
event: true,
content: {},
});
const mockCrypto = {
decryptEvent: async (_ev): Promise<IEventDecryptionResult> => {
throw new Error("can't decrypt");
},
} as Parameters<MatrixEvent["attemptDecryption"]>[0];
await mxEvent.attemptDecryption(mockCrypto);
mxEvent["_decryptionFailureReason"] = DecryptionFailureCode.SENDER_IDENTITY_PREVIOUSLY_VERIFIED;
const { container } = getComponent();
await act(flushPromises);
const eventTiles = container.getElementsByClassName("mx_EventTile");
expect(eventTiles).toHaveLength(1);
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(0);
});
});
it("should update the warning when the event is edited", async () => {

View file

@ -14,7 +14,7 @@ import userEvent from "@testing-library/user-event";
import * as pinnedEventHooks from "../../../../../src/hooks/usePinnedEvents";
import { PinnedMessageBanner } from "../../../../../src/components/views/rooms/PinnedMessageBanner";
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
import { makePollStartEvent, stubClient } from "../../../../test-utils";
import { makePollStartEvent, stubClient, withClientContextRenderOptions } from "../../../../test-utils";
import dis from "../../../../../src/dispatcher/dispatcher";
import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelStore";
import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases";
@ -76,7 +76,10 @@ describe("<PinnedMessageBanner />", () => {
* Render the banner
*/
function renderBanner() {
return render(<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} />);
return render(
<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} />,
withClientContextRenderOptions(mockClient),
);
}
it("should render nothing when there are no pinned events", async () => {
@ -92,7 +95,7 @@ describe("<PinnedMessageBanner />", () => {
const { asFragment } = renderBanner();
expect(screen.getByText("First pinned message")).toBeVisible();
await expect(screen.findByText("First pinned message")).resolves.toBeVisible();
expect(screen.queryByRole("button", { name: "View all" })).toBeNull();
expect(asFragment()).toMatchSnapshot();
});
@ -103,7 +106,7 @@ describe("<PinnedMessageBanner />", () => {
const { asFragment } = renderBanner();
expect(screen.getByText("Second pinned message")).toBeVisible();
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
expect(screen.getByTestId("banner-counter")).toHaveTextContent("2 of 2 Pinned messages");
expect(screen.getAllByTestId("banner-indicator")).toHaveLength(2);
expect(screen.queryByRole("button", { name: "View all" })).toBeVisible();
@ -121,7 +124,7 @@ describe("<PinnedMessageBanner />", () => {
const { asFragment } = renderBanner();
expect(screen.getByText("Fourth pinned message")).toBeVisible();
await expect(screen.findByText("Fourth pinned message")).resolves.toBeVisible();
expect(screen.getByTestId("banner-counter")).toHaveTextContent("4 of 4 Pinned messages");
expect(screen.getAllByTestId("banner-indicator")).toHaveLength(3);
expect(screen.queryByRole("button", { name: "View all" })).toBeVisible();
@ -143,7 +146,7 @@ describe("<PinnedMessageBanner />", () => {
]);
jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2, event3]);
rerender(<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} />);
expect(screen.getByText("Third pinned message")).toBeVisible();
await expect(screen.findByText("Third pinned message")).resolves.toBeVisible();
expect(asFragment()).toMatchSnapshot();
});
@ -152,7 +155,7 @@ describe("<PinnedMessageBanner />", () => {
jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2]);
renderBanner();
expect(screen.getByText("Second pinned message")).toBeVisible();
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
await userEvent.click(screen.getByRole("button", { name: "View the pinned message in the timeline." }));
expect(screen.getByText("First pinned message")).toBeVisible();
@ -182,14 +185,14 @@ describe("<PinnedMessageBanner />", () => {
["m.audio", "Audio"],
["m.video", "Video"],
["m.image", "Image"],
])("should display the %s event type", (msgType, label) => {
])("should display the %s event type", async (msgType, label) => {
const body = `Message with ${msgType} type`;
const event = makePinEvent({ content: { body, msgtype: msgType } });
jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event.getId()!]);
jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event]);
const { asFragment } = renderBanner();
expect(screen.getByTestId("banner-message")).toHaveTextContent(`${label}: ${body}`);
await expect(screen.findByTestId("banner-message")).resolves.toHaveTextContent(`${label}: ${body}`);
expect(asFragment()).toMatchSnapshot();
});
@ -199,7 +202,7 @@ describe("<PinnedMessageBanner />", () => {
jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event]);
const { asFragment } = renderBanner();
expect(screen.getByTestId("banner-message")).toHaveTextContent("Poll: Alice?");
await expect(screen.findByTestId("banner-message")).resolves.toHaveTextContent("Poll: Alice?");
expect(asFragment()).toMatchSnapshot();
});

View file

@ -37,12 +37,12 @@ exports[`<PinnedMessageBanner /> should display display a poll event 1`] = `
/>
</svg>
<span
class="mx_PinnedMessageBanner_message"
class="mx_EventPreview mx_PinnedMessageBanner_message"
data-testid="banner-message"
>
<span>
<span
class="mx_PinnedMessageBanner_prefix"
class="mx_EventPreview_prefix"
>
Poll:
</span>
@ -113,8 +113,9 @@ exports[`<PinnedMessageBanner /> should display the last message when the pinned
</span>
</div>
<span
class="mx_PinnedMessageBanner_message"
class="mx_EventPreview mx_PinnedMessageBanner_message"
data-testid="banner-message"
title="Third pinned message"
>
Third pinned message
</span>
@ -170,12 +171,12 @@ exports[`<PinnedMessageBanner /> should display the m.audio event type 1`] = `
/>
</svg>
<span
class="mx_PinnedMessageBanner_message"
class="mx_EventPreview mx_PinnedMessageBanner_message"
data-testid="banner-message"
>
<span>
<span
class="mx_PinnedMessageBanner_prefix"
class="mx_EventPreview_prefix"
>
Audio:
</span>
@ -225,12 +226,12 @@ exports[`<PinnedMessageBanner /> should display the m.file event type 1`] = `
/>
</svg>
<span
class="mx_PinnedMessageBanner_message"
class="mx_EventPreview mx_PinnedMessageBanner_message"
data-testid="banner-message"
>
<span>
<span
class="mx_PinnedMessageBanner_prefix"
class="mx_EventPreview_prefix"
>
File:
</span>
@ -280,12 +281,12 @@ exports[`<PinnedMessageBanner /> should display the m.image event type 1`] = `
/>
</svg>
<span
class="mx_PinnedMessageBanner_message"
class="mx_EventPreview mx_PinnedMessageBanner_message"
data-testid="banner-message"
>
<span>
<span
class="mx_PinnedMessageBanner_prefix"
class="mx_EventPreview_prefix"
>
Image:
</span>
@ -335,12 +336,12 @@ exports[`<PinnedMessageBanner /> should display the m.video event type 1`] = `
/>
</svg>
<span
class="mx_PinnedMessageBanner_message"
class="mx_EventPreview mx_PinnedMessageBanner_message"
data-testid="banner-message"
>
<span>
<span
class="mx_PinnedMessageBanner_prefix"
class="mx_EventPreview_prefix"
>
Video:
</span>
@ -407,8 +408,9 @@ exports[`<PinnedMessageBanner /> should render 2 pinned event 1`] = `
</span>
</div>
<span
class="mx_PinnedMessageBanner_message"
class="mx_EventPreview mx_PinnedMessageBanner_message"
data-testid="banner-message"
title="Second pinned message"
>
Second pinned message
</span>
@ -485,8 +487,9 @@ exports[`<PinnedMessageBanner /> should render 4 pinned event 1`] = `
</span>
</div>
<span
class="mx_PinnedMessageBanner_message"
class="mx_EventPreview mx_PinnedMessageBanner_message"
data-testid="banner-message"
title="Fourth pinned message"
>
Fourth pinned message
</span>
@ -542,8 +545,9 @@ exports[`<PinnedMessageBanner /> should render a single pinned event 1`] = `
/>
</svg>
<span
class="mx_PinnedMessageBanner_message"
class="mx_EventPreview mx_PinnedMessageBanner_message"
data-testid="banner-message"
title="First pinned message"
>
First pinned message
</span>

View file

@ -417,7 +417,7 @@ describe("message", () => {
expect(mockClient.sendMessage).toHaveBeenCalledTimes(0);
expect(mockClient.cancelPendingEvent).toHaveBeenCalledTimes(1);
expect(mockCreateRedactEventDialog).toHaveBeenCalledTimes(1);
expect(spyDispatcher).toHaveBeenCalledTimes(0);
expect(spyDispatcher).toHaveBeenCalledTimes(1);
});
it("Should do nothing if the content is unmodified", async () => {

View file

@ -14,6 +14,7 @@ import { mocked } from "jest-mock";
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
import * as TestUtils from "../../../../test-utils";
import CryptographyPanel from "../../../../../src/components/views/settings/CryptographyPanel";
import { withClientContextRenderOptions } from "../../../../test-utils";
describe("CryptographyPanel", () => {
it("shows the session ID and key", async () => {
@ -28,7 +29,7 @@ describe("CryptographyPanel", () => {
mocked(client.getCrypto()!.getOwnDeviceKeys).mockResolvedValue({ ed25519: sessionKey, curve25519: "1234" });
// When we render the CryptographyPanel
const rendered = render(<CryptographyPanel />);
const rendered = render(<CryptographyPanel />, withClientContextRenderOptions(client));
// Then it displays info about the user's session
const codes = rendered.container.querySelectorAll("code");
@ -52,7 +53,7 @@ describe("CryptographyPanel", () => {
mocked(client.getCrypto()!.getOwnDeviceKeys).mockRejectedValue(new Error("bleh"));
// When we render the CryptographyPanel
const rendered = render(<CryptographyPanel />);
const rendered = render(<CryptographyPanel />, withClientContextRenderOptions(client));
// Then it displays info about the user's session
const codes = rendered.container.querySelectorAll("code");

View file

@ -59,7 +59,7 @@ describe("<JoinRuleSettings />", () => {
onError: jest.fn(),
};
const getComponent = (props: Partial<JoinRuleSettingsProps> = {}) =>
render(<JoinRuleSettings {...defaultProps} {...props} />);
render(<JoinRuleSettings {...defaultProps} {...props} />, { legacyRoot: false });
const setRoomStateEvents = (
room: Room,
@ -202,7 +202,7 @@ describe("<JoinRuleSettings />", () => {
await flushPromises();
expect(within(dialog).getByText("Loading new room")).toBeInTheDocument();
await expect(within(dialog).findByText("Loading new room")).resolves.toBeInTheDocument();
// "create" our new room, have it come thru sync
client.getRoom.mockImplementation((id) => {
@ -250,7 +250,7 @@ describe("<JoinRuleSettings />", () => {
await flushPromises();
expect(within(dialog).getByText("Loading new room")).toBeInTheDocument();
await expect(within(dialog).findByText("Loading new room")).resolves.toBeInTheDocument();
// "create" our new room, have it come thru sync
client.getRoom.mockImplementation((id) => {

View file

@ -130,10 +130,8 @@ describe("<SecureBackupPanel />", () => {
})
.mockResolvedValue(null);
getComponent();
// flush checkKeyBackup promise
await flushPromises();
fireEvent.click(screen.getByText("Delete Backup"));
fireEvent.click(await screen.findByText("Delete Backup"));
const dialog = await screen.findByRole("dialog");

View file

@ -119,6 +119,7 @@ describe("<SessionManagerTab />", () => {
const mockVerificationRequest = {
cancel: jest.fn(),
on: jest.fn(),
off: jest.fn(),
} as unknown as VerificationRequest;
const mockCrypto = mocked({

View file

@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
import { mocked } from "jest-mock";
import EditorModel from "../../../src/editor/model";
import { htmlSerializeIfNeeded } from "../../../src/editor/serialize";
import { htmlSerializeFromMdIfNeeded, htmlSerializeIfNeeded } from "../../../src/editor/serialize";
import { createPartCreator } from "./mock";
import { IConfigOptions } from "../../../src/IConfigOptions";
import SettingsStore from "../../../src/settings/SettingsStore";
@ -71,6 +71,12 @@ describe("editor/serialize", function () {
const html = htmlSerializeIfNeeded(model, {});
expect(html).toBe("*hello* world");
});
it("escaped markdown should not retain backslashes around other markdown", function () {
const pc = createPartCreator();
const model = new EditorModel([pc.plain("\\*hello\\* **world**")], pc);
const html = htmlSerializeIfNeeded(model, {});
expect(html).toBe("*hello* <strong>world</strong>");
});
it("escaped markdown should convert HTML entities", function () {
const pc = createPartCreator();
const model = new EditorModel([pc.plain("\\*hello\\* world < hey world!")], pc);
@ -153,6 +159,14 @@ describe("editor/serialize", function () {
const html = htmlSerializeIfNeeded(model, { forceHTML: true, useMarkdown: false });
expect(html).toBe("hello world");
});
it("should treat tags not in allowlist as plaintext", () => {
const html = htmlSerializeFromMdIfNeeded("<b>test</b>", {});
expect(html).toBeUndefined();
});
it("should treat tags not in allowlist as plaintext even if escaped", () => {
const html = htmlSerializeFromMdIfNeeded("\\<b>test</b>", {});
expect(html).toBe("&lt;b&gt;test&lt;/b&gt;");
});
});
describe("feature_latex_maths", () => {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 460 B

View file

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="19px" height="19px" viewBox="0 0 19 19" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: sketchtool 39.1 (31720) - http://www.bohemiancoding.com/sketch -->
<title>ED5D3E59-2561-4AC1-9B43-82FBC51767FC</title>
<desc>Created with sketchtool.</desc>
<defs></defs>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="icon_context">
<g>
<path d="M9.5,19 C14.7467051,19 19,14.7467051 19,9.5 C19,4.25329488 14.7467051,0 9.5,0 C4.25329488,0 0,4.25329488 0,9.5 C0,14.7467051 4.25329488,19 9.5,19 Z" id="Oval-69" fill="#ECECEC"></path>
<path d="M4.5,9.50063771 C4.5,9.13148623 4.59887838,8.85242947 4.7966381,8.66345907 C4.99439782,8.47448867 5.28224377,8.38000488 5.66018457,8.38000488 C6.0249414,8.38000488 6.3072941,8.47668596 6.50725115,8.67005103 C6.70720821,8.86341609 6.80718523,9.14027555 6.80718523,9.50063771 C6.80718523,9.84781589 6.70610956,10.1213794 6.50395517,10.3213365 C6.30180079,10.5212935 6.02054674,10.6212705 5.66018457,10.6212705 C5.29103309,10.6212705 5.00538444,10.5234908 4.80323006,10.3279284 C4.60107568,10.132366 4.5,9.85660521 4.5,9.50063771 L4.5,9.50063771 Z M8.3431114,9.50063771 C8.3431114,9.13148623 8.44198978,8.85242947 8.63974951,8.66345907 C8.83750923,8.47448867 9.12755247,8.38000488 9.50988794,8.38000488 C9.87464476,8.38000488 10.1569975,8.47668596 10.3569545,8.67005103 C10.5569116,8.86341609 10.6568886,9.14027555 10.6568886,9.50063771 C10.6568886,9.84781589 10.5558129,10.1213794 10.3536585,10.3213365 C10.1515042,10.5212935 9.8702501,10.6212705 9.50988794,10.6212705 C9.13634179,10.6212705 8.84849585,10.5234908 8.64634146,10.3279284 C8.44418708,10.132366 8.3431114,9.85660521 8.3431114,9.50063771 L8.3431114,9.50063771 Z M12.1928148,9.50063771 C12.1928148,9.13148623 12.2916931,8.85242947 12.4894529,8.66345907 C12.6872126,8.47448867 12.9750585,8.38000488 13.3529993,8.38000488 C13.7177562,8.38000488 14.0001089,8.47668596 14.2000659,8.67005103 C14.400023,8.86341609 14.5,9.14027555 14.5,9.50063771 C14.5,9.84781589 14.3989243,10.1213794 14.1967699,10.3213365 C13.9946156,10.5212935 13.7133615,10.6212705 13.3529993,10.6212705 C12.9838479,10.6212705 12.6981992,10.5234908 12.4960448,10.3279284 C12.2938904,10.132366 12.1928148,9.85660521 12.1928148,9.50063771 L12.1928148,9.50063771 Z" id="…" fill="#9B9B9B"></path>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -19,9 +19,9 @@ describe("requestMediaPermissions", () => {
const audioStream = {} as MediaStream;
const itShouldLogTheErrorAndShowTheNoMediaPermissionsModal = () => {
it("should log the error and show the »No media permissions« modal", () => {
it("should log the error and show the »No media permissions« modal", async () => {
expect(logger.log).toHaveBeenCalledWith("Failed to list userMedia devices", error);
screen.getByText("No media permissions");
await screen.findByText("No media permissions");
});
};

View file

@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { render } from "jest-matrix-react";
import { act, render } from "jest-matrix-react";
import { MatrixEvent, ConditionKind, EventType, PushRuleActionName, Room, TweakName } from "matrix-js-sdk/src/matrix";
import { mocked } from "jest-mock";
@ -15,6 +15,7 @@ import { pillifyLinks } from "../../../src/utils/pillify";
import { stubClient } from "../../test-utils";
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
import DMRoomMap from "../../../src/utils/DMRoomMap";
import { ReactRootManager } from "../../../src/utils/react.tsx";
describe("pillify", () => {
const roomId = "!room:id";
@ -84,51 +85,55 @@ describe("pillify", () => {
it("should do nothing for empty element", () => {
const { container } = render(<div />);
const originalHtml = container.outerHTML;
const containers: Element[] = [];
const containers = new ReactRootManager();
pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
expect(containers).toHaveLength(0);
expect(containers.elements).toHaveLength(0);
expect(container.outerHTML).toEqual(originalHtml);
});
it("should pillify @room", () => {
const { container } = render(<div>@room</div>);
const containers: Element[] = [];
pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
expect(containers).toHaveLength(1);
const containers = new ReactRootManager();
act(() => pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers));
expect(containers.elements).toHaveLength(1);
expect(container.querySelector(".mx_Pill.mx_AtRoomPill")?.textContent).toBe("!@room");
});
it("should pillify @room in an intentional mentions world", () => {
mocked(MatrixClientPeg.safeGet().supportsIntentionalMentions).mockReturnValue(true);
const { container } = render(<div>@room</div>);
const containers: Element[] = [];
pillifyLinks(
MatrixClientPeg.safeGet(),
[container],
new MatrixEvent({
room_id: roomId,
type: EventType.RoomMessage,
content: {
"body": "@room",
"m.mentions": {
room: true,
const containers = new ReactRootManager();
act(() =>
pillifyLinks(
MatrixClientPeg.safeGet(),
[container],
new MatrixEvent({
room_id: roomId,
type: EventType.RoomMessage,
content: {
"body": "@room",
"m.mentions": {
room: true,
},
},
},
}),
containers,
}),
containers,
),
);
expect(containers).toHaveLength(1);
expect(containers.elements).toHaveLength(1);
expect(container.querySelector(".mx_Pill.mx_AtRoomPill")?.textContent).toBe("!@room");
});
it("should not double up pillification on repeated calls", () => {
const { container } = render(<div>@room</div>);
const containers: Element[] = [];
pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
expect(containers).toHaveLength(1);
const containers = new ReactRootManager();
act(() => {
pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
});
expect(containers.elements).toHaveLength(1);
expect(container.querySelector(".mx_Pill.mx_AtRoomPill")?.textContent).toBe("!@room");
});
});

View file

@ -12,6 +12,7 @@ import { act, render } from "jest-matrix-react";
import { tooltipifyLinks } from "../../../src/utils/tooltipify";
import PlatformPeg from "../../../src/PlatformPeg";
import BasePlatform from "../../../src/BasePlatform";
import { ReactRootManager } from "../../../src/utils/react.tsx";
describe("tooltipify", () => {
jest.spyOn(PlatformPeg, "get").mockReturnValue({ needsUrlTooltips: () => true } as unknown as BasePlatform);
@ -19,9 +20,9 @@ describe("tooltipify", () => {
it("does nothing for empty element", () => {
const { container: root } = render(<div />);
const originalHtml = root.outerHTML;
const containers: Element[] = [];
const containers = new ReactRootManager();
tooltipifyLinks([root], [], containers);
expect(containers).toHaveLength(0);
expect(containers.elements).toHaveLength(0);
expect(root.outerHTML).toEqual(originalHtml);
});
@ -31,9 +32,9 @@ describe("tooltipify", () => {
<a href="/foo">click</a>
</div>,
);
const containers: Element[] = [];
const containers = new ReactRootManager();
tooltipifyLinks([root], [], containers);
expect(containers).toHaveLength(1);
expect(containers.elements).toHaveLength(1);
const anchor = root.querySelector("a");
expect(anchor?.getAttribute("href")).toEqual("/foo");
const tooltip = anchor!.querySelector(".mx_TextWithTooltip_target");
@ -47,9 +48,9 @@ describe("tooltipify", () => {
</div>,
);
const originalHtml = root.outerHTML;
const containers: Element[] = [];
const containers = new ReactRootManager();
tooltipifyLinks([root], [root.children[0]], containers);
expect(containers).toHaveLength(0);
expect(containers.elements).toHaveLength(0);
expect(root.outerHTML).toEqual(originalHtml);
});
@ -59,12 +60,12 @@ describe("tooltipify", () => {
<a href="/foo">click</a>
</div>,
);
const containers: Element[] = [];
const containers = new ReactRootManager();
tooltipifyLinks([root], [], containers);
tooltipifyLinks([root], [], containers);
tooltipifyLinks([root], [], containers);
tooltipifyLinks([root], [], containers);
expect(containers).toHaveLength(1);
expect(containers.elements).toHaveLength(1);
const anchor = root.querySelector("a");
expect(anchor?.getAttribute("href")).toEqual("/foo");
const tooltip = anchor!.querySelector(".mx_TextWithTooltip_target");

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" });
});
});