Prompt users to set up recovery (#30075)

* Show indicator in settings dialog when user doesn't have recovery set up

* Update settings headers to use red dot for recommended settings

* update recovery setup toast and remember if the user dismisses it

* update playwright snapshots

* use typed event emitters

* reverse logic for the account data flag

* fix comment and type
This commit is contained in:
Hubert Chathi
2025-06-18 12:20:17 -04:00
committed by GitHub
parent 2034f8b6bb
commit af984c0e80
26 changed files with 269 additions and 33 deletions

View File

@@ -7,9 +7,9 @@ Please see LICENSE files in the repository root for full details.
*/
import React, { type ReactElement } from "react";
import { render, screen } from "jest-matrix-react";
import { render, screen, waitFor } from "jest-matrix-react";
import { mocked, type MockedObject } from "jest-mock";
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { ClientEvent, MatrixEvent, type MatrixClient } from "matrix-js-sdk/src/matrix";
import SettingsStore, { type CallbackFn } from "../../../../../src/settings/SettingsStore";
import SdkConfig from "../../../../../src/SdkConfig";
@@ -250,4 +250,28 @@ describe("<UserSettingsDialog />", () => {
// unwatches settings on unmount
expect(mockSettingsStore.unwatchSetting).toHaveBeenCalledWith("mock-watcher-id-feature_mjolnir");
});
it("displays an indicator when user needs to set up recovery", async () => {
// Initially, the user doesn't have secret storage, so it should display
// an indicator.
mockClient.secretStorage.getDefaultKeyId.mockResolvedValue(null);
const { container } = render(getComponent());
await waitFor(() => {
expect(container.querySelector(".mx_SettingsDialog_tabLabelsAlert")).toBeInTheDocument();
});
// Test that the handler ignores unknown account data
mockClient.emit(ClientEvent.AccountData, new MatrixEvent({ type: "bar" }));
// The user now has secret storage. Trigger an update and check that
// the indicator disappears.
mockClient.secretStorage.getDefaultKeyId.mockResolvedValue("foo");
mockClient.emit(ClientEvent.AccountData, new MatrixEvent({ type: "m.secret_storage.default_key" }));
await waitFor(() => {
expect(container.querySelector(".mx_SettingsDialog_tabLabelsAlert")).not.toBeInTheDocument();
});
});
});

View File

@@ -5,7 +5,7 @@ exports[`<SettingsHeader /> should render the component 1`] = `
<h2
class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93 mx_SettingsHeader"
>
Settings Header
Settings Header
</h2>
</DocumentFragment>
`;
@@ -13,12 +13,9 @@ exports[`<SettingsHeader /> should render the component 1`] = `
exports[`<SettingsHeader /> should render the component with the recommended tag 1`] = `
<DocumentFragment>
<h2
class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93 mx_SettingsHeader"
class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93 mx_SettingsHeader mx_SettingsHeader_recommended"
>
Settings Header
<span>
Recommended
</span>
Settings Header
</h2>
</DocumentFragment>
`;

View File

@@ -104,12 +104,14 @@ describe("<ChangeRecoveryKey />", () => {
expect(screen.getByText("The recovery key you entered is not correct.")).toBeInTheDocument();
expect(asFragment()).toMatchSnapshot();
const setAccountDataSpy = jest.spyOn(matrixClient, "setAccountData");
await userEvent.clear(input);
// If the user enters the correct recovery key, the finish button should be enabled
await userEvent.type(input, "encoded private key");
await waitFor(() => expect(finishButton).not.toHaveAttribute("aria-disabled", "true"));
await user.click(finishButton);
expect(setAccountDataSpy).toHaveBeenCalledWith("io.element.recovery", { enabled: true });
expect(onFinish).toHaveBeenCalledWith();
});

View File

@@ -12,7 +12,7 @@ exports[`<RecoveryPanel /> should allow to change the recovery key when everythi
<h2
class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93 mx_SettingsHeader"
>
Recovery
Recovery
</h2>
Recover your cryptographic identity and message history with a recovery key if youve lost all your existing devices.
</div>
@@ -51,12 +51,9 @@ exports[`<RecoveryPanel /> should ask to set up a recovery key when there is no
class="mx_SettingsSection_header"
>
<h2
class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93 mx_SettingsHeader"
class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93 mx_SettingsHeader mx_SettingsHeader_recommended"
>
Recovery
<span>
Recommended
</span>
Recovery
</h2>
Recover your cryptographic identity and message history with a recovery key if youve lost all your existing devices.
</div>
@@ -97,7 +94,7 @@ exports[`<RecoveryPanel /> should be in loading state when checking the recovery
<h2
class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93 mx_SettingsHeader"
>
Recovery
Recovery
</h2>
Recover your cryptographic identity and message history with a recovery key if youve lost all your existing devices.
</div>

View File

@@ -12,7 +12,7 @@ exports[`<RecoveyPanelOutOfSync /> should render 1`] = `
<h2
class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93 mx_SettingsHeader"
>
Recovery
Recovery
</h2>
<div
class="mx_SettingsSubheader"

View File

@@ -18,7 +18,7 @@ exports[`<EncryptionUserSettingsTab /> should display a verify button when the e
<h2
class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93 mx_SettingsHeader"
>
Device not verified
Device not verified
</h2>
<div
class="mx_SettingsSubheader"
@@ -100,7 +100,7 @@ exports[`<EncryptionUserSettingsTab /> should display the recovery out of sync p
<h2
class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93 mx_SettingsHeader"
>
Recovery
Recovery
</h2>
<div
class="mx_SettingsSubheader"