diff --git a/res/css/views/settings/_CryptographyPanel.pcss b/res/css/views/settings/_CryptographyPanel.pcss
index 855949d013d..3440ce65543 100644
--- a/res/css/views/settings/_CryptographyPanel.pcss
+++ b/res/css/views/settings/_CryptographyPanel.pcss
@@ -32,13 +32,9 @@ limitations under the License.
}
}
-.mx_CryptographyPanel_importExportButtons .mx_AccessibleButton {
- margin-right: 10px;
-}
-
.mx_CryptographyPanel_importExportButtons {
- margin-bottom: 15px;
display: inline-flex;
flex-flow: wrap;
- row-gap: 10px;
+ row-gap: $spacing-8;
+ column-gap: $spacing-8;
}
diff --git a/res/css/views/settings/tabs/_SettingsTab.pcss b/res/css/views/settings/tabs/_SettingsTab.pcss
index fac189e8587..b060a025418 100644
--- a/res/css/views/settings/tabs/_SettingsTab.pcss
+++ b/res/css/views/settings/tabs/_SettingsTab.pcss
@@ -15,7 +15,6 @@ limitations under the License.
*/
.mx_SettingsTab {
- --SettingsTab_section-margin-bottom-preferences-labs: 30px;
--SettingsTab_heading_nth_child-margin-top: 30px; /* TODO: Use a spacing variable */
--SettingsTab_fullWidthField-margin-inline-end: 100px;
--SettingsTab_tooltip-max-width: 120px; /* So it fits in the space provided by the page */
diff --git a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.pcss b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.pcss
index 81777dc2a68..ad5b3f8a111 100644
--- a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.pcss
+++ b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.pcss
@@ -14,43 +14,36 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-.mx_SecurityUserSettingsTab_bulkOptions .mx_AccessibleButton {
- margin-right: 10px;
+.mx_SecurityUserSettingsTab_bulkOptions {
+ display: flex;
+ flex-direction: row;
+ column-gap: $spacing-8;
}
.mx_SecurityUserSettingsTab_ignoredUser {
- margin-bottom: 5px;
+ margin-bottom: $spacing-4;
}
.mx_SecurityUserSettingsTab_ignoredUser .mx_AccessibleButton {
- margin-right: 10px;
+ margin-right: $spacing-8;
}
-.mx_SecurityUserSettingsTab {
- .mx_SettingsTab_section {
- .mx_AccessibleButton_kind_link {
- font-size: inherit;
- }
- }
-
- .mx_SecurityUserSettingsTab_warning {
- color: $alert;
- position: relative;
- padding-left: 40px;
- margin-top: 30px;
-
- &::before {
- mask-repeat: no-repeat;
- mask-position: 0 center;
- mask-size: $font-24px;
- position: absolute;
- width: $font-24px;
- height: $font-24px;
- content: "";
- top: 0;
- left: 0;
- background-color: $alert;
- mask-image: url("$(res)/img/feather-customised/alert-triangle.svg");
- }
+.mx_SecurityUserSettingsTab_warning {
+ color: $alert;
+ position: relative;
+ padding-left: 40px;
+
+ &::before {
+ mask-repeat: no-repeat;
+ mask-position: 0 center;
+ mask-size: $font-24px;
+ position: absolute;
+ width: $font-24px;
+ height: $font-24px;
+ content: "";
+ top: 0;
+ left: 0;
+ background-color: $alert;
+ mask-image: url("$(res)/img/feather-customised/alert-triangle.svg");
}
}
diff --git a/src/components/views/settings/CrossSigningPanel.tsx b/src/components/views/settings/CrossSigningPanel.tsx
index d3926d954fa..68ec515b542 100644
--- a/src/components/views/settings/CrossSigningPanel.tsx
+++ b/src/components/views/settings/CrossSigningPanel.tsx
@@ -28,6 +28,7 @@ import ConfirmDestroyCrossSigningDialog from "../dialogs/security/ConfirmDestroy
import SetupEncryptionDialog from "../dialogs/security/SetupEncryptionDialog";
import { accessSecretStorage } from "../../../SecurityManager";
import AccessibleButton from "../elements/AccessibleButton";
+import { SettingsSubsectionText } from "./shared/SettingsSubsection";
interface IState {
error?: Error;
@@ -178,22 +179,38 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> {
if (homeserverSupportsCrossSigning === undefined) {
summarisedStatus = ;
} else if (!homeserverSupportsCrossSigning) {
- summarisedStatus =
{_t("Your homeserver does not support cross-signing.")}
;
+ summarisedStatus = (
+
+ {_t("Your homeserver does not support cross-signing.")}
+
+ );
} else if (crossSigningReady && crossSigningPrivateKeysInStorage) {
- summarisedStatus = ✅ {_t("Cross-signing is ready for use.")}
;
+ summarisedStatus = (
+
+ ✅ {_t("Cross-signing is ready for use.")}
+
+ );
} else if (crossSigningReady && !crossSigningPrivateKeysInStorage) {
- summarisedStatus = ⚠️ {_t("Cross-signing is ready but keys are not backed up.")}
;
+ summarisedStatus = (
+
+ ⚠️ {_t("Cross-signing is ready but keys are not backed up.")}
+
+ );
} else if (crossSigningPrivateKeysInStorage) {
summarisedStatus = (
-
+
{_t(
"Your account has a cross-signing identity in secret storage, " +
"but it is not yet trusted by this session.",
)}
-
+
);
} else {
- summarisedStatus = {_t("Cross-signing is not set up.")}
;
+ summarisedStatus = (
+
+ {_t("Cross-signing is not set up.")}
+
+ );
}
const keysExistAnywhere =
@@ -238,7 +255,7 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> {
}
return (
-
+ <>
{summarisedStatus}
{_t("Advanced")}
@@ -275,7 +292,7 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> {
{errorSection}
{actionRow}
-
+ >
);
}
}
diff --git a/src/components/views/settings/CryptographyPanel.tsx b/src/components/views/settings/CryptographyPanel.tsx
index 79ddad2544e..d947946b755 100644
--- a/src/components/views/settings/CryptographyPanel.tsx
+++ b/src/components/views/settings/CryptographyPanel.tsx
@@ -26,6 +26,7 @@ import * as FormattingUtils from "../../../utils/FormattingUtils";
import SettingsStore from "../../../settings/SettingsStore";
import SettingsFlag from "../elements/SettingsFlag";
import { SettingLevel } from "../../../settings/SettingLevel";
+import SettingsSubsection, { SettingsSubsectionText } from "./shared/SettingsSubsection";
interface IProps {}
@@ -72,27 +73,28 @@ export default class CryptographyPanel extends React.Component {
}
return (
-
-
{_t("Cryptography")}
-
-
- {_t("Session ID:")} |
-
- {deviceId}
- |
-
-
- {_t("Session key:")} |
-
-
- {identityKey}
-
- |
-
-
+
+
+
+
+ {_t("Session ID:")} |
+
+ {deviceId}
+ |
+
+
+ {_t("Session key:")} |
+
+
+ {identityKey}
+
+ |
+
+
+
{importExportButtons}
{noSendUnverifiedSetting}
-
+
);
}
diff --git a/src/components/views/settings/E2eAdvancedPanel.tsx b/src/components/views/settings/E2eAdvancedPanel.tsx
index 2b1d2645046..a2e45642942 100644
--- a/src/components/views/settings/E2eAdvancedPanel.tsx
+++ b/src/components/views/settings/E2eAdvancedPanel.tsx
@@ -20,21 +20,20 @@ import { _t } from "../../../languageHandler";
import { SettingLevel } from "../../../settings/SettingLevel";
import SettingsStore from "../../../settings/SettingsStore";
import SettingsFlag from "../elements/SettingsFlag";
+import SettingsSubsection, { SettingsSubsectionText } from "./shared/SettingsSubsection";
const SETTING_MANUALLY_VERIFY_ALL_SESSIONS = "e2ee.manuallyVerifyAllSessions";
const E2eAdvancedPanel: React.FC = () => {
return (
-
-
{_t("Encryption")}
-
+
-
+
{_t(
"Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.",
)}
-
-
+
+
);
};
diff --git a/src/components/views/settings/EventIndexPanel.tsx b/src/components/views/settings/EventIndexPanel.tsx
index f0b3c54c99d..eb33f6707df 100644
--- a/src/components/views/settings/EventIndexPanel.tsx
+++ b/src/components/views/settings/EventIndexPanel.tsx
@@ -27,6 +27,7 @@ import { SettingLevel } from "../../../settings/SettingLevel";
import SeshatResetDialog from "../dialogs/SeshatResetDialog";
import InlineSpinner from "../elements/InlineSpinner";
import ExternalLink from "../elements/ExternalLink";
+import { SettingsSubsectionText } from "./shared/SettingsSubsection";
interface IState {
enabling: boolean;
@@ -145,8 +146,8 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
if (EventIndexPeg.get() !== null) {
eventIndexingSettings = (
-
-
+ <>
+
{_t(
"Securely cache encrypted messages locally for them " +
"to appear in search results, using %(size)s to store messages from %(rooms)s rooms.",
@@ -158,27 +159,25 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
rooms: formatCountLong(this.state.roomCount),
},
)}
-
-
-
+
+
+ {_t("Manage")}
+
+ >
);
} else if (!this.state.eventIndexingEnabled && EventIndexPeg.supportIsInstalled()) {
eventIndexingSettings = (
-
-
+ <>
+
{_t("Securely cache encrypted messages locally for them to appear in search results.")}
-
+
{_t("Enable")}
{this.state.enabling ?
:
}
-
+ >
);
} else if (EventIndexPeg.platformHasSupport() && !EventIndexPeg.supportIsInstalled()) {
const nativeLink =
@@ -187,7 +186,7 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
"adding-seshat-for-search-in-e2e-encrypted-rooms";
eventIndexingSettings = (
-
+
{_t(
"%(brand)s is missing some components required for securely " +
"caching encrypted messages locally. If you'd like to " +
@@ -204,11 +203,11 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
),
},
)}
-
+
);
} else if (!EventIndexPeg.platformHasSupport()) {
eventIndexingSettings = (
-
+
{_t(
"%(brand)s can't securely cache encrypted messages locally " +
"while running in a web browser. Use %(brand)s Desktop " +
@@ -228,24 +227,28 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
),
},
)}
-
+
);
} else {
eventIndexingSettings = (
-
-
{this.state.enabling ? : _t("Message search initialisation failed")}
+ <>
+
+ {this.state.enabling ? : _t("Message search initialisation failed")}
+
{EventIndexPeg.error && (
-
- {_t("Advanced")}
- {EventIndexPeg.error.message}
-
-
- {_t("Reset")}
-
-
-
+
+
+ {_t("Advanced")}
+ {EventIndexPeg.error.message}
+
+
+ {_t("Reset")}
+
+
+
+
)}
-
+ >
);
}
diff --git a/src/components/views/settings/SecureBackupPanel.tsx b/src/components/views/settings/SecureBackupPanel.tsx
index 4d249c8df8f..874123be574 100644
--- a/src/components/views/settings/SecureBackupPanel.tsx
+++ b/src/components/views/settings/SecureBackupPanel.tsx
@@ -31,6 +31,7 @@ import AccessibleButton from "../elements/AccessibleButton";
import QuestionDialog from "../dialogs/QuestionDialog";
import RestoreKeyBackupDialog from "../dialogs/security/RestoreKeyBackupDialog";
import { accessSecretStorage } from "../../../SecurityManager";
+import { SettingsSubsectionText } from "./shared/SettingsSubsection";
interface IState {
loading: boolean;
@@ -247,7 +248,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
} else {
statusDescription = (
<>
-
+
{_t(
"This session is not backing up your keys, " +
"but you do have an existing backup you can restore from " +
@@ -255,13 +256,13 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
{},
{ b: (sub) => {sub} },
)}
-
-
+
+
{_t(
"Connect this session to key backup before signing out to avoid " +
"losing any keys that may only be on this session.",
)}
-
+
>
);
restoreButtonCaption = _t("Connect this session to Key Backup");
@@ -425,14 +426,16 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
} else {
statusDescription = (
<>
-
+
{_t(
"Your keys are not being backed up from this session.",
{},
{ b: (sub) => {sub} },
)}
-
- {_t("Back up your keys before signing out to avoid losing them.")}
+
+
+ {_t("Back up your keys before signing out to avoid losing them.")}
+
>
);
actions.push(
@@ -466,14 +469,14 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
}
return (
-
-
+ <>
+
{_t(
"Back up your encryption keys with your account data in case you " +
"lose access to your sessions. Your keys will be secured with a " +
"unique Security Key.",
)}
-
+
{statusDescription}
{_t("Advanced")}
@@ -502,7 +505,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
{extraDetails}
{actionRow}
-
+ >
);
}
}
diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
index 71afe65d98b..97135c5804f 100644
--- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
@@ -477,10 +477,6 @@ export default class SecurityRoomSettingsTab extends React.Component
- //
- //
{_t("Security & Privacy")}
-
- //
);
}
}
diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx
index 76f243e6ab2..8e94ca69889 100644
--- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx
+++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2019 - 2022 The Matrix.org Foundation C.I.C.
+Copyright 2019 - 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -41,6 +41,9 @@ import { privateShouldBeEncrypted } from "../../../../../utils/rooms";
import LoginWithQR, { Mode } from "../../../auth/LoginWithQR";
import LoginWithQRSection from "../../devices/LoginWithQRSection";
import type { IServerVersions } from "matrix-js-sdk/src/matrix";
+import SettingsTab from "../SettingsTab";
+import { SettingsSection } from "../../shared/SettingsSection";
+import SettingsSubsection, { SettingsSubsectionText } from "../../shared/SettingsSubsection";
interface IIgnoredUserProps {
userId: string;
@@ -245,10 +248,9 @@ export default class SecurityUserSettingsTab extends React.Component
- {_t("Ignored users")}
- {userIds}
-
+
+ {userIds}
+
);
}
@@ -260,24 +262,25 @@ export default class SecurityUserSettingsTab extends React.Component
- {_t("Bulk options")}
-
- {_t("Accept all %(invitedRooms)s invites", { invitedRooms: invitedRoomIds.size })}
-
-
- {_t("Reject all %(invitedRooms)s invites", { invitedRooms: invitedRoomIds.size })}
-
- {this.state.managingInvites ? : }
-
+
+
+
+ {_t("Accept all %(invitedRooms)s invites", { invitedRooms: invitedRoomIds.size })}
+
+
+ {_t("Reject all %(invitedRooms)s invites", { invitedRooms: invitedRoomIds.size })}
+
+ {this.state.managingInvites ?
:
}
+
+
);
}
@@ -291,19 +294,15 @@ export default class SecurityUserSettingsTab extends React.Component
- {_t("Secure Backup")}
-
-
-
-
+
+
+
);
const eventIndex = (
-
- {_t("Message search")}
+
-
+
);
// XXX: There's no such panel in the current cross-signing designs, but
@@ -311,12 +310,9 @@ export default class SecurityUserSettingsTab extends React.Component
- {_t("Cross-signing")}
-
-
-
-
+
+
+
);
let warning;
@@ -340,28 +336,24 @@ export default class SecurityUserSettingsTab extends React.Component
- {_t("Privacy")}
-
-
{_t("Analytics")}
-
-
- {_t(
- "Share anonymous data to help us identify issues. Nothing personal. " +
- "No third parties.",
- )}
-
-
- {_t("Learn more")}
-
-
+
+
+
+ {_t("Learn more")}
+
{PosthogAnalytics.instance.isEnabled() && (
)}
- {_t("Sessions")}
+
+
-
-
+
+
);
}
@@ -373,67 +365,60 @@ export default class SecurityUserSettingsTab extends React.Component
- {_t("Advanced")}
-
- {ignoreUsersPanel}
- {invitesPanel}
- {e2ePanel}
-
- >
+
+ {ignoreUsersPanel}
+ {invitesPanel}
+ {e2ePanel}
+
);
}
}
const useNewSessionManager = SettingsStore.getValue("feature_new_device_manager");
const devicesSection = useNewSessionManager ? null : (
- <>
- {_t("Where you're signed in")}
-
-
- {_t(
- "Manage your signed-in devices below. " +
- "A device's name is visible to people you communicate with.",
- )}
-
-
-
+
+
+ {_t(
+ "Manage your signed-in devices below. " +
+ "A device's name is visible to people you communicate with.",
+ )}
+
+
- >
+
);
const client = MatrixClientPeg.get();
if (this.state.showLoginWithQR) {
return (
-
+
-
+
);
}
return (
-
+
{warning}
{devicesSection}
- {_t("Encryption")}
-
+
{secureBackup}
{eventIndex}
{crossSigning}
-
+
{privacySection}
{advancedSection}
-
+
);
}
}
diff --git a/src/components/views/settings/tabs/user/SessionManagerTab.tsx b/src/components/views/settings/tabs/user/SessionManagerTab.tsx
index 2b86e5b60f7..1c338681fca 100644
--- a/src/components/views/settings/tabs/user/SessionManagerTab.tsx
+++ b/src/components/views/settings/tabs/user/SessionManagerTab.tsx
@@ -259,6 +259,7 @@ const SessionManagerTab: React.FC = () => {
`from any session that you don't recognize or use anymore.`,
)}
data-testid="other-sessions-section"
+ stretchContent
>
", () => {
+ const userId = "@alice:server.org";
+ const mockClient = getMockClientWithEventEmitter({
+ ...mockClientMethodsUser(userId),
+ ...mockClientMethodsCrypto(),
+ doesServerSupportUnstableFeature: jest.fn(),
+ });
+ const getComponent = () => render();
+
+ beforeEach(() => {
+ mockClient.doesServerSupportUnstableFeature.mockResolvedValue(true);
+ mockClient.isCrossSigningReady.mockResolvedValue(false);
+ mocked(mockClient.crypto!.crossSigningInfo).isStoredInSecretStorage.mockClear().mockResolvedValue(null);
+ });
+
+ it("should render a spinner while loading", () => {
+ getComponent();
+
+ expect(screen.getByRole("progressbar")).toBeInTheDocument();
+ });
+
+ it("should render when homeserver does not support cross-signing", async () => {
+ mockClient.doesServerSupportUnstableFeature.mockResolvedValue(false);
+
+ getComponent();
+ await flushPromises();
+
+ expect(screen.getByText("Your homeserver does not support cross-signing.")).toBeInTheDocument();
+ });
+
+ describe("when cross signing is ready", () => {
+ beforeEach(() => {
+ mockClient.isCrossSigningReady.mockResolvedValue(true);
+ });
+
+ it("should render when keys are not backed up", async () => {
+ getComponent();
+ await flushPromises();
+
+ expect(screen.getByTestId("summarised-status").innerHTML).toEqual(
+ "⚠️ Cross-signing is ready but keys are not backed up.",
+ );
+ expect(screen.getByText("Cross-signing private keys:").parentElement!).toMatchSnapshot();
+ });
+
+ it("should render when keys are backed up", async () => {
+ mocked(mockClient.crypto!.crossSigningInfo).isStoredInSecretStorage.mockResolvedValue({ test: {} });
+ getComponent();
+ await flushPromises();
+
+ expect(screen.getByTestId("summarised-status").innerHTML).toEqual("✅ Cross-signing is ready for use.");
+ expect(screen.getByText("Cross-signing private keys:").parentElement!).toMatchSnapshot();
+ expect(mockClient.crypto!.crossSigningInfo.isStoredInSecretStorage).toHaveBeenCalledWith(
+ mockClient.crypto!.secretStorage,
+ );
+ });
+ });
+
+ describe("when cross signing is not ready", () => {
+ beforeEach(() => {
+ mockClient.isCrossSigningReady.mockResolvedValue(false);
+ });
+
+ it("should render when keys are not backed up", async () => {
+ getComponent();
+ await flushPromises();
+
+ expect(screen.getByTestId("summarised-status").innerHTML).toEqual("Cross-signing is not set up.");
+ });
+
+ it("should render when keys are backed up", async () => {
+ mocked(mockClient.crypto!.crossSigningInfo).isStoredInSecretStorage.mockResolvedValue({ test: {} });
+ getComponent();
+ await flushPromises();
+
+ expect(screen.getByTestId("summarised-status").innerHTML).toEqual(
+ "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.",
+ );
+ expect(screen.getByText("Cross-signing private keys:").parentElement!).toMatchSnapshot();
+ expect(mockClient.crypto!.crossSigningInfo.isStoredInSecretStorage).toHaveBeenCalledWith(
+ mockClient.crypto!.secretStorage,
+ );
+ });
+ });
+});
diff --git a/test/components/views/settings/EventIndexPanel-test.tsx b/test/components/views/settings/EventIndexPanel-test.tsx
new file mode 100644
index 00000000000..34956f44c1b
--- /dev/null
+++ b/test/components/views/settings/EventIndexPanel-test.tsx
@@ -0,0 +1,201 @@
+/*
+Copyright 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from "react";
+import { fireEvent, render, screen, within } from "@testing-library/react";
+import { defer, IDeferred } from "matrix-js-sdk/src/utils";
+
+import EventIndexPanel from "../../../../src/components/views/settings/EventIndexPanel";
+import EventIndexPeg from "../../../../src/indexing/EventIndexPeg";
+import EventIndex from "../../../../src/indexing/EventIndex";
+import { clearAllModals, flushPromises, getMockClientWithEventEmitter } from "../../../test-utils";
+import SettingsStore from "../../../../src/settings/SettingsStore";
+import { SettingLevel } from "../../../../src/settings/SettingLevel";
+
+describe("", () => {
+ getMockClientWithEventEmitter({
+ getRooms: jest.fn().mockReturnValue([]),
+ });
+
+ const getComponent = () => render();
+
+ beforeEach(() => {
+ jest.spyOn(EventIndexPeg, "get").mockRestore();
+ jest.spyOn(EventIndexPeg, "platformHasSupport").mockReturnValue(false);
+ jest.spyOn(EventIndexPeg, "supportIsInstalled").mockReturnValue(false);
+ jest.spyOn(EventIndexPeg, "initEventIndex").mockClear().mockResolvedValue(true);
+ jest.spyOn(EventIndexPeg, "deleteEventIndex").mockClear();
+ jest.spyOn(SettingsStore, "getValueAt").mockReturnValue(false);
+ jest.spyOn(SettingsStore, "setValue").mockClear();
+
+ // @ts-ignore private property
+ EventIndexPeg.error = null;
+ });
+
+ afterEach(async () => {
+ await clearAllModals();
+ });
+
+ describe("when event index is initialised", () => {
+ it("renders event index information", () => {
+ jest.spyOn(EventIndexPeg, "get").mockReturnValue(new EventIndex());
+
+ const { container } = getComponent();
+
+ expect(container).toMatchSnapshot();
+ });
+
+ it("opens event index management dialog", async () => {
+ jest.spyOn(EventIndexPeg, "get").mockReturnValue(new EventIndex());
+ getComponent();
+
+ fireEvent.click(screen.getByText("Manage"));
+
+ const dialog = await screen.findByRole("dialog");
+ expect(within(dialog).getByText("Message search")).toBeInTheDocument();
+
+ // close the modal
+ fireEvent.click(within(dialog).getByText("Done"));
+ });
+ });
+
+ describe("when event indexing is fully supported and enabled but not initialised", () => {
+ beforeEach(() => {
+ jest.spyOn(EventIndexPeg, "supportIsInstalled").mockReturnValue(true);
+ jest.spyOn(EventIndexPeg, "platformHasSupport").mockReturnValue(true);
+ jest.spyOn(SettingsStore, "getValueAt").mockReturnValue(true);
+
+ // @ts-ignore private property
+ EventIndexPeg.error = { message: "Test error message" };
+ });
+
+ it("displays an error when no event index is found and enabling not in progress", () => {
+ getComponent();
+
+ expect(screen.getByText("Message search initialisation failed")).toBeInTheDocument();
+ });
+
+ it("displays an error from the event index", () => {
+ getComponent();
+
+ expect(screen.getByText("Test error message")).toBeInTheDocument();
+ });
+
+ it("asks for confirmation when resetting seshat", async () => {
+ getComponent();
+
+ fireEvent.click(screen.getByText("Reset"));
+
+ // wait for reset modal to open
+ await screen.findByText("Reset event store?");
+ const dialog = await screen.findByRole("dialog");
+
+ expect(within(dialog).getByText("Reset event store?")).toBeInTheDocument();
+ fireEvent.click(within(dialog).getByText("Cancel"));
+
+ // didn't reset
+ expect(SettingsStore.setValue).not.toHaveBeenCalled();
+ expect(EventIndexPeg.deleteEventIndex).not.toHaveBeenCalled();
+ });
+
+ it("resets seshat", async () => {
+ getComponent();
+
+ fireEvent.click(screen.getByText("Reset"));
+
+ // wait for reset modal to open
+ await screen.findByText("Reset event store?");
+ const dialog = await screen.findByRole("dialog");
+
+ fireEvent.click(within(dialog).getByText("Reset event store"));
+
+ await flushPromises();
+
+ expect(SettingsStore.setValue).toHaveBeenCalledWith(
+ "enableEventIndexing",
+ null,
+ SettingLevel.DEVICE,
+ false,
+ );
+ expect(EventIndexPeg.deleteEventIndex).toHaveBeenCalled();
+
+ await clearAllModals();
+ });
+ });
+
+ describe("when event indexing is supported but not enabled", () => {
+ it("renders enable text", () => {
+ jest.spyOn(EventIndexPeg, "supportIsInstalled").mockReturnValue(true);
+
+ getComponent();
+
+ expect(
+ screen.getByText("Securely cache encrypted messages locally for them to appear in search results."),
+ ).toBeInTheDocument();
+ });
+ it("enables event indexing on enable button click", async () => {
+ jest.spyOn(EventIndexPeg, "supportIsInstalled").mockReturnValue(true);
+ let deferredInitEventIndex: IDeferred | undefined;
+ jest.spyOn(EventIndexPeg, "initEventIndex").mockImplementation(() => {
+ deferredInitEventIndex = defer();
+ return deferredInitEventIndex.promise;
+ });
+
+ getComponent();
+
+ fireEvent.click(screen.getByText("Enable"));
+
+ await flushPromises();
+ // spinner shown while enabling
+ expect(screen.getByLabelText("Loading…")).toBeInTheDocument();
+
+ // add an event indx to the peg and resolve the init promise
+ jest.spyOn(EventIndexPeg, "get").mockReturnValue(new EventIndex());
+ expect(EventIndexPeg.initEventIndex).toHaveBeenCalled();
+ deferredInitEventIndex!.resolve(true);
+ await flushPromises();
+ expect(SettingsStore.setValue).toHaveBeenCalledWith("enableEventIndexing", null, SettingLevel.DEVICE, true);
+
+ // message for enabled event index
+ expect(
+ screen.getByText(
+ "Securely cache encrypted messages locally for them to appear in search results, using 0 Bytes to store messages from 0 rooms.",
+ ),
+ ).toBeInTheDocument();
+ });
+ });
+
+ describe("when event indexing is supported but not installed", () => {
+ it("renders link to install seshat", () => {
+ jest.spyOn(EventIndexPeg, "supportIsInstalled").mockReturnValue(false);
+ jest.spyOn(EventIndexPeg, "platformHasSupport").mockReturnValue(true);
+
+ const { container } = getComponent();
+
+ expect(container).toMatchSnapshot();
+ });
+ });
+
+ describe("when event indexing is not supported", () => {
+ it("renders link to download a desktop client", () => {
+ jest.spyOn(EventIndexPeg, "platformHasSupport").mockReturnValue(false);
+
+ const { container } = getComponent();
+
+ expect(container).toMatchSnapshot();
+ });
+ });
+});
diff --git a/test/components/views/settings/__snapshots__/CrossSigningPanel-test.tsx.snap b/test/components/views/settings/__snapshots__/CrossSigningPanel-test.tsx.snap
new file mode 100644
index 00000000000..d484ba4a3be
--- /dev/null
+++ b/test/components/views/settings/__snapshots__/CrossSigningPanel-test.tsx.snap
@@ -0,0 +1,40 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` when cross signing is not ready should render when keys are backed up 1`] = `
+
+
+ Cross-signing private keys:
+ |
+
+ in secret storage
+ |
+
+`;
+
+exports[` when cross signing is ready should render when keys are backed up 1`] = `
+
+
+ Cross-signing private keys:
+ |
+
+ in secret storage
+ |
+
+`;
+
+exports[` when cross signing is ready should render when keys are not backed up 1`] = `
+
+
+ Cross-signing private keys:
+ |
+
+ not found in storage
+ |
+
+`;
diff --git a/test/components/views/settings/__snapshots__/EventIndexPanel-test.tsx.snap b/test/components/views/settings/__snapshots__/EventIndexPanel-test.tsx.snap
new file mode 100644
index 00000000000..305b9e2d6a5
--- /dev/null
+++ b/test/components/views/settings/__snapshots__/EventIndexPanel-test.tsx.snap
@@ -0,0 +1,66 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` when event index is initialised renders event index information 1`] = `
+
+
+ Securely cache encrypted messages locally for them to appear in search results, using 0 Bytes to store messages from 0 rooms.
+
+
+ Manage
+
+
+`;
+
+exports[` when event indexing is not supported renders link to download a desktop client 1`] = `
+
+
+
+ Element can't securely cache encrypted messages locally while running in a web browser. Use
+
+ Element Desktop
+
+
+ for encrypted messages to appear in search results.
+
+
+
+`;
+
+exports[` when event indexing is supported but not installed renders link to install seshat 1`] = `
+
+
+
+ Element is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom Element Desktop with
+
+ search components added
+
+
+ .
+
+
+
+`;
diff --git a/test/components/views/settings/__snapshots__/SecureBackupPanel-test.tsx.snap b/test/components/views/settings/__snapshots__/SecureBackupPanel-test.tsx.snap
index e17dfd0064c..bc8f2400cc8 100644
--- a/test/components/views/settings/__snapshots__/SecureBackupPanel-test.tsx.snap
+++ b/test/components/views/settings/__snapshots__/SecureBackupPanel-test.tsx.snap
@@ -2,114 +2,118 @@
exports[` suggests connecting session to key backup when backup exists 1`] = `
-
-
- Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key.
-
-
-
- This session is
-
- not backing up your keys
-
- , but you do have an existing backup you can restore from and add to going forward.
-
-
-
- Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.
-
-
-
- Advanced
-
-
-
-
- Backup key stored:
- |
-
- not stored
- |
-
-
-
- Backup key cached:
- |
-
- not found locally
-
- |
-
-
-
- Secret storage public key:
- |
-
- not found
- |
-
-
-
- Secret storage:
- |
-
- not ready
- |
-
-
-
- Backup version:
- |
-
- 1
- |
-
-
-
- Algorithm:
- |
-
- test
- |
-
-
-
-
- Backup is not signed by any of your sessions
-
-
-
+
+ Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key.
+
+
+
+ This session is
+
+ not backing up your keys
+
+ , but you do have an existing backup you can restore from and add to going forward.
+
+
+
+ Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.
+
+
+
+ Advanced
+
+
+
+
+ Backup key stored:
+ |
+
+ not stored
+ |
+
+
+
+ Backup key cached:
+ |
+
+ not found locally
+
+ |
+
+
+
+ Secret storage public key:
+ |
+
+ not found
+ |
+
+
+
+ Secret storage:
+ |
+
+ not ready
+ |
+
+
+
+ Backup version:
+ |
+
+ 1
+ |
+
+
+
+ Algorithm:
+ |
+
+ test
+ |
+
+
+
+
+ Backup is not signed by any of your sessions
+
+
+
+