Wire up the "Forgot recovery key" button for the "Key storage out of sync" toast (#29138)

* Wire up the "Forgot recovery key" button for the "Key storage out of sync" toast

* Unused import & fix test

* Test 'forgot' variant

* Fix dependencies

* Add more toast tests

* Unused import

* Test initialState in Encryption Tab

* Let's see if github has any more luck running this test than me

* Working playwright test with screenshot

* year

* Convert playwright test to use the bot client

* Disambiguate

Co-authored-by: Florian Duros <florianduros@element.io>

* Add doc & do other part of rename

* Split out into custom hook

* Fix tests

---------

Co-authored-by: Florian Duros <florianduros@element.io>
This commit is contained in:
David Baker
2025-02-04 17:40:31 +00:00
committed by GitHub
parent 1c4e35606c
commit 9657d39cd6
13 changed files with 389 additions and 35 deletions

View File

@@ -57,9 +57,9 @@ describe("<UserSettingsDialog />", () => {
let sdkContext: SdkContextClass;
const defaultProps = { onFinished: jest.fn() };
const getComponent = (props: Partial<typeof defaultProps & { initialTabId?: UserTab }> = {}): ReactElement => (
<UserSettingsDialog sdkContext={sdkContext} {...defaultProps} {...props} />
);
const getComponent = (
props: Partial<typeof defaultProps & { initialTabId?: UserTab; props: Record<string, any> }> = {},
): ReactElement => <UserSettingsDialog sdkContext={sdkContext} {...defaultProps} {...props} />;
beforeEach(() => {
jest.clearAllMocks();

View File

@@ -25,7 +25,7 @@ describe("<ResetIdentityPanel />", () => {
const onFinish = jest.fn();
const { asFragment } = render(
<ResetIdentityPanel onFinish={onFinish} onCancelClick={jest.fn()} />,
<ResetIdentityPanel variant="compromised" onFinish={onFinish} onCancelClick={jest.fn()} />,
withClientContextRenderOptions(matrixClient),
);
expect(asFragment()).toMatchSnapshot();
@@ -34,4 +34,13 @@ describe("<ResetIdentityPanel />", () => {
expect(matrixClient.getCrypto()!.resetEncryption).toHaveBeenCalled();
expect(onFinish).toHaveBeenCalled();
});
it("should display the 'forgot recovery key' variant correctly", async () => {
const onFinish = jest.fn();
const { asFragment } = render(
<ResetIdentityPanel variant="forgot" onFinish={onFinish} onCancelClick={jest.fn()} />,
withClientContextRenderOptions(matrixClient),
);
expect(asFragment()).toMatchSnapshot();
});
});

View File

@@ -1,5 +1,185 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<ResetIdentityPanel /> should display the 'forgot recovery key' variant correctly 1`] = `
<DocumentFragment>
<nav
class="_breadcrumb_ikpbb_17"
>
<button
aria-label="Back"
class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
role="button"
style="--cpd-icon-button-size: 28px;"
tabindex="0"
>
<div
class="_indicator-icon_133tf_26"
style="--cpd-icon-button-size: 100%;"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m13.3 17.3-4.6-4.6a.877.877 0 0 1-.213-.325A1.106 1.106 0 0 1 8.425 12c0-.133.02-.258.062-.375A.878.878 0 0 1 8.7 11.3l4.6-4.6a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7L10.8 12l3.9 3.9a.949.949 0 0 1 .275.7.948.948 0 0 1-.275.7.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
/>
</svg>
</div>
</button>
<ol
class="_pages_ikpbb_26"
>
<li>
<a
class="_link_ue21z_17"
data-kind="primary"
data-size="small"
rel="noreferrer noopener"
role="button"
tabindex="0"
>
Encryption
</a>
</li>
<li>
<span
aria-current="page"
class="_last-page_ikpbb_39"
>
Reset encryption
</span>
</li>
</ol>
</nav>
<div
class="mx_EncryptionCard mx_ResetIdentityPanel"
>
<div
class="mx_EncryptionCard_header"
>
<div
class="_content_md016_17 _destructive_md016_43"
data-size="large"
>
<svg
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>
</div>
<h2
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Forgot your recovery key? Youll need to reset your identity.
</h2>
</div>
<div
class="mx_ResetIdentityPanel_content"
>
<ul
class="_visual-list_4dcf8_17"
>
<li
class="_visual-list-item_bqeu7_17"
>
<svg
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26 _visual-list-item-icon-success_bqeu7_31"
fill="currentColor"
height="24px"
viewBox="0 0 24 24"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
/>
</svg>
Your account details, contacts, preferences, and chat list will be kept
</li>
<li
class="_visual-list-item_bqeu7_17"
>
<svg
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26"
fill="currentColor"
height="24px"
viewBox="0 0 24 24"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.287 7.287A.968.968 0 0 1 12 7c.283 0 .52.096.713.287.191.192.287.43.287.713s-.096.52-.287.713A.968.968 0 0 1 12 9a.968.968 0 0 1-.713-.287A.967.967 0 0 1 11 8c0-.283.096-.52.287-.713Zm0 4A.968.968 0 0 1 12 11c.283 0 .52.096.713.287.191.192.287.43.287.713v4a.97.97 0 0 1-.287.712A.968.968 0 0 1 12 17a.968.968 0 0 1-.713-.288A.968.968 0 0 1 11 16v-4c0-.283.096-.52.287-.713Z"
/>
<path
clip-rule="evenodd"
d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10Zm-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0Z"
fill-rule="evenodd"
/>
</svg>
You will lose any message history thats stored only on the server
</li>
<li
class="_visual-list-item_bqeu7_17"
>
<svg
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26"
fill="currentColor"
height="24px"
viewBox="0 0 24 24"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.287 7.287A.968.968 0 0 1 12 7c.283 0 .52.096.713.287.191.192.287.43.287.713s-.096.52-.287.713A.968.968 0 0 1 12 9a.968.968 0 0 1-.713-.287A.967.967 0 0 1 11 8c0-.283.096-.52.287-.713Zm0 4A.968.968 0 0 1 12 11c.283 0 .52.096.713.287.191.192.287.43.287.713v4a.97.97 0 0 1-.287.712A.968.968 0 0 1 12 17a.968.968 0 0 1-.713-.288A.968.968 0 0 1 11 16v-4c0-.283.096-.52.287-.713Z"
/>
<path
clip-rule="evenodd"
d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10Zm-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0Z"
fill-rule="evenodd"
/>
</svg>
You will need to verify all your existing devices and contacts again
</li>
</ul>
</div>
<div
class="mx_ResetIdentityPanel_footer"
>
<button
class="_button_i91xf_17 _destructive_i91xf_116"
data-kind="primary"
data-size="lg"
role="button"
tabindex="0"
>
Continue
</button>
<button
class="_button_i91xf_17"
data-kind="tertiary"
data-size="lg"
role="button"
tabindex="0"
>
Cancel
</button>
</div>
</div>
</DocumentFragment>
`;
exports[`<ResetIdentityPanel /> should reset the encryption when the continue button is clicked 1`] = `
<DocumentFragment>
<nav

View File

@@ -12,7 +12,10 @@ import { waitFor } from "@testing-library/dom";
import userEvent from "@testing-library/user-event";
import { mocked } from "jest-mock";
import { EncryptionUserSettingsTab } from "../../../../../../../src/components/views/settings/tabs/user/EncryptionUserSettingsTab";
import {
EncryptionUserSettingsTab,
State,
} from "../../../../../../../src/components/views/settings/tabs/user/EncryptionUserSettingsTab";
import { createTestClient, withClientContextRenderOptions } from "../../../../../../test-utils";
import Modal from "../../../../../../../src/Modal";
import { accessSecretStorage } from "../../../../../../../src/SecurityManager";
@@ -43,8 +46,8 @@ describe("<EncryptionUserSettingsTab />", () => {
mocked(accessSecretStorage).mockClear().mockResolvedValue();
});
function renderComponent() {
return render(<EncryptionUserSettingsTab />, withClientContextRenderOptions(matrixClient));
function renderComponent(props: { initialState?: State } = {}) {
return render(<EncryptionUserSettingsTab {...props} />, withClientContextRenderOptions(matrixClient));
}
it("should display a loading state when the encryption state is computed", () => {
@@ -139,4 +142,12 @@ describe("<EncryptionUserSettingsTab />", () => {
);
expect(asFragment()).toMatchSnapshot();
});
it("should enter reset flow when showResetIdentity is set", () => {
renderComponent({ initialState: "reset_identity_forgot" });
expect(
screen.getByRole("heading", { name: "Forgot your recovery key? Youll need to reset your identity." }),
).toBeVisible();
});
});

View File

@@ -7,9 +7,18 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import { render, screen } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import ToastContainer from "../../../src/components/structures/ToastContainer";
import { Kind, showToast } from "../../../src/toasts/SetupEncryptionToast";
import dis from "../../../src/dispatcher/dispatcher";
import DeviceListener from "../../../src/DeviceListener";
jest.mock("../../../src/dispatcher/dispatcher", () => ({
dispatch: jest.fn(),
register: jest.fn(),
unregister: jest.fn(),
}));
describe("SetupEncryptionToast", () => {
beforeEach(() => {
@@ -19,7 +28,18 @@ describe("SetupEncryptionToast", () => {
it("should render the 'set up recovery' toast", async () => {
showToast(Kind.SET_UP_RECOVERY);
await expect(screen.findByText("Set up recovery")).resolves.toBeInTheDocument();
await expect(await screen.findByRole("heading", { name: "Set up recovery" })).toBeInTheDocument();
});
it("should dismiss toast when 'not now' button clicked", async () => {
jest.spyOn(DeviceListener.sharedInstance(), "dismissEncryptionSetup");
showToast(Kind.SET_UP_RECOVERY);
const user = userEvent.setup();
await user.click(await screen.findByRole("button", { name: "Not now" }));
expect(DeviceListener.sharedInstance().dismissEncryptionSetup).toHaveBeenCalled();
});
it("should render the 'key storage out of sync' toast", async () => {
@@ -27,4 +47,17 @@ describe("SetupEncryptionToast", () => {
await expect(screen.findByText("Your key storage is out of sync.")).resolves.toBeInTheDocument();
});
it("should open settings to the reset flow when 'forgot recovery key' clicked", async () => {
showToast(Kind.KEY_STORAGE_OUT_OF_SYNC);
const user = userEvent.setup();
await user.click(await screen.findByText("Forgot recovery key?"));
expect(dis.dispatch).toHaveBeenCalledWith({
action: "view_user_settings",
initialTabId: "USER_ENCRYPTION_TAB",
props: { showResetIdentity: true },
});
});
});