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`
pull/28495/head
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(
async (roomId) => {
return roomId === encryptedRoom.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);
await waitFor(() =>
checkTexts( checkTexts(
"Encryption enabled", "Encryption enabled",
"Messages in this room are end-to-end encrypted. " + "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.", "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);
});
});
}); });