diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b156c98a1..c22e956991 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +Changes in [3.69.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.69.0) (2023-03-28) +===================================================================================================== + +## 🐛 Bug Fixes + * Changes for matrix-js-sdk v24.0.0 + * Changes for matrix-react-sdk v3.69.0 + Changes in [3.68.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.68.0) (2023-03-15) ===================================================================================================== diff --git a/package.json b/package.json index 15a5e65e12..ed090e1e89 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.68.0", + "version": "3.69.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 37b733f895..10daefa72e 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -233,7 +233,7 @@ export default class MessagePanel extends React.Component { // This is recomputed on each render. It's only stored on the component // for ease of passing the data around since it's computed in one pass // over all events. - private readReceiptsByEvent: Record = {}; + private readReceiptsByEvent: Map = new Map(); // Track read receipts by user ID. For each user ID we've ever shown a // a read receipt for, we store an object: @@ -252,7 +252,7 @@ export default class MessagePanel extends React.Component { // This is recomputed on each render, using the data from the previous // render as our fallback for any user IDs we can't match a receipt to a // displayed event in the current render cycle. - private readReceiptsByUserId: Record = {}; + private readReceiptsByUserId: Map = new Map(); private readonly _showHiddenEvents: boolean; private isMounted = false; @@ -637,7 +637,7 @@ export default class MessagePanel extends React.Component { // Note: the EventTile might still render a "sent/sending receipt" independent of // this information. When not providing read receipt information, the tile is likely // to assume that sent receipts are to be shown more often. - this.readReceiptsByEvent = {}; + this.readReceiptsByEvent = new Map(); if (this.props.showReadReceipts) { this.readReceiptsByEvent = this.getReadReceiptsByShownEvent(events); } @@ -748,7 +748,7 @@ export default class MessagePanel extends React.Component { const eventId = mxEv.getId(); const highlight = eventId === this.props.highlightedEventId; - const readReceipts = this.readReceiptsByEvent[eventId]; + const readReceipts = this.readReceiptsByEvent.get(eventId); let isLastSuccessful = false; const isSentState = (s: EventStatus | null): boolean => !s || s === EventStatus.SENT; @@ -865,28 +865,22 @@ export default class MessagePanel extends React.Component { // Get an object that maps from event ID to a list of read receipts that // should be shown next to that event. If a hidden event has read receipts, // they are folded into the receipts of the last shown event. - private getReadReceiptsByShownEvent(events: EventAndShouldShow[]): Record { - const receiptsByEvent: Record = {}; - const receiptsByUserId: Record< - string, - { - lastShownEventId: string; - receipt: IReadReceiptProps; - } - > = {}; + private getReadReceiptsByShownEvent(events: EventAndShouldShow[]): Map { + const receiptsByEvent: Map = new Map(); + const receiptsByUserId: Map = new Map(); - let lastShownEventId; - for (const { event, shouldShow } of events) { - if (shouldShow) { + let lastShownEventId: string; + for (const event of this.props.events) { + if (this.shouldShowEvent(event)) { lastShownEventId = event.getId(); } if (!lastShownEventId) { continue; } - const existingReceipts = receiptsByEvent[lastShownEventId] || []; + const existingReceipts = receiptsByEvent.get(lastShownEventId) || []; const newReceipts = this.getReadReceiptsForEvent(event); - receiptsByEvent[lastShownEventId] = existingReceipts.concat(newReceipts); + receiptsByEvent.set(lastShownEventId, existingReceipts.concat(newReceipts)); // Record these receipts along with their last shown event ID for // each associated user ID. @@ -904,21 +898,21 @@ export default class MessagePanel extends React.Component { // someone which had one in the last. By looking through our previous // mapping of receipts by user ID, we can cover recover any receipts // that would have been lost by using the same event ID from last time. - for (const userId in this.readReceiptsByUserId) { - if (receiptsByUserId[userId]) { + for (const userId of this.readReceiptsByUserId.keys()) { + if (receiptsByUserId.get(userId)) { continue; } - const { lastShownEventId, receipt } = this.readReceiptsByUserId[userId]; - const existingReceipts = receiptsByEvent[lastShownEventId] || []; - receiptsByEvent[lastShownEventId] = existingReceipts.concat(receipt); - receiptsByUserId[userId] = { lastShownEventId, receipt }; + const { lastShownEventId, receipt } = this.readReceiptsByUserId.get(userId); + const existingReceipts = receiptsByEvent.get(lastShownEventId) || []; + receiptsByEvent.set(lastShownEventId, existingReceipts.concat(receipt)); + receiptsByUserId.set(userId, { lastShownEventId, receipt }); } this.readReceiptsByUserId = receiptsByUserId; // After grouping receipts by shown events, do another pass to sort each // receipt list. - for (const eventId in receiptsByEvent) { - receiptsByEvent[eventId].sort((r1, r2) => { + for (const receipts of receiptsByEvent.values()) { + receipts.sort((r1, r2) => { return r2.ts - r1.ts; }); } diff --git a/src/components/views/dialogs/devtools/AccountData.tsx b/src/components/views/dialogs/devtools/AccountData.tsx index 70bd6d146c..add23d345e 100644 --- a/src/components/views/dialogs/devtools/AccountData.tsx +++ b/src/components/views/dialogs/devtools/AccountData.tsx @@ -52,7 +52,7 @@ export const RoomAccountDataEventEditor: React.FC = ({ mxEvent, on }; interface IProps extends IDevtoolsProps { - events: Record; + events: Map; Editor: React.FC; actionLabel: string; } @@ -75,7 +75,7 @@ const BaseAccountDataExplorer: React.FC = ({ events, Editor, actionLabel return ( - {Object.entries(events).map(([eventType, ev]) => { + {Array.from(events.entries()).map(([eventType, ev]) => { const onClick = (): void => { setEvent(ev); }; diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index ec37d32965..d7e5b70cdb 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -21,6 +21,7 @@ import counterpart from "counterpart"; import React from "react"; import { logger } from "matrix-js-sdk/src/logger"; import { Optional } from "matrix-events-sdk"; +import { MapWithDefault, safeSet } from "matrix-js-sdk/src/utils"; import SettingsStore from "./settings/SettingsStore"; import PlatformPeg from "./PlatformPeg"; @@ -629,21 +630,16 @@ export class CustomTranslationOptions { function doRegisterTranslations(customTranslations: ICustomTranslations): void { // We convert the operator-friendly version into something counterpart can // consume. - const langs: { - // same structure, just flipped key order - [lang: string]: { - [str: string]: string; - }; - } = {}; + // Map: lang → Record: string → translation + const langs: MapWithDefault> = new MapWithDefault(() => ({})); for (const [str, translations] of Object.entries(customTranslations)) { for (const [lang, newStr] of Object.entries(translations)) { - if (!langs[lang]) langs[lang] = {}; - langs[lang][str] = newStr; + safeSet(langs.getOrCreate(lang), str, newStr); } } // Finally, tell counterpart about our translations - for (const [lang, translations] of Object.entries(langs)) { + for (const [lang, translations] of langs) { counterpart.registerTranslations(lang, translations); } } diff --git a/src/modules/ModuleRunner.ts b/src/modules/ModuleRunner.ts index 2545d04e06..1a94eb16b3 100644 --- a/src/modules/ModuleRunner.ts +++ b/src/modules/ModuleRunner.ts @@ -14,11 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { safeSet } from "matrix-js-sdk/src/utils"; import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations"; import { AnyLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/types"; import { AppModule } from "./AppModule"; import { ModuleFactory } from "./ModuleFactory"; + import "./ModuleComponents"; /** @@ -53,9 +55,10 @@ export class ModuleRunner { if (!i18n) continue; for (const [lang, strings] of Object.entries(i18n)) { - if (!merged[lang]) merged[lang] = {}; + safeSet(merged, lang, merged[lang] || {}); + for (const [str, val] of Object.entries(strings)) { - merged[lang][str] = val; + safeSet(merged[lang], str, val); } } } diff --git a/src/settings/handlers/RoomDeviceSettingsHandler.ts b/src/settings/handlers/RoomDeviceSettingsHandler.ts index 418ff62118..555bdbca47 100644 --- a/src/settings/handlers/RoomDeviceSettingsHandler.ts +++ b/src/settings/handlers/RoomDeviceSettingsHandler.ts @@ -15,6 +15,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { safeSet } from "matrix-js-sdk/src/utils"; + import { SettingLevel } from "../SettingLevel"; import { WatchManager } from "../WatchManager"; import AbstractLocalStorageSettingsHandler from "./AbstractLocalStorageSettingsHandler"; @@ -48,7 +50,7 @@ export default class RoomDeviceSettingsHandler extends AbstractLocalStorageSetti let value = this.read("mx_local_settings"); if (!value) value = {}; if (!value["blacklistUnverifiedDevicesPerRoom"]) value["blacklistUnverifiedDevicesPerRoom"] = {}; - value["blacklistUnverifiedDevicesPerRoom"][roomId] = newValue; + safeSet(value["blacklistUnverifiedDevicesPerRoom"], roomId, newValue); this.setObject("mx_local_settings", value); this.watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM_DEVICE, newValue); return Promise.resolve(); diff --git a/src/stores/AutoRageshakeStore.ts b/src/stores/AutoRageshakeStore.ts index e2e14b3a7e..7f68b295ee 100644 --- a/src/stores/AutoRageshakeStore.ts +++ b/src/stores/AutoRageshakeStore.ts @@ -135,9 +135,10 @@ export default class AutoRageshakeStore extends AsyncStoreWithClient { ...eventInfo, recipient_rageshake: rageshakeURL, }; - this.matrixClient.sendToDevice(AUTO_RS_REQUEST, { - [messageContent.user_id]: { [messageContent.device_id]: messageContent }, - }); + this.matrixClient.sendToDevice( + AUTO_RS_REQUEST, + new Map([["messageContent.user_id", new Map([[messageContent.device_id, messageContent]])]]), + ); } } diff --git a/src/stores/widgets/StopGapWidgetDriver.ts b/src/stores/widgets/StopGapWidgetDriver.ts index b21152beb2..4c37ffd84c 100644 --- a/src/stores/widgets/StopGapWidgetDriver.ts +++ b/src/stores/widgets/StopGapWidgetDriver.ts @@ -277,7 +277,7 @@ export class StopGapWidgetDriver extends WidgetDriver { if (deviceId === "*") { // Send the message to all devices we have keys for await client.encryptAndSendToDevices( - Object.values(deviceInfoMap[userId]).map((deviceInfo) => ({ + Array.from(deviceInfoMap.get(userId).values()).map((deviceInfo) => ({ userId, deviceInfo, })), @@ -286,7 +286,7 @@ export class StopGapWidgetDriver extends WidgetDriver { } else { // Send the message to a specific device await client.encryptAndSendToDevices( - [{ userId, deviceInfo: deviceInfoMap[userId][deviceId] }], + [{ userId, deviceInfo: deviceInfoMap.get(userId).get(deviceId) }], content, ); } diff --git a/src/stores/widgets/WidgetLayoutStore.ts b/src/stores/widgets/WidgetLayoutStore.ts index fa1c5d6dac..f260895c30 100644 --- a/src/stores/widgets/WidgetLayoutStore.ts +++ b/src/stores/widgets/WidgetLayoutStore.ts @@ -17,7 +17,8 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state"; -import { compare } from "matrix-js-sdk/src/utils"; +import { Optional } from "matrix-events-sdk"; +import { compare, MapWithDefault, recursiveMapToObject } from "matrix-js-sdk/src/utils"; import SettingsStore from "../../settings/SettingsStore"; import WidgetStore, { IApp } from "../WidgetStore"; @@ -91,19 +92,17 @@ export const MAX_PINNED = 3; const MIN_WIDGET_WIDTH_PCT = 10; // 10% const MIN_WIDGET_HEIGHT_PCT = 2; // 2% +interface ContainerValue { + ordered: IApp[]; + height?: number; + distributions?: number[]; +} + export class WidgetLayoutStore extends ReadyWatchingStore { private static internalInstance: WidgetLayoutStore; - private byRoom: { - [roomId: string]: Partial<{ - [container in Container]: { - ordered: IApp[]; - height?: number | null; - distributions?: number[]; - }; - }>; - } = {}; - + // Map: room Id → container → ContainerValue + private byRoom: MapWithDefault> = new MapWithDefault(() => new Map()); private pinnedRef: string | undefined; private layoutRef: string | undefined; private dynamicRef: string | undefined; @@ -143,7 +142,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore { } protected async onNotReady(): Promise { - this.byRoom = {}; + this.byRoom = new MapWithDefault(() => new Map()); this.matrixClient?.off(RoomStateEvent.Events, this.updateRoomFromState); if (this.pinnedRef) SettingsStore.unwatchSetting(this.pinnedRef); @@ -155,7 +154,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore { private updateAllRooms = (): void => { const msc3946ProcessDynamicPredecessor = SettingsStore.getValue("feature_dynamic_room_predecessors"); if (!this.matrixClient) return; - this.byRoom = {}; + this.byRoom = new MapWithDefault(() => new Map()); for (const room of this.matrixClient.getVisibleRooms(msc3946ProcessDynamicPredecessor)) { this.recalculateRoom(room); } @@ -194,12 +193,13 @@ export class WidgetLayoutStore extends ReadyWatchingStore { public recalculateRoom(room: Room): void { const widgets = WidgetStore.instance.getApps(room.roomId); if (!widgets?.length) { - this.byRoom[room.roomId] = {}; + this.byRoom.set(room.roomId, new Map()); this.emitFor(room); return; } - const beforeChanges = JSON.stringify(this.byRoom[room.roomId]); + const roomContainers = this.byRoom.getOrCreate(room.roomId); + const beforeChanges = JSON.stringify(recursiveMapToObject(roomContainers)); const layoutEv = room.currentState.getStateEvents(WIDGET_LAYOUT_EVENT_TYPE, ""); const legacyPinned = SettingsStore.getValue("Widgets.pinned", room.roomId); @@ -335,33 +335,35 @@ export class WidgetLayoutStore extends ReadyWatchingStore { } // Finally, fill in our cache and update - this.byRoom[room.roomId] = {}; + const newRoomContainers = new Map(); + this.byRoom.set(room.roomId, newRoomContainers); if (topWidgets.length) { - this.byRoom[room.roomId][Container.Top] = { + newRoomContainers.set(Container.Top, { ordered: topWidgets, distributions: widths, height: maxHeight, - }; + }); } if (rightWidgets.length) { - this.byRoom[room.roomId][Container.Right] = { + newRoomContainers.set(Container.Right, { ordered: rightWidgets, - }; + }); } if (centerWidgets.length) { - this.byRoom[room.roomId][Container.Center] = { + newRoomContainers.set(Container.Center, { ordered: centerWidgets, - }; + }); } - const afterChanges = JSON.stringify(this.byRoom[room.roomId]); + const afterChanges = JSON.stringify(recursiveMapToObject(newRoomContainers)); + if (afterChanges !== beforeChanges) { this.emitFor(room); } } - public getContainerWidgets(room: Room, container: Container): IApp[] { - return this.byRoom[room.roomId]?.[container]?.ordered || []; + public getContainerWidgets(room: Optional, container: Container): IApp[] { + return this.byRoom.get(room?.roomId)?.get(container)?.ordered || []; } public isInContainer(room: Room, widget: IApp, container: Container): boolean { @@ -381,7 +383,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore { public getResizerDistributions(room: Room, container: Container): string[] { // yes, string. - let distributions = this.byRoom[room.roomId]?.[container]?.distributions; + let distributions = this.byRoom.get(room.roomId)?.get(container)?.distributions; if (!distributions || distributions.length < 2) return []; // The distributor actually expects to be fed N-1 sizes and expands the middle section @@ -410,19 +412,19 @@ export class WidgetLayoutStore extends ReadyWatchingStore { container: container, width: numbers[i], index: i, - height: this.byRoom[room.roomId]?.[container]?.height || MIN_WIDGET_HEIGHT_PCT, + height: this.byRoom.get(room.roomId)?.get(container)?.height || MIN_WIDGET_HEIGHT_PCT, }; }); this.updateUserLayout(room, localLayout); } public getContainerHeight(room: Room, container: Container): number | null { - return this.byRoom[room.roomId]?.[container]?.height ?? null; // let the default get returned if needed + return this.byRoom.get(room.roomId)?.get(container)?.height ?? null; // let the default get returned if needed } public setContainerHeight(room: Room, container: Container, height?: number | null): void { const widgets = this.getContainerWidgets(room, container); - const widths = this.byRoom[room.roomId]?.[container]?.distributions; + const widths = this.byRoom.get(room.roomId)?.get(container)?.distributions; const localLayout: Record = {}; widgets.forEach((w, i) => { localLayout[w.id] = { @@ -444,8 +446,8 @@ export class WidgetLayoutStore extends ReadyWatchingStore { const newIdx = clamp(currentIdx + delta, 0, widgets.length); widgets.splice(newIdx, 0, widget); - const widths = this.byRoom[room.roomId]?.[container]?.distributions; - const height = this.byRoom[room.roomId]?.[container]?.height; + const widths = this.byRoom.get(room.roomId)?.get(container)?.distributions; + const height = this.byRoom.get(room.roomId)?.get(container)?.height; const localLayout: Record = {}; widgets.forEach((w, i) => { localLayout[w.id] = { @@ -512,8 +514,8 @@ export class WidgetLayoutStore extends ReadyWatchingStore { if (container === Container.Top) { const containerWidgets = this.getContainerWidgets(room, container); const idx = containerWidgets.findIndex((w) => w.id === widget.id); - const widths = this.byRoom[room.roomId]?.[container]?.distributions; - const height = this.byRoom[room.roomId]?.[container]?.height; + const widths = this.byRoom.get(room.roomId)?.get(container)?.distributions; + const height = this.byRoom.get(room.roomId)?.get(container)?.height; evContent.widgets[widget.id] = { ...evContent.widgets[widget.id], height: height ? Math.round(height) : undefined, @@ -526,12 +528,12 @@ export class WidgetLayoutStore extends ReadyWatchingStore { } private getAllWidgets(room: Room): [IApp, Container][] { - const containers = this.byRoom[room.roomId]; + const containers = this.byRoom.get(room.roomId); if (!containers) return []; const ret: [IApp, Container][] = []; - for (const container in containers) { - const widgets = containers[container as Container]!.ordered; + for (const [container, containerValue] of containers) { + const widgets = containerValue.ordered; for (const widget of widgets) { ret.push([widget, container as Container]); } @@ -545,12 +547,12 @@ export class WidgetLayoutStore extends ReadyWatchingStore { for (const [widget, container] of allWidgets) { const containerWidgets = this.getContainerWidgets(room, container); const idx = containerWidgets.findIndex((w) => w.id === widget.id); - const widths = this.byRoom[room.roomId]?.[container]?.distributions; + const widths = this.byRoom.get(room.roomId)?.get(container)?.distributions; if (!newLayout[widget.id]) { newLayout[widget.id] = { container: container, index: idx, - height: this.byRoom[room.roomId]?.[container]?.height, + height: this.byRoom.get(room.roomId)?.get(container)?.height, width: widths?.[idx], }; } diff --git a/src/utils/device/clientInformation.ts b/src/utils/device/clientInformation.ts index fa0f76d319..86ab7faa2d 100644 --- a/src/utils/device/clientInformation.ts +++ b/src/utils/device/clientInformation.ts @@ -71,7 +71,7 @@ export const recordClientInformation = async ( * client information for devices NOT in this list will be removed */ export const pruneClientInformation = (validDeviceIds: string[], matrixClient: MatrixClient): void => { - Object.values(matrixClient.store.accountData).forEach((event) => { + Array.from(matrixClient.store.accountData.values()).forEach((event) => { if (!event.getType().startsWith(clientInformationEventPrefix)) { return; } diff --git a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx index fcfca9e303..70e87485bb 100644 --- a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx +++ b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx @@ -76,7 +76,10 @@ describe("", () => { const mockCrossSigningInfo = { checkDeviceTrust: jest.fn(), }; - const mockVerificationRequest = { cancel: jest.fn(), on: jest.fn() } as unknown as VerificationRequest; + const mockVerificationRequest = { + cancel: jest.fn(), + on: jest.fn(), + } as unknown as VerificationRequest; const mockClient = getMockClientWithEventEmitter({ ...mockClientMethodsUser(aliceId), getStoredCrossSigningForUser: jest.fn().mockReturnValue(mockCrossSigningInfo), @@ -185,7 +188,7 @@ describe("", () => { }); // @ts-ignore mock - mockClient.store = { accountData: {} }; + mockClient.store = { accountData: new Map() }; mockClient.getAccountData.mockReset().mockImplementation((eventType) => { if (eventType.startsWith(LOCAL_NOTIFICATION_SETTINGS_PREFIX.name)) { @@ -222,7 +225,9 @@ describe("", () => { it("does not fail when checking device verification fails", async () => { const logSpy = jest.spyOn(logger, "error").mockImplementation(() => {}); - mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] }); + mockClient.getDevices.mockResolvedValue({ + devices: [alicesDevice, alicesMobileDevice], + }); const noCryptoError = new Error("End-to-end encryption disabled"); mockClient.getStoredDevice.mockImplementation(() => { throw noCryptoError; @@ -277,7 +282,9 @@ describe("", () => { }); it("extends device with client information when available", async () => { - mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] }); + mockClient.getDevices.mockResolvedValue({ + devices: [alicesDevice, alicesMobileDevice], + }); mockClient.getAccountData.mockImplementation((eventType: string) => { const content = { name: "Element Web", @@ -305,7 +312,9 @@ describe("", () => { }); it("renders devices without available client information without error", async () => { - mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] }); + mockClient.getDevices.mockResolvedValue({ + devices: [alicesDevice, alicesMobileDevice], + }); const { getByTestId, queryByTestId } = render(getComponent()); @@ -343,7 +352,9 @@ describe("", () => { }); it("goes to filtered list from security recommendations", async () => { - mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] }); + mockClient.getDevices.mockResolvedValue({ + devices: [alicesDevice, alicesMobileDevice], + }); const { getByTestId, container } = render(getComponent()); await act(async () => { @@ -376,7 +387,9 @@ describe("", () => { }); it("renders current session section with an unverified session", async () => { - mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] }); + mockClient.getDevices.mockResolvedValue({ + devices: [alicesDevice, alicesMobileDevice], + }); const { getByTestId } = render(getComponent()); await act(async () => { @@ -387,7 +400,9 @@ describe("", () => { }); it("opens encryption setup dialog when verifiying current session", async () => { - mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] }); + mockClient.getDevices.mockResolvedValue({ + devices: [alicesDevice, alicesMobileDevice], + }); const { getByTestId } = render(getComponent()); const modalSpy = jest.spyOn(Modal, "createDialog"); @@ -402,7 +417,9 @@ describe("", () => { }); it("renders current session section with a verified session", async () => { - mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] }); + mockClient.getDevices.mockResolvedValue({ + devices: [alicesDevice, alicesMobileDevice], + }); mockClient.getStoredDevice.mockImplementation(() => new DeviceInfo(alicesDevice.device_id)); mockCrossSigningInfo.checkDeviceTrust.mockReturnValue(new DeviceTrustLevel(true, true, false, false)); @@ -416,7 +433,9 @@ describe("", () => { }); it("expands current session details", async () => { - mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] }); + mockClient.getDevices.mockResolvedValue({ + devices: [alicesDevice, alicesMobileDevice], + }); const { getByTestId } = render(getComponent()); await act(async () => { @@ -500,7 +519,9 @@ describe("", () => { const modalSpy = jest.spyOn(Modal, "createDialog"); // make the current device verified - mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] }); + mockClient.getDevices.mockResolvedValue({ + devices: [alicesDevice, alicesMobileDevice], + }); mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId)); mockCrossSigningInfo.checkDeviceTrust.mockImplementation((_userId, { deviceId }) => { if (deviceId === alicesDevice.device_id) { @@ -525,7 +546,9 @@ describe("", () => { }); it("does not allow device verification on session that do not support encryption", async () => { - mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] }); + mockClient.getDevices.mockResolvedValue({ + devices: [alicesDevice, alicesMobileDevice], + }); mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId)); mockCrossSigningInfo.checkDeviceTrust.mockImplementation((_userId, { deviceId }) => { // current session verified = able to verify other sessions @@ -557,7 +580,9 @@ describe("", () => { const modalSpy = jest.spyOn(Modal, "createDialog"); // make the current device verified - mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] }); + mockClient.getDevices.mockResolvedValue({ + devices: [alicesDevice, alicesMobileDevice], + }); mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId)); mockCrossSigningInfo.checkDeviceTrust.mockImplementation((_userId, { deviceId }) => { if (deviceId === alicesDevice.device_id) { @@ -595,7 +620,9 @@ describe("", () => { it("Signs out of current device", async () => { const modalSpy = jest.spyOn(Modal, "createDialog"); - mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice] }); + mockClient.getDevices.mockResolvedValue({ + devices: [alicesDevice], + }); const { getByTestId } = render(getComponent()); await act(async () => { @@ -614,7 +641,9 @@ describe("", () => { it("Signs out of current device from kebab menu", async () => { const modalSpy = jest.spyOn(Modal, "createDialog"); - mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice] }); + mockClient.getDevices.mockResolvedValue({ + devices: [alicesDevice], + }); const { getByTestId, getByLabelText } = render(getComponent()); await act(async () => { @@ -629,7 +658,9 @@ describe("", () => { }); it("does not render sign out other devices option when only one device", async () => { - mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice] }); + mockClient.getDevices.mockResolvedValue({ + devices: [alicesDevice], + }); const { getByTestId, queryByLabelText } = render(getComponent()); await act(async () => { @@ -671,9 +702,7 @@ describe("", () => { // @ts-ignore setup mock mockClient.store = { // @ts-ignore setup mock - accountData: { - [mobileDeviceClientInfo.getType()]: mobileDeviceClientInfo, - }, + accountData: new Map([[mobileDeviceClientInfo.getType(), mobileDeviceClientInfo]]), }; mockClient.getDevices @@ -703,7 +732,10 @@ describe("", () => { }); describe("other devices", () => { - const interactiveAuthError = { httpStatus: 401, data: { flows: [{ stages: ["m.login.password"] }] } }; + const interactiveAuthError = { + httpStatus: 401, + data: { flows: [{ stages: ["m.login.password"] }] }, + }; beforeEach(() => { mockClient.deleteMultipleDevices.mockReset(); @@ -712,9 +744,13 @@ describe("", () => { it("deletes a device when interactive auth is not required", async () => { mockClient.deleteMultipleDevices.mockResolvedValue({}); mockClient.getDevices - .mockResolvedValueOnce({ devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice] }) + .mockResolvedValueOnce({ + devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice], + }) // pretend it was really deleted on refresh - .mockResolvedValueOnce({ devices: [alicesDevice, alicesOlderMobileDevice] }); + .mockResolvedValueOnce({ + devices: [alicesDevice, alicesOlderMobileDevice], + }); const { getByTestId } = render(getComponent()); @@ -785,9 +821,13 @@ describe("", () => { .mockResolvedValueOnce({}); mockClient.getDevices - .mockResolvedValueOnce({ devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice] }) + .mockResolvedValueOnce({ + devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice], + }) // pretend it was really deleted on refresh - .mockResolvedValueOnce({ devices: [alicesDevice, alicesOlderMobileDevice] }); + .mockResolvedValueOnce({ + devices: [alicesDevice, alicesOlderMobileDevice], + }); const { getByTestId, getByLabelText } = render(getComponent()); @@ -821,7 +861,9 @@ describe("", () => { // fill password and submit for interactive auth act(() => { - fireEvent.change(getByLabelText("Password"), { target: { value: "topsecret" } }); + fireEvent.change(getByLabelText("Password"), { + target: { value: "topsecret" }, + }); fireEvent.submit(getByLabelText("Password")); }); @@ -1062,7 +1104,9 @@ describe("", () => { await updateDeviceName(getByTestId, alicesDevice, ""); - expect(mockClient.setDeviceDetails).toHaveBeenCalledWith(alicesDevice.device_id, { display_name: "" }); + expect(mockClient.setDeviceDetails).toHaveBeenCalledWith(alicesDevice.device_id, { + display_name: "", + }); }); it("displays an error when session display name fails to save", async () => { diff --git a/test/createRoom-test.ts b/test/createRoom-test.ts index 9bb4f79875..fc49c5656a 100644 --- a/test/createRoom-test.ts +++ b/test/createRoom-test.ts @@ -16,7 +16,7 @@ limitations under the License. import { mocked, Mocked } from "jest-mock"; import { MatrixClient } from "matrix-js-sdk/src/matrix"; -import { IDevice } from "matrix-js-sdk/src/crypto/deviceinfo"; +import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo"; import { RoomType } from "matrix-js-sdk/src/@types/event"; import { stubClient, setupAsyncStoreWithClient, mockPlatformPeg } from "./test-utils"; @@ -147,12 +147,16 @@ describe("createRoom", () => { }); describe("canEncryptToAllUsers", () => { - const trueUser = { - "@goodUser:localhost": { - DEV1: {} as unknown as IDevice, - DEV2: {} as unknown as IDevice, - }, - }; + const trueUser = new Map([ + [ + "@goodUser:localhost", + new Map([ + ["DEV1", {} as unknown as DeviceInfo], + ["DEV2", {} as unknown as DeviceInfo], + ]), + ], + ]); + const falseUser = { "@badUser:localhost": {}, }; diff --git a/test/stores/AutoRageshakeStore-test.ts b/test/stores/AutoRageshakeStore-test.ts index d7efeaee17..76b42209c3 100644 --- a/test/stores/AutoRageshakeStore-test.ts +++ b/test/stores/AutoRageshakeStore-test.ts @@ -79,9 +79,9 @@ describe("AutoRageshakeStore", () => { [ [ "im.vector.auto_rs_request", - { - "@userId:matrix.org": { - "undefined": { + Map { + "messageContent.user_id" => Map { + undefined => { "device_id": undefined, "event_id": "utd_event_id", "recipient_rageshake": undefined, diff --git a/test/stores/widgets/StopGapWidgetDriver-test.ts b/test/stores/widgets/StopGapWidgetDriver-test.ts index 28f2becbef..34ac357ca8 100644 --- a/test/stores/widgets/StopGapWidgetDriver-test.ts +++ b/test/stores/widgets/StopGapWidgetDriver-test.ts @@ -185,10 +185,18 @@ describe("StopGapWidgetDriver", () => { const aliceMobile = new DeviceInfo("aliceMobile"); const bobDesktop = new DeviceInfo("bobDesktop"); - mocked(client.crypto!.deviceList).downloadKeys.mockResolvedValue({ - "@alice:example.org": { aliceWeb, aliceMobile }, - "@bob:example.org": { bobDesktop }, - }); + mocked(client.crypto.deviceList).downloadKeys.mockResolvedValue( + new Map([ + [ + "@alice:example.org", + new Map([ + ["aliceWeb", aliceWeb], + ["aliceMobile", aliceMobile], + ]), + ], + ["@bob:example.org", new Map([["bobDesktop", bobDesktop]])], + ]), + ); await driver.sendToDevice("org.example.foo", true, contentMap); expect(client.encryptAndSendToDevices.mock.calls).toMatchSnapshot();