First batch: Replace MatrixClient.isRoomEncrypted by MatrixClient.CryptoApi.isEncryptionEnabledInRoom (#28242)

* Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in `DeviceListener.ts`

* Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in `Searching.ts`

* Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in `SlidingSyncManager.ts`

* Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in `EncryptionEvent.tsx`

* Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in `ReportEventDialog.tsx`

* Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in `RoomNotifications.tsx`

* Fix MessagePanel-test.tsx

* ReplaceReplace `MatrixCient..isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in `shouldSkipSetupEncryption.ts`

* Add missing `await`

* Use `Promise.any` instead of `asyncSome`

* Add `asyncSomeParallel`

* Use `asyncSomeParallel` instead of  `asyncSome`
This commit is contained in:
Florian Duros 2024-11-19 11:09:25 +01:00 committed by GitHub
parent c8e4ffe1dd
commit d4ab40990b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 129 additions and 43 deletions

View file

@ -46,6 +46,7 @@ import SettingsStore, { CallbackFn } from "./settings/SettingsStore";
import { UIFeature } from "./settings/UIFeature"; import { UIFeature } from "./settings/UIFeature";
import { isBulkUnverifiedDeviceReminderSnoozed } from "./utils/device/snoozeBulkUnverifiedDeviceReminder"; import { isBulkUnverifiedDeviceReminderSnoozed } from "./utils/device/snoozeBulkUnverifiedDeviceReminder";
import { getUserDeviceIds } from "./utils/crypto/deviceInfo"; import { getUserDeviceIds } from "./utils/crypto/deviceInfo";
import { asyncSomeParallel } from "./utils/arrays.ts";
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000; const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
@ -240,13 +241,16 @@ export default class DeviceListener {
return this.keyBackupInfo; return this.keyBackupInfo;
} }
private shouldShowSetupEncryptionToast(): boolean { private async shouldShowSetupEncryptionToast(): Promise<boolean> {
// If we're in the middle of a secret storage operation, we're likely // If we're in the middle of a secret storage operation, we're likely
// modifying the state involved here, so don't add new toasts to setup. // modifying the state involved here, so don't add new toasts to setup.
if (isSecretStorageBeingAccessed()) return false; if (isSecretStorageBeingAccessed()) return false;
// Show setup toasts once the user is in at least one encrypted room. // Show setup toasts once the user is in at least one encrypted room.
const cli = this.client; const cli = this.client;
return cli?.getRooms().some((r) => cli.isRoomEncrypted(r.roomId)) ?? false; const cryptoApi = cli?.getCrypto();
if (!cli || !cryptoApi) return false;
return await asyncSomeParallel(cli.getRooms(), ({ roomId }) => cryptoApi.isEncryptionEnabledInRoom(roomId));
} }
private recheck(): void { private recheck(): void {
@ -283,7 +287,7 @@ export default class DeviceListener {
hideSetupEncryptionToast(); hideSetupEncryptionToast();
this.checkKeyBackupStatus(); this.checkKeyBackupStatus();
} else if (this.shouldShowSetupEncryptionToast()) { } else if (await this.shouldShowSetupEncryptionToast()) {
// make sure our keys are finished downloading // make sure our keys are finished downloading
await crypto.getUserDeviceInfo([cli.getSafeUserId()]); await crypto.getUserDeviceInfo([cli.getSafeUserId()]);

View file

@ -596,7 +596,7 @@ async function combinedPagination(
return result; return result;
} }
function eventIndexSearch( async function eventIndexSearch(
client: MatrixClient, client: MatrixClient,
term: string, term: string,
roomId?: string, roomId?: string,
@ -605,7 +605,7 @@ function eventIndexSearch(
let searchPromise: Promise<ISearchResults>; let searchPromise: Promise<ISearchResults>;
if (roomId !== undefined) { if (roomId !== undefined) {
if (client.isRoomEncrypted(roomId)) { if (await client.getCrypto()?.isEncryptionEnabledInRoom(roomId)) {
// The search is for a single encrypted room, use our local // The search is for a single encrypted room, use our local
// search method. // search method.
searchPromise = localSearchProcess(client, term, roomId); searchPromise = localSearchProcess(client, term, roomId);

View file

@ -229,7 +229,7 @@ export class SlidingSyncManager {
subscriptions.delete(roomId); subscriptions.delete(roomId);
} }
const room = this.client?.getRoom(roomId); const room = this.client?.getRoom(roomId);
let shouldLazyLoad = !this.client?.isRoomEncrypted(roomId); let shouldLazyLoad = !(await this.client?.getCrypto()?.isEncryptionEnabledInRoom(roomId));
if (!room) { if (!room) {
// default to safety: request all state if we can't work it out. This can happen if you // default to safety: request all state if we can't work it out. This can happen if you
// refresh the app whilst viewing a room: we call setRoomVisible before we know anything // refresh the app whilst viewing a room: we call setRoomVisible before we know anything

View file

@ -427,7 +427,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} }
} else if ( } else if (
(await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) && (await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) &&
!shouldSkipSetupEncryption(cli) !(await shouldSkipSetupEncryption(cli))
) { ) {
// if cross-signing is not yet set up, do so now if possible. // if cross-signing is not yet set up, do so now if possible.
this.setStateForNewView({ view: Views.E2E_SETUP }); this.setStateForNewView({ view: Views.E2E_SETUP });

View file

@ -43,6 +43,10 @@ interface IState {
// If we know it, the nature of the abuse, as specified by MSC3215. // If we know it, the nature of the abuse, as specified by MSC3215.
nature?: ExtendedNature; nature?: ExtendedNature;
ignoreUserToo: boolean; // if true, user will be ignored/blocked on submit ignoreUserToo: boolean; // if true, user will be ignored/blocked on submit
/*
* Whether the room is encrypted.
*/
isRoomEncrypted: boolean;
} }
const MODERATED_BY_STATE_EVENT_TYPE = [ const MODERATED_BY_STATE_EVENT_TYPE = [
@ -188,9 +192,20 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
// If specified, the nature of the abuse, as specified by MSC3215. // If specified, the nature of the abuse, as specified by MSC3215.
nature: undefined, nature: undefined,
ignoreUserToo: false, // default false, for now. Could easily be argued as default true ignoreUserToo: false, // default false, for now. Could easily be argued as default true
isRoomEncrypted: false, // async, will be set later
}; };
} }
public componentDidMount = async (): Promise<void> => {
const crypto = MatrixClientPeg.safeGet().getCrypto();
const roomId = this.props.mxEvent.getRoomId();
if (!crypto || !roomId) return;
this.setState({
isRoomEncrypted: await crypto.isEncryptionEnabledInRoom(roomId),
});
};
private onIgnoreUserTooChanged = (newVal: boolean): void => { private onIgnoreUserTooChanged = (newVal: boolean): void => {
this.setState({ ignoreUserToo: newVal }); this.setState({ ignoreUserToo: newVal });
}; };
@ -319,7 +334,6 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
if (this.moderation) { if (this.moderation) {
// Display report-to-moderator dialog. // Display report-to-moderator dialog.
// We let the user pick a nature. // We let the user pick a nature.
const client = MatrixClientPeg.safeGet();
const homeServerName = SdkConfig.get("validated_server_config")!.hsName; const homeServerName = SdkConfig.get("validated_server_config")!.hsName;
let subtitle: string; let subtitle: string;
switch (this.state.nature) { switch (this.state.nature) {
@ -336,7 +350,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
subtitle = _t("report_content|nature_spam"); subtitle = _t("report_content|nature_spam");
break; break;
case NonStandardValue.Admin: case NonStandardValue.Admin:
if (client.isRoomEncrypted(this.props.mxEvent.getRoomId()!)) { if (this.state.isRoomEncrypted) {
subtitle = _t("report_content|nature_nonstandard_admin_encrypted", { subtitle = _t("report_content|nature_nonstandard_admin_encrypted", {
homeserver: homeServerName, homeserver: homeServerName,
}); });

View file

@ -17,6 +17,7 @@ import { determineUnreadState } from "../../../../RoomNotifs";
import { humanReadableNotificationLevel } from "../../../../stores/notifications/NotificationLevel"; import { humanReadableNotificationLevel } from "../../../../stores/notifications/NotificationLevel";
import { doesRoomOrThreadHaveUnreadMessages } from "../../../../Unread"; import { doesRoomOrThreadHaveUnreadMessages } from "../../../../Unread";
import BaseTool, { DevtoolsContext, IDevtoolsProps } from "./BaseTool"; import BaseTool, { DevtoolsContext, IDevtoolsProps } from "./BaseTool";
import { useIsEncrypted } from "../../../../hooks/useIsEncrypted.ts";
function UserReadUpTo({ target }: { target: ReadReceipt<any, any> }): JSX.Element { function UserReadUpTo({ target }: { target: ReadReceipt<any, any> }): JSX.Element {
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
@ -59,6 +60,7 @@ function UserReadUpTo({ target }: { target: ReadReceipt<any, any> }): JSX.Elemen
export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Element { export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Element {
const { room } = useContext(DevtoolsContext); const { room } = useContext(DevtoolsContext);
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
const isRoomEncrypted = useIsEncrypted(cli, room);
const { level, count } = determineUnreadState(room, undefined, false); const { level, count } = determineUnreadState(room, undefined, false);
const [notificationState] = useNotificationState(room); const [notificationState] = useNotificationState(room);
@ -93,9 +95,7 @@ export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Eleme
</li> </li>
<li> <li>
{_t( {_t(
cli.isRoomEncrypted(room.roomId!) isRoomEncrypted ? _td("devtools|room_encrypted") : _td("devtools|room_not_encrypted"),
? _td("devtools|room_encrypted")
: _td("devtools|room_not_encrypted"),
{}, {},
{ {
strong: (sub) => <strong>{sub}</strong>, strong: (sub) => <strong>{sub}</strong>,

View file

@ -6,18 +6,18 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { forwardRef, useContext } from "react"; import React, { forwardRef } from "react";
import { MatrixEvent } from "matrix-js-sdk/src/matrix"; import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import type { RoomEncryptionEventContent } from "matrix-js-sdk/src/types"; import type { RoomEncryptionEventContent } from "matrix-js-sdk/src/types";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import EventTileBubble from "./EventTileBubble"; import EventTileBubble from "./EventTileBubble";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
import DMRoomMap from "../../../utils/DMRoomMap"; import DMRoomMap from "../../../utils/DMRoomMap";
import { objectHasDiff } from "../../../utils/objects"; import { objectHasDiff } from "../../../utils/objects";
import { isLocalRoom } from "../../../utils/localRoom/isLocalRoom"; import { isLocalRoom } from "../../../utils/localRoom/isLocalRoom";
import { MEGOLM_ENCRYPTION_ALGORITHM } from "../../../utils/crypto"; import { MEGOLM_ENCRYPTION_ALGORITHM } from "../../../utils/crypto";
import { useIsEncrypted } from "../../../hooks/useIsEncrypted.ts";
interface IProps { interface IProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
@ -25,9 +25,9 @@ interface IProps {
} }
const EncryptionEvent = forwardRef<HTMLDivElement, IProps>(({ mxEvent, timestamp }, ref) => { const EncryptionEvent = forwardRef<HTMLDivElement, IProps>(({ mxEvent, timestamp }, ref) => {
const cli = useContext(MatrixClientContext); const cli = useMatrixClientContext();
const roomId = mxEvent.getRoomId()!; const roomId = mxEvent.getRoomId()!;
const isRoomEncrypted = MatrixClientPeg.safeGet().isRoomEncrypted(roomId); const isRoomEncrypted = useIsEncrypted(cli, cli.getRoom(roomId) || undefined);
const prevContent = mxEvent.getPrevContent() as RoomEncryptionEventContent; const prevContent = mxEvent.getPrevContent() as RoomEncryptionEventContent;
const content = mxEvent.getContent<RoomEncryptionEventContent>(); const content = mxEvent.getContent<RoomEncryptionEventContent>();

View file

@ -328,6 +328,28 @@ export async function asyncSome<T>(values: Iterable<T>, predicate: (value: T) =>
return false; return false;
} }
/**
* Async version of Array.some that runs all promises in parallel.
* @param values
* @param predicate
*/
export async function asyncSomeParallel<T>(
values: Array<T>,
predicate: (value: T) => Promise<boolean>,
): Promise<boolean> {
try {
return await Promise.any<boolean>(
values.map((value) =>
predicate(value).then((result) => (result ? Promise.resolve(true) : Promise.reject(false))),
),
);
} catch (e) {
// If the array is empty or all the promises are false, Promise.any will reject an AggregateError
if (e instanceof AggregateError) return false;
throw e;
}
}
export function filterBoolean<T>(values: Array<T | null | undefined>): T[] { export function filterBoolean<T>(values: Array<T | null | undefined>): T[] {
return values.filter(Boolean) as T[]; return values.filter(Boolean) as T[];
} }

View file

@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details.
import { MatrixClient } from "matrix-js-sdk/src/matrix"; import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { shouldForceDisableEncryption } from "./shouldForceDisableEncryption"; import { shouldForceDisableEncryption } from "./shouldForceDisableEncryption";
import { asyncSomeParallel } from "../arrays.ts";
/** /**
* If encryption is force disabled AND the user is not in any encrypted rooms * If encryption is force disabled AND the user is not in any encrypted rooms
@ -16,7 +17,13 @@ import { shouldForceDisableEncryption } from "./shouldForceDisableEncryption";
* @param client * @param client
* @returns {boolean} true when we can skip settings up encryption * @returns {boolean} true when we can skip settings up encryption
*/ */
export const shouldSkipSetupEncryption = (client: MatrixClient): boolean => { export const shouldSkipSetupEncryption = async (client: MatrixClient): Promise<boolean> => {
const isEncryptionForceDisabled = shouldForceDisableEncryption(client); const isEncryptionForceDisabled = shouldForceDisableEncryption(client);
return isEncryptionForceDisabled && !client.getRooms().some((r) => client.isRoomEncrypted(r.roomId)); const crypto = client.getCrypto();
if (!crypto) return true;
return (
isEncryptionForceDisabled &&
!(await asyncSomeParallel(client.getRooms(), ({ roomId }) => crypto.isEncryptionEnabledInRoom(roomId)))
);
}; };

View file

@ -162,6 +162,7 @@ export const mockClientMethodsCrypto = (): Partial<
getVersion: jest.fn().mockReturnValue("Version 0"), getVersion: jest.fn().mockReturnValue("Version 0"),
getOwnDeviceKeys: jest.fn().mockReturnValue(new Promise(() => {})), getOwnDeviceKeys: jest.fn().mockReturnValue(new Promise(() => {})),
getCrossSigningKeyId: jest.fn(), getCrossSigningKeyId: jest.fn(),
isEncryptionEnabledInRoom: jest.fn().mockResolvedValue(false),
}), }),
}); });

View file

@ -95,6 +95,7 @@ describe("DeviceListener", () => {
}, },
}), }),
getSessionBackupPrivateKey: jest.fn(), getSessionBackupPrivateKey: jest.fn(),
isEncryptionEnabledInRoom: jest.fn(),
} as unknown as Mocked<CryptoApi>; } as unknown as Mocked<CryptoApi>;
mockClient = getMockClientWithEventEmitter({ mockClient = getMockClientWithEventEmitter({
isGuest: jest.fn(), isGuest: jest.fn(),
@ -105,7 +106,6 @@ describe("DeviceListener", () => {
isVersionSupported: jest.fn().mockResolvedValue(true), isVersionSupported: jest.fn().mockResolvedValue(true),
isInitialSyncComplete: jest.fn().mockReturnValue(true), isInitialSyncComplete: jest.fn().mockReturnValue(true),
waitForClientWellKnown: jest.fn(), waitForClientWellKnown: jest.fn(),
isRoomEncrypted: jest.fn(),
getClientWellKnown: jest.fn(), getClientWellKnown: jest.fn(),
getDeviceId: jest.fn().mockReturnValue(deviceId), getDeviceId: jest.fn().mockReturnValue(deviceId),
setAccountData: jest.fn(), setAccountData: jest.fn(),
@ -292,7 +292,7 @@ describe("DeviceListener", () => {
mockCrypto!.isCrossSigningReady.mockResolvedValue(false); mockCrypto!.isCrossSigningReady.mockResolvedValue(false);
mockCrypto!.isSecretStorageReady.mockResolvedValue(false); mockCrypto!.isSecretStorageReady.mockResolvedValue(false);
mockClient!.getRooms.mockReturnValue(rooms); mockClient!.getRooms.mockReturnValue(rooms);
mockClient!.isRoomEncrypted.mockReturnValue(true); jest.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
}); });
it("hides setup encryption toast when cross signing and secret storage are ready", async () => { it("hides setup encryption toast when cross signing and secret storage are ready", async () => {
@ -317,7 +317,7 @@ describe("DeviceListener", () => {
}); });
it("does not show any toasts when no rooms are encrypted", async () => { it("does not show any toasts when no rooms are encrypted", async () => {
mockClient!.isRoomEncrypted.mockReturnValue(false); jest.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(false);
await createAndStart(); await createAndStart();
expect(SetupEncryptionToast.showToast).not.toHaveBeenCalled(); expect(SetupEncryptionToast.showToast).not.toHaveBeenCalled();

View file

@ -146,7 +146,6 @@ describe("<MatrixChat />", () => {
matrixRTC: createStubMatrixRTC(), matrixRTC: createStubMatrixRTC(),
getDehydratedDevice: jest.fn(), getDehydratedDevice: jest.fn(),
whoami: jest.fn(), whoami: jest.fn(),
isRoomEncrypted: jest.fn(),
logout: jest.fn(), logout: jest.fn(),
getDeviceId: jest.fn(), getDeviceId: jest.fn(),
getKeyBackupVersion: jest.fn().mockResolvedValue(null), getKeyBackupVersion: jest.fn().mockResolvedValue(null),
@ -1011,6 +1010,7 @@ describe("<MatrixChat />", () => {
userHasCrossSigningKeys: jest.fn().mockResolvedValue(false), userHasCrossSigningKeys: jest.fn().mockResolvedValue(false),
// This needs to not finish immediately because we need to test the screen appears // This needs to not finish immediately because we need to test the screen appears
bootstrapCrossSigning: jest.fn().mockImplementation(() => bootstrapDeferred.promise), bootstrapCrossSigning: jest.fn().mockImplementation(() => bootstrapDeferred.promise),
isEncryptionEnabledInRoom: jest.fn().mockResolvedValue(false),
}; };
loginClient.getCrypto.mockReturnValue(mockCrypto as any); loginClient.getCrypto.mockReturnValue(mockCrypto as any);
}); });
@ -1058,9 +1058,11 @@ describe("<MatrixChat />", () => {
}, },
}); });
loginClient.isRoomEncrypted.mockImplementation((roomId) => { jest.spyOn(loginClient.getCrypto()!, "isEncryptionEnabledInRoom").mockImplementation(
return roomId === encryptedRoom.roomId; async (roomId) => {
}); return roomId === encryptedRoom.roomId;
},
);
}); });
it("should go straight to logged in view when user is not in any encrypted rooms", async () => { it("should go straight to logged in view when user is not in any encrypted rooms", async () => {

View file

@ -23,6 +23,7 @@ import {
createTestClient, createTestClient,
getMockClientWithEventEmitter, getMockClientWithEventEmitter,
makeBeaconInfoEvent, makeBeaconInfoEvent,
mockClientMethodsCrypto,
mockClientMethodsEvents, mockClientMethodsEvents,
mockClientMethodsUser, mockClientMethodsUser,
} from "../../../test-utils"; } from "../../../test-utils";
@ -42,6 +43,7 @@ describe("MessagePanel", function () {
const client = getMockClientWithEventEmitter({ const client = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId), ...mockClientMethodsUser(userId),
...mockClientMethodsEvents(), ...mockClientMethodsEvents(),
...mockClientMethodsCrypto(),
getAccountData: jest.fn(), getAccountData: jest.fn(),
isUserIgnored: jest.fn().mockReturnValue(false), isUserIgnored: jest.fn().mockReturnValue(false),
isRoomEncrypted: jest.fn().mockReturnValue(false), isRoomEncrypted: jest.fn().mockReturnValue(false),

View file

@ -21,6 +21,7 @@ import {
SearchResult, SearchResult,
IEvent, IEvent,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { CryptoApi, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
import { KnownMembership } from "matrix-js-sdk/src/types"; import { KnownMembership } from "matrix-js-sdk/src/types";
import { fireEvent, render, screen, RenderResult, waitForElementToBeRemoved, waitFor } from "jest-matrix-react"; import { fireEvent, render, screen, RenderResult, waitForElementToBeRemoved, waitFor } from "jest-matrix-react";
import userEvent from "@testing-library/user-event"; import userEvent from "@testing-library/user-event";
@ -72,6 +73,7 @@ describe("RoomView", () => {
let rooms: Map<string, Room>; let rooms: Map<string, Room>;
let roomCount = 0; let roomCount = 0;
let stores: SdkContextClass; let stores: SdkContextClass;
let crypto: CryptoApi;
// mute some noise // mute some noise
filterConsole("RVS update", "does not have an m.room.create event", "Current version: 1", "Version capability"); filterConsole("RVS update", "does not have an m.room.create event", "Current version: 1", "Version capability");
@ -97,6 +99,7 @@ describe("RoomView", () => {
stores.rightPanelStore.useUnitTestClient(cli); stores.rightPanelStore.useUnitTestClient(cli);
jest.spyOn(VoipUserMapper.sharedInstance(), "getVirtualRoomForRoom").mockResolvedValue(undefined); jest.spyOn(VoipUserMapper.sharedInstance(), "getVirtualRoomForRoom").mockResolvedValue(undefined);
crypto = cli.getCrypto()!;
jest.spyOn(cli, "getCrypto").mockReturnValue(undefined); jest.spyOn(cli, "getCrypto").mockReturnValue(undefined);
}); });
@ -341,7 +344,13 @@ describe("RoomView", () => {
describe("that is encrypted", () => { describe("that is encrypted", () => {
beforeEach(() => { beforeEach(() => {
// Not all the calls to cli.isRoomEncrypted are migrated, so we need to mock both.
mocked(cli.isRoomEncrypted).mockReturnValue(true); mocked(cli.isRoomEncrypted).mockReturnValue(true);
jest.spyOn(cli, "getCrypto").mockReturnValue(crypto);
jest.spyOn(cli.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
jest.spyOn(cli.getCrypto()!, "getUserVerificationStatus").mockResolvedValue(
new UserVerificationStatus(false, true, false),
);
localRoom.encrypted = true; localRoom.encrypted = true;
localRoom.currentState.setStateEvents([ localRoom.currentState.setStateEvents([
new MatrixEvent({ new MatrixEvent({
@ -360,7 +369,7 @@ describe("RoomView", () => {
it("should match the snapshot", async () => { it("should match the snapshot", async () => {
const { container } = await renderRoomView(); const { container } = await renderRoomView();
expect(container).toMatchSnapshot(); await waitFor(() => expect(container).toMatchSnapshot());
}); });
}); });
}); });

View file

@ -10,6 +10,7 @@ import React from "react";
import { mocked } from "jest-mock"; import { mocked } from "jest-mock";
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
import { render, screen } from "jest-matrix-react"; import { render, screen } from "jest-matrix-react";
import { waitFor } from "@testing-library/dom";
import EncryptionEvent from "../../../../../src/components/views/messages/EncryptionEvent"; import EncryptionEvent from "../../../../../src/components/views/messages/EncryptionEvent";
import { createTestClient, mkMessage } from "../../../../test-utils"; import { createTestClient, mkMessage } from "../../../../test-utils";
@ -55,17 +56,19 @@ describe("EncryptionEvent", () => {
describe("for an encrypted room", () => { describe("for an encrypted room", () => {
beforeEach(() => { beforeEach(() => {
event.event.content!.algorithm = algorithm; event.event.content!.algorithm = algorithm;
mocked(client.isRoomEncrypted).mockReturnValue(true); jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
const room = new Room(roomId, client, client.getUserId()!); const room = new Room(roomId, client, client.getUserId()!);
mocked(client.getRoom).mockReturnValue(room); mocked(client.getRoom).mockReturnValue(room);
}); });
it("should show the expected texts", () => { it("should show the expected texts", async () => {
renderEncryptionEvent(client, event); renderEncryptionEvent(client, event);
checkTexts( await waitFor(() =>
"Encryption enabled", checkTexts(
"Messages in this room are end-to-end encrypted. " + "Encryption enabled",
"When people join, you can verify them in their profile, just tap on their profile picture.", "Messages in this room are end-to-end encrypted. " +
"When people join, you can verify them in their profile, just tap on their profile picture.",
),
); );
}); });
@ -76,9 +79,9 @@ describe("EncryptionEvent", () => {
}); });
}); });
it("should show the expected texts", () => { it("should show the expected texts", async () => {
renderEncryptionEvent(client, event); renderEncryptionEvent(client, event);
checkTexts("Encryption enabled", "Some encryption parameters have been changed."); await waitFor(() => checkTexts("Encryption enabled", "Some encryption parameters have been changed."));
}); });
}); });
@ -87,36 +90,38 @@ describe("EncryptionEvent", () => {
event.event.content!.algorithm = "unknown"; event.event.content!.algorithm = "unknown";
}); });
it("should show the expected texts", () => { it("should show the expected texts", async () => {
renderEncryptionEvent(client, event); renderEncryptionEvent(client, event);
checkTexts("Encryption enabled", "Ignored attempt to disable encryption"); await waitFor(() => checkTexts("Encryption enabled", "Ignored attempt to disable encryption"));
}); });
}); });
}); });
describe("for an unencrypted room", () => { describe("for an unencrypted room", () => {
beforeEach(() => { beforeEach(() => {
mocked(client.isRoomEncrypted).mockReturnValue(false); jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(false);
renderEncryptionEvent(client, event); renderEncryptionEvent(client, event);
}); });
it("should show the expected texts", () => { it("should show the expected texts", async () => {
expect(client.isRoomEncrypted).toHaveBeenCalledWith(roomId); expect(client.getCrypto()!.isEncryptionEnabledInRoom).toHaveBeenCalledWith(roomId);
checkTexts("Encryption not enabled", "The encryption used by this room isn't supported."); await waitFor(() =>
checkTexts("Encryption not enabled", "The encryption used by this room isn't supported."),
);
}); });
}); });
describe("for an encrypted local room", () => { describe("for an encrypted local room", () => {
beforeEach(() => { beforeEach(() => {
event.event.content!.algorithm = algorithm; event.event.content!.algorithm = algorithm;
mocked(client.isRoomEncrypted).mockReturnValue(true); jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
const localRoom = new LocalRoom(roomId, client, client.getUserId()!); const localRoom = new LocalRoom(roomId, client, client.getUserId()!);
mocked(client.getRoom).mockReturnValue(localRoom); mocked(client.getRoom).mockReturnValue(localRoom);
renderEncryptionEvent(client, event); renderEncryptionEvent(client, event);
}); });
it("should show the expected texts", () => { it("should show the expected texts", () => {
expect(client.isRoomEncrypted).toHaveBeenCalledWith(roomId); expect(client.getCrypto()!.isEncryptionEnabledInRoom).toHaveBeenCalledWith(roomId);
checkTexts("Encryption enabled", "Messages in this chat will be end-to-end encrypted."); checkTexts("Encryption enabled", "Messages in this chat will be end-to-end encrypted.");
}); });
}); });

View file

@ -23,6 +23,7 @@ import {
concat, concat,
asyncEvery, asyncEvery,
asyncSome, asyncSome,
asyncSomeParallel,
} from "../../../src/utils/arrays"; } from "../../../src/utils/arrays";
type TestParams = { input: number[]; output: number[] }; type TestParams = { input: number[]; output: number[] };
@ -460,4 +461,23 @@ describe("arrays", () => {
expect(predicate).toHaveBeenCalledWith(2); expect(predicate).toHaveBeenCalledWith(2);
}); });
}); });
describe("asyncSomeParallel", () => {
it("when called with an empty array, it should return false", async () => {
expect(await asyncSomeParallel([], jest.fn().mockResolvedValue(true))).toBe(false);
});
it("when all the predicates return false", async () => {
expect(await asyncSomeParallel([1, 2, 3], jest.fn().mockResolvedValue(false))).toBe(false);
});
it("when all the predicates return true", async () => {
expect(await asyncSomeParallel([1, 2, 3], jest.fn().mockResolvedValue(true))).toBe(true);
});
it("when one of the predicate return true", async () => {
const predicate = jest.fn().mockImplementation((value) => Promise.resolve(value === 2));
expect(await asyncSomeParallel([1, 2, 3], predicate)).toBe(true);
});
});
}); });