+ );
+}
diff --git a/packages/shared-components/src/message-body/DecryptionFailureBodyView/__snapshots__/DecryptionFailureBodyView.test.tsx.snap b/packages/shared-components/src/message-body/DecryptionFailureBodyView/__snapshots__/DecryptionFailureBodyView.test.tsx.snap
new file mode 100644
index 0000000000..7bd5899caf
--- /dev/null
+++ b/packages/shared-components/src/message-body/DecryptionFailureBodyView/__snapshots__/DecryptionFailureBodyView.test.tsx.snap
@@ -0,0 +1,187 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`DecryptionFailureBodyView > Should display "The sender has blocked you from receiving this message and device verification is false" 1`] = `
+
+
+ The sender has blocked you from receiving this message because your device is unverified
+
+
+`;
+
+exports[`DecryptionFailureBodyView > Should display "The sender has blocked you from receiving this message and device verification is true" 1`] = `
+
+
+ The sender has blocked you from receiving this message because your device is unverified
+
+
+`;
+
+exports[`DecryptionFailureBodyView > Should display "Unable to decrypt message and device verification is false" 1`] = `
+
+
+ Unable to decrypt message
+
+
+`;
+
+exports[`DecryptionFailureBodyView > Should display "Unable to decrypt message and device verification is true" 1`] = `
+
+
+ Unable to decrypt message
+
+
+`;
+
+exports[`DecryptionFailureBodyView > Should display with extra class names 1`] = `
+
+
+ Unable to decrypt message
+
+
+`;
+
+exports[`DecryptionFailureBodyView > should handle historical messages with no key backup and device verification is false 1`] = `
+
+
+ Historical messages are not available on this device
+
+
+`;
+
+exports[`DecryptionFailureBodyView > should handle historical messages with no key backup and device verification is true 1`] = `
+
+
+ Historical messages are not available on this device
+
+
+`;
+
+exports[`DecryptionFailureBodyView > should handle messages from unverified devices and device verification is false 1`] = `
+
+
+
+
+ Sent from an insecure device.
+
+
+
+`;
+
+exports[`DecryptionFailureBodyView > should handle messages from unverified devices and device verification is true 1`] = `
+
+
+
+
+ Sent from an insecure device.
+
+
+
+`;
+
+exports[`DecryptionFailureBodyView > should handle messages from users who change identities after verification and device verification is false 1`] = `
+
+
+
+
+ Sender's verified identity was reset
+
+
+
+`;
+
+exports[`DecryptionFailureBodyView > should handle messages from users who change identities after verification and device verification is true 1`] = `
+
+
+
+
+ Sender's verified identity was reset
+
+
+
+`;
+
+exports[`DecryptionFailureBodyView > should handle undecryptable pre-join messages and device verification is false 1`] = `
+
+
+ You don't have access to this message
+
+
+`;
+
+exports[`DecryptionFailureBodyView > should handle undecryptable pre-join messages and device verification is true 1`] = `
+
+
+ You don't have access to this message
+
+
+`;
diff --git a/packages/shared-components/src/message-body/DecryptionFailureBodyView/index.tsx b/packages/shared-components/src/message-body/DecryptionFailureBodyView/index.tsx
new file mode 100644
index 0000000000..bc533a89fd
--- /dev/null
+++ b/packages/shared-components/src/message-body/DecryptionFailureBodyView/index.tsx
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2026 Element Creations Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+export {
+ DecryptionFailureBodyView,
+ DecryptionFailureReason,
+ type DecryptionFailureBodyViewModel,
+ type DecryptionFailureBodyViewSnapshot,
+} from "./DecryptionFailureBodyView";
diff --git a/res/css/_components.pcss b/res/css/_components.pcss
index a93f040b6c..60bca97841 100644
--- a/res/css/_components.pcss
+++ b/res/css/_components.pcss
@@ -220,7 +220,6 @@
@import "./views/messages/_CallEvent.pcss";
@import "./views/messages/_CreateEvent.pcss";
@import "./views/messages/_DateSeparator.pcss";
-@import "./views/messages/_DecryptionFailureBody.pcss";
@import "./views/messages/_DisambiguatedProfile.pcss";
@import "./views/messages/_EventTileBubble.pcss";
@import "./views/messages/_HiddenBody.pcss";
diff --git a/res/css/views/messages/_DecryptionFailureBody.pcss b/res/css/views/messages/_DecryptionFailureBody.pcss
deleted file mode 100644
index 4a4940abe3..0000000000
--- a/res/css/views/messages/_DecryptionFailureBody.pcss
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2022 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-.mx_DecryptionFailureBody {
- color: $secondary-content;
- font-style: italic;
-}
-
-/* Formatting for errors due to sender trust requirement failures */
-.mx_DecryptionFailureSenderTrustRequirement > span {
- /* some space between the (/) icon and text */
- display: inline-flex;
- gap: var(--cpd-space-1x);
-
- /* Center vertically */
- align-items: center;
-}
diff --git a/src/components/structures/MatrixClientContextProvider.tsx b/src/components/structures/MatrixClientContextProvider.tsx
index 7d555f5809..f99ad901e6 100644
--- a/src/components/structures/MatrixClientContextProvider.tsx
+++ b/src/components/structures/MatrixClientContextProvider.tsx
@@ -11,9 +11,9 @@ import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
import { logger } from "matrix-js-sdk/src/logger";
+import { LocalDeviceVerificationStateContext } from "../../contexts/LocalDeviceVerificationStateContext";
import MatrixClientContext from "../../contexts/MatrixClientContext";
import { useEventEmitter } from "../../hooks/useEventEmitter";
-import { LocalDeviceVerificationStateContext } from "../../contexts/LocalDeviceVerificationStateContext";
/**
* A React hook whose value is whether the local device has been "verified".
diff --git a/src/components/views/messages/DecryptionFailureBody.tsx b/src/components/views/messages/DecryptionFailureBody.tsx
deleted file mode 100644
index f75a7c48f8..0000000000
--- a/src/components/views/messages/DecryptionFailureBody.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2022-2024 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import classNames from "classnames";
-import React, { type JSX, useContext } from "react";
-import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
-import { DecryptionFailureCode } from "matrix-js-sdk/src/crypto-api";
-import { BlockIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
-
-import { _t } from "../../../languageHandler";
-import { type IBodyProps } from "./IBodyProps";
-import { LocalDeviceVerificationStateContext } from "../../../contexts/LocalDeviceVerificationStateContext";
-
-function getErrorMessage(mxEvent: MatrixEvent, isVerified: boolean | undefined): string | JSX.Element {
- switch (mxEvent.decryptionFailureReason) {
- case DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE:
- return _t("timeline|decryption_failure|blocked");
-
- case DecryptionFailureCode.HISTORICAL_MESSAGE_NO_KEY_BACKUP:
- return _t("timeline|decryption_failure|historical_event_no_key_backup");
-
- case DecryptionFailureCode.HISTORICAL_MESSAGE_BACKUP_UNCONFIGURED:
- if (isVerified === false) {
- // The user seems to have a key backup, so prompt them to verify in the hope that doing so will
- // mean we can restore from backup and we'll get the key for this message.
- return _t("timeline|decryption_failure|historical_event_unverified_device");
- }
- // otherwise, use the default.
- break;
-
- case DecryptionFailureCode.HISTORICAL_MESSAGE_USER_NOT_JOINED:
- // TODO: event should be hidden instead of showing this error.
- // To be revisited as part of https://github.com/element-hq/element-meta/issues/2449
- return _t("timeline|decryption_failure|historical_event_user_not_joined");
-
- case DecryptionFailureCode.SENDER_IDENTITY_PREVIOUSLY_VERIFIED:
- return (
-
-
- {_t("timeline|decryption_failure|sender_identity_previously_verified")}
-
- );
-
- case DecryptionFailureCode.UNSIGNED_SENDER_DEVICE:
- // TODO: event should be hidden instead of showing this error.
- // To be revisited as part of https://github.com/element-hq/element-meta/issues/2449
- return (
-
-
- {_t("timeline|decryption_failure|sender_unsigned_device")}
-
- );
- }
- return _t("timeline|decryption_failure|unable_to_decrypt");
-}
-
-/** Get an extra CSS class, specific to the decryption failure reason */
-function errorClassName(mxEvent: MatrixEvent): string | null {
- switch (mxEvent.decryptionFailureReason) {
- case DecryptionFailureCode.SENDER_IDENTITY_PREVIOUSLY_VERIFIED:
- case DecryptionFailureCode.UNSIGNED_SENDER_DEVICE:
- return "mx_DecryptionFailureSenderTrustRequirement";
-
- default:
- return null;
- }
-}
-
-// A placeholder element for messages that could not be decrypted
-export const DecryptionFailureBody = ({ mxEvent, ref }: IBodyProps): JSX.Element => {
- const verificationState = useContext(LocalDeviceVerificationStateContext);
- const classes = classNames("mx_DecryptionFailureBody", "mx_EventTile_content", errorClassName(mxEvent));
-
- return (
-
- {getErrorMessage(mxEvent, verificationState)}
-
- );
-};
diff --git a/src/components/views/messages/MessageEvent.tsx b/src/components/views/messages/MessageEvent.tsx
index 6d124c88a8..6bb28fd188 100644
--- a/src/components/views/messages/MessageEvent.tsx
+++ b/src/components/views/messages/MessageEvent.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import mime from "mime";
-import React, { createRef } from "react";
+import React, { type JSX, createRef, useContext, useEffect } from "react";
import { logger } from "matrix-js-sdk/src/logger";
import {
EventType,
@@ -18,7 +18,9 @@ import {
M_POLL_START,
type IContent,
} from "matrix-js-sdk/src/matrix";
+import { useCreateAutoDisposedViewModel, DecryptionFailureBodyView } from "@element-hq/web-shared-components";
+import { LocalDeviceVerificationStateContext } from "../../../contexts/LocalDeviceVerificationStateContext";
import SettingsStore from "../../../settings/SettingsStore";
import { Mjolnir } from "../../../mjolnir/Mjolnir";
import RedactedBody from "./RedactedBody";
@@ -36,8 +38,8 @@ import MPollBody from "./MPollBody";
import MLocationBody from "./MLocationBody";
import MjolnirBody from "./MjolnirBody";
import MBeaconBody from "./MBeaconBody";
-import { DecryptionFailureBody } from "./DecryptionFailureBody";
import { type GetRelationsForEvent, type IEventTileOps } from "../rooms/EventTile";
+import { DecryptionFailureBodyViewModel } from "../../../viewmodels/message-body/DecryptionFailureBodyViewModel";
// onMessageAllowed is handled internally
interface IProps extends Omit {
@@ -248,7 +250,7 @@ export default class MessageEvent extends React.Component implements IMe
if (!this.props.mxEvent.isRedacted()) {
// only resolve BodyType if event is not redacted
if (this.props.mxEvent.isDecryptionFailure()) {
- BodyType = DecryptionFailureBody;
+ BodyType = DecryptionFailureBodyWrapper;
} else if (type && this.evTypes.has(type)) {
BodyType = this.evTypes.get(type)!;
} else if (msgtype && this.bodyTypes.has(msgtype)) {
@@ -328,3 +330,22 @@ const CaptionBody: React.FunctionComponent
);
+
+/**
+ * Bridge decryption-failure events into the view model using current local verification state.
+ * This wrapper can be removed after MessageEvent has been changed to a function component.
+ */
+function DecryptionFailureBodyWrapper({ mxEvent, ref }: IBodyProps): JSX.Element {
+ const verificationState = useContext(LocalDeviceVerificationStateContext);
+ const vm = useCreateAutoDisposedViewModel(
+ () =>
+ new DecryptionFailureBodyViewModel({
+ decryptionFailureCode: mxEvent.decryptionFailureReason,
+ verificationState,
+ }),
+ );
+ useEffect(() => {
+ vm.setVerificationState(verificationState);
+ }, [verificationState, vm]);
+ return ;
+}
diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx
index 2546936ab5..93225a43dc 100644
--- a/src/components/views/rooms/EventTile.tsx
+++ b/src/components/views/rooms/EventTile.tsx
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
-import React, { createRef, type JSX, type Ref, type MouseEvent, type ReactNode } from "react";
+import React, { createRef, useContext, useEffect, type JSX, type Ref, type MouseEvent, type ReactNode } from "react";
import classNames from "classnames";
import {
EventStatus,
@@ -36,13 +36,14 @@ import {
import { Tooltip } from "@vector-im/compound-web";
import { uniqueId } from "lodash";
import { CircleIcon, CheckCircleIcon, ThreadsIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
+import { useCreateAutoDisposedViewModel, DecryptionFailureBodyView } from "@element-hq/web-shared-components";
+import { LocalDeviceVerificationStateContext } from "../../../contexts/LocalDeviceVerificationStateContext";
import ReplyChain from "../elements/ReplyChain";
import { _t } from "../../../languageHandler";
import dis from "../../../dispatcher/dispatcher";
import { Layout } from "../../../settings/enums/Layout";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
-import { DecryptionFailureBody } from "../messages/DecryptionFailureBody";
import RoomAvatar from "../avatars/RoomAvatar";
import MessageContextMenu from "../context_menus/MessageContextMenu";
import { aboveRightOf } from "../../structures/ContextMenu";
@@ -84,6 +85,7 @@ import PinningUtils from "../../../utils/PinningUtils";
import { PinnedMessageBadge } from "../messages/PinnedMessageBadge";
import { EventPreview } from "./EventPreview";
import { ElementCallEventType } from "../../../call-types";
+import { DecryptionFailureBodyViewModel } from "../../../viewmodels/message-body/DecryptionFailureBodyViewModel";
import { E2eMessageSharedIcon } from "./EventTile/E2eMessageSharedIcon.tsx";
import { E2ePadlock, E2ePadlockIcon } from "./EventTile/E2ePadlock.tsx";
@@ -1373,7 +1375,7 @@ export class UnwrappedEventTile extends React.Component
{this.props.mxEvent.isRedacted() ? (
) : this.props.mxEvent.isDecryptionFailure() ? (
-
+
) : (
)}
@@ -1569,3 +1571,23 @@ function SentReceipt({ messageState }: ISentReceiptProps): JSX.Element {
);
}
+
+/**
+ * Bridge decryption-failure events into the view model using current local verification state.
+ * This wrapper can be removed after EventTile has been changed to a function component.
+ */
+function DecryptionFailureBodyWrapper({ mxEvent }: { mxEvent: MatrixEvent }): JSX.Element {
+ const verificationState = useContext(LocalDeviceVerificationStateContext);
+ const vm = useCreateAutoDisposedViewModel(
+ () =>
+ new DecryptionFailureBodyViewModel({
+ decryptionFailureCode: mxEvent.decryptionFailureReason,
+ verificationState,
+ }),
+ );
+ useEffect(() => {
+ vm.setVerificationState(verificationState);
+ }, [verificationState, vm]);
+
+ return ;
+}
diff --git a/src/contexts/LocalDeviceVerificationStateContext.ts b/src/contexts/LocalDeviceVerificationStateContext.ts
index df5af67252..9d0a24ede0 100644
--- a/src/contexts/LocalDeviceVerificationStateContext.ts
+++ b/src/contexts/LocalDeviceVerificationStateContext.ts
@@ -1,10 +1,9 @@
/*
-Copyright 2024 New Vector Ltd.
-Copyright 2024 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
+ * Copyright 2026 Element Creations Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
import { createContext } from "react";
@@ -13,7 +12,5 @@ import { createContext } from "react";
*
* (Specifically, this is true if we have done enough verification to confirm that the published public cross-signing
* keys are genuine -- which normally means that we or another device will have published a signature of this device.)
- *
- * This context is available to all components under {@link LoggedInView}, via {@link MatrixClientContextProvider}.
*/
export const LocalDeviceVerificationStateContext = createContext(false);
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index c9e5e1e028..a5b5ec50ca 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -3394,12 +3394,7 @@
"creation_summary_dm": "%(creator)s created this DM.",
"creation_summary_room": "%(creator)s created and configured the room.",
"decryption_failure": {
- "blocked": "The sender has blocked you from receiving this message because your device is unverified",
- "historical_event_no_key_backup": "Historical messages are not available on this device",
- "historical_event_unverified_device": "You need to verify this device for access to historical messages",
- "historical_event_user_not_joined": "You don't have access to this message",
"sender_identity_previously_verified": "Sender's verified identity was reset",
- "sender_unsigned_device": "Sent from an insecure device.",
"unable_to_decrypt": "Unable to decrypt message"
},
"disambiguated_profile": "%(displayName)s (%(matrixId)s)",
diff --git a/src/viewmodels/message-body/DecryptionFailureBodyViewModel.ts b/src/viewmodels/message-body/DecryptionFailureBodyViewModel.ts
new file mode 100644
index 0000000000..965c4a0942
--- /dev/null
+++ b/src/viewmodels/message-body/DecryptionFailureBodyViewModel.ts
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2026 Element Creations Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { DecryptionFailureCode } from "matrix-js-sdk/src/crypto-api";
+import {
+ BaseViewModel,
+ DecryptionFailureReason,
+ type DecryptionFailureBodyViewSnapshot as DecryptionFailureBodyViewSnapshotInterface,
+ type DecryptionFailureBodyViewModel as DecryptionFailureBodyViewModelInterface,
+} from "@element-hq/web-shared-components";
+
+export interface DecryptionFailureBodyViewModelProps {
+ /**
+ * The message event being rendered.
+ */
+ decryptionFailureCode: DecryptionFailureCode | null;
+ /**
+ * The local device verification state.
+ */
+ verificationState?: boolean;
+ /**
+ * Extra CSS classes to apply to the component
+ */
+ extraClassNames?: string[];
+}
+
+/**
+ * ViewModel for the decryption failure body, providing the current state of the component.
+ */
+export class DecryptionFailureBodyViewModel
+ extends BaseViewModel
+ implements DecryptionFailureBodyViewModelInterface
+{
+ /**
+ * Convert enum DecryptionFailureCode to enum DecryptionFailureReason.
+ */
+ private static getDecryptionReasonFromCode(
+ decryptionFailureCode: DecryptionFailureCode | null,
+ ): DecryptionFailureReason {
+ switch (decryptionFailureCode) {
+ case DecryptionFailureCode.HISTORICAL_MESSAGE_BACKUP_UNCONFIGURED:
+ return DecryptionFailureReason.HISTORICAL_MESSAGE_BACKUP_UNCONFIGURED;
+ case DecryptionFailureCode.HISTORICAL_MESSAGE_NO_KEY_BACKUP:
+ return DecryptionFailureReason.HISTORICAL_MESSAGE_NO_KEY_BACKUP;
+ case DecryptionFailureCode.HISTORICAL_MESSAGE_USER_NOT_JOINED:
+ return DecryptionFailureReason.HISTORICAL_MESSAGE_USER_NOT_JOINED;
+ case DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE:
+ return DecryptionFailureReason.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE;
+ case DecryptionFailureCode.SENDER_IDENTITY_PREVIOUSLY_VERIFIED:
+ return DecryptionFailureReason.SENDER_IDENTITY_PREVIOUSLY_VERIFIED;
+ case DecryptionFailureCode.UNSIGNED_SENDER_DEVICE:
+ return DecryptionFailureReason.UNSIGNED_SENDER_DEVICE;
+ default:
+ return DecryptionFailureReason.UNABLE_TO_DECRYPT;
+ }
+ }
+
+ /**
+ * @param decryptionFailureCode - The decryption failure code for the event.
+ * @param verificationState - The local device verification state.
+ * @param extraClassNames - Extra CSS classes to apply to the component.
+ */
+ private static readonly computeSnapshot = (
+ decryptionFailureCode: DecryptionFailureCode | null,
+ verificationState?: boolean,
+ extraClassNames?: string[],
+ ): DecryptionFailureBodyViewSnapshotInterface => {
+ // Keep mx_DecryptionFailureBody and mx_EventTile_content to support the compatibility with existing timeline and the all the layout
+ const defaultClassNames = ["mx_DecryptionFailureBody", "mx_EventTile_content"];
+ return {
+ decryptionFailureReason: DecryptionFailureBodyViewModel.getDecryptionReasonFromCode(decryptionFailureCode),
+ isLocalDeviceVerified: verificationState,
+ extraClassNames: extraClassNames ? defaultClassNames.concat(extraClassNames) : defaultClassNames,
+ };
+ };
+
+ public constructor(props: DecryptionFailureBodyViewModelProps) {
+ super(
+ props,
+ DecryptionFailureBodyViewModel.computeSnapshot(
+ props.decryptionFailureCode,
+ props.verificationState,
+ props.extraClassNames,
+ ),
+ );
+ }
+
+ /**
+ * Updates the properties of the view model and recomputes the snapshot.
+ * @param verificationState - The updated local device verification state.
+ */
+ public setVerificationState(verificationState?: boolean): void {
+ this.props.verificationState = verificationState;
+ this.snapshot.merge({ isLocalDeviceVerified: verificationState });
+ }
+}
diff --git a/test/unit-tests/components/structures/MatrixClientContextProvider-test.tsx b/test/unit-tests/components/structures/MatrixClientContextProvider-test.tsx
index 2710dcd57a..b70017ee4f 100644
--- a/test/unit-tests/components/structures/MatrixClientContextProvider-test.tsx
+++ b/test/unit-tests/components/structures/MatrixClientContextProvider-test.tsx
@@ -11,9 +11,9 @@ import React, { useContext } from "react";
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { CryptoEvent, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
+import { LocalDeviceVerificationStateContext } from "../../../../src/contexts/LocalDeviceVerificationStateContext";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import { MatrixClientContextProvider } from "../../../../src/components/structures/MatrixClientContextProvider";
-import { LocalDeviceVerificationStateContext } from "../../../../src/contexts/LocalDeviceVerificationStateContext";
import {
flushPromises,
getMockClientWithEventEmitter,
diff --git a/test/unit-tests/components/views/messages/DecryptionFailureBody-test.tsx b/test/unit-tests/components/views/messages/DecryptionFailureBody-test.tsx
deleted file mode 100644
index ce628da309..0000000000
--- a/test/unit-tests/components/views/messages/DecryptionFailureBody-test.tsx
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright 2024 New Vector Ltd.
- * Copyright 2023 The Matrix.org Foundation C.I.C.
- *
- * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
- * Please see LICENSE files in the repository root for full details.
- */
-
-import React from "react";
-import { render } from "jest-matrix-react";
-import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
-import { mkDecryptionFailureMatrixEvent } from "matrix-js-sdk/src/testing";
-import { DecryptionFailureCode } from "matrix-js-sdk/src/crypto-api";
-
-import { mkEvent } from "../../../../test-utils";
-import { DecryptionFailureBody } from "../../../../../src/components/views/messages/DecryptionFailureBody";
-import { LocalDeviceVerificationStateContext } from "../../../../../src/contexts/LocalDeviceVerificationStateContext";
-
-describe("DecryptionFailureBody", () => {
- function customRender(event: MatrixEvent, localDeviceVerified: boolean = false) {
- return render(
-
-
- ,
- );
- }
-
- it(`Should display "Unable to decrypt message"`, () => {
- // When
- const event = mkEvent({
- type: "m.room.message",
- room: "myfakeroom",
- user: "myfakeuser",
- content: {
- msgtype: "m.bad.encrypted",
- },
- event: true,
- });
- const { container } = customRender(event);
-
- // Then
- expect(container).toMatchSnapshot();
- });
-
- it(`Should display "The sender has blocked you from receiving this message"`, async () => {
- // When
- const event = await mkDecryptionFailureMatrixEvent({
- code: DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE,
- msg: "withheld",
- roomId: "myfakeroom",
- sender: "myfakeuser",
- });
-
- const { container } = customRender(event);
-
- // Then
- expect(container).toMatchSnapshot();
- });
-
- it("should handle historical messages with no key backup", async () => {
- // When
- const event = await mkDecryptionFailureMatrixEvent({
- code: DecryptionFailureCode.HISTORICAL_MESSAGE_NO_KEY_BACKUP,
- msg: "No backup",
- roomId: "fakeroom",
- sender: "fakesender",
- });
- const { container } = customRender(event);
-
- // Then
- expect(container).toHaveTextContent("Historical messages are not available on this device");
- });
-
- it.each([true, false])(
- "should handle historical messages when there is a backup and device verification is %s",
- async (verified) => {
- // When
- const event = await mkDecryptionFailureMatrixEvent({
- code: DecryptionFailureCode.HISTORICAL_MESSAGE_BACKUP_UNCONFIGURED,
- msg: "Failure",
- roomId: "fakeroom",
- sender: "fakesender",
- });
- const { container } = customRender(event, verified);
-
- // Then
- expect(container).toHaveTextContent(
- verified ? "Unable to decrypt" : "You need to verify this device for access to historical messages",
- );
- },
- );
-
- it("should handle undecryptable pre-join messages", async () => {
- // When
- const event = await mkDecryptionFailureMatrixEvent({
- code: DecryptionFailureCode.HISTORICAL_MESSAGE_USER_NOT_JOINED,
- msg: "Not joined",
- roomId: "fakeroom",
- sender: "fakesender",
- });
- const { container } = customRender(event);
-
- // Then
- expect(container).toHaveTextContent("You don't have access to this message");
- });
-
- it("should handle messages from users who change identities after verification", async () => {
- // When
- const event = await mkDecryptionFailureMatrixEvent({
- code: DecryptionFailureCode.SENDER_IDENTITY_PREVIOUSLY_VERIFIED,
- msg: "User previously verified",
- roomId: "fakeroom",
- sender: "fakesender",
- });
- const { container } = customRender(event);
-
- // Then
- expect(container).toMatchSnapshot();
- });
-
- it("should handle messages from unverified devices", async () => {
- // When
- const event = await mkDecryptionFailureMatrixEvent({
- code: DecryptionFailureCode.UNSIGNED_SENDER_DEVICE,
- msg: "Unsigned device",
- roomId: "fakeroom",
- sender: "fakesender",
- });
- const { container } = customRender(event);
-
- // Then
- expect(container).toHaveTextContent("Sent from an insecure device");
- });
-});
diff --git a/test/unit-tests/components/views/messages/__snapshots__/DecryptionFailureBody-test.tsx.snap b/test/unit-tests/components/views/messages/__snapshots__/DecryptionFailureBody-test.tsx.snap
deleted file mode 100644
index 823b1f5e6c..0000000000
--- a/test/unit-tests/components/views/messages/__snapshots__/DecryptionFailureBody-test.tsx.snap
+++ /dev/null
@@ -1,45 +0,0 @@
-// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
-
-exports[`DecryptionFailureBody Should display "The sender has blocked you from receiving this message" 1`] = `
-
-
- The sender has blocked you from receiving this message because your device is unverified
-
-
-`;
-
-exports[`DecryptionFailureBody Should display "Unable to decrypt message" 1`] = `
-
-
- Unable to decrypt message
-
-
-`;
-
-exports[`DecryptionFailureBody should handle messages from users who change identities after verification 1`] = `
-