Compare commits
7 commits
rav/wasm_i
...
develop
Author | SHA1 | Date | |
---|---|---|---|
|
943b817194 | ||
|
2aa72bb40b | ||
|
a755e399cf | ||
|
8dff758153 | ||
|
cf3bdbdc7a | ||
|
ba98c2085d | ||
|
b330de5d6e |
|
@ -88,7 +88,7 @@
|
|||
"@matrix-org/spec": "^1.7.0",
|
||||
"@sentry/browser": "^8.0.0",
|
||||
"@vector-im/compound-design-tokens": "^2.0.1",
|
||||
"@vector-im/compound-web": "^7.4.0",
|
||||
"@vector-im/compound-web": "^7.5.0",
|
||||
"@vector-im/matrix-wysiwyg": "2.37.13",
|
||||
"@zxcvbn-ts/core": "^3.0.4",
|
||||
"@zxcvbn-ts/language-common": "^3.0.4",
|
||||
|
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.5 KiB |
2
src/@types/global.d.ts
vendored
|
@ -44,6 +44,7 @@ import { IConfigOptions } from "../IConfigOptions";
|
|||
import { MatrixDispatcher } from "../dispatcher/dispatcher";
|
||||
import { DeepReadonly } from "./common";
|
||||
import MatrixChat from "../components/structures/MatrixChat";
|
||||
import { InitialCryptoSetupStore } from "../stores/InitialCryptoSetupStore";
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
|
@ -117,6 +118,7 @@ declare global {
|
|||
mxPerformanceEntryNames: any;
|
||||
mxUIStore: UIStore;
|
||||
mxSetupEncryptionStore?: SetupEncryptionStore;
|
||||
mxInitialCryptoStore?: InitialCryptoSetupStore;
|
||||
mxRoomScrollStateStore?: RoomScrollStateStore;
|
||||
mxActiveWidgetStore?: ActiveWidgetStore;
|
||||
mxOnRecaptchaLoaded?: () => void;
|
||||
|
|
|
@ -132,6 +132,7 @@ import { SessionLockStolenView } from "./auth/SessionLockStolenView";
|
|||
import { ConfirmSessionLockTheftView } from "./auth/ConfirmSessionLockTheftView";
|
||||
import { LoginSplashView } from "./auth/LoginSplashView";
|
||||
import { cleanUpDraftsIfRequired } from "../../DraftCleaner";
|
||||
import { InitialCryptoSetupStore } from "../../stores/InitialCryptoSetupStore";
|
||||
|
||||
// legacy export
|
||||
export { default as Views } from "../../Views";
|
||||
|
@ -428,6 +429,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
!(await shouldSkipSetupEncryption(cli))
|
||||
) {
|
||||
// if cross-signing is not yet set up, do so now if possible.
|
||||
InitialCryptoSetupStore.sharedInstance().startInitialCryptoSetup(
|
||||
cli,
|
||||
Boolean(this.tokenLogin),
|
||||
this.stores,
|
||||
this.onCompleteSecurityE2eSetupFinished,
|
||||
);
|
||||
this.setStateForNewView({ view: Views.E2E_SETUP });
|
||||
} else {
|
||||
this.onLoggedIn();
|
||||
|
@ -2073,14 +2080,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
} else if (this.state.view === Views.COMPLETE_SECURITY) {
|
||||
view = <CompleteSecurity onFinished={this.onCompleteSecurityE2eSetupFinished} />;
|
||||
} else if (this.state.view === Views.E2E_SETUP) {
|
||||
view = (
|
||||
<E2eSetup
|
||||
matrixClient={MatrixClientPeg.safeGet()}
|
||||
onFinished={this.onCompleteSecurityE2eSetupFinished}
|
||||
accountPassword={this.stores.accountPasswordStore.getPassword()}
|
||||
tokenLogin={!!this.tokenLogin}
|
||||
/>
|
||||
);
|
||||
view = <E2eSetup onFinished={this.onCompleteSecurityE2eSetupFinished} />;
|
||||
} else if (this.state.view === Views.LOGGED_IN) {
|
||||
// `ready` and `view==LOGGED_IN` may be set before `page_type` (because the
|
||||
// latter is set via the dispatcher). If we don't yet have a `page_type`,
|
||||
|
|
|
@ -7,17 +7,13 @@ Please see LICENSE files in the repository root for full details.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import AuthPage from "../../views/auth/AuthPage";
|
||||
import CompleteSecurityBody from "../../views/auth/CompleteSecurityBody";
|
||||
import { InitialCryptoSetupDialog } from "../../views/dialogs/security/InitialCryptoSetupDialog";
|
||||
|
||||
interface IProps {
|
||||
matrixClient: MatrixClient;
|
||||
onFinished: () => void;
|
||||
accountPassword?: string;
|
||||
tokenLogin: boolean;
|
||||
}
|
||||
|
||||
export default class E2eSetup extends React.Component<IProps> {
|
||||
|
@ -25,12 +21,7 @@ export default class E2eSetup extends React.Component<IProps> {
|
|||
return (
|
||||
<AuthPage>
|
||||
<CompleteSecurityBody>
|
||||
<InitialCryptoSetupDialog
|
||||
matrixClient={this.props.matrixClient}
|
||||
onFinished={this.props.onFinished}
|
||||
accountPassword={this.props.accountPassword}
|
||||
tokenLogin={this.props.tokenLogin}
|
||||
/>
|
||||
<InitialCryptoSetupDialog onFinished={this.props.onFinished} />
|
||||
</CompleteSecurityBody>
|
||||
</AuthPage>
|
||||
);
|
||||
|
|
|
@ -7,20 +7,15 @@ 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, { useCallback, useEffect, useState } from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import React, { useCallback } from "react";
|
||||
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import DialogButtons from "../../elements/DialogButtons";
|
||||
import BaseDialog from "../BaseDialog";
|
||||
import Spinner from "../../elements/Spinner";
|
||||
import { createCrossSigning } from "../../../../CreateCrossSigning";
|
||||
import { InitialCryptoSetupStore, useInitialCryptoSetupStatus } from "../../../../stores/InitialCryptoSetupStore";
|
||||
|
||||
interface Props {
|
||||
matrixClient: MatrixClient;
|
||||
accountPassword?: string;
|
||||
tokenLogin: boolean;
|
||||
onFinished: (success?: boolean) => void;
|
||||
}
|
||||
|
||||
|
@ -29,54 +24,27 @@ interface Props {
|
|||
* In most cases, only a spinner is shown, but for more
|
||||
* complex auth like SSO, the user may need to complete some steps to proceed.
|
||||
*/
|
||||
export const InitialCryptoSetupDialog: React.FC<Props> = ({
|
||||
matrixClient,
|
||||
accountPassword,
|
||||
tokenLogin,
|
||||
onFinished,
|
||||
}) => {
|
||||
const [error, setError] = useState(false);
|
||||
export const InitialCryptoSetupDialog: React.FC<Props> = ({ onFinished }) => {
|
||||
const onRetryClick = useCallback(() => {
|
||||
InitialCryptoSetupStore.sharedInstance().retry();
|
||||
}, []);
|
||||
|
||||
const doSetup = useCallback(async () => {
|
||||
const cryptoApi = matrixClient.getCrypto();
|
||||
if (!cryptoApi) return;
|
||||
|
||||
setError(false);
|
||||
|
||||
try {
|
||||
await createCrossSigning(matrixClient, tokenLogin, accountPassword);
|
||||
|
||||
onFinished(true);
|
||||
} catch (e) {
|
||||
if (tokenLogin) {
|
||||
// ignore any failures, we are relying on grace period here
|
||||
onFinished(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setError(true);
|
||||
logger.error("Error bootstrapping cross-signing", e);
|
||||
}
|
||||
}, [matrixClient, tokenLogin, accountPassword, onFinished]);
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
const onCancelClick = useCallback(() => {
|
||||
onFinished(false);
|
||||
}, [onFinished]);
|
||||
|
||||
useEffect(() => {
|
||||
doSetup();
|
||||
}, [doSetup]);
|
||||
const status = useInitialCryptoSetupStatus(InitialCryptoSetupStore.sharedInstance());
|
||||
|
||||
let content;
|
||||
if (error) {
|
||||
if (status === "error") {
|
||||
content = (
|
||||
<div>
|
||||
<p>{_t("encryption|unable_to_setup_keys_error")}</p>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<DialogButtons
|
||||
primaryButton={_t("action|retry")}
|
||||
onPrimaryButtonClick={doSetup}
|
||||
onCancel={onCancel}
|
||||
onPrimaryButtonClick={onRetryClick}
|
||||
onCancel={onCancelClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
140
src/stores/InitialCryptoSetupStore.ts
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
Copyright 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 EventEmitter from "events";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { createCrossSigning } from "../CreateCrossSigning";
|
||||
import { SdkContextClass } from "../contexts/SDKContext";
|
||||
|
||||
type Status = "in_progress" | "complete" | "error" | undefined;
|
||||
|
||||
export const useInitialCryptoSetupStatus = (store: InitialCryptoSetupStore): Status => {
|
||||
const [status, setStatus] = useState<Status>(store.getStatus());
|
||||
|
||||
useEffect(() => {
|
||||
const update = (): void => {
|
||||
setStatus(store.getStatus());
|
||||
};
|
||||
|
||||
store.on("update", update);
|
||||
|
||||
return () => {
|
||||
store.off("update", update);
|
||||
};
|
||||
}, [store]);
|
||||
|
||||
return status;
|
||||
};
|
||||
|
||||
/**
|
||||
* Logic for setting up crypto state that's done immediately after
|
||||
* a user registers. Should be transparent to the user, not requiring
|
||||
* interaction in most cases.
|
||||
* As distinct from SetupEncryptionStore which is for setting up
|
||||
* 4S or verifying the device, will always require interaction
|
||||
* from the user in some form.
|
||||
*/
|
||||
export class InitialCryptoSetupStore extends EventEmitter {
|
||||
private status: Status = undefined;
|
||||
|
||||
private client?: MatrixClient;
|
||||
private isTokenLogin?: boolean;
|
||||
private stores?: SdkContextClass;
|
||||
private onFinished?: (success: boolean) => void;
|
||||
|
||||
public static sharedInstance(): InitialCryptoSetupStore {
|
||||
if (!window.mxInitialCryptoStore) window.mxInitialCryptoStore = new InitialCryptoSetupStore();
|
||||
return window.mxInitialCryptoStore;
|
||||
}
|
||||
|
||||
public getStatus(): Status {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the initial crypto setup process.
|
||||
*
|
||||
* @param {MatrixClient} client The client to use for the setup
|
||||
* @param {boolean} isTokenLogin True if the user logged in via a token login, otherwise false
|
||||
* @param {SdkContextClass} stores The stores to use for the setup
|
||||
*/
|
||||
public startInitialCryptoSetup(
|
||||
client: MatrixClient,
|
||||
isTokenLogin: boolean,
|
||||
stores: SdkContextClass,
|
||||
onFinished: (success: boolean) => void,
|
||||
): void {
|
||||
this.client = client;
|
||||
this.isTokenLogin = isTokenLogin;
|
||||
this.stores = stores;
|
||||
this.onFinished = onFinished;
|
||||
|
||||
// We just start this process: it's progress is tracked by the events rather
|
||||
// than returning a promise, so we don't bother.
|
||||
this.doSetup().catch(() => logger.error("Initial crypto setup failed"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry the initial crypto setup process.
|
||||
*
|
||||
* If no crypto setup is currently in process, this will return false.
|
||||
*
|
||||
* @returns {boolean} True if a retry was initiated, otherwise false
|
||||
*/
|
||||
public retry(): boolean {
|
||||
if (this.client === undefined || this.isTokenLogin === undefined || this.stores == undefined) return false;
|
||||
|
||||
this.doSetup().catch(() => logger.error("Initial crypto setup failed"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private reset(): void {
|
||||
this.client = undefined;
|
||||
this.isTokenLogin = undefined;
|
||||
this.stores = undefined;
|
||||
}
|
||||
|
||||
private async doSetup(): Promise<void> {
|
||||
if (this.client === undefined || this.isTokenLogin === undefined || this.stores == undefined) {
|
||||
throw new Error("No setup is in progress");
|
||||
}
|
||||
|
||||
const cryptoApi = this.client.getCrypto();
|
||||
if (!cryptoApi) throw new Error("No crypto module found!");
|
||||
|
||||
this.status = "in_progress";
|
||||
this.emit("update");
|
||||
|
||||
try {
|
||||
await createCrossSigning(this.client, this.isTokenLogin, this.stores.accountPasswordStore.getPassword());
|
||||
|
||||
this.reset();
|
||||
|
||||
this.status = "complete";
|
||||
this.emit("update");
|
||||
this.onFinished?.(true);
|
||||
} catch (e) {
|
||||
if (this.isTokenLogin) {
|
||||
// ignore any failures, we are relying on grace period here
|
||||
this.reset();
|
||||
|
||||
this.status = "complete";
|
||||
this.emit("update");
|
||||
this.onFinished?.(true);
|
||||
|
||||
return;
|
||||
}
|
||||
logger.error("Error bootstrapping cross-signing", e);
|
||||
this.status = "error";
|
||||
this.emit("update");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,6 +33,11 @@ export enum Phase {
|
|||
ConfirmReset = 6,
|
||||
}
|
||||
|
||||
/**
|
||||
* Logic for setting up 4S and/or verifying the user's device: a process requiring
|
||||
* ongoing interaction with the user, as distinct from InitialCryptoSetupStore which
|
||||
* a (usually) non-interactive process that happens immediately after registration.
|
||||
*/
|
||||
export class SetupEncryptionStore extends EventEmitter {
|
||||
private started?: boolean;
|
||||
public phase?: Phase;
|
||||
|
|
|
@ -7,31 +7,22 @@ Please see LICENSE files in the repository root for full details.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render, screen, waitFor } from "jest-matrix-react";
|
||||
import { mocked } from "jest-mock";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { render, screen } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import { createCrossSigning } from "../../../../../src/CreateCrossSigning";
|
||||
import { InitialCryptoSetupDialog } from "../../../../../src/components/views/dialogs/security/InitialCryptoSetupDialog";
|
||||
import { createTestClient } from "../../../../test-utils";
|
||||
|
||||
jest.mock("../../../../../src/CreateCrossSigning", () => ({
|
||||
createCrossSigning: jest.fn(),
|
||||
}));
|
||||
import { InitialCryptoSetupStore } from "../../../../../src/stores/InitialCryptoSetupStore";
|
||||
|
||||
describe("InitialCryptoSetupDialog", () => {
|
||||
let client: MatrixClient;
|
||||
let createCrossSigningResolve: () => void;
|
||||
let createCrossSigningReject: (e: Error) => void;
|
||||
const storeMock = {
|
||||
getStatus: jest.fn(),
|
||||
retry: jest.fn(),
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
client = createTestClient();
|
||||
mocked(createCrossSigning).mockImplementation(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
createCrossSigningResolve = resolve;
|
||||
createCrossSigningReject = reject;
|
||||
});
|
||||
});
|
||||
jest.spyOn(InitialCryptoSetupStore, "sharedInstance").mockReturnValue(storeMock as any);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -39,93 +30,32 @@ describe("InitialCryptoSetupDialog", () => {
|
|||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("should call createCrossSigning and show a spinner while it runs", async () => {
|
||||
it("should show a spinner while the setup is in progress", async () => {
|
||||
const onFinished = jest.fn();
|
||||
|
||||
render(
|
||||
<InitialCryptoSetupDialog
|
||||
matrixClient={client}
|
||||
accountPassword="hunter2"
|
||||
tokenLogin={false}
|
||||
onFinished={onFinished}
|
||||
/>,
|
||||
);
|
||||
storeMock.getStatus.mockReturnValue("in_progress");
|
||||
|
||||
render(<InitialCryptoSetupDialog onFinished={onFinished} />);
|
||||
|
||||
expect(createCrossSigning).toHaveBeenCalledWith(client, false, "hunter2");
|
||||
expect(screen.getByTestId("spinner")).toBeInTheDocument();
|
||||
|
||||
createCrossSigningResolve!();
|
||||
|
||||
await waitFor(() => expect(onFinished).toHaveBeenCalledWith(true));
|
||||
});
|
||||
|
||||
it("should display an error if createCrossSigning fails", async () => {
|
||||
render(
|
||||
<InitialCryptoSetupDialog
|
||||
matrixClient={client}
|
||||
accountPassword="hunter2"
|
||||
tokenLogin={false}
|
||||
onFinished={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
it("should display an error if setup has failed", async () => {
|
||||
storeMock.getStatus.mockReturnValue("error");
|
||||
|
||||
createCrossSigningReject!(new Error("generic error message"));
|
||||
render(<InitialCryptoSetupDialog onFinished={jest.fn()} />);
|
||||
|
||||
await expect(await screen.findByRole("button", { name: "Retry" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("ignores failures when tokenLogin is true", async () => {
|
||||
it("calls retry when retry button pressed", async () => {
|
||||
const onFinished = jest.fn();
|
||||
storeMock.getStatus.mockReturnValue("error");
|
||||
|
||||
render(
|
||||
<InitialCryptoSetupDialog
|
||||
matrixClient={client}
|
||||
accountPassword="hunter2"
|
||||
tokenLogin={true}
|
||||
onFinished={onFinished}
|
||||
/>,
|
||||
);
|
||||
render(<InitialCryptoSetupDialog onFinished={onFinished} />);
|
||||
|
||||
createCrossSigningReject!(new Error("generic error message"));
|
||||
await userEvent.click(await screen.findByRole("button", { name: "Retry" }));
|
||||
|
||||
await waitFor(() => expect(onFinished).toHaveBeenCalledWith(false));
|
||||
});
|
||||
|
||||
it("cancels the dialog when the cancel button is clicked", async () => {
|
||||
const onFinished = jest.fn();
|
||||
|
||||
render(
|
||||
<InitialCryptoSetupDialog
|
||||
matrixClient={client}
|
||||
accountPassword="hunter2"
|
||||
tokenLogin={false}
|
||||
onFinished={onFinished}
|
||||
/>,
|
||||
);
|
||||
|
||||
createCrossSigningReject!(new Error("generic error message"));
|
||||
|
||||
const cancelButton = await screen.findByRole("button", { name: "Cancel" });
|
||||
cancelButton.click();
|
||||
|
||||
expect(onFinished).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it("should retry when the retry button is clicked", async () => {
|
||||
render(
|
||||
<InitialCryptoSetupDialog
|
||||
matrixClient={client}
|
||||
accountPassword="hunter2"
|
||||
tokenLogin={false}
|
||||
onFinished={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
createCrossSigningReject!(new Error("generic error message"));
|
||||
|
||||
const retryButton = await screen.findByRole("button", { name: "Retry" });
|
||||
retryButton.click();
|
||||
|
||||
expect(createCrossSigning).toHaveBeenCalledTimes(2);
|
||||
expect(storeMock.retry).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -135,8 +135,9 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
|
|||
style="--mx-box-flex: 1;"
|
||||
>
|
||||
<a
|
||||
class="_link_1mzip_17"
|
||||
class="_link_ue21z_17"
|
||||
data-kind="primary"
|
||||
data-size="medium"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<p
|
||||
|
@ -752,8 +753,9 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
|
|||
style="--mx-box-flex: 1;"
|
||||
>
|
||||
<a
|
||||
class="_link_1mzip_17"
|
||||
class="_link_ue21z_17"
|
||||
data-kind="primary"
|
||||
data-size="medium"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<p
|
||||
|
@ -1406,8 +1408,9 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
|
|||
style="--mx-box-flex: 1;"
|
||||
>
|
||||
<a
|
||||
class="_link_1mzip_17"
|
||||
class="_link_ue21z_17"
|
||||
data-kind="primary"
|
||||
data-size="medium"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<p
|
||||
|
|
|
@ -10,6 +10,7 @@ import React, { ComponentProps } from "react";
|
|||
import { render, screen, waitFor } from "jest-matrix-react";
|
||||
import { RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import {
|
||||
determineAvatarPosition,
|
||||
|
@ -20,6 +21,9 @@ import * as languageHandler from "../../../../../src/languageHandler";
|
|||
import { stubClient } from "../../../../test-utils";
|
||||
import dispatcher from "../../../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import { formatDate } from "../../../../../src/DateUtils";
|
||||
|
||||
jest.mock("../../../../../src/DateUtils");
|
||||
|
||||
describe("ReadReceiptGroup", () => {
|
||||
describe("TooltipText", () => {
|
||||
|
@ -87,6 +91,10 @@ describe("ReadReceiptGroup", () => {
|
|||
describe("<ReadReceiptPerson />", () => {
|
||||
stubClient();
|
||||
|
||||
// We pick a fixed time but this can still vary depending on the locale
|
||||
// the tests are run in. We are not testing date formatting here, so stub it out.
|
||||
mocked(formatDate).mockReturnValue("==MOCK FORMATTED DATE==");
|
||||
|
||||
const ROOM_ID = "roomId";
|
||||
const USER_ID = "@alice:example.org";
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ exports[`ReadReceiptGroup <ReadReceiptPerson /> should render 1`] = `
|
|||
<p
|
||||
class="mx_ReadReceiptGroup_secondary"
|
||||
>
|
||||
Wed, 15 May, 0:00
|
||||
==MOCK FORMATTED DATE==
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -19,14 +19,14 @@ exports[`<LayoutSwitcher /> should render 1`] = `
|
|||
class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
|
||||
>
|
||||
<form
|
||||
class="_root_dgy0u_24 mx_LayoutSwitcher_LayoutSelector"
|
||||
class="_root_ssths_24 mx_LayoutSwitcher_LayoutSelector"
|
||||
>
|
||||
<div
|
||||
class="_field_dgy0u_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
>
|
||||
<label
|
||||
aria-label="Modern"
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:r0:"
|
||||
>
|
||||
<div
|
||||
|
@ -149,11 +149,11 @@ exports[`<LayoutSwitcher /> should render 1`] = `
|
|||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="_field_dgy0u_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
>
|
||||
<label
|
||||
aria-label="Message bubbles"
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:r9:"
|
||||
>
|
||||
<div
|
||||
|
@ -275,11 +275,11 @@ exports[`<LayoutSwitcher /> should render 1`] = `
|
|||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="_field_dgy0u_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
>
|
||||
<label
|
||||
aria-label="IRC (experimental)"
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:ri:"
|
||||
>
|
||||
<div
|
||||
|
@ -402,13 +402,13 @@ exports[`<LayoutSwitcher /> should render 1`] = `
|
|||
</div>
|
||||
</form>
|
||||
<form
|
||||
class="_root_dgy0u_24"
|
||||
class="_root_ssths_24"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40"
|
||||
class="_inline-field_ssths_40"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_qnvru_18"
|
||||
|
@ -427,16 +427,16 @@ exports[`<LayoutSwitcher /> should render 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:rr:"
|
||||
>
|
||||
Show compact text and messages
|
||||
</label>
|
||||
<span
|
||||
class="_message_dgy0u_98 _help-message_dgy0u_104"
|
||||
class="_message_ssths_93 _help-message_ssths_99"
|
||||
id="radix-:rs:"
|
||||
>
|
||||
Modern layout must be selected to use this feature.
|
||||
|
|
|
@ -19,13 +19,13 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
|||
class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
|
||||
>
|
||||
<form
|
||||
class="_root_dgy0u_24"
|
||||
class="_root_ssths_24"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40"
|
||||
class="_inline-field_ssths_40"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_qnvru_18"
|
||||
|
@ -43,10 +43,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:r28:"
|
||||
>
|
||||
Match system theme
|
||||
|
@ -55,13 +55,13 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
|||
</div>
|
||||
</form>
|
||||
<form
|
||||
class="_root_dgy0u_24 mx_ThemeChoicePanel_ThemeSelectors"
|
||||
class="_root_ssths_24 mx_ThemeChoicePanel_ThemeSelectors"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
|
@ -81,10 +81,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r29:"
|
||||
>
|
||||
Light
|
||||
|
@ -92,10 +92,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
|
@ -114,10 +114,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r2a:"
|
||||
>
|
||||
Dark
|
||||
|
@ -125,10 +125,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-light"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-light"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
|
@ -147,10 +147,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r2b:"
|
||||
>
|
||||
High contrast
|
||||
|
@ -158,10 +158,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
|
@ -180,10 +180,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r2c:"
|
||||
>
|
||||
Alice theme
|
||||
|
@ -195,13 +195,13 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
|||
class="mx_ThemeChoicePanel_CustomTheme"
|
||||
>
|
||||
<form
|
||||
class="_root_dgy0u_24 mx_ThemeChoicePanel_CustomTheme_EditInPlace"
|
||||
class="_root_ssths_24 mx_ThemeChoicePanel_CustomTheme_EditInPlace"
|
||||
>
|
||||
<div
|
||||
class="_field_dgy0u_34"
|
||||
class="_field_ssths_34"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:r2d:"
|
||||
>
|
||||
Add custom theme
|
||||
|
@ -219,7 +219,7 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
|||
/>
|
||||
</div>
|
||||
<span
|
||||
class="_message_dgy0u_98 _help-message_dgy0u_104"
|
||||
class="_message_ssths_93 _help-message_ssths_99"
|
||||
id="radix-:r2e:"
|
||||
>
|
||||
Enter the URL of a custom theme you want to apply.
|
||||
|
@ -296,13 +296,13 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
|||
class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
|
||||
>
|
||||
<form
|
||||
class="_root_dgy0u_24"
|
||||
class="_root_ssths_24"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40"
|
||||
class="_inline-field_ssths_40"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_qnvru_18"
|
||||
|
@ -320,10 +320,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:r10:"
|
||||
>
|
||||
Match system theme
|
||||
|
@ -332,13 +332,13 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
|||
</div>
|
||||
</form>
|
||||
<form
|
||||
class="_root_dgy0u_24 mx_ThemeChoicePanel_ThemeSelectors"
|
||||
class="_root_ssths_24 mx_ThemeChoicePanel_ThemeSelectors"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
|
@ -358,10 +358,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r11:"
|
||||
>
|
||||
Light
|
||||
|
@ -369,10 +369,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
|
@ -391,10 +391,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r12:"
|
||||
>
|
||||
Dark
|
||||
|
@ -402,10 +402,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-light"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-light"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
|
@ -424,10 +424,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r13:"
|
||||
>
|
||||
High contrast
|
||||
|
@ -435,10 +435,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
|
@ -457,10 +457,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r14:"
|
||||
>
|
||||
Alice theme
|
||||
|
@ -472,13 +472,13 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
|||
class="mx_ThemeChoicePanel_CustomTheme"
|
||||
>
|
||||
<form
|
||||
class="_root_dgy0u_24 mx_ThemeChoicePanel_CustomTheme_EditInPlace"
|
||||
class="_root_ssths_24 mx_ThemeChoicePanel_CustomTheme_EditInPlace"
|
||||
>
|
||||
<div
|
||||
class="_field_dgy0u_34"
|
||||
class="_field_ssths_34"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:r15:"
|
||||
>
|
||||
Add custom theme
|
||||
|
@ -496,7 +496,7 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
|||
/>
|
||||
</div>
|
||||
<span
|
||||
class="_message_dgy0u_98 _help-message_dgy0u_104"
|
||||
class="_message_ssths_93 _help-message_ssths_99"
|
||||
id="radix-:r16:"
|
||||
>
|
||||
Enter the URL of a custom theme you want to apply.
|
||||
|
@ -573,13 +573,13 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
|
|||
class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
|
||||
>
|
||||
<form
|
||||
class="_root_dgy0u_24"
|
||||
class="_root_ssths_24"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40"
|
||||
class="_inline-field_ssths_40"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_qnvru_18"
|
||||
|
@ -597,10 +597,10 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:r0:"
|
||||
>
|
||||
Match system theme
|
||||
|
@ -609,13 +609,13 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
|
|||
</div>
|
||||
</form>
|
||||
<form
|
||||
class="_root_dgy0u_24 mx_ThemeChoicePanel_ThemeSelectors"
|
||||
class="_root_ssths_24 mx_ThemeChoicePanel_ThemeSelectors"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
|
@ -635,10 +635,10 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r1:"
|
||||
>
|
||||
Light
|
||||
|
@ -646,10 +646,10 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
|
@ -668,10 +668,10 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r2:"
|
||||
>
|
||||
Dark
|
||||
|
@ -679,10 +679,10 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-light"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-light"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
|
@ -701,10 +701,10 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r3:"
|
||||
>
|
||||
High contrast
|
||||
|
|
|
@ -32,13 +32,13 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
|||
class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
|
||||
>
|
||||
<form
|
||||
class="_root_dgy0u_24 mx_ThemeChoicePanel_ThemeSelectors"
|
||||
class="_root_ssths_24 mx_ThemeChoicePanel_ThemeSelectors"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-light"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-light"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
|
@ -58,10 +58,10 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r0:"
|
||||
>
|
||||
Light
|
||||
|
@ -69,10 +69,10 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-dark"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-dark"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
|
@ -92,10 +92,10 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r1:"
|
||||
>
|
||||
Dark
|
||||
|
@ -103,10 +103,10 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-light"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-light"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
|
@ -126,10 +126,10 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r2:"
|
||||
>
|
||||
High contrast
|
||||
|
@ -162,14 +162,14 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
|||
class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
|
||||
>
|
||||
<form
|
||||
class="_root_dgy0u_24 mx_LayoutSwitcher_LayoutSelector"
|
||||
class="_root_ssths_24 mx_LayoutSwitcher_LayoutSelector"
|
||||
>
|
||||
<div
|
||||
class="_field_dgy0u_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
>
|
||||
<label
|
||||
aria-label="Modern"
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:r3:"
|
||||
>
|
||||
<div
|
||||
|
@ -292,11 +292,11 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
|||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="_field_dgy0u_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
>
|
||||
<label
|
||||
aria-label="Message bubbles"
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:rc:"
|
||||
>
|
||||
<div
|
||||
|
@ -418,11 +418,11 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
|||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="_field_dgy0u_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
>
|
||||
<label
|
||||
aria-label="IRC (experimental)"
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:rl:"
|
||||
>
|
||||
<div
|
||||
|
@ -545,13 +545,13 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
|||
</div>
|
||||
</form>
|
||||
<form
|
||||
class="_root_dgy0u_24"
|
||||
class="_root_ssths_24"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40"
|
||||
class="_inline-field_ssths_40"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_qnvru_18"
|
||||
|
@ -570,16 +570,16 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:ru:"
|
||||
>
|
||||
Show compact text and messages
|
||||
</label>
|
||||
<span
|
||||
class="_message_dgy0u_98 _help-message_dgy0u_104"
|
||||
class="_message_ssths_93 _help-message_ssths_99"
|
||||
id="radix-:rv:"
|
||||
>
|
||||
Modern layout must be selected to use this feature.
|
||||
|
|
|
@ -8,6 +8,7 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
|
|||
>
|
||||
<div
|
||||
class="mx_UserMenu"
|
||||
data-floating-ui-inert=""
|
||||
>
|
||||
<div
|
||||
aria-expanded="false"
|
||||
|
@ -42,6 +43,7 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
|
|||
<ul
|
||||
aria-label="Spaces"
|
||||
class="mx_AutoHideScrollbar mx_SpaceTreeLevel"
|
||||
data-floating-ui-inert=""
|
||||
data-rbd-droppable-context-id="0"
|
||||
data-rbd-droppable-id="top-level-spaces"
|
||||
role="tree"
|
||||
|
@ -236,6 +238,7 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
|
|||
aria-label="Threads"
|
||||
aria-labelledby=":r14:"
|
||||
class="_icon-button_bh2qc_17 mx_ThreadsActivityCentreButton"
|
||||
data-floating-ui-inert=""
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
tabindex="0"
|
||||
|
@ -260,6 +263,7 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
|
|||
</button>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
data-floating-ui-inert=""
|
||||
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||
tabindex="-1"
|
||||
/>
|
||||
|
@ -272,6 +276,7 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
|
|||
/>
|
||||
<span
|
||||
aria-owns=":r19:"
|
||||
data-floating-ui-inert=""
|
||||
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||
/>
|
||||
<span
|
||||
|
@ -286,6 +291,7 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
|
|||
aria-expanded="false"
|
||||
aria-label="Quick settings"
|
||||
class="mx_AccessibleButton mx_QuickSettingsButton"
|
||||
data-floating-ui-inert=""
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
|
|
|
@ -477,9 +477,7 @@ exports[`ThreadsActivityCentre should order the room with the same notification
|
|||
|
||||
exports[`ThreadsActivityCentre should render the release announcement 1`] = `
|
||||
<body>
|
||||
<div
|
||||
data-floating-ui-inert=""
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="mx_ThreadsActivityCentre_container"
|
||||
>
|
||||
|
@ -491,6 +489,7 @@ exports[`ThreadsActivityCentre should render the release announcement 1`] = `
|
|||
aria-label="Threads"
|
||||
aria-labelledby=":rc:"
|
||||
class="_icon-button_bh2qc_17 mx_ThreadsActivityCentreButton"
|
||||
data-floating-ui-inert=""
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
tabindex="0"
|
||||
|
@ -515,6 +514,7 @@ exports[`ThreadsActivityCentre should render the release announcement 1`] = `
|
|||
</button>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
data-floating-ui-inert=""
|
||||
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||
tabindex="-1"
|
||||
/>
|
||||
|
@ -527,6 +527,7 @@ exports[`ThreadsActivityCentre should render the release announcement 1`] = `
|
|||
/>
|
||||
<span
|
||||
aria-owns=":rh:"
|
||||
data-floating-ui-inert=""
|
||||
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||
/>
|
||||
<span
|
||||
|
@ -585,7 +586,6 @@ exports[`ThreadsActivityCentre should render the release announcement 1`] = `
|
|||
>
|
||||
<span
|
||||
data-floating-ui-focus-guard=""
|
||||
data-floating-ui-inert=""
|
||||
data-type="inside"
|
||||
role="button"
|
||||
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||
|
@ -648,7 +648,6 @@ exports[`ThreadsActivityCentre should render the release announcement 1`] = `
|
|||
</div>
|
||||
<span
|
||||
data-floating-ui-focus-guard=""
|
||||
data-floating-ui-inert=""
|
||||
data-type="inside"
|
||||
role="button"
|
||||
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||
|
|
85
test/unit-tests/stores/InitialCryptoSetupStore-test.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
Copyright 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 { mocked } from "jest-mock";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { waitFor } from "jest-matrix-react";
|
||||
|
||||
import { createCrossSigning } from "../../../src/CreateCrossSigning";
|
||||
import { InitialCryptoSetupStore } from "../../../src/stores/InitialCryptoSetupStore";
|
||||
import { SdkContextClass } from "../../../src/contexts/SDKContext";
|
||||
import { createTestClient } from "../../test-utils";
|
||||
import { AccountPasswordStore } from "../../../src/stores/AccountPasswordStore";
|
||||
|
||||
jest.mock("../../../src/CreateCrossSigning", () => ({
|
||||
createCrossSigning: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("InitialCryptoSetupStore", () => {
|
||||
let testStore: InitialCryptoSetupStore;
|
||||
let client: MatrixClient;
|
||||
let stores: SdkContextClass;
|
||||
|
||||
let createCrossSigningResolve: () => void;
|
||||
let createCrossSigningReject: (e: Error) => void;
|
||||
|
||||
beforeEach(() => {
|
||||
testStore = new InitialCryptoSetupStore();
|
||||
client = createTestClient();
|
||||
stores = {
|
||||
accountPasswordStore: {
|
||||
getPassword: jest.fn(),
|
||||
} as unknown as AccountPasswordStore,
|
||||
} as unknown as SdkContextClass;
|
||||
|
||||
mocked(createCrossSigning).mockImplementation(() => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
createCrossSigningResolve = resolve;
|
||||
createCrossSigningReject = reject;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should call createCrossSigning when startInitialCryptoSetup is called", async () => {
|
||||
testStore.startInitialCryptoSetup(client, false, stores, jest.fn());
|
||||
|
||||
await waitFor(() => expect(createCrossSigning).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it("emits an update event when createCrossSigning resolves", async () => {
|
||||
const updateSpy = jest.fn();
|
||||
testStore.on("update", updateSpy);
|
||||
|
||||
testStore.startInitialCryptoSetup(client, false, stores, jest.fn());
|
||||
createCrossSigningResolve();
|
||||
|
||||
await waitFor(() => expect(updateSpy).toHaveBeenCalled());
|
||||
expect(testStore.getStatus()).toBe("complete");
|
||||
});
|
||||
|
||||
it("emits an update event when createCrossSigning rejects", async () => {
|
||||
const updateSpy = jest.fn();
|
||||
testStore.on("update", updateSpy);
|
||||
|
||||
testStore.startInitialCryptoSetup(client, false, stores, jest.fn());
|
||||
createCrossSigningReject(new Error("Test error"));
|
||||
|
||||
await waitFor(() => expect(updateSpy).toHaveBeenCalled());
|
||||
expect(testStore.getStatus()).toBe("error");
|
||||
});
|
||||
|
||||
it("should ignore failures if tokenLogin is true", async () => {
|
||||
const updateSpy = jest.fn();
|
||||
testStore.on("update", updateSpy);
|
||||
|
||||
testStore.startInitialCryptoSetup(client, true, stores, jest.fn());
|
||||
createCrossSigningReject(new Error("Test error"));
|
||||
|
||||
await waitFor(() => expect(updateSpy).toHaveBeenCalled());
|
||||
expect(testStore.getStatus()).toBe("complete");
|
||||
});
|
||||
});
|
|
@ -178,22 +178,26 @@ describe("formatDate", () => {
|
|||
|
||||
it("should return time string if date is within same day", () => {
|
||||
const date = new Date(REPEATABLE_DATE.getTime() + 2 * HOUR_MS + 12 * MINUTE_MS);
|
||||
expect(formatDate(date, false, "en-GB")).toMatchInlineSnapshot(`"19:10"`);
|
||||
// We use en-US for these tests because there was a change in Node 22.12 which removed
|
||||
// the comma after the weekday for en-GB which makes the test output different things
|
||||
// on different node versions. I'm not sure what a better fix would be, so let's just use
|
||||
// a locale that happens to have a more stable formatting right now.
|
||||
expect(formatDate(date, false, "en-US")).toMatchInlineSnapshot(`"19:10"`);
|
||||
});
|
||||
|
||||
it("should return time string with weekday if date is within last 6 days", () => {
|
||||
const date = new Date(REPEATABLE_DATE.getTime() - 6 * DAY_MS + 2 * HOUR_MS + 12 * MINUTE_MS);
|
||||
expect(formatDate(date, false, "en-GB")).toMatchInlineSnapshot(`"Fri 19:10"`);
|
||||
expect(formatDate(date, false, "en-US")).toMatchInlineSnapshot(`"Fri 19:10"`);
|
||||
});
|
||||
|
||||
it("should return time & date string without year if it is within the same year", () => {
|
||||
const date = new Date(REPEATABLE_DATE.getTime() - 66 * DAY_MS + 2 * HOUR_MS + 12 * MINUTE_MS);
|
||||
expect(formatDate(date, false, "en-GB")).toMatchInlineSnapshot(`"Mon, 12 Sept, 19:10"`);
|
||||
expect(formatDate(date, false, "en-US")).toMatchInlineSnapshot(`"Mon, Sep 12, 19:10"`);
|
||||
});
|
||||
|
||||
it("should return full time & date string otherwise", () => {
|
||||
const date = new Date(REPEATABLE_DATE.getTime() - 666 * DAY_MS + 2 * HOUR_MS + 12 * MINUTE_MS);
|
||||
expect(formatDate(date, false, "en-GB")).toMatchInlineSnapshot(`"Wed, 20 Jan 2021, 19:10"`);
|
||||
expect(formatDate(date, false, "en-US")).toMatchInlineSnapshot(`"Wed, Jan 20, 2021, 19:10"`);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
96
yarn.lock
|
@ -1065,9 +1065,9 @@
|
|||
esutils "^2.0.2"
|
||||
|
||||
"@babel/preset-react@^7.12.10", "@babel/preset-react@^7.18.6":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.25.9.tgz#5f473035dc2094bcfdbc7392d0766bd42dce173e"
|
||||
integrity sha512-D3to0uSPiWE7rBrdIICCd0tJSIGpLaaGptna2+w7Pft5xMqLpA1sz99DK5TZ1TjGbdQ/VI1eCSZ06dv3lT4JOw==
|
||||
version "7.26.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.26.3.tgz#7c5e028d623b4683c1f83a0bd4713b9100560caa"
|
||||
integrity sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.25.9"
|
||||
"@babel/helper-validator-option" "^7.25.9"
|
||||
|
@ -1584,10 +1584,10 @@
|
|||
dependencies:
|
||||
"@floating-ui/dom" "^1.0.0"
|
||||
|
||||
"@floating-ui/react@^0.26.24":
|
||||
version "0.26.25"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.25.tgz#cf4c8a2b89fab1a71712d15e6551df3bfbd2ea1d"
|
||||
integrity sha512-hZOmgN0NTOzOuZxI1oIrDu3Gcl8WViIkvPMpB4xdd4QD6xAMtwgwr3VPoiyH/bLtRcS1cDnhxLSD1NsMJmwh/A==
|
||||
"@floating-ui/react@^0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.27.0.tgz#e0931fd09374ab4b8ce1a1af5cb44d1ccd1bb95a"
|
||||
integrity sha512-WLEksq7fJapXSJbmfiyq9pAW0a7ZFMEJToFE4oTDESxGjoa+nZu3YMjmZE2KvoUtQhqOK2yMMfWQFZyeWD0wGQ==
|
||||
dependencies:
|
||||
"@floating-ui/react-dom" "^2.1.2"
|
||||
"@floating-ui/utils" "^0.2.8"
|
||||
|
@ -1608,36 +1608,37 @@
|
|||
resolved "https://registry.yarnpkg.com/@fontsource/inter/-/inter-5.1.0.tgz#ab629b2c662457022d2d6a29854b8dc8ba538c47"
|
||||
integrity sha512-zKZR3kf1G0noIes1frLfOHP5EXVVm0M7sV/l9f/AaYf+M/DId35FO4LkigWjqWYjTJZGgplhdv4cB+ssvCqr5A==
|
||||
|
||||
"@formatjs/ecma402-abstract@2.2.4":
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.4.tgz#355e42d375678229d46dc8ad7a7139520dd03e7b"
|
||||
integrity sha512-lFyiQDVvSbQOpU+WFd//ILolGj4UgA/qXrKeZxdV14uKiAUiPAtX6XAn7WBCRi7Mx6I7EybM9E5yYn4BIpZWYg==
|
||||
"@formatjs/ecma402-abstract@2.3.1":
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.1.tgz#cdeb3ffe1aeea9c4284b85b7e37e8e8615314c39"
|
||||
integrity sha512-Ip9uV+/MpLXWRk03U/GzeJMuPeOXpJBSB5V1tjA6kJhvqssye5J5LoYLc7Z5IAHb7nR62sRoguzrFiVCP/hnzw==
|
||||
dependencies:
|
||||
"@formatjs/fast-memoize" "2.2.3"
|
||||
"@formatjs/intl-localematcher" "0.5.8"
|
||||
"@formatjs/fast-memoize" "2.2.5"
|
||||
"@formatjs/intl-localematcher" "0.5.9"
|
||||
decimal.js "10"
|
||||
tslib "2"
|
||||
|
||||
"@formatjs/fast-memoize@2.2.3":
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.3.tgz#74e64109279d5244f9fc281f3ae90c407cece823"
|
||||
integrity sha512-3jeJ+HyOfu8osl3GNSL4vVHUuWFXR03Iz9jjgI7RwjG6ysu/Ymdr0JRCPHfF5yGbTE6JCrd63EpvX1/WybYRbA==
|
||||
"@formatjs/fast-memoize@2.2.5":
|
||||
version "2.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.5.tgz#54a4a1793d773b72c372d3dcab3595149aee7880"
|
||||
integrity sha512-6PoewUMrrcqxSoBXAOJDiW1m+AmkrAj0RiXnOMD59GRaswjXhm3MDhgepXPBgonc09oSirAJTsAggzAGQf6A6g==
|
||||
dependencies:
|
||||
tslib "2"
|
||||
|
||||
"@formatjs/intl-localematcher@0.5.8":
|
||||
version "0.5.8"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.8.tgz#b11bbd04bd3551f7cadcb1ef1e231822d0e3c97e"
|
||||
integrity sha512-I+WDNWWJFZie+jkfkiK5Mp4hEDyRSEvmyfYadflOno/mmKJKcB17fEpEH0oJu/OWhhCJ8kJBDz2YMd/6cDl7Mg==
|
||||
"@formatjs/intl-localematcher@0.5.9":
|
||||
version "0.5.9"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.9.tgz#43c6ee22be85b83340bcb09bdfed53657a2720db"
|
||||
integrity sha512-8zkGu/sv5euxbjfZ/xmklqLyDGQSxsLqg8XOq88JW3cmJtzhCP8EtSJXlaKZnVO4beEaoiT9wj4eIoCQ9smwxA==
|
||||
dependencies:
|
||||
tslib "2"
|
||||
|
||||
"@formatjs/intl-segmenter@^11.5.7":
|
||||
version "11.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/intl-segmenter/-/intl-segmenter-11.7.4.tgz#f99d87ee3f98515069285438a4913681fc243252"
|
||||
integrity sha512-pyHgFO86/CReKl20oK9jgaTMzSaG/nIMteMW8YuwUcS22EoMI1qbGTZ65oQ38KMT05SiHiMee2CP3WZvCi8YSQ==
|
||||
version "11.7.7"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/intl-segmenter/-/intl-segmenter-11.7.7.tgz#8a5aaa316e11ca2d31b99222e6fcf1ab539b085e"
|
||||
integrity sha512-610J5xz5DxtEpa16zNR89CrvA9qWHxQFkUB3FKiGao0Nwn7i8cl+oyBhuH9SvtXF9j2LUOM9VMdVCMzJkVANNw==
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract" "2.2.4"
|
||||
"@formatjs/intl-localematcher" "0.5.8"
|
||||
"@formatjs/ecma402-abstract" "2.3.1"
|
||||
"@formatjs/intl-localematcher" "0.5.9"
|
||||
tslib "2"
|
||||
|
||||
"@humanwhocodes/config-array@^0.13.0":
|
||||
|
@ -1880,9 +1881,9 @@
|
|||
chalk "^4.0.0"
|
||||
|
||||
"@jridgewell/gen-mapping@^0.3.5":
|
||||
version "0.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36"
|
||||
integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==
|
||||
version "0.3.8"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142"
|
||||
integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==
|
||||
dependencies:
|
||||
"@jridgewell/set-array" "^1.2.1"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
|
@ -3047,9 +3048,9 @@
|
|||
"@types/node" "*"
|
||||
|
||||
"@types/jsrsasign@^10.5.4":
|
||||
version "10.5.14"
|
||||
resolved "https://registry.yarnpkg.com/@types/jsrsasign/-/jsrsasign-10.5.14.tgz#61d1dbd791ecd11db556c1ca5d82453fc7207338"
|
||||
integrity sha512-lppSlfK6etu+cuKs40K4rg8As79PH6hzIB+v55zSqImbSH3SE6Fm8MBHCiI91cWlAP3Z4igtJK1VL3fSN09blQ==
|
||||
version "10.5.15"
|
||||
resolved "https://registry.yarnpkg.com/@types/jsrsasign/-/jsrsasign-10.5.15.tgz#5cf1ee506b2fa2435b6e1786a873285c7110eb82"
|
||||
integrity sha512-3stUTaSRtN09PPzVWR6aySD9gNnuymz+WviNHoTb85dKu+BjaV4uBbWWGykBBJkfwPtcNZVfTn2lbX00U+yhpQ==
|
||||
|
||||
"@types/katex@^0.16.0":
|
||||
version "0.16.7"
|
||||
|
@ -3113,9 +3114,9 @@
|
|||
undici-types "~6.20.0"
|
||||
|
||||
"@types/node@18":
|
||||
version "18.19.66"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.66.tgz#0937a47904ceba5994eedf5cf4b6d503d8d6136c"
|
||||
integrity sha512-14HmtUdGxFUalGRfLLn9Gc1oNWvWh5zNbsyOLo5JV6WARSeN1QcEBKRnZm9QqNfrutgsl/hY4eJW63aZ44aBCg==
|
||||
version "18.19.68"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.68.tgz#f4f10d9927a7eaf3568c46a6d739cc0967ccb701"
|
||||
integrity sha512-QGtpFH1vB99ZmTa63K4/FU8twThj4fuVSBkGddTp7uIL/cuoLWIUSL2RcOaigBhfR+hg5pgGkBnkoOxrTVBMKw==
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
|
@ -3400,12 +3401,12 @@
|
|||
resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-2.1.1.tgz#d6175a99fe4b97688464126f255386990f3048d6"
|
||||
integrity sha512-QnUi2K14D9KTXxcLQKUU3V75cforZLMwhaaJDNftT8F5mG86950hAM+qhgDNEpEU+pkTffQj0/g/5859YmqWzQ==
|
||||
|
||||
"@vector-im/compound-web@^7.4.0":
|
||||
version "7.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-7.4.0.tgz#a5af8af6346f8ff6c14c70f5d4eb2eab7357a7cc"
|
||||
integrity sha512-ZRBUeEGNmj/fTkIRa8zGnyVN7ytowpfOtHChqNm+m/+OTJN3o/lOMuQHDV8jeSEW2YwPJqGvPuG/dRr89IcQkA==
|
||||
"@vector-im/compound-web@^7.5.0":
|
||||
version "7.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-7.5.0.tgz#1547af5f0ee27b94f79ab11eee006059f3d09707"
|
||||
integrity sha512-Xhef8H5WrRmPuanzRBs8rnl+hwbcQnC7nKSCupUczAQ5hjlieBx4vcQYQ/nMkrs4rMGjgfFtR3E18wT5LlML/A==
|
||||
dependencies:
|
||||
"@floating-ui/react" "^0.26.24"
|
||||
"@floating-ui/react" "^0.27.0"
|
||||
"@radix-ui/react-context-menu" "^2.2.1"
|
||||
"@radix-ui/react-dropdown-menu" "^2.1.1"
|
||||
"@radix-ui/react-form" "^0.1.0"
|
||||
|
@ -5027,7 +5028,7 @@ decamelize@^1.2.0:
|
|||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
|
||||
|
||||
decimal.js@^10.4.2:
|
||||
decimal.js@10, decimal.js@^10.4.2:
|
||||
version "10.4.3"
|
||||
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
|
||||
integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==
|
||||
|
@ -7828,16 +7829,21 @@ jsdom@^20.0.0:
|
|||
ws "^8.11.0"
|
||||
xml-name-validator "^4.0.0"
|
||||
|
||||
jsesc@^3.0.2, jsesc@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e"
|
||||
integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==
|
||||
jsesc@^3.0.2:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d"
|
||||
integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==
|
||||
|
||||
jsesc@~0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
|
||||
integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==
|
||||
|
||||
jsesc@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e"
|
||||
integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==
|
||||
|
||||
json-buffer@3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
|
||||
|
|