Removed DecryptionFailureBar.tsx (#11027)

This commit is contained in:
Florian Duros 2023-06-05 17:02:20 +02:00 committed by GitHub
parent 6b46d6e4f8
commit 68ebcd2956
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 0 additions and 1739 deletions

View file

@ -1,277 +0,0 @@
/*
Copyright 2022 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 type { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { UserCredentials } from "../../support/login";
import { handleVerificationRequest } from "./utils";
import { skipIfRustCrypto } from "../../support/util";
const ROOM_NAME = "Test room";
const TEST_USER = "Alia";
const BOT_USER = "Benjamin";
type EmojiMapping = [emoji: string, name: string];
const waitForVerificationRequest = (cli: MatrixClient): Promise<VerificationRequest> => {
return new Promise<VerificationRequest>((resolve) => {
const onVerificationRequestEvent = (request: VerificationRequest) => {
// @ts-ignore CryptoEvent is not exported to window.matrixcs; using the string value here
cli.off("crypto.verification.request", onVerificationRequestEvent);
resolve(request);
};
// @ts-ignore
cli.on("crypto.verification.request", onVerificationRequestEvent);
});
};
const checkTimelineNarrow = (button = true) => {
cy.viewport(800, 600); // SVGA
cy.get(".mx_LeftPanel_minimized").should("exist"); // Wait until the left panel is minimized
cy.findByRole("button", { name: "Room info" }).click(); // Open the right panel to make the timeline narrow
cy.get(".mx_BaseCard").should("exist");
// Ensure the failure bar does not cover the timeline
cy.get(".mx_RoomView_body .mx_EventTile.mx_EventTile_last").should("be.visible");
// Ensure the indicator does not overflow the timeline
cy.findByTestId("decryption-failure-bar-indicator").should("be.visible");
if (button) {
// Ensure the button does not overflow the timeline
cy.get("[data-testid='decryption-failure-bar-button']:last-of-type").should("be.visible");
}
cy.findByRole("button", { name: "Room info" }).click(); // Close the right panel
cy.get(".mx_BaseCard").should("not.exist");
cy.viewport(1000, 660); // Reset to the default size
};
describe("Decryption Failure Bar", () => {
let homeserver: HomeserverInstance | undefined;
let testUser: UserCredentials | undefined;
let bot: MatrixClient | undefined;
let roomId: string;
beforeEach(function () {
skipIfRustCrypto();
cy.startHomeserver("default").then((hs: HomeserverInstance) => {
homeserver = hs;
cy.initTestUser(homeserver, TEST_USER)
.then((creds: UserCredentials) => {
testUser = creds;
})
.then(() => {
cy.getBot(homeserver, { displayName: BOT_USER }).then((cli) => {
bot = cli;
});
})
.then(() => {
cy.createRoom({ name: ROOM_NAME }).then((id) => {
roomId = id;
});
})
.then(() => {
cy.inviteUser(roomId, bot.getUserId());
cy.visit("/#/room/" + roomId);
cy.findByText(BOT_USER + " joined the room").should("exist");
})
.then(() => {
cy.getClient()
.then(async (cli) => {
await cli.setRoomEncryption(roomId, { algorithm: "m.megolm.v1.aes-sha2" });
await bot.setRoomEncryption(roomId, { algorithm: "m.megolm.v1.aes-sha2" });
})
.then(() => {
bot.getRoom(roomId).setBlacklistUnverifiedDevices(true);
});
});
});
});
afterEach(() => {
cy.stopHomeserver(homeserver);
});
it(
"should prompt the user to verify, if this device isn't verified " +
"and there are other verified devices or backups",
() => {
let otherDevice: MatrixClient | undefined;
cy.loginBot(homeserver, testUser.username, testUser.password, { bootstrapCrossSigning: true })
.then(async (cli) => {
otherDevice = cli;
})
.then(() => {
cy.botSendMessage(bot, roomId, "test");
cy.get(".mx_DecryptionFailureBar_start_headline").within(() => {
cy.findByText("Decrypting messages…").should("be.visible");
cy.findByText("Verify this device to access all messages").should("be.visible");
});
checkTimelineNarrow();
cy.get(".mx_DecryptionFailureBar").percySnapshotElement(
"DecryptionFailureBar prompts user to verify",
{
widths: [320, 640],
},
);
cy.get(".mx_DecryptionFailureBar_end").within(() => {
cy.findByText("Resend key requests").should("not.exist");
cy.findByRole("button", { name: "Verify" }).click();
});
const verificationRequestPromise = waitForVerificationRequest(otherDevice);
cy.findByRole("button", { name: "Verify with another device" }).click();
cy.findByText("To proceed, please accept the verification request on your other device.").should(
"be.visible",
);
cy.wrap(verificationRequestPromise).then((verificationRequest: VerificationRequest) => {
cy.wrap(verificationRequest.accept());
cy.wrap(
handleVerificationRequest(verificationRequest),
// extra timeout, as this sometimes takes a while
{ timeout: 30_000 },
).then((emojis: EmojiMapping[]) => {
cy.get(".mx_VerificationShowSas_emojiSas_block").then((emojiBlocks) => {
emojis.forEach((emoji: EmojiMapping, index: number) => {
expect(emojiBlocks[index].textContent.toLowerCase()).to.eq(emoji[0] + emoji[1]);
});
});
});
});
});
cy.findByRole("button", { name: "They match" }).click();
cy.get(".mx_VerificationPanel_verified_section .mx_E2EIcon_verified").should("exist");
cy.findByRole("button", { name: "Got it" }).click();
cy.get(".mx_DecryptionFailureBar_start_headline").within(() => {
cy.findByText("Open another device to load encrypted messages").should("be.visible");
});
checkTimelineNarrow();
cy.get(".mx_DecryptionFailureBar").percySnapshotElement(
"DecryptionFailureBar prompts user to open another device, with Resend Key Requests button",
{
widths: [320, 640],
},
);
cy.intercept("/_matrix/client/r0/sendToDevice/m.room_key_request/*").as("keyRequest");
cy.findByRole("button", { name: "Resend key requests" }).click();
cy.wait("@keyRequest");
cy.get(".mx_DecryptionFailureBar_end").within(() => {
cy.findByText("Resend key requests").should("not.exist");
cy.findByRole("button", { name: "View your device list" }).should("be.visible");
});
checkTimelineNarrow();
cy.get(".mx_DecryptionFailureBar").percySnapshotElement(
"DecryptionFailureBar prompts user to open another device, without Resend Key Requests button",
{
widths: [320, 640],
},
);
},
);
it(
"should prompt the user to reset keys, if this device isn't verified " +
"and there are no other verified devices or backups",
() => {
cy.loginBot(homeserver, testUser.username, testUser.password, { bootstrapCrossSigning: true }).then(
async (cli) => {
await cli.logout(true);
},
);
cy.botSendMessage(bot, roomId, "test");
cy.get(".mx_DecryptionFailureBar_start_headline").within(() => {
cy.findByText("Reset your keys to prevent future decryption errors").should("be.visible");
});
checkTimelineNarrow();
cy.get(".mx_DecryptionFailureBar").percySnapshotElement("DecryptionFailureBar prompts user to reset keys", {
widths: [320, 640],
});
cy.findByRole("button", { name: "Reset" }).click();
// Set up key backup
cy.get(".mx_Dialog").within(() => {
cy.findByRole("button", { name: "Continue" }).click();
cy.get(".mx_CreateSecretStorageDialog_recoveryKey code").invoke("text").as("securityKey");
// Clicking download instead of Copy because of https://github.com/cypress-io/cypress/issues/2851
cy.findByRole("button", { name: "Download" }).click();
cy.get(".mx_Dialog_primary:not([disabled])").should("have.length", 3);
cy.findByRole("button", { name: "Continue" }).click();
cy.findByRole("button", { name: "Done" }).click();
});
cy.get(".mx_DecryptionFailureBar_start_headline").within(() => {
cy.findByText("Some messages could not be decrypted").should("be.visible");
});
checkTimelineNarrow(false); // button should not be rendered here
cy.get(".mx_DecryptionFailureBar").percySnapshotElement(
"DecryptionFailureBar displays general message with no call to action",
{
widths: [320, 640],
},
);
},
);
it("should appear and disappear as undecryptable messages enter and leave view", () => {
cy.getClient().then((cli) => {
for (let i = 0; i < 25; i++) {
cy.botSendMessage(cli, roomId, `test ${i}`);
}
});
cy.botSendMessage(bot, roomId, "test");
cy.get(".mx_DecryptionFailureBar").should("exist");
cy.get(".mx_DecryptionFailureBar .mx_Spinner").should("exist");
cy.get(".mx_DecryptionFailureBar").percySnapshotElement("DecryptionFailureBar displays loading spinner", {
allowSpinners: true,
widths: [320, 640],
});
checkTimelineNarrow();
cy.wait(5000);
cy.get(".mx_DecryptionFailureBar .mx_Spinner").should("not.exist");
cy.findByTestId("decryption-failure-bar-icon").should("be.visible");
cy.get(".mx_RoomView_messagePanel").scrollTo("top");
cy.get(".mx_DecryptionFailureBar").should("not.exist");
cy.botSendMessage(bot, roomId, "another test");
cy.get(".mx_DecryptionFailureBar").should("not.exist");
cy.get(".mx_RoomView_messagePanel").scrollTo("bottom");
cy.get(".mx_DecryptionFailureBar").should("exist");
checkTimelineNarrow();
});
});

View file

@ -62,7 +62,6 @@ import RoomPreviewBar from "../views/rooms/RoomPreviewBar";
import RoomPreviewCard from "../views/rooms/RoomPreviewCard";
import SearchBar, { SearchScope } from "../views/rooms/SearchBar";
import RoomUpgradeWarningBar from "../views/rooms/RoomUpgradeWarningBar";
import { DecryptionFailureBar } from "../views/rooms/DecryptionFailureBar";
import AuxPanel from "../views/rooms/AuxPanel";
import RoomHeader, { ISearchInfo } from "../views/rooms/RoomHeader";
import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore";
@ -227,8 +226,6 @@ export interface IRoomState {
threadId?: string;
liveTimeline?: EventTimeline;
narrow: boolean;
// List of undecryptable events currently visible on-screen
visibleDecryptionFailures?: MatrixEvent[];
msc3946ProcessDynamicPredecessor: boolean;
}
@ -428,7 +425,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
timelineRenderingType: TimelineRenderingType.Room,
liveTimeline: undefined,
narrow: false,
visibleDecryptionFailures: [],
msc3946ProcessDynamicPredecessor: SettingsStore.getValue("feature_dynamic_room_predecessors"),
};
@ -1244,7 +1240,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private onEventDecrypted = (ev: MatrixEvent): void => {
if (!this.state.room || !this.state.matrixClientIsReady) return; // not ready at all
if (ev.getRoomId() !== this.state.room.roomId) return; // not for us
this.updateVisibleDecryptionFailures();
if (ev.isDecryptionFailure()) return;
this.handleEffects(ev);
};
@ -1552,20 +1547,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}
};
private updateVisibleDecryptionFailures = throttle(
() =>
this.setState((prevState) => ({
visibleDecryptionFailures:
this.messagePanel?.getVisibleDecryptionFailures(
// If there were visible failures last time we checked,
// add a margin to provide hysteresis and prevent flickering
(prevState.visibleDecryptionFailures?.length ?? 0) > 0,
) ?? [],
})),
500,
{ leading: false, trailing: true },
);
private onMessageListScroll = (): void => {
if (this.messagePanel?.isAtEndOfLiveTimeline()) {
this.setState({
@ -1578,7 +1559,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
});
}
this.updateTopUnreadMessagesBar();
this.updateVisibleDecryptionFailures();
};
private resetJumpToEvent = (eventId?: string): void => {
@ -2203,11 +2183,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
);
}
let decryptionFailureBar: JSX.Element | undefined;
if (this.state.visibleDecryptionFailures && this.state.visibleDecryptionFailures.length > 0) {
decryptionFailureBar = <DecryptionFailureBar failures={this.state.visibleDecryptionFailures} />;
}
if (this.state.room?.isSpaceRoom() && !this.props.forceTimeline) {
return (
<SpaceRoomView
@ -2233,7 +2208,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
resizeNotifier={this.props.resizeNotifier}
>
{aux}
{decryptionFailureBar}
</AuxPanel>
);

View file

@ -1,270 +0,0 @@
/*
Copyright 2022 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, { useCallback, useContext, useEffect, useState } from "react";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
import Modal from "../../../Modal";
import { _t } from "../../../languageHandler";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import AccessibleButton from "../elements/AccessibleButton";
import { OpenToTabPayload } from "../../../dispatcher/payloads/OpenToTabPayload";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import SetupEncryptionDialog from "../dialogs/security/SetupEncryptionDialog";
import { SetupEncryptionStore } from "../../../stores/SetupEncryptionStore";
import Spinner from "../elements/Spinner";
interface IProps {
failures: MatrixEvent[];
}
// Number of milliseconds to display a loading spinner before prompting the user for action
const WAIT_PERIOD = 5000;
export const DecryptionFailureBar: React.FC<IProps> = ({ failures }) => {
const context = useContext(MatrixClientContext);
// Display a spinner for a few seconds before presenting an error message,
// in case keys are about to arrive
const [waiting, setWaiting] = useState<boolean>(true);
useEffect(() => {
const timeout = setTimeout(() => setWaiting(false), WAIT_PERIOD);
return () => clearTimeout(timeout);
}, []);
// Is this device unverified?
const [needsVerification, setNeedsVerification] = useState<boolean>(false);
// Does this user have verified devices other than this device?
const [hasOtherVerifiedDevices, setHasOtherVerifiedDevices] = useState<boolean>(false);
// Does this user have key backups?
const [hasKeyBackup, setHasKeyBackup] = useState<boolean>(false);
// Keep track of session IDs that the user has sent key
// requests for using the Resend Key Requests button
const [requestedSessions, setRequestedSessions] = useState<Set<string>>(new Set());
// Keep track of whether there are any sessions the user has not yet sent requests for
const [anyUnrequestedSessions, setAnyUnrequestedSessions] = useState<boolean>(true);
useEffect(() => {
setAnyUnrequestedSessions(
failures.some((event) => {
const sessionId = event.getWireContent().session_id;
return sessionId && !requestedSessions.has(sessionId);
}),
);
}, [failures, requestedSessions, setAnyUnrequestedSessions]);
// Send key requests for any sessions that we haven't previously
// sent requests for. This is important if, for instance, we
// failed to decrypt a message because a key was withheld (in
// which case, we wouldn't bother requesting the key), but have
// since verified our device. In that case, now that the device is
// verified, other devices might be willing to share the key with us
// now.
const sendKeyRequests = useCallback(() => {
const newRequestedSessions = new Set(requestedSessions);
for (const event of failures) {
const sessionId = event.getWireContent().session_id;
if (!sessionId || newRequestedSessions.has(sessionId)) continue;
newRequestedSessions.add(sessionId);
context.cancelAndResendEventRoomKeyRequest(event);
}
setRequestedSessions(newRequestedSessions);
}, [context, requestedSessions, setRequestedSessions, failures]);
// Recheck which devices are verified and whether we have key backups
const updateDeviceInfo = useCallback(async () => {
const deviceId = context.getDeviceId()!;
let verified = true; // if we can't get a clear answer, don't bug the user about verifying
try {
verified = context.checkIfOwnDeviceCrossSigned(deviceId);
} catch (e) {
console.error("Error getting device cross-signing info", e);
}
setNeedsVerification(!verified);
let otherVerifiedDevices = false;
try {
const devices = context.getStoredDevicesForUser(context.getUserId()!);
otherVerifiedDevices = devices.some(
(device) => device.deviceId !== deviceId && context.checkIfOwnDeviceCrossSigned(device.deviceId),
);
} catch (e) {
console.error("Error getting info about other devices", e);
}
setHasOtherVerifiedDevices(otherVerifiedDevices);
let keyBackup = false;
try {
const keys = await context.isSecretStored("m.cross_signing.master");
keyBackup = keys !== null && Object.keys(keys).length > 0;
} catch (e) {
console.error("Error getting info about key backups", e);
}
setHasKeyBackup(keyBackup);
}, [context]);
// Update our device info on initial render, and continue updating
// it whenever the client has an update
useEffect(() => {
updateDeviceInfo().catch(console.error);
context.on(CryptoEvent.DevicesUpdated, updateDeviceInfo);
return () => {
context.off(CryptoEvent.DevicesUpdated, updateDeviceInfo);
};
}, [context, updateDeviceInfo]);
const onVerifyClick = (): void => {
Modal.createDialog(SetupEncryptionDialog);
};
const onDeviceListClick = (): void => {
const payload: OpenToTabPayload = { action: Action.ViewUserDeviceSettings };
defaultDispatcher.dispatch(payload);
};
const onResetClick = (): void => {
const store = SetupEncryptionStore.sharedInstance();
store.resetConfirm();
};
const statusIndicator = waiting ? (
<Spinner w={24} h={24} />
) : (
<div className="mx_DecryptionFailureBar_start_status_icon" data-testid="decryption-failure-bar-icon" />
);
let className;
let headline: JSX.Element;
let message: JSX.Element;
let button = <React.Fragment />;
if (waiting) {
className = "mx_DecryptionFailureBar";
headline = <React.Fragment>{_t("Decrypting messages…")}</React.Fragment>;
message = (
<React.Fragment>
{_t("Please wait as we try to decrypt your messages. This may take a few moments.")}
</React.Fragment>
);
} else if (needsVerification) {
if (hasOtherVerifiedDevices || hasKeyBackup) {
className = "mx_DecryptionFailureBar mx_DecryptionFailureBar--withEnd";
headline = <React.Fragment>{_t("Verify this device to access all messages")}</React.Fragment>;
message = (
<React.Fragment>
{_t("This device was unable to decrypt some messages because it has not been verified yet.")}
</React.Fragment>
);
button = (
<AccessibleButton
className="mx_DecryptionFailureBar_end_button"
kind="primary"
onClick={onVerifyClick}
data-testid="decryption-failure-bar-button"
>
{_t("Verify")}
</AccessibleButton>
);
} else {
className = "mx_DecryptionFailureBar mx_DecryptionFailureBar--withEnd";
headline = <React.Fragment>{_t("Reset your keys to prevent future decryption errors")}</React.Fragment>;
message = (
<React.Fragment>
{_t(
"You will not be able to access old undecryptable messages, " +
"but resetting your keys will allow you to receive new messages.",
)}
</React.Fragment>
);
button = (
<AccessibleButton
className="mx_DecryptionFailureBar_end_button"
kind="primary"
onClick={onResetClick}
data-testid="decryption-failure-bar-button"
>
{_t("Reset")}
</AccessibleButton>
);
}
} else if (hasOtherVerifiedDevices) {
className = "mx_DecryptionFailureBar mx_DecryptionFailureBar--withEnd";
headline = <React.Fragment>{_t("Open another device to load encrypted messages")}</React.Fragment>;
message = (
<React.Fragment>
{_t(
"This device is requesting decryption keys from your other devices. " +
"Opening one of your other devices may speed this up.",
)}
</React.Fragment>
);
button = (
<AccessibleButton
className="mx_DecryptionFailureBar_end_button"
kind="primary_outline"
onClick={onDeviceListClick}
data-testid="decryption-failure-bar-button"
>
{_t("View your device list")}
</AccessibleButton>
);
} else {
className = "mx_DecryptionFailureBar";
headline = <React.Fragment>{_t("Some messages could not be decrypted")}</React.Fragment>;
message = (
<React.Fragment>
{_t(
"Unfortunately, there are no other verified devices to request decryption keys from. " +
"Signing in and verifying other devices may help avoid this situation in the future.",
)}
</React.Fragment>
);
}
let keyRequestButton = <React.Fragment />;
if (!needsVerification && hasOtherVerifiedDevices && anyUnrequestedSessions) {
className = "mx_DecryptionFailureBar mx_DecryptionFailureBar--withEnd";
keyRequestButton = (
<AccessibleButton
className="mx_DecryptionFailureBar_end_button"
kind="primary"
onClick={sendKeyRequests}
data-testid="decryption-failure-bar-button"
>
{_t("Resend key requests")}
</AccessibleButton>
);
}
return (
<div className={className}>
<div className="mx_DecryptionFailureBar_start">
<div className="mx_DecryptionFailureBar_start_status">
<div data-testid="decryption-failure-bar-indicator">{statusIndicator}</div>
</div>
<div className="mx_DecryptionFailureBar_start_headline">{headline}</div>
<div className="mx_DecryptionFailureBar_start_message">{message}</div>
</div>
<div className="mx_DecryptionFailureBar_end">
{button}
{keyRequestButton}
</div>
</div>
);
};

View file

@ -1884,18 +1884,6 @@
"Remove %(phone)s?": "Remove %(phone)s?",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.",
"Phone Number": "Phone Number",
"Decrypting messages…": "Decrypting messages…",
"Please wait as we try to decrypt your messages. This may take a few moments.": "Please wait as we try to decrypt your messages. This may take a few moments.",
"Verify this device to access all messages": "Verify this device to access all messages",
"This device was unable to decrypt some messages because it has not been verified yet.": "This device was unable to decrypt some messages because it has not been verified yet.",
"Reset your keys to prevent future decryption errors": "Reset your keys to prevent future decryption errors",
"You will not be able to access old undecryptable messages, but resetting your keys will allow you to receive new messages.": "You will not be able to access old undecryptable messages, but resetting your keys will allow you to receive new messages.",
"Open another device to load encrypted messages": "Open another device to load encrypted messages",
"This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up.": "This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up.",
"View your device list": "View your device list",
"Some messages could not be decrypted": "Some messages could not be decrypted",
"Unfortunately, there are no other verified devices to request decryption keys from. Signing in and verifying other devices may help avoid this situation in the future.": "Unfortunately, there are no other verified devices to request decryption keys from. Signing in and verifying other devices may help avoid this situation in the future.",
"Resend key requests": "Resend key requests",
"This user has not verified all of their sessions.": "This user has not verified all of their sessions.",
"You have not verified this user.": "You have not verified this user.",
"You have verified this user. This user has verified all of their sessions.": "You have verified this user. This user has verified all of their sessions.",

View file

@ -1,449 +0,0 @@
/*
Copyright 2022 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 { act, fireEvent, render, screen, waitFor, RenderResult } from "@testing-library/react";
import "@testing-library/jest-dom";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import { DecryptionFailureBar } from "../../../../src/components/views/rooms/DecryptionFailureBar";
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
import { Action } from "../../../../src/dispatcher/actions";
type MockDevice = { deviceId: string };
const verifiedDevice1: MockDevice = { deviceId: "verified1" };
const verifiedDevice2: MockDevice = { deviceId: "verified2" };
const unverifiedDevice1: MockDevice = { deviceId: "unverified1" };
const unverifiedDevice2: MockDevice = { deviceId: "unverified2" };
const mockEvent1 = {
event: { event_id: "mockEvent1" },
getWireContent: () => ({ session_id: "sessionA" }),
};
const mockEvent2 = {
event: { event_id: "mockEvent2" },
getWireContent: () => ({ session_id: "sessionB" }),
};
const mockEvent3 = {
event: { event_id: "mockEvent3" },
getWireContent: () => ({ session_id: "sessionB" }),
};
const userId = "@user:example.com";
let ourDevice: MockDevice | undefined;
let allDevices: MockDevice[] | undefined;
let keyBackup = false;
let callback = async () => {};
const mockClient = {
getUserId: () => userId,
getDeviceId: () => ourDevice?.deviceId,
getStoredDevicesForUser: () => allDevices,
isSecretStored: jest.fn(() => Promise.resolve(keyBackup ? { key: "yes" } : null)),
checkIfOwnDeviceCrossSigned: (deviceId: string) => deviceId.startsWith("verified"),
downloadKeys: jest.fn(() => {}),
cancelAndResendEventRoomKeyRequest: jest.fn(() => {}),
on: (_: any, cb: () => Promise<void>) => {
callback = cb;
},
off: () => {},
};
function getBar(wrapper: RenderResult) {
return wrapper.container.querySelector(".mx_DecryptionFailureBar");
}
describe("<DecryptionFailureBar />", () => {
beforeEach(() => {
jest.useFakeTimers();
jest.spyOn(defaultDispatcher, "dispatch").mockRestore();
});
afterEach(() => {
const container = document.body.firstChild;
container && document.body.removeChild(container);
jest.runOnlyPendingTimers();
jest.useRealTimers();
mockClient.cancelAndResendEventRoomKeyRequest.mockClear();
});
it("Displays a loading spinner", async () => {
ourDevice = unverifiedDevice1;
allDevices = [unverifiedDevice1];
const bar = render(
// @ts-ignore
<MatrixClientContext.Provider value={mockClient}>
<DecryptionFailureBar
failures={[
// @ts-ignore
mockEvent1,
]}
/>
,
</MatrixClientContext.Provider>,
);
await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled());
expect(getBar(bar)).toMatchSnapshot();
bar.unmount();
});
it("Prompts the user to verify if they have other devices", async () => {
ourDevice = unverifiedDevice1;
allDevices = [unverifiedDevice1, verifiedDevice1];
keyBackup = false;
const bar = render(
// @ts-ignore
<MatrixClientContext.Provider value={mockClient}>
<DecryptionFailureBar
failures={[
// @ts-ignore
mockEvent1,
]}
/>
,
</MatrixClientContext.Provider>,
);
await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled());
act(() => {
jest.advanceTimersByTime(5000);
});
expect(getBar(bar)).toMatchSnapshot();
bar.unmount();
});
it("Prompts the user to verify if they have backups", async () => {
ourDevice = unverifiedDevice1;
allDevices = [unverifiedDevice1];
keyBackup = true;
const bar = render(
// @ts-ignore
<MatrixClientContext.Provider value={mockClient}>
<DecryptionFailureBar
failures={[
// @ts-ignore
mockEvent1,
]}
/>
,
</MatrixClientContext.Provider>,
);
await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled());
act(() => {
jest.advanceTimersByTime(5000);
});
expect(getBar(bar)).toMatchSnapshot();
bar.unmount();
});
it("Prompts the user to reset if they have no other verified devices and no backups", async () => {
ourDevice = unverifiedDevice1;
allDevices = [unverifiedDevice1, unverifiedDevice2];
keyBackup = false;
const bar = render(
// @ts-ignore
<MatrixClientContext.Provider value={mockClient}>
<DecryptionFailureBar
failures={[
// @ts-ignore
mockEvent1,
]}
/>
,
</MatrixClientContext.Provider>,
);
await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled());
act(() => {
jest.advanceTimersByTime(5000);
});
expect(getBar(bar)).toMatchSnapshot();
bar.unmount();
});
it("Recommends opening other devices if there are other verified devices", async () => {
ourDevice = verifiedDevice1;
allDevices = [verifiedDevice1, verifiedDevice2];
keyBackup = false;
const bar = render(
// @ts-ignore
<MatrixClientContext.Provider value={mockClient}>
<DecryptionFailureBar
failures={[
// @ts-ignore
mockEvent1,
]}
/>
,
</MatrixClientContext.Provider>,
);
await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled());
act(() => {
jest.advanceTimersByTime(5000);
});
expect(getBar(bar)).toMatchSnapshot();
bar.unmount();
});
it("Displays a general error message if there are no other verified devices", async () => {
ourDevice = verifiedDevice1;
allDevices = [verifiedDevice1, unverifiedDevice1];
keyBackup = true;
const bar = render(
// @ts-ignore
<MatrixClientContext.Provider value={mockClient}>
<DecryptionFailureBar
failures={[
// @ts-ignore
mockEvent1,
]}
/>
,
</MatrixClientContext.Provider>,
);
await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled());
act(() => {
jest.advanceTimersByTime(5000);
});
expect(getBar(bar)).toMatchSnapshot();
bar.unmount();
});
it("Displays button to resend key requests if we are verified", async () => {
ourDevice = verifiedDevice1;
allDevices = [verifiedDevice1, verifiedDevice2];
const bar = render(
// @ts-ignore
<MatrixClientContext.Provider value={mockClient}>
<DecryptionFailureBar
failures={[
// @ts-ignore
mockEvent1,
// @ts-ignore
mockEvent2,
// @ts-ignore
mockEvent3,
]}
/>
,
</MatrixClientContext.Provider>,
);
await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled());
act(() => {
jest.advanceTimersByTime(5000);
});
expect(getBar(bar)).toMatchSnapshot();
fireEvent.click(screen.getByText("Resend key requests"));
expect(mockClient.cancelAndResendEventRoomKeyRequest).toHaveBeenCalledTimes(2);
expect(mockClient.cancelAndResendEventRoomKeyRequest).toHaveBeenCalledWith(mockEvent1);
expect(mockClient.cancelAndResendEventRoomKeyRequest).toHaveBeenCalledWith(mockEvent2);
expect(getBar(bar)).toMatchSnapshot();
bar.unmount();
});
it("Displays button to review device list if we are verified", async () => {
// stub so we dont have to deal with launching modals
jest.spyOn(defaultDispatcher, "dispatch").mockImplementation(() => {});
ourDevice = verifiedDevice1;
allDevices = [verifiedDevice1, verifiedDevice2];
const bar = render(
// @ts-ignore
<MatrixClientContext.Provider value={mockClient}>
<DecryptionFailureBar
failures={[
// @ts-ignore
mockEvent1,
// @ts-ignore
mockEvent2,
// @ts-ignore
mockEvent3,
]}
/>
,
</MatrixClientContext.Provider>,
);
await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled());
act(() => {
jest.advanceTimersByTime(5000);
});
fireEvent.click(screen.getByText("View your device list"));
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: Action.ViewUserDeviceSettings });
bar.unmount();
});
it("Does not display a button to send key requests if we are unverified", async () => {
ourDevice = unverifiedDevice1;
allDevices = [unverifiedDevice1, verifiedDevice2];
const bar = render(
// @ts-ignore
<MatrixClientContext.Provider value={mockClient}>
<DecryptionFailureBar
failures={[
// @ts-ignore
mockEvent1,
// @ts-ignore
mockEvent2,
]}
/>
,
</MatrixClientContext.Provider>,
);
await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled());
act(() => {
jest.advanceTimersByTime(5000);
});
expect(getBar(bar)).toMatchSnapshot();
bar.unmount();
});
it("Displays the button to resend key requests only if there are sessions we haven't already requested", async () => {
ourDevice = verifiedDevice1;
allDevices = [verifiedDevice1, verifiedDevice2];
const bar = render(
// @ts-ignore
<MatrixClientContext.Provider value={mockClient}>
<DecryptionFailureBar
failures={[
// @ts-ignore
mockEvent3,
]}
/>
,
</MatrixClientContext.Provider>,
);
await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled());
act(() => {
jest.advanceTimersByTime(5000);
});
expect(getBar(bar)).toMatchSnapshot();
fireEvent.click(screen.getByText("Resend key requests"));
expect(mockClient.cancelAndResendEventRoomKeyRequest).toHaveBeenCalledTimes(1);
expect(mockClient.cancelAndResendEventRoomKeyRequest).toHaveBeenCalledWith(mockEvent3);
expect(getBar(bar)).toMatchSnapshot();
bar.rerender(
// @ts-ignore
<MatrixClientContext.Provider value={mockClient}>
<DecryptionFailureBar
failures={[
// @ts-ignore
mockEvent1,
// @ts-ignore
mockEvent2,
// @ts-ignore
mockEvent3,
]}
/>
,
</MatrixClientContext.Provider>,
);
expect(getBar(bar)).toMatchSnapshot();
mockClient.cancelAndResendEventRoomKeyRequest.mockClear();
fireEvent.click(screen.getByText("Resend key requests"));
expect(mockClient.cancelAndResendEventRoomKeyRequest).toHaveBeenCalledTimes(1);
expect(mockClient.cancelAndResendEventRoomKeyRequest).toHaveBeenCalledWith(mockEvent1);
expect(getBar(bar)).toMatchSnapshot();
bar.unmount();
});
it("Handles device updates", async () => {
ourDevice = unverifiedDevice1;
allDevices = [unverifiedDevice1, verifiedDevice2];
const bar = render(
// @ts-ignore
<MatrixClientContext.Provider value={mockClient}>
<DecryptionFailureBar
failures={[
// @ts-ignore
mockEvent1,
// @ts-ignore
mockEvent2,
]}
/>
,
</MatrixClientContext.Provider>,
);
await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled());
act(() => {
jest.advanceTimersByTime(5000);
});
expect(getBar(bar)).toMatchSnapshot();
ourDevice = verifiedDevice1;
await act(callback);
expect(getBar(bar)).toMatchSnapshot();
bar.unmount();
});
});

View file

@ -1,705 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<DecryptionFailureBar /> Displays a general error message if there are no other verified devices 1`] = `
<div
class="mx_DecryptionFailureBar"
>
<div
class="mx_DecryptionFailureBar_start"
>
<div
class="mx_DecryptionFailureBar_start_status"
>
<div
data-testid="decryption-failure-bar-indicator"
>
<div
class="mx_DecryptionFailureBar_start_status_icon"
data-testid="decryption-failure-bar-icon"
/>
</div>
</div>
<div
class="mx_DecryptionFailureBar_start_headline"
>
Some messages could not be decrypted
</div>
<div
class="mx_DecryptionFailureBar_start_message"
>
Unfortunately, there are no other verified devices to request decryption keys from. Signing in and verifying other devices may help avoid this situation in the future.
</div>
</div>
<div
class="mx_DecryptionFailureBar_end"
/>
</div>
`;
exports[`<DecryptionFailureBar /> Displays a loading spinner 1`] = `
<div
class="mx_DecryptionFailureBar"
>
<div
class="mx_DecryptionFailureBar_start"
>
<div
class="mx_DecryptionFailureBar_start_status"
>
<div
data-testid="decryption-failure-bar-indicator"
>
<div
class="mx_Spinner"
>
<div
aria-label="Loading…"
class="mx_Spinner_icon"
data-testid="spinner"
role="progressbar"
style="width: 24px; height: 24px;"
/>
</div>
</div>
</div>
<div
class="mx_DecryptionFailureBar_start_headline"
>
Decrypting messages…
</div>
<div
class="mx_DecryptionFailureBar_start_message"
>
Please wait as we try to decrypt your messages. This may take a few moments.
</div>
</div>
<div
class="mx_DecryptionFailureBar_end"
/>
</div>
`;
exports[`<DecryptionFailureBar /> Displays button to resend key requests if we are verified 1`] = `
<div
class="mx_DecryptionFailureBar mx_DecryptionFailureBar--withEnd"
>
<div
class="mx_DecryptionFailureBar_start"
>
<div
class="mx_DecryptionFailureBar_start_status"
>
<div
data-testid="decryption-failure-bar-indicator"
>
<div
class="mx_DecryptionFailureBar_start_status_icon"
data-testid="decryption-failure-bar-icon"
/>
</div>
</div>
<div
class="mx_DecryptionFailureBar_start_headline"
>
Open another device to load encrypted messages
</div>
<div
class="mx_DecryptionFailureBar_start_message"
>
This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up.
</div>
</div>
<div
class="mx_DecryptionFailureBar_end"
>
<div
class="mx_AccessibleButton mx_DecryptionFailureBar_end_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
data-testid="decryption-failure-bar-button"
role="button"
tabindex="0"
>
View your device list
</div>
<div
class="mx_AccessibleButton mx_DecryptionFailureBar_end_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
data-testid="decryption-failure-bar-button"
role="button"
tabindex="0"
>
Resend key requests
</div>
</div>
</div>
`;
exports[`<DecryptionFailureBar /> Displays button to resend key requests if we are verified 2`] = `
<div
class="mx_DecryptionFailureBar mx_DecryptionFailureBar--withEnd"
>
<div
class="mx_DecryptionFailureBar_start"
>
<div
class="mx_DecryptionFailureBar_start_status"
>
<div
data-testid="decryption-failure-bar-indicator"
>
<div
class="mx_DecryptionFailureBar_start_status_icon"
data-testid="decryption-failure-bar-icon"
/>
</div>
</div>
<div
class="mx_DecryptionFailureBar_start_headline"
>
Open another device to load encrypted messages
</div>
<div
class="mx_DecryptionFailureBar_start_message"
>
This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up.
</div>
</div>
<div
class="mx_DecryptionFailureBar_end"
>
<div
class="mx_AccessibleButton mx_DecryptionFailureBar_end_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
data-testid="decryption-failure-bar-button"
role="button"
tabindex="0"
>
View your device list
</div>
</div>
</div>
`;
exports[`<DecryptionFailureBar /> Displays the button to resend key requests only if there are sessions we haven't already requested 1`] = `
<div
class="mx_DecryptionFailureBar mx_DecryptionFailureBar--withEnd"
>
<div
class="mx_DecryptionFailureBar_start"
>
<div
class="mx_DecryptionFailureBar_start_status"
>
<div
data-testid="decryption-failure-bar-indicator"
>
<div
class="mx_DecryptionFailureBar_start_status_icon"
data-testid="decryption-failure-bar-icon"
/>
</div>
</div>
<div
class="mx_DecryptionFailureBar_start_headline"
>
Open another device to load encrypted messages
</div>
<div
class="mx_DecryptionFailureBar_start_message"
>
This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up.
</div>
</div>
<div
class="mx_DecryptionFailureBar_end"
>
<div
class="mx_AccessibleButton mx_DecryptionFailureBar_end_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
data-testid="decryption-failure-bar-button"
role="button"
tabindex="0"
>
View your device list
</div>
<div
class="mx_AccessibleButton mx_DecryptionFailureBar_end_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
data-testid="decryption-failure-bar-button"
role="button"
tabindex="0"
>
Resend key requests
</div>
</div>
</div>
`;
exports[`<DecryptionFailureBar /> Displays the button to resend key requests only if there are sessions we haven't already requested 2`] = `
<div
class="mx_DecryptionFailureBar mx_DecryptionFailureBar--withEnd"
>
<div
class="mx_DecryptionFailureBar_start"
>
<div
class="mx_DecryptionFailureBar_start_status"
>
<div
data-testid="decryption-failure-bar-indicator"
>
<div
class="mx_DecryptionFailureBar_start_status_icon"
data-testid="decryption-failure-bar-icon"
/>
</div>
</div>
<div
class="mx_DecryptionFailureBar_start_headline"
>
Open another device to load encrypted messages
</div>
<div
class="mx_DecryptionFailureBar_start_message"
>
This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up.
</div>
</div>
<div
class="mx_DecryptionFailureBar_end"
>
<div
class="mx_AccessibleButton mx_DecryptionFailureBar_end_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
data-testid="decryption-failure-bar-button"
role="button"
tabindex="0"
>
View your device list
</div>
</div>
</div>
`;
exports[`<DecryptionFailureBar /> Displays the button to resend key requests only if there are sessions we haven't already requested 3`] = `
<div
class="mx_DecryptionFailureBar mx_DecryptionFailureBar--withEnd"
>
<div
class="mx_DecryptionFailureBar_start"
>
<div
class="mx_DecryptionFailureBar_start_status"
>
<div
data-testid="decryption-failure-bar-indicator"
>
<div
class="mx_DecryptionFailureBar_start_status_icon"
data-testid="decryption-failure-bar-icon"
/>
</div>
</div>
<div
class="mx_DecryptionFailureBar_start_headline"
>
Open another device to load encrypted messages
</div>
<div
class="mx_DecryptionFailureBar_start_message"
>
This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up.
</div>
</div>
<div
class="mx_DecryptionFailureBar_end"
>
<div
class="mx_AccessibleButton mx_DecryptionFailureBar_end_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
data-testid="decryption-failure-bar-button"
role="button"
tabindex="0"
>
View your device list
</div>
<div
class="mx_AccessibleButton mx_DecryptionFailureBar_end_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
data-testid="decryption-failure-bar-button"
role="button"
tabindex="0"
>
Resend key requests
</div>
</div>
</div>
`;
exports[`<DecryptionFailureBar /> Displays the button to resend key requests only if there are sessions we haven't already requested 4`] = `
<div
class="mx_DecryptionFailureBar mx_DecryptionFailureBar--withEnd"
>
<div
class="mx_DecryptionFailureBar_start"
>
<div
class="mx_DecryptionFailureBar_start_status"
>
<div
data-testid="decryption-failure-bar-indicator"
>
<div
class="mx_DecryptionFailureBar_start_status_icon"
data-testid="decryption-failure-bar-icon"
/>
</div>
</div>
<div
class="mx_DecryptionFailureBar_start_headline"
>
Open another device to load encrypted messages
</div>
<div
class="mx_DecryptionFailureBar_start_message"
>
This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up.
</div>
</div>
<div
class="mx_DecryptionFailureBar_end"
>
<div
class="mx_AccessibleButton mx_DecryptionFailureBar_end_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
data-testid="decryption-failure-bar-button"
role="button"
tabindex="0"
>
View your device list
</div>
</div>
</div>
`;
exports[`<DecryptionFailureBar /> Does not display a button to send key requests if we are unverified 1`] = `
<div
class="mx_DecryptionFailureBar mx_DecryptionFailureBar--withEnd"
>
<div
class="mx_DecryptionFailureBar_start"
>
<div
class="mx_DecryptionFailureBar_start_status"
>
<div
data-testid="decryption-failure-bar-indicator"
>
<div
class="mx_DecryptionFailureBar_start_status_icon"
data-testid="decryption-failure-bar-icon"
/>
</div>
</div>
<div
class="mx_DecryptionFailureBar_start_headline"
>
Verify this device to access all messages
</div>
<div
class="mx_DecryptionFailureBar_start_message"
>
This device was unable to decrypt some messages because it has not been verified yet.
</div>
</div>
<div
class="mx_DecryptionFailureBar_end"
>
<div
class="mx_AccessibleButton mx_DecryptionFailureBar_end_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
data-testid="decryption-failure-bar-button"
role="button"
tabindex="0"
>
Verify
</div>
</div>
</div>
`;
exports[`<DecryptionFailureBar /> Handles device updates 1`] = `
<div
class="mx_DecryptionFailureBar mx_DecryptionFailureBar--withEnd"
>
<div
class="mx_DecryptionFailureBar_start"
>
<div
class="mx_DecryptionFailureBar_start_status"
>
<div
data-testid="decryption-failure-bar-indicator"
>
<div
class="mx_DecryptionFailureBar_start_status_icon"
data-testid="decryption-failure-bar-icon"
/>
</div>
</div>
<div
class="mx_DecryptionFailureBar_start_headline"
>
Verify this device to access all messages
</div>
<div
class="mx_DecryptionFailureBar_start_message"
>
This device was unable to decrypt some messages because it has not been verified yet.
</div>
</div>
<div
class="mx_DecryptionFailureBar_end"
>
<div
class="mx_AccessibleButton mx_DecryptionFailureBar_end_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
data-testid="decryption-failure-bar-button"
role="button"
tabindex="0"
>
Verify
</div>
</div>
</div>
`;
exports[`<DecryptionFailureBar /> Handles device updates 2`] = `
<div
class="mx_DecryptionFailureBar mx_DecryptionFailureBar--withEnd"
>
<div
class="mx_DecryptionFailureBar_start"
>
<div
class="mx_DecryptionFailureBar_start_status"
>
<div
data-testid="decryption-failure-bar-indicator"
>
<div
class="mx_DecryptionFailureBar_start_status_icon"
data-testid="decryption-failure-bar-icon"
/>
</div>
</div>
<div
class="mx_DecryptionFailureBar_start_headline"
>
Open another device to load encrypted messages
</div>
<div
class="mx_DecryptionFailureBar_start_message"
>
This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up.
</div>
</div>
<div
class="mx_DecryptionFailureBar_end"
>
<div
class="mx_AccessibleButton mx_DecryptionFailureBar_end_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
data-testid="decryption-failure-bar-button"
role="button"
tabindex="0"
>
View your device list
</div>
<div
class="mx_AccessibleButton mx_DecryptionFailureBar_end_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
data-testid="decryption-failure-bar-button"
role="button"
tabindex="0"
>
Resend key requests
</div>
</div>
</div>
`;
exports[`<DecryptionFailureBar /> Prompts the user to reset if they have no other verified devices and no backups 1`] = `
<div
class="mx_DecryptionFailureBar mx_DecryptionFailureBar--withEnd"
>
<div
class="mx_DecryptionFailureBar_start"
>
<div
class="mx_DecryptionFailureBar_start_status"
>
<div
data-testid="decryption-failure-bar-indicator"
>
<div
class="mx_DecryptionFailureBar_start_status_icon"
data-testid="decryption-failure-bar-icon"
/>
</div>
</div>
<div
class="mx_DecryptionFailureBar_start_headline"
>
Reset your keys to prevent future decryption errors
</div>
<div
class="mx_DecryptionFailureBar_start_message"
>
You will not be able to access old undecryptable messages, but resetting your keys will allow you to receive new messages.
</div>
</div>
<div
class="mx_DecryptionFailureBar_end"
>
<div
class="mx_AccessibleButton mx_DecryptionFailureBar_end_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
data-testid="decryption-failure-bar-button"
role="button"
tabindex="0"
>
Reset
</div>
</div>
</div>
`;
exports[`<DecryptionFailureBar /> Prompts the user to verify if they have backups 1`] = `
<div
class="mx_DecryptionFailureBar mx_DecryptionFailureBar--withEnd"
>
<div
class="mx_DecryptionFailureBar_start"
>
<div
class="mx_DecryptionFailureBar_start_status"
>
<div
data-testid="decryption-failure-bar-indicator"
>
<div
class="mx_DecryptionFailureBar_start_status_icon"
data-testid="decryption-failure-bar-icon"
/>
</div>
</div>
<div
class="mx_DecryptionFailureBar_start_headline"
>
Verify this device to access all messages
</div>
<div
class="mx_DecryptionFailureBar_start_message"
>
This device was unable to decrypt some messages because it has not been verified yet.
</div>
</div>
<div
class="mx_DecryptionFailureBar_end"
>
<div
class="mx_AccessibleButton mx_DecryptionFailureBar_end_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
data-testid="decryption-failure-bar-button"
role="button"
tabindex="0"
>
Verify
</div>
</div>
</div>
`;
exports[`<DecryptionFailureBar /> Prompts the user to verify if they have other devices 1`] = `
<div
class="mx_DecryptionFailureBar mx_DecryptionFailureBar--withEnd"
>
<div
class="mx_DecryptionFailureBar_start"
>
<div
class="mx_DecryptionFailureBar_start_status"
>
<div
data-testid="decryption-failure-bar-indicator"
>
<div
class="mx_DecryptionFailureBar_start_status_icon"
data-testid="decryption-failure-bar-icon"
/>
</div>
</div>
<div
class="mx_DecryptionFailureBar_start_headline"
>
Verify this device to access all messages
</div>
<div
class="mx_DecryptionFailureBar_start_message"
>
This device was unable to decrypt some messages because it has not been verified yet.
</div>
</div>
<div
class="mx_DecryptionFailureBar_end"
>
<div
class="mx_AccessibleButton mx_DecryptionFailureBar_end_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
data-testid="decryption-failure-bar-button"
role="button"
tabindex="0"
>
Verify
</div>
</div>
</div>
`;
exports[`<DecryptionFailureBar /> Recommends opening other devices if there are other verified devices 1`] = `
<div
class="mx_DecryptionFailureBar mx_DecryptionFailureBar--withEnd"
>
<div
class="mx_DecryptionFailureBar_start"
>
<div
class="mx_DecryptionFailureBar_start_status"
>
<div
data-testid="decryption-failure-bar-indicator"
>
<div
class="mx_DecryptionFailureBar_start_status_icon"
data-testid="decryption-failure-bar-icon"
/>
</div>
</div>
<div
class="mx_DecryptionFailureBar_start_headline"
>
Open another device to load encrypted messages
</div>
<div
class="mx_DecryptionFailureBar_start_message"
>
This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up.
</div>
</div>
<div
class="mx_DecryptionFailureBar_end"
>
<div
class="mx_AccessibleButton mx_DecryptionFailureBar_end_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
data-testid="decryption-failure-bar-button"
role="button"
tabindex="0"
>
View your device list
</div>
<div
class="mx_AccessibleButton mx_DecryptionFailureBar_end_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
data-testid="decryption-failure-bar-button"
role="button"
tabindex="0"
>
Resend key requests
</div>
</div>
</div>
`;