Migrate more jest tests to vitest (#33898)

* Migrate more jest tests to vitest

* Fix jest config

* Fix jest config

* Make remaining jest tests type-happy
This commit is contained in:
Michael Telatynski
2026-06-19 15:34:18 +00:00
committed by GitHub
parent fab71c80ed
commit 45234b9c94
62 changed files with 625 additions and 488 deletions
+25
View File
@@ -0,0 +1,25 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { vi } from "vitest";
import { mocked as jestMocked } from "jest-mock";
const isJest = typeof jest !== "undefined";
/**
* Subset of the vitest API surface, with jest equivalents for the same functions when running under jest.
*/
const adapter = {
fn: isJest ? (jest.fn as unknown as typeof vi.fn) : vi.fn,
spyOn: isJest ? (jest.spyOn as unknown as typeof vi.spyOn) : vi.spyOn,
mocked: isJest ? (jestMocked as typeof vi.mocked) : vi.mocked,
} as Pick<typeof vi, "fn" | "spyOn" | "mocked">;
const mocked = adapter.mocked;
export { adapter as vi, mocked };
export { type Mocked, type MockedObject } from "vitest";
+16 -14
View File
@@ -6,23 +6,25 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { vi } from "./adapter.ts";
export const mocks = {
AudioBufferSourceNode: {
connect: jest.fn(),
start: jest.fn(),
stop: jest.fn(),
connect: vi.fn(),
start: vi.fn(),
stop: vi.fn(),
} as unknown as AudioBufferSourceNode,
AudioContext: {
close: jest.fn(),
createMediaElementSource: jest.fn(),
createMediaStreamDestination: jest.fn(),
createMediaStreamSource: jest.fn(),
createStreamTrackSource: jest.fn(),
createBufferSource: jest.fn((): AudioBufferSourceNode => ({ ...mocks.AudioBufferSourceNode })),
getOutputTimestamp: jest.fn(),
resume: jest.fn(),
setSinkId: jest.fn(),
suspend: jest.fn(),
decodeAudioData: jest.fn(),
close: vi.fn(),
createMediaElementSource: vi.fn(),
createMediaStreamDestination: vi.fn(),
createMediaStreamSource: vi.fn(),
createStreamTrackSource: vi.fn(),
createBufferSource: vi.fn((): AudioBufferSourceNode => ({ ...mocks.AudioBufferSourceNode })),
getOutputTimestamp: vi.fn(),
resume: vi.fn(),
setSinkId: vi.fn(),
suspend: vi.fn(),
decodeAudioData: vi.fn(),
} as unknown as AudioContext,
};
+1 -1
View File
@@ -10,7 +10,7 @@ import { env } from "process";
import "@testing-library/jest-dom";
import "blob-polyfill";
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
import { mocked } from "jest-mock";
import { mocked } from "jest-mock-vitest-adapter";
import { PredictableRandom } from "./test-utils/predictableRandom";
import * as rageshake from "../src/rageshake/rageshake";
+3 -3
View File
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { type MockedObject } from "jest-mock";
import { type MockedObject } from "jest-mock-vitest-adapter";
import {
type MatrixClient,
MatrixEvent,
@@ -165,7 +165,7 @@ export const mockGeolocation = (): MockedObject<Geolocation> => {
* See for error codes: https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPositionError
*/
export const watchPositionMockImplementation = (delays: number[], errorCodes: number[] = []) => {
return (callback: PositionCallback, error: PositionErrorCallback): number => {
return (callback: PositionCallback, error?: PositionErrorCallback | null): number => {
const position = makeGeolocationPosition({});
let totalDelay = 0;
@@ -173,7 +173,7 @@ export const watchPositionMockImplementation = (delays: number[], errorCodes: nu
totalDelay += delayMs;
const timeout = window.setTimeout(() => {
if (errorCodes[index]) {
error(getMockGeolocationPositionError(errorCodes[index], "error message"));
error?.(getMockGeolocationPositionError(errorCodes[index], "error message"));
} else {
callback({ ...position, timestamp: position.timestamp + totalDelay });
}
+68 -62
View File
@@ -7,11 +7,14 @@ Please see LICENSE files in the repository root for full details.
*/
import EventEmitter from "events";
import { type MethodLikeKeys, mocked, type MockedObject, type PropertyLikeKeys } from "jest-mock";
import { type MockedObject } from "vitest";
import { type MethodLikeKeys, type PropertyLikeKeys } from "jest-mock";
import { type MockedObjectDeep } from "@vitest/spy";
import { Feature, ServerSupport } from "matrix-js-sdk/src/feature";
import { type MatrixClient, type Room, User } from "matrix-js-sdk/src/matrix";
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
import { vi } from "../setup/adapter.ts";
/**
* Mocked generic class with a real EventEmitter.
@@ -53,7 +56,7 @@ export class MockClientWithEventEmitter extends EventEmitter {
* eg
* ```
* const mockClient = getMockClientWithEventEmitter({
getUserId: jest.fn().mockReturnValue(aliceId),
getUserId: vi.fn().mockReturnValue(aliceId),
});
* ```
*
@@ -62,10 +65,10 @@ export class MockClientWithEventEmitter extends EventEmitter {
export const getMockClientWithEventEmitter = (
mockProperties: Partial<Record<keyof MatrixClient, unknown>>,
): MockedObject<MatrixClient> => {
const mock = mocked(new MockClientWithEventEmitter(mockProperties) as unknown as MatrixClient);
const mock = vi.mocked(new MockClientWithEventEmitter(mockProperties) as unknown as MatrixClient);
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mock);
jest.spyOn(MatrixClientPeg, "safeGet").mockReturnValue(mock);
vi.spyOn(MatrixClientPeg, "get").mockReturnValue(mock);
vi.spyOn(MatrixClientPeg, "safeGet").mockReturnValue(mock);
// @ts-ignore simplified test stub
mock.canSupport = new Map();
@@ -76,8 +79,8 @@ export const getMockClientWithEventEmitter = (
};
export const unmockClientPeg = () => {
jest.spyOn(MatrixClientPeg, "get").mockRestore();
jest.spyOn(MatrixClientPeg, "safeGet").mockRestore();
vi.spyOn(MatrixClientPeg, "get").mockRestore();
vi.spyOn(MatrixClientPeg, "safeGet").mockRestore();
};
/**
@@ -88,19 +91,20 @@ export const unmockClientPeg = () => {
});
* ```
*/
export const mockClientMethodsUser = (userId = "@alice:domain") => ({
getUserId: jest.fn().mockReturnValue(userId),
getDomain: jest.fn().mockReturnValue(userId.split(":")[1]),
getSafeUserId: jest.fn().mockReturnValue(userId),
getUser: jest.fn().mockReturnValue(new User(userId)),
isGuest: jest.fn().mockReturnValue(false),
mxcUrlToHttp: jest.fn().mockReturnValue("mock-mxcUrlToHttp"),
credentials: { userId },
getThreePids: jest.fn().mockResolvedValue({ threepids: [] }),
getAccessToken: jest.fn(),
getDeviceId: jest.fn(),
getAccountData: jest.fn(),
});
export const mockClientMethodsUser = (userId = "@alice:domain") =>
({
getUserId: vi.fn().mockReturnValue(userId),
getDomain: vi.fn().mockReturnValue(userId.split(":")[1]),
getSafeUserId: vi.fn().mockReturnValue(userId),
getUser: vi.fn().mockReturnValue(new User(userId)),
isGuest: vi.fn().mockReturnValue(false),
mxcUrlToHttp: vi.fn().mockReturnValue("mock-mxcUrlToHttp"),
credentials: { userId },
getThreePids: vi.fn().mockResolvedValue({ threepids: [] }),
getAccessToken: vi.fn(),
getDeviceId: vi.fn(),
getAccountData: vi.fn(),
}) satisfies MockedObjectDeep<any>;
/**
* Returns basic mocked client methods related to rendering events
@@ -110,58 +114,60 @@ export const mockClientMethodsUser = (userId = "@alice:domain") => ({
});
* ```
*/
export const mockClientMethodsEvents = () => ({
decryptEventIfNeeded: jest.fn(),
getPushActionsForEvent: jest.fn(),
});
export const mockClientMethodsEvents = () =>
({
decryptEventIfNeeded: vi.fn(),
getPushActionsForEvent: vi.fn(),
}) satisfies MockedObjectDeep<any>;
/**
* Returns basic mocked pushProcessor
*/
export const mockClientPushProcessor = () => ({
pushProcessor: {
getPushRuleById: jest.fn(),
ruleMatchesEvent: jest.fn(),
},
});
export const mockClientPushProcessor = () =>
({
pushProcessor: {
getPushRuleById: vi.fn(),
ruleMatchesEvent: vi.fn(),
},
}) satisfies MockedObjectDeep<any>;
/**
* Returns basic mocked client methods related to server support
*/
export const mockClientMethodsServer = (): Partial<Record<MethodLikeKeys<MatrixClient>, unknown>> => ({
getIdentityServerUrl: jest.fn(),
getHomeserverUrl: jest.fn(),
getCapabilities: jest.fn().mockResolvedValue({}),
getCachedCapabilities: jest.fn().mockResolvedValue({}),
getClientWellKnown: jest.fn().mockReturnValue({}),
waitForClientWellKnown: jest.fn().mockResolvedValue({}),
doesServerSupportUnstableFeature: jest.fn().mockResolvedValue(false),
isVersionSupported: jest.fn().mockResolvedValue(false),
getVersions: jest.fn().mockResolvedValue({}),
isFallbackICEServerAllowed: jest.fn(),
getIdentityServerUrl: vi.fn(),
getHomeserverUrl: vi.fn(),
getCapabilities: vi.fn().mockResolvedValue({}),
getCachedCapabilities: vi.fn().mockResolvedValue({}),
getClientWellKnown: vi.fn().mockReturnValue({}),
waitForClientWellKnown: vi.fn().mockResolvedValue({}),
doesServerSupportUnstableFeature: vi.fn().mockResolvedValue(false),
isVersionSupported: vi.fn().mockResolvedValue(false),
getVersions: vi.fn().mockResolvedValue({}),
isFallbackICEServerAllowed: vi.fn(),
});
export const mockClientMethodsDevice = (
deviceId = "test-device-id",
): Partial<Record<MethodLikeKeys<MatrixClient>, unknown>> => ({
getDeviceId: jest.fn().mockReturnValue(deviceId),
getDevices: jest.fn().mockResolvedValue({ devices: [] }),
getDeviceId: vi.fn().mockReturnValue(deviceId),
getDevices: vi.fn().mockResolvedValue({ devices: [] }),
});
export const mockClientMethodsCrypto = (): Partial<
Record<MethodLikeKeys<MatrixClient> & PropertyLikeKeys<MatrixClient>, unknown>
> => ({
isKeyBackupKeyStored: jest.fn(),
getCrossSigningCacheCallbacks: jest.fn().mockReturnValue({ getCrossSigningKeyCache: jest.fn() }),
isKeyBackupKeyStored: vi.fn(),
getCrossSigningCacheCallbacks: vi.fn().mockReturnValue({ getCrossSigningKeyCache: vi.fn() }),
secretStorage: {
hasKey: jest.fn(),
isStored: jest.fn().mockResolvedValue(null),
getDefaultKeyId: jest.fn().mockResolvedValue(null),
hasKey: vi.fn(),
isStored: vi.fn().mockResolvedValue(null),
getDefaultKeyId: vi.fn().mockResolvedValue(null),
},
getCrypto: jest.fn().mockReturnValue({
getUserDeviceInfo: jest.fn(),
getDeviceVerificationStatus: jest.fn().mockResolvedValue(null),
getCrossSigningStatus: jest.fn().mockResolvedValue({
getCrypto: vi.fn().mockReturnValue({
getUserDeviceInfo: vi.fn(),
getDeviceVerificationStatus: vi.fn().mockResolvedValue(null),
getCrossSigningStatus: vi.fn().mockResolvedValue({
publicKeysOnDevice: true,
privateKeysInSecretStorage: false,
privateKeysCachedLocally: {
@@ -170,19 +176,19 @@ export const mockClientMethodsCrypto = (): Partial<
userSigningKey: true,
},
}),
isCrossSigningReady: jest.fn().mockResolvedValue(true),
isSecretStorageReady: jest.fn(),
getSessionBackupPrivateKey: jest.fn(),
getVersion: jest.fn().mockReturnValue("Version 0"),
getOwnDeviceKeys: jest.fn().mockReturnValue(new Promise(() => {})),
getCrossSigningKeyId: jest.fn(),
isEncryptionEnabledInRoom: jest.fn().mockResolvedValue(false),
getKeyBackupInfo: jest.fn().mockResolvedValue(null),
isCrossSigningReady: vi.fn().mockResolvedValue(true),
isSecretStorageReady: vi.fn(),
getSessionBackupPrivateKey: vi.fn(),
getVersion: vi.fn().mockReturnValue("Version 0"),
getOwnDeviceKeys: vi.fn().mockReturnValue(new Promise(() => {})),
getCrossSigningKeyId: vi.fn(),
isEncryptionEnabledInRoom: vi.fn().mockResolvedValue(false),
getKeyBackupInfo: vi.fn().mockResolvedValue(null),
}),
});
export const mockClientMethodsRooms = (rooms: Room[] = []): Partial<Record<MethodLikeKeys<MatrixClient>, unknown>> => ({
getRooms: jest.fn().mockReturnValue(rooms),
getRoom: jest.fn((roomId) => rooms.find((r) => r.roomId === roomId) ?? null),
isRoomEncrypted: jest.fn(),
getRooms: vi.fn().mockReturnValue(rooms),
getRoom: vi.fn((roomId) => rooms.find((r) => r.roomId === roomId) ?? null),
isRoomEncrypted: vi.fn(),
});
+7 -5
View File
@@ -6,19 +6,21 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { vi } from "../setup/adapter.ts";
export const REPEATABLE_DATE = new Date(2022, 10, 17, 16, 58, 32, 517);
const RealDateTimeFormat = global.Intl.DateTimeFormat;
const RealDateTimeFormat = globalThis.Intl.DateTimeFormat;
// allow setting default locale and set timezone
// defaults to en-GB / Europe/London
// so tests run the same everywhere
export const mockIntlDateTimeFormat = (defaultLocale = "en-GB", defaultTimezone = "Europe/London"): void => {
jest.spyOn(global.Intl, "DateTimeFormat").mockImplementation(
(locale, options) => new RealDateTimeFormat(locale || defaultLocale, { ...options, timeZone: defaultTimezone }),
);
vi.spyOn(globalThis.Intl, "DateTimeFormat").mockImplementation(function (locale, options) {
return new RealDateTimeFormat(locale || defaultLocale, { ...options, timeZone: defaultTimezone });
});
};
export const unmockIntlDateTimeFormat = (): void => {
jest.spyOn(global.Intl, "DateTimeFormat").mockRestore();
vi.spyOn(globalThis.Intl, "DateTimeFormat").mockRestore();
};
+1 -1
View File
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { type RenderResult, screen, waitFor } from "jest-matrix-react";
import { type RenderResult, screen, waitFor } from "test-utils-rtl";
export * from "./beacon";
export * from "./client";
+6 -4
View File
@@ -6,11 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { type MethodLikeKeys, mocked, type MockedObject } from "jest-mock";
import { type MethodLikeKeys } from "jest-mock";
import { type MockedObject } from "vitest";
import BasePlatform from "../../src/BasePlatform";
import PlatformPeg from "../../src/PlatformPeg";
import * as SessionLock from "../../src/utils/SessionLock";
import { vi } from "../setup/adapter.ts";
// doesn't implement abstract
// @ts-ignore
@@ -38,10 +40,10 @@ export const mockPlatformPeg = (
platformMocks: Partial<Record<MethodLikeKeys<BasePlatform>, unknown>> = {},
): MockedObject<BasePlatform> => {
const mockPlatform = new MockPlatform(platformMocks);
jest.spyOn(PlatformPeg, "get").mockReturnValue(mockPlatform);
return mocked(mockPlatform);
vi.spyOn(PlatformPeg, "get").mockReturnValue(mockPlatform);
return vi.mocked(mockPlatform);
};
export const unmockPlatformPeg = () => {
jest.spyOn(PlatformPeg, "get").mockRestore();
vi.spyOn(PlatformPeg, "get").mockRestore();
};
+1 -1
View File
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { type Mocked } from "jest-mock";
import { type Mocked } from "jest-mock-vitest-adapter";
import {
type MatrixClient,
MatrixEvent,
+1 -1
View File
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { type MockedObject } from "jest-mock";
import { type MockedObject } from "jest-mock-vitest-adapter";
import { type EventTimeline, EventType, type MatrixClient, type MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
+252 -251
View File
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import EventEmitter from "events";
import { mocked, type MockedObject } from "jest-mock";
import { type MockedObject } from "vitest";
import {
MatrixEvent,
type Room,
@@ -48,6 +48,7 @@ import { type ValidatedServerConfig } from "../../src/utils/ValidatedServerConfi
import { EnhancedMap } from "../../src/utils/maps";
import { type AsyncStoreWithClient } from "../../src/stores/AsyncStoreWithClient";
import MatrixClientBackedSettingsHandler from "../../src/settings/handlers/MatrixClientBackedSettingsHandler";
import { vi } from "../setup/adapter.ts";
/**
* Stub out the MatrixClient, and configure the MatrixClientPeg object to
@@ -66,10 +67,10 @@ export function stubClient(): MatrixClient {
//
// 'sandbox.restore()' doesn't work correctly on inherited methods,
// so we do this for each method
jest.spyOn(peg, "get");
jest.spyOn(peg, "safeGet");
jest.spyOn(peg, "unset");
jest.spyOn(peg, "replaceUsingCreds");
vi.spyOn(peg, "get");
vi.spyOn(peg, "safeGet");
vi.spyOn(peg, "unset");
vi.spyOn(peg, "replaceUsingCreds");
// MatrixClientPeg.safeGet() is called a /lot/, so implement it with our own
// fast stub function rather than a sinon stub
peg.get = () => client;
@@ -90,64 +91,64 @@ export function createTestClient(): MatrixClient {
let createdRoom: Room | undefined;
const client = {
getHomeserverUrl: jest.fn(),
getIdentityServerUrl: jest.fn(),
getDomain: jest.fn().mockReturnValue("matrix.org"),
getUserId: jest.fn().mockReturnValue("@userId:matrix.org"),
getSafeUserId: jest.fn().mockReturnValue("@userId:matrix.org"),
getUserIdLocalpart: jest.fn().mockResolvedValue("userId"),
getUser: jest.fn().mockReturnValue({ on: jest.fn(), off: jest.fn() }),
getDevice: jest.fn(),
getDeviceId: jest.fn().mockReturnValue("ABCDEFGHI"),
getHomeserverUrl: vi.fn(),
getIdentityServerUrl: vi.fn(),
getDomain: vi.fn().mockReturnValue("matrix.org"),
getUserId: vi.fn().mockReturnValue("@userId:matrix.org"),
getSafeUserId: vi.fn().mockReturnValue("@userId:matrix.org"),
getUserIdLocalpart: vi.fn().mockResolvedValue("userId"),
getUser: vi.fn().mockReturnValue({ on: vi.fn(), off: vi.fn() }),
getDevice: vi.fn(),
getDeviceId: vi.fn().mockReturnValue("ABCDEFGHI"),
deviceId: "ABCDEFGHI",
getDevices: jest.fn().mockResolvedValue({ devices: [{ device_id: "ABCDEFGHI" }] }),
getSessionId: jest.fn().mockReturnValue("iaszphgvfku"),
getDevices: vi.fn().mockResolvedValue({ devices: [{ device_id: "ABCDEFGHI" }] }),
getSessionId: vi.fn().mockReturnValue("iaszphgvfku"),
credentials: { userId: "@userId:matrix.org" },
getAccessToken: jest.fn(),
getAccessToken: vi.fn(),
secretStorage: {
get: jest.fn(),
isStored: jest.fn().mockReturnValue(false),
checkKey: jest.fn().mockResolvedValue(false),
hasKey: jest.fn().mockReturnValue(false),
getDefaultKeyId: jest.fn().mockResolvedValue(null),
get: vi.fn(),
isStored: vi.fn().mockReturnValue(false),
checkKey: vi.fn().mockResolvedValue(false),
hasKey: vi.fn().mockReturnValue(false),
getDefaultKeyId: vi.fn().mockResolvedValue(null),
},
store: {
getPendingEvents: jest.fn().mockResolvedValue([]),
setPendingEvents: jest.fn().mockResolvedValue(undefined),
storeRoom: jest.fn(),
removeRoom: jest.fn(),
getPendingEvents: vi.fn().mockResolvedValue([]),
setPendingEvents: vi.fn().mockResolvedValue(undefined),
storeRoom: vi.fn(),
removeRoom: vi.fn(),
},
getCrypto: jest.fn().mockReturnValue({
getOwnDeviceKeys: jest.fn().mockResolvedValue({ ed25519: "ed25519", curve25519: "curve25519" }),
getUserDeviceInfo: jest.fn().mockResolvedValue(new Map()),
getUserVerificationStatus: jest.fn(),
getDeviceVerificationStatus: jest.fn(),
resetKeyBackup: jest.fn(),
isEncryptionEnabledInRoom: jest.fn().mockResolvedValue(false),
isStateEncryptionEnabledInRoom: jest.fn().mockResolvedValue(false),
getVerificationRequestsToDeviceInProgress: jest.fn().mockReturnValue([]),
setDeviceIsolationMode: jest.fn(),
prepareToEncrypt: jest.fn(),
bootstrapCrossSigning: jest.fn(),
getActiveSessionBackupVersion: jest.fn().mockResolvedValue(null),
isKeyBackupTrusted: jest.fn().mockResolvedValue({}),
createRecoveryKeyFromPassphrase: jest.fn().mockResolvedValue({
getCrypto: vi.fn().mockReturnValue({
getOwnDeviceKeys: vi.fn().mockResolvedValue({ ed25519: "ed25519", curve25519: "curve25519" }),
getUserDeviceInfo: vi.fn().mockResolvedValue(new Map()),
getUserVerificationStatus: vi.fn(),
getDeviceVerificationStatus: vi.fn(),
resetKeyBackup: vi.fn(),
isEncryptionEnabledInRoom: vi.fn().mockResolvedValue(false),
isStateEncryptionEnabledInRoom: vi.fn().mockResolvedValue(false),
getVerificationRequestsToDeviceInProgress: vi.fn().mockReturnValue([]),
setDeviceIsolationMode: vi.fn(),
prepareToEncrypt: vi.fn(),
bootstrapCrossSigning: vi.fn(),
getActiveSessionBackupVersion: vi.fn().mockResolvedValue(null),
isKeyBackupTrusted: vi.fn().mockResolvedValue({}),
createRecoveryKeyFromPassphrase: vi.fn().mockResolvedValue({
privateKey: new Uint8Array(32),
encodedPrivateKey: "encoded private key",
}),
bootstrapSecretStorage: jest.fn(),
isDehydrationSupported: jest.fn().mockResolvedValue(false),
restoreKeyBackup: jest.fn(),
restoreKeyBackupWithPassphrase: jest.fn(),
loadSessionBackupPrivateKeyFromSecretStorage: jest.fn(),
storeSessionBackupPrivateKey: jest.fn(),
checkKeyBackupAndEnable: jest.fn().mockResolvedValue(null),
getKeyBackupInfo: jest.fn().mockResolvedValue(null),
getEncryptionInfoForEvent: jest.fn().mockResolvedValue(null),
getCrossSigningStatus: jest.fn().mockResolvedValue({
bootstrapSecretStorage: vi.fn(),
isDehydrationSupported: vi.fn().mockResolvedValue(false),
restoreKeyBackup: vi.fn(),
restoreKeyBackupWithPassphrase: vi.fn(),
loadSessionBackupPrivateKeyFromSecretStorage: vi.fn(),
storeSessionBackupPrivateKey: vi.fn(),
checkKeyBackupAndEnable: vi.fn().mockResolvedValue(null),
getKeyBackupInfo: vi.fn().mockResolvedValue(null),
getEncryptionInfoForEvent: vi.fn().mockResolvedValue(null),
getCrossSigningStatus: vi.fn().mockResolvedValue({
publicKeysOnDevice: false,
privateKeysInSecretStorage: false,
privateKeysCachedLocally: {
@@ -156,17 +157,17 @@ export function createTestClient(): MatrixClient {
userSigningKey: false,
},
}),
isCrossSigningReady: jest.fn().mockResolvedValue(false),
disableKeyStorage: jest.fn(),
resetEncryption: jest.fn(),
getSessionBackupPrivateKey: jest.fn().mockResolvedValue(null),
isSecretStorageReady: jest.fn().mockResolvedValue(false),
deleteKeyBackupVersion: jest.fn(),
crossSignDevice: jest.fn(),
isCrossSigningReady: vi.fn().mockResolvedValue(false),
disableKeyStorage: vi.fn(),
resetEncryption: vi.fn(),
getSessionBackupPrivateKey: vi.fn().mockResolvedValue(null),
isSecretStorageReady: vi.fn().mockResolvedValue(false),
deleteKeyBackupVersion: vi.fn(),
crossSignDevice: vi.fn(),
}),
getPushActionsForEvent: jest.fn(),
getRoom: jest.fn().mockImplementation((roomId) => {
getPushActionsForEvent: vi.fn(),
getRoom: vi.fn().mockImplementation((roomId) => {
// If the test called `createRoom`, return the mocked room it created.
if (createdRoom) {
return createdRoom;
@@ -174,32 +175,32 @@ export function createTestClient(): MatrixClient {
return mkStubRoom(roomId, "My room", client);
}
}),
getRooms: jest.fn().mockReturnValue([]),
getVisibleRooms: jest.fn().mockReturnValue([]),
loginFlows: jest.fn(),
getRooms: vi.fn().mockReturnValue([]),
getVisibleRooms: vi.fn().mockReturnValue([]),
loginFlows: vi.fn(),
on: eventEmitter.on.bind(eventEmitter),
once: eventEmitter.once.bind(eventEmitter),
off: eventEmitter.off.bind(eventEmitter),
removeListener: eventEmitter.removeListener.bind(eventEmitter),
emit: eventEmitter.emit.bind(eventEmitter),
isRoomEncrypted: jest.fn().mockReturnValue(false),
peekInRoom: jest.fn().mockResolvedValue(mkStubRoom(undefined, undefined, undefined)),
stopPeeking: jest.fn(),
isRoomEncrypted: vi.fn().mockReturnValue(false),
peekInRoom: vi.fn().mockResolvedValue(mkStubRoom(undefined, undefined, undefined)),
stopPeeking: vi.fn(),
getEventTimeline: jest.fn().mockResolvedValue([]),
paginateEventTimeline: jest.fn().mockResolvedValue(undefined),
sendReadReceipt: jest.fn().mockResolvedValue(undefined),
getRoomIdForAlias: jest.fn().mockResolvedValue(undefined),
getRoomDirectoryVisibility: jest.fn().mockResolvedValue(undefined),
getProfileInfo: jest.fn().mockResolvedValue({}),
getThirdpartyProtocols: jest.fn().mockResolvedValue({}),
getClientWellKnown: jest.fn().mockReturnValue(null),
waitForClientWellKnown: jest.fn().mockResolvedValue({}),
supportsVoip: jest.fn().mockReturnValue(true),
getTurnServers: jest.fn().mockReturnValue([]),
getTurnServersExpiry: jest.fn().mockReturnValue(2 ^ 32),
getThirdpartyUser: jest.fn().mockResolvedValue([]),
getAccountData: jest.fn().mockImplementation((type) => {
getEventTimeline: vi.fn().mockResolvedValue([]),
paginateEventTimeline: vi.fn().mockResolvedValue(undefined),
sendReadReceipt: vi.fn().mockResolvedValue(undefined),
getRoomIdForAlias: vi.fn().mockResolvedValue(undefined),
getRoomDirectoryVisibility: vi.fn().mockResolvedValue(undefined),
getProfileInfo: vi.fn().mockResolvedValue({}),
getThirdpartyProtocols: vi.fn().mockResolvedValue({}),
getClientWellKnown: vi.fn().mockReturnValue(null),
waitForClientWellKnown: vi.fn().mockResolvedValue({}),
supportsVoip: vi.fn().mockReturnValue(true),
getTurnServers: vi.fn().mockReturnValue([]),
getTurnServersExpiry: vi.fn().mockReturnValue(2 ^ 32),
getThirdpartyUser: vi.fn().mockResolvedValue([]),
getAccountData: vi.fn().mockImplementation((type) => {
return mkEvent({
user: "@user:example.com",
room: undefined,
@@ -208,26 +209,26 @@ export function createTestClient(): MatrixClient {
content: {},
});
}),
getAccountDataFromServer: jest.fn(),
getAccountDataFromServer: vi.fn(),
mxcUrlToHttp: jest.fn().mockImplementation((mxc: string) => `http://this.is.a.url/${mxc.substring(6)}`),
setAccountData: jest.fn(),
deleteAccountData: jest.fn(),
setRoomAccountData: jest.fn(),
setRoomName: jest.fn(),
setRoomTopic: jest.fn(),
setRoomReadMarkers: jest.fn().mockResolvedValue({}),
sendTyping: jest.fn().mockResolvedValue({}),
sendMessage: jest.fn().mockResolvedValue({}),
sendStateEvent: jest.fn().mockResolvedValue(undefined),
sendRtcDecline: jest.fn().mockResolvedValue(undefined),
getSyncState: jest.fn().mockReturnValue("SYNCING"),
mxcUrlToHttp: vi.fn().mockImplementation((mxc: string) => `http://this.is.a.url/${mxc.substring(6)}`),
setAccountData: vi.fn(),
deleteAccountData: vi.fn(),
setRoomAccountData: vi.fn(),
setRoomName: vi.fn(),
setRoomTopic: vi.fn(),
setRoomReadMarkers: vi.fn().mockResolvedValue({}),
sendTyping: vi.fn().mockResolvedValue({}),
sendMessage: vi.fn().mockResolvedValue({}),
sendStateEvent: vi.fn().mockResolvedValue(undefined),
sendRtcDecline: vi.fn().mockResolvedValue(undefined),
getSyncState: vi.fn().mockReturnValue("SYNCING"),
generateClientSecret: () => "t35tcl1Ent5ECr3T",
isGuest: jest.fn().mockReturnValue(false),
getRoomHierarchy: jest.fn().mockReturnValue({
isGuest: vi.fn().mockReturnValue(false),
getRoomHierarchy: vi.fn().mockReturnValue({
rooms: [],
}),
createRoom: jest.fn(async (createOpts?: ICreateRoomOpts) => {
createRoom: vi.fn(async (createOpts?: ICreateRoomOpts) => {
const initialState = createOpts?.initial_state?.map((event, i) =>
mkEvent({
...event,
@@ -244,60 +245,60 @@ export function createTestClient(): MatrixClient {
);
return { room_id: "!1:example.org" };
}),
setPowerLevel: jest.fn().mockResolvedValue(undefined),
setPowerLevel: vi.fn().mockResolvedValue(undefined),
pushRules: {},
decryptEventIfNeeded: () => Promise.resolve(),
isUserIgnored: jest.fn().mockReturnValue(false),
getCapabilities: jest.fn().mockResolvedValue({}),
getCachedCapabilities: jest.fn().mockReturnValue({}),
supportsThreads: jest.fn().mockReturnValue(false),
supportsIntentionalMentions: jest.fn().mockReturnValue(false),
getRoomUpgradeHistory: jest.fn().mockReturnValue([]),
getOpenIdToken: jest.fn().mockResolvedValue(undefined),
registerWithIdentityServer: jest.fn().mockResolvedValue({}),
getIdentityAccount: jest.fn().mockResolvedValue({}),
getTerms: jest.fn().mockResolvedValue({ policies: [] }),
agreeToTerms: jest.fn(),
doesServerSupportUnstableFeature: jest.fn().mockResolvedValue(undefined),
isVersionSupported: jest.fn().mockResolvedValue(undefined),
getPushRules: jest.fn().mockResolvedValue(undefined),
getPushers: jest.fn().mockResolvedValue({ pushers: [] }),
getThreePids: jest.fn().mockResolvedValue({ threepids: [] }),
bulkLookupThreePids: jest.fn().mockResolvedValue({ threepids: [] }),
setAvatarUrl: jest.fn().mockResolvedValue(undefined),
setDisplayName: jest.fn().mockResolvedValue(undefined),
setPusher: jest.fn().mockResolvedValue(undefined),
setPushRuleEnabled: jest.fn().mockResolvedValue(undefined),
setPushRuleActions: jest.fn().mockResolvedValue(undefined),
relations: jest.fn().mockResolvedValue({
isUserIgnored: vi.fn().mockReturnValue(false),
getCapabilities: vi.fn().mockResolvedValue({}),
getCachedCapabilities: vi.fn().mockReturnValue({}),
supportsThreads: vi.fn().mockReturnValue(false),
supportsIntentionalMentions: vi.fn().mockReturnValue(false),
getRoomUpgradeHistory: vi.fn().mockReturnValue([]),
getOpenIdToken: vi.fn().mockResolvedValue(undefined),
registerWithIdentityServer: vi.fn().mockResolvedValue({}),
getIdentityAccount: vi.fn().mockResolvedValue({}),
getTerms: vi.fn().mockResolvedValue({ policies: [] }),
agreeToTerms: vi.fn(),
doesServerSupportUnstableFeature: vi.fn().mockResolvedValue(undefined),
isVersionSupported: vi.fn().mockResolvedValue(undefined),
getPushRules: vi.fn().mockResolvedValue(undefined),
getPushers: vi.fn().mockResolvedValue({ pushers: [] }),
getThreePids: vi.fn().mockResolvedValue({ threepids: [] }),
bulkLookupThreePids: vi.fn().mockResolvedValue({ threepids: [] }),
setAvatarUrl: vi.fn().mockResolvedValue(undefined),
setDisplayName: vi.fn().mockResolvedValue(undefined),
setPusher: vi.fn().mockResolvedValue(undefined),
setPushRuleEnabled: vi.fn().mockResolvedValue(undefined),
setPushRuleActions: vi.fn().mockResolvedValue(undefined),
relations: vi.fn().mockResolvedValue({
events: [],
}),
hasLazyLoadMembersEnabled: jest.fn().mockReturnValue(false),
isInitialSyncComplete: jest.fn().mockReturnValue(true),
fetchRoomEvent: jest.fn().mockRejectedValue({}),
makeTxnId: jest.fn().mockImplementation(() => `t${txnId++}`),
sendToDevice: jest.fn().mockResolvedValue(undefined),
queueToDevice: jest.fn().mockResolvedValue(undefined),
cancelPendingEvent: jest.fn(),
hasLazyLoadMembersEnabled: vi.fn().mockReturnValue(false),
isInitialSyncComplete: vi.fn().mockReturnValue(true),
fetchRoomEvent: vi.fn().mockRejectedValue({}),
makeTxnId: vi.fn().mockImplementation(() => `t${txnId++}`),
sendToDevice: vi.fn().mockResolvedValue(undefined),
queueToDevice: vi.fn().mockResolvedValue(undefined),
cancelPendingEvent: vi.fn(),
getMediaHandler: jest.fn().mockReturnValue({
setVideoInput: jest.fn(),
setAudioInput: jest.fn(),
setAudioSettings: jest.fn(),
stopAllStreams: jest.fn(),
getMediaHandler: vi.fn().mockReturnValue({
setVideoInput: vi.fn(),
setAudioInput: vi.fn(),
setAudioSettings: vi.fn(),
stopAllStreams: vi.fn(),
} as unknown as MediaHandler),
uploadContent: jest.fn(),
uploadContent: vi.fn(),
getEventMapper: (_options?: MapperOpts) => (event: Partial<IEvent>) => new MatrixEvent(event),
leaveRoomChain: jest.fn((roomId) => ({ [roomId]: null })),
requestPasswordEmailToken: jest.fn().mockRejectedValue({}),
setPassword: jest.fn().mockRejectedValue({}),
leaveRoomChain: vi.fn((roomId) => ({ [roomId]: null })),
requestPasswordEmailToken: vi.fn().mockRejectedValue({}),
setPassword: vi.fn().mockRejectedValue({}),
groupCallEventHandler: { groupCalls: new Map<string, GroupCall>() },
redactEvent: jest.fn(),
redactEvent: vi.fn(),
createMessagesRequest: jest.fn().mockResolvedValue({
createMessagesRequest: vi.fn().mockResolvedValue({
chunk: [],
}),
sendEvent: jest.fn().mockImplementation((roomId, type, content) => {
sendEvent: vi.fn().mockImplementation((roomId, type, content) => {
return new MatrixEvent({
type,
sender: "@me:localhost",
@@ -306,61 +307,61 @@ export function createTestClient(): MatrixClient {
room_id: roomId,
});
}),
resendEvent: jest.fn().mockResolvedValue({}),
resendEvent: vi.fn().mockResolvedValue({}),
_unstable_sendDelayedEvent: jest.fn(),
_unstable_sendDelayedStateEvent: jest.fn(),
_unstable_cancelScheduledDelayedEvent: jest.fn(),
_unstable_restartScheduledDelayedEvent: jest.fn(),
_unstable_sendScheduledDelayedEvent: jest.fn(),
_unstable_sendStickyEvent: jest.fn(),
_unstable_sendStickyDelayedEvent: jest.fn(),
_unstable_getRTCTransports: jest.fn(),
searchUserDirectory: jest.fn().mockResolvedValue({ limited: false, results: [] }),
setDeviceVerified: jest.fn(),
joinRoom: jest.fn(),
getSyncStateData: jest.fn(),
getDehydratedDevice: jest.fn(),
exportRoomKeys: jest.fn(),
knockRoom: jest.fn(),
leave: jest.fn(),
getVersions: jest.fn().mockResolvedValue({ versions: ["v1.1"] }),
requestAdd3pidEmailToken: jest.fn(),
requestAdd3pidMsisdnToken: jest.fn(),
submitMsisdnTokenOtherUrl: jest.fn(),
deleteThreePid: jest.fn().mockResolvedValue({}),
bindThreePid: jest.fn().mockResolvedValue({}),
unbindThreePid: jest.fn().mockResolvedValue({}),
requestEmailToken: jest.fn(),
addThreePidOnly: jest.fn(),
requestMsisdnToken: jest.fn(),
submitMsisdnToken: jest.fn(),
getMediaConfig: jest.fn(),
_unstable_sendDelayedEvent: vi.fn(),
_unstable_sendDelayedStateEvent: vi.fn(),
_unstable_cancelScheduledDelayedEvent: vi.fn(),
_unstable_restartScheduledDelayedEvent: vi.fn(),
_unstable_sendScheduledDelayedEvent: vi.fn(),
_unstable_sendStickyEvent: vi.fn(),
_unstable_sendStickyDelayedEvent: vi.fn(),
_unstable_getRTCTransports: vi.fn(),
searchUserDirectory: vi.fn().mockResolvedValue({ limited: false, results: [] }),
setDeviceVerified: vi.fn(),
joinRoom: vi.fn(),
getSyncStateData: vi.fn(),
getDehydratedDevice: vi.fn(),
exportRoomKeys: vi.fn(),
knockRoom: vi.fn(),
leave: vi.fn(),
getVersions: vi.fn().mockResolvedValue({ versions: ["v1.1"] }),
requestAdd3pidEmailToken: vi.fn(),
requestAdd3pidMsisdnToken: vi.fn(),
submitMsisdnTokenOtherUrl: vi.fn(),
deleteThreePid: vi.fn().mockResolvedValue({}),
bindThreePid: vi.fn().mockResolvedValue({}),
unbindThreePid: vi.fn().mockResolvedValue({}),
requestEmailToken: vi.fn(),
addThreePidOnly: vi.fn(),
requestMsisdnToken: vi.fn(),
submitMsisdnToken: vi.fn(),
getMediaConfig: vi.fn(),
baseUrl: "https://matrix-client.matrix.org",
matrixRTC: createStubMatrixRTC(),
isFallbackICEServerAllowed: jest.fn().mockReturnValue(false),
getAuthIssuer: jest.fn(),
getOrCreateFilter: jest.fn(),
sendStickerMessage: jest.fn(),
getLocalAliases: jest.fn().mockReturnValue([]),
uploadDeviceSigningKeys: jest.fn(),
isKeyBackupKeyStored: jest.fn().mockResolvedValue(null),
getIgnoredUsers: jest.fn().mockReturnValue([]),
setIgnoredUsers: jest.fn(),
reportRoom: jest.fn(),
isFallbackICEServerAllowed: vi.fn().mockReturnValue(false),
getAuthIssuer: vi.fn(),
getOrCreateFilter: vi.fn(),
sendStickerMessage: vi.fn(),
getLocalAliases: vi.fn().mockReturnValue([]),
uploadDeviceSigningKeys: vi.fn(),
isKeyBackupKeyStored: vi.fn().mockResolvedValue(null),
getIgnoredUsers: vi.fn().mockReturnValue([]),
setIgnoredUsers: vi.fn(),
reportRoom: vi.fn(),
pushProcessor: {
getPushRuleById: jest.fn(),
getPushRuleById: vi.fn(),
},
search: jest.fn().mockResolvedValue({}),
processRoomEventsSearch: jest.fn().mockResolvedValue({ highlights: [], results: [] }),
invite: jest.fn(),
kick: jest.fn(),
ban: jest.fn(),
sendTextMessage: jest.fn(),
deleteRoomTag: jest.fn().mockResolvedValue({}),
setRoomTag: jest.fn().mockResolvedValue({}),
getExtendedProfileProperty: jest.fn(),
setExtendedProfileProperty: jest.fn().mockResolvedValue(undefined),
search: vi.fn().mockResolvedValue({}),
processRoomEventsSearch: vi.fn().mockResolvedValue({ highlights: [], results: [] }),
invite: vi.fn(),
kick: vi.fn(),
ban: vi.fn(),
sendTextMessage: vi.fn(),
deleteRoomTag: vi.fn().mockResolvedValue({}),
setRoomTag: vi.fn().mockResolvedValue({}),
getExtendedProfileProperty: vi.fn(),
setExtendedProfileProperty: vi.fn().mockResolvedValue(undefined),
} as unknown as MatrixClient;
client.reEmitter = new ReEmitter(client);
@@ -379,7 +380,7 @@ export function createTestClient(): MatrixClient {
export function createStubMatrixRTC(): MatrixRTCSessionManager {
const eventEmitterMatrixRTCSessionManager = new EventEmitter();
const mockGetRoomSession = jest.fn();
const mockGetRoomSession = vi.fn();
mockGetRoomSession.mockImplementation((roomId) => {
const session = new EventEmitter() as MatrixRTCSession;
session.memberships = [];
@@ -388,9 +389,9 @@ export function createStubMatrixRTC(): MatrixRTCSessionManager {
return session;
});
return {
start: jest.fn(),
stop: jest.fn(),
getActiveRoomSession: jest.fn(),
start: vi.fn(),
stop: vi.fn(),
getActiveRoomSession: vi.fn(),
getRoomSession: mockGetRoomSession,
on: eventEmitterMatrixRTCSessionManager.on.bind(eventEmitterMatrixRTCSessionManager),
off: eventEmitterMatrixRTCSessionManager.off.bind(eventEmitterMatrixRTCSessionManager),
@@ -673,42 +674,42 @@ export function mkStubRoom(
const eventEmitter = new EventEmitter();
return {
canInvite: jest.fn().mockReturnValue(false),
canInvite: vi.fn().mockReturnValue(false),
client,
findThreadForEvent: jest.fn(),
createThreadsTimelineSets: jest.fn().mockReturnValue(new Promise(() => {})),
findThreadForEvent: vi.fn(),
createThreadsTimelineSets: vi.fn().mockReturnValue(new Promise(() => {})),
currentState: {
getStateEvents: jest.fn((_type, key) => (key === undefined ? [] : null)),
getMember: jest.fn(),
mayClientSendStateEvent: jest.fn().mockReturnValue(true),
maySendStateEvent: jest.fn().mockReturnValue(true),
maySendRedactionForEvent: jest.fn().mockReturnValue(true),
maySendEvent: jest.fn().mockReturnValue(true),
maySendMessage: jest.fn().mockReturnValue(true),
getStateEvents: vi.fn((_type, key) => (key === undefined ? [] : null)),
getMember: vi.fn(),
mayClientSendStateEvent: vi.fn().mockReturnValue(true),
maySendStateEvent: vi.fn().mockReturnValue(true),
maySendRedactionForEvent: vi.fn().mockReturnValue(true),
maySendEvent: vi.fn().mockReturnValue(true),
maySendMessage: vi.fn().mockReturnValue(true),
members: {},
getHistoryVisibility: jest.fn().mockReturnValue(HistoryVisibility.Shared),
getJoinRule: jest.fn().mockReturnValue(JoinRule.Invite),
on: jest.fn(),
off: jest.fn(),
removeListener: jest.fn(),
getHistoryVisibility: vi.fn().mockReturnValue(HistoryVisibility.Shared),
getJoinRule: vi.fn().mockReturnValue(JoinRule.Invite),
on: vi.fn(),
off: vi.fn(),
removeListener: vi.fn(),
} as unknown as RoomState,
eventShouldLiveIn: jest.fn().mockReturnValue({ shouldLiveInRoom: true, shouldLiveInThread: false }),
fetchRoomThreads: jest.fn().mockReturnValue(Promise.resolve()),
findEventById: jest.fn().mockReturnValue(undefined),
findPredecessor: jest.fn().mockReturnValue({ roomId: "", eventId: null }),
getAltAliases: jest.fn().mockReturnValue([]),
eventShouldLiveIn: vi.fn().mockReturnValue({ shouldLiveInRoom: true, shouldLiveInThread: false }),
fetchRoomThreads: vi.fn().mockReturnValue(Promise.resolve()),
findEventById: vi.fn().mockReturnValue(undefined),
findPredecessor: vi.fn().mockReturnValue({ roomId: "", eventId: null }),
getAltAliases: vi.fn().mockReturnValue([]),
getAvatarUrl: () => "mxc://avatar.url/room.png",
getCanonicalAlias: jest.fn(),
getDMInviter: jest.fn(),
getEventReadUpTo: jest.fn(() => null),
getInvitedAndJoinedMemberCount: jest.fn().mockReturnValue(1),
getJoinRule: jest.fn().mockReturnValue("invite"),
getJoinedMemberCount: jest.fn().mockReturnValue(1),
getJoinedMembers: jest.fn().mockReturnValue([]),
getLiveTimeline: jest.fn().mockReturnValue(stubTimeline),
getLastLiveEvent: jest.fn().mockReturnValue(undefined),
getLastActiveTimestamp: jest.fn().mockReturnValue(1183140000),
getMember: jest.fn().mockReturnValue({
getCanonicalAlias: vi.fn(),
getDMInviter: vi.fn(),
getEventReadUpTo: vi.fn(() => null),
getInvitedAndJoinedMemberCount: vi.fn().mockReturnValue(1),
getJoinRule: vi.fn().mockReturnValue("invite"),
getJoinedMemberCount: vi.fn().mockReturnValue(1),
getJoinedMembers: vi.fn().mockReturnValue([]),
getLiveTimeline: vi.fn().mockReturnValue(stubTimeline),
getLastLiveEvent: vi.fn().mockReturnValue(undefined),
getLastActiveTimestamp: vi.fn().mockReturnValue(1183140000),
getMember: vi.fn().mockReturnValue({
userId: "@member:domain.bla",
name: "Member",
rawDisplayName: "Member",
@@ -718,29 +719,29 @@ export function mkStubRoom(
events: {},
isKicked: () => false,
}),
getMembers: jest.fn().mockReturnValue([]),
getEncryptionTargetMembers: jest.fn().mockReturnValue([]),
getMembersWithMembership: jest.fn().mockReturnValue([]),
getMembers: vi.fn().mockReturnValue([]),
getEncryptionTargetMembers: vi.fn().mockReturnValue([]),
getMembersWithMembership: vi.fn().mockReturnValue([]),
getMxcAvatarUrl: () => "mxc://avatar.url/room.png",
getMyMembership: jest.fn().mockReturnValue(KnownMembership.Join),
getPendingEvents: jest.fn().mockReturnValue([]),
getReceiptsForEvent: jest.fn().mockReturnValue([]),
getRecommendedVersion: jest.fn().mockReturnValue(Promise.resolve("")),
getThreads: jest.fn().mockReturnValue([]),
getType: jest.fn().mockReturnValue(undefined),
getUnfilteredTimelineSet: jest.fn(),
getUnreadNotificationCount: jest.fn(() => 0),
getRoomUnreadNotificationCount: jest.fn().mockReturnValue(0),
getVersion: jest.fn().mockReturnValue("1"),
getBumpStamp: jest.fn().mockReturnValue(0),
getAccountData: jest.fn(),
getMyMembership: vi.fn().mockReturnValue(KnownMembership.Join),
getPendingEvents: vi.fn().mockReturnValue([]),
getReceiptsForEvent: vi.fn().mockReturnValue([]),
getRecommendedVersion: vi.fn().mockReturnValue(Promise.resolve("")),
getThreads: vi.fn().mockReturnValue([]),
getType: vi.fn().mockReturnValue(undefined),
getUnfilteredTimelineSet: vi.fn(),
getUnreadNotificationCount: vi.fn(() => 0),
getRoomUnreadNotificationCount: vi.fn().mockReturnValue(0),
getVersion: vi.fn().mockReturnValue("1"),
getBumpStamp: vi.fn().mockReturnValue(0),
getAccountData: vi.fn(),
hasMembershipState: () => false,
isElementVideoRoom: jest.fn().mockReturnValue(false),
isSpaceRoom: jest.fn().mockReturnValue(false),
isCallRoom: jest.fn().mockReturnValue(false),
hasEncryptionStateEvent: jest.fn().mockReturnValue(false),
loadMembersIfNeeded: jest.fn(),
maySendMessage: jest.fn().mockReturnValue(true),
isElementVideoRoom: vi.fn().mockReturnValue(false),
isSpaceRoom: vi.fn().mockReturnValue(false),
isCallRoom: vi.fn().mockReturnValue(false),
hasEncryptionStateEvent: vi.fn().mockReturnValue(false),
loadMembersIfNeeded: vi.fn(),
maySendMessage: vi.fn().mockReturnValue(true),
myUserId: client?.getUserId(),
name,
normalizedName: normalize(name || ""),
@@ -750,8 +751,8 @@ export function mkStubRoom(
removeListener: eventEmitter.removeListener.bind(eventEmitter),
emit: eventEmitter.emit.bind(eventEmitter),
roomId,
setBlacklistUnverifiedDevices: jest.fn(),
setUnreadNotificationCount: jest.fn(),
setBlacklistUnverifiedDevices: vi.fn(),
setUnreadNotificationCount: vi.fn(),
tags: {},
timeline: [],
} as unknown as Room;
@@ -828,8 +829,8 @@ export const mkRoom = (
roomId: string,
rooms?: ReturnType<typeof mkStubRoom>[],
): MockedObject<Room> => {
const room = mocked(mkStubRoom(roomId, roomId, client));
mocked(room.currentState).getStateEvents.mockImplementation(mockStateEventImplementation([]));
const room = vi.mocked(mkStubRoom(roomId, roomId, client));
vi.mocked(room.currentState).getStateEvents.mockImplementation(mockStateEventImplementation([]));
rooms?.push(room);
return room;
};
@@ -858,10 +859,10 @@ export const mkSpace = (
rooms?: ReturnType<typeof mkStubRoom>[],
children: string[] = [],
): MockedObject<Room> => {
const space = mocked(mkRoom(client, spaceId, rooms));
const space = vi.mocked(mkRoom(client, spaceId, rooms));
space.isSpaceRoom.mockReturnValue(true);
space.getType.mockReturnValue(RoomType.Space);
mocked(space.currentState).getStateEvents.mockImplementation(
vi.mocked(space.currentState).getStateEvents.mockImplementation(
mockStateEventImplementation(
children.map((roomId) =>
mkEvent({
+1 -1
View File
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { act } from "jest-matrix-react";
import { act } from "test-utils-rtl";
import type EventEmitter from "events";
import { type ActionPayload } from "../../src/dispatcher/payloads";
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { type Mocked, mocked, type MockedObject } from "jest-mock";
import { type Mocked, mocked } from "jest-mock-vitest-adapter";
import {
MatrixEvent,
type Room,
@@ -1468,7 +1468,7 @@ describe("DeviceListener", () => {
});
});
function mockKeyBackupFromServer(client: MockedObject<MatrixClient>, enabled: boolean) {
function mockKeyBackupFromServer(client: Mocked<MatrixClient>, enabled: boolean) {
client.getAccountDataFromServer.mockImplementation(async (eventType: string) => {
switch (eventType) {
case ACCOUNT_DATA_KEY_M_KEY_BACKUP:
+1 -1
View File
@@ -11,7 +11,7 @@ import { logger } from "matrix-js-sdk/src/logger";
import * as MatrixJs from "matrix-js-sdk/src/matrix";
import { decodeBase64, encodeUnpaddedBase64 } from "matrix-js-sdk/src/matrix";
import * as encryptAESSecretStorageItemModule from "matrix-js-sdk/src/utils/encryptAESSecretStorageItem";
import { mocked, type MockedObject } from "jest-mock";
import { mocked, type MockedObject } from "jest-mock-vitest-adapter";
import fetchMock from "@fetch-mock/jest";
import StorageEvictedDialog from "../../src/components/views/dialogs/StorageEvictedDialog";
+2 -2
View File
@@ -5,7 +5,7 @@ Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { mocked, type MockedObject } from "jest-mock";
import { mocked, type MockedObject } from "jest-mock-vitest-adapter";
import {
ClientEvent,
type MatrixClient,
@@ -602,7 +602,7 @@ describe("Notifier", () => {
slotDescription: { application: "m.call", id: "" },
} as unknown as MatrixRTCSession;
mockClient.matrixRTC.getRoomSession.mockReturnValue(mockRtcSession);
mocked(mockClient.matrixRTC.getRoomSession).mockReturnValue(mockRtcSession);
emitCallNotificationEvent();
expect(ToastStore.sharedInstance().addOrReplaceToast).not.toHaveBeenCalled();
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
import "fake-indexeddb/auto";
import React, { type ComponentProps, createRef, type RefObject } from "react";
import { fireEvent, render, type RenderResult, screen, waitFor, within, act } from "jest-matrix-react";
import { type Mocked, mocked } from "jest-mock";
import { type Mocked, mocked } from "jest-mock-vitest-adapter";
import { ClientEvent, type MatrixClient, MatrixEvent, Room, SyncState } from "matrix-js-sdk/src/matrix";
import { type MediaHandler } from "matrix-js-sdk/src/webrtc/mediaHandler";
import * as MatrixJs from "matrix-js-sdk/src/matrix";
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import { fireEvent, render, screen, waitForElementToBeRemoved } from "jest-matrix-react";
import { mocked, type MockedObject } from "jest-mock";
import { mocked, type MockedObject } from "jest-mock-vitest-adapter";
import fetchMock from "@fetch-mock/jest";
import { DELEGATED_OIDC_COMPATIBILITY, IdentityProviderBrand, type OidcClientConfig } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import { fireEvent, render, screen, waitFor, waitForElementToBeRemoved } from "jest-matrix-react";
import { createClient, type MatrixClient, MatrixError, type OidcClientConfig } from "matrix-js-sdk/src/matrix";
import { mocked, type MockedObject } from "jest-mock";
import { mocked, type MockedObject } from "jest-mock-vitest-adapter";
import fetchMock from "@fetch-mock/jest";
import SdkConfig, { DEFAULTS } from "../../../../../src/SdkConfig";
@@ -12,7 +12,7 @@ import userEvent from "@testing-library/user-event";
import { type MatrixClient, MatrixError, Room, RoomType } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { sleep } from "matrix-js-sdk/src/utils";
import { mocked, type Mocked } from "jest-mock";
import { mocked, type Mocked } from "jest-mock-vitest-adapter";
import { UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
import InviteDialog from "../../../../../src/components/views/dialogs/InviteDialog";
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { mocked, type MockedObject } from "jest-mock";
import { mocked, type MockedObject } from "jest-mock-vitest-adapter";
import { Device, DeviceVerification, type MatrixClient } from "matrix-js-sdk/src/matrix";
import { type CryptoApi, DeviceVerificationStatus, type KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
import { fireEvent, render, type RenderResult, screen, waitFor } from "jest-matrix-react";
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type ReactElement } from "react";
import { render, screen, waitFor } from "jest-matrix-react";
import { mocked, type MockedObject } from "jest-mock";
import { mocked, type MockedObject } from "jest-mock-vitest-adapter";
import { ClientEvent, MatrixEvent, type MatrixClient } from "matrix-js-sdk/src/matrix";
import SettingsStore, { type CallbackFn } from "../../../../../src/settings/SettingsStore";
@@ -258,7 +258,7 @@ describe("<UserSettingsDialog />", () => {
it("displays an indicator when user needs to set up recovery", async () => {
// Initially, the user doesn't have secret storage, so it should display
// an indicator.
mockClient.secretStorage.getDefaultKeyId.mockResolvedValue(null);
mocked(mockClient.secretStorage.getDefaultKeyId).mockResolvedValue(null);
const { container } = render(getComponent());
@@ -271,7 +271,7 @@ describe("<UserSettingsDialog />", () => {
// The user now has secret storage. Trigger an update and check that
// the indicator disappears.
mockClient.secretStorage.getDefaultKeyId.mockResolvedValue("foo");
mocked(mockClient.secretStorage.getDefaultKeyId).mockResolvedValue("foo");
mockClient.emit(ClientEvent.AccountData, new MatrixEvent({ type: "m.secret_storage.default_key" }));
await waitFor(() => {
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
import React, { act } from "react";
import { render } from "jest-matrix-react";
import { type CryptoApi } from "matrix-js-sdk/src/crypto-api";
import { type Mocked } from "jest-mock";
import { type Mocked } from "jest-mock-vitest-adapter";
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { getMockClientWithEventEmitter } from "../../../../../test-utils";
@@ -10,7 +10,7 @@ import React from "react";
import { EventType, getHttpUriForMxc, type IContent, type MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { fireEvent, render, screen } from "jest-matrix-react";
import fetchMock from "@fetch-mock/jest";
import { type MockedObject } from "jest-mock";
import { type MockedObject } from "jest-mock-vitest-adapter";
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
import { type RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type ComponentProps } from "react";
import { type MatrixClient, type MatrixEvent, PushRuleKind, type Room } from "matrix-js-sdk/src/matrix";
import { mocked, type MockedObject } from "jest-mock";
import { mocked, type MockedObject } from "jest-mock-vitest-adapter";
import { act, render, waitFor } from "jest-matrix-react";
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
@@ -11,7 +11,7 @@ import React from "react";
import { type MatrixClient, ThreepidMedium } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import userEvent from "@testing-library/user-event";
import { type MockedObject } from "jest-mock";
import { type MockedObject } from "jest-mock-vitest-adapter";
import AccountUserSettingsTab from "../../../../../../../src/components/views/settings/tabs/user/AccountUserSettingsTab";
import { SdkContextClass, SDKContext } from "../../../../../../../src/contexts/SDKContext";
@@ -18,7 +18,7 @@ import {
} from "../../../../../../test-utils";
import MatrixClientBackedController from "../../../../../../../src/settings/controllers/MatrixClientBackedController";
import MatrixClientBackedSettingsHandler from "../../../../../../../src/settings/handlers/MatrixClientBackedSettingsHandler";
import type { MockedObject } from "jest-mock";
import type { MockedObject } from "jest-mock-vitest-adapter";
import {
MEDIA_PREVIEW_ACCOUNT_DATA_TYPE,
type MediaPreviewConfig,
@@ -33,7 +33,7 @@ import {
MatrixError,
type MatrixClient,
} from "matrix-js-sdk/src/matrix";
import { mocked, type MockedObject } from "jest-mock";
import { mocked, type MockedObject } from "jest-mock-vitest-adapter";
import fetchMock from "@fetch-mock/jest";
import {
@@ -17,7 +17,7 @@ import {
Visibility,
} from "matrix-js-sdk/src/matrix";
import userEvent from "@testing-library/user-event";
import { type MockedObject } from "jest-mock";
import { type MockedObject } from "jest-mock-vitest-adapter";
import * as createRoomModule from "../../../../../src/createRoom";
import SpaceCreateMenu, { createSpace } from "../../../../../src/components/views/spaces/SpaceCreateMenu";
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import React, { type ComponentProps } from "react";
import { mocked, type Mocked } from "jest-mock";
import { mocked, type Mocked } from "jest-mock-vitest-adapter";
import { render, type RenderResult } from "jest-matrix-react";
import { TypedEventEmitter, type IMyDevice, type MatrixClient, Device } from "matrix-js-sdk/src/matrix";
import { type VerificationRequest, VerificationRequestEvent } from "matrix-js-sdk/src/crypto-api";
@@ -1,78 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import defaultDispatcher from "../../../src/dispatcher/dispatcher";
import { Action } from "../../../src/dispatcher/actions";
import { AsyncActionPayload } from "../../../src/dispatcher/payloads";
describe("MatrixDispatcher", () => {
it("should throw error if unregistering unknown token", () => {
expect(() => defaultDispatcher.unregister("not-a-real-token")).toThrow(
"Dispatcher.unregister(...): 'not-a-real-token' does not map to a registered callback.",
);
});
it("should execute callbacks in registered order", async () => {
const deferred1 = Promise.withResolvers<number>();
const deferred2 = Promise.withResolvers<number>();
const fn1 = jest.fn(() => deferred1.resolve(1));
const fn2 = jest.fn(() => deferred2.resolve(2));
defaultDispatcher.register(fn1);
defaultDispatcher.register(fn2);
defaultDispatcher.dispatch({ action: Action.OnLoggedIn });
const res = await Promise.race([deferred1.promise, deferred2.promise]);
expect(res).toBe(1);
});
it("should skip the queue for the given callback", async () => {
const deferred1 = Promise.withResolvers<number>();
const deferred2 = Promise.withResolvers<number>();
const fn1 = jest.fn(() => deferred1.resolve(1));
const fn2 = jest.fn(() => deferred2.resolve(2));
defaultDispatcher.register(() => {
defaultDispatcher.waitFor([id2]);
});
defaultDispatcher.register(fn1);
const id2 = defaultDispatcher.register(fn2);
defaultDispatcher.dispatch({ action: Action.OnLoggedIn });
const res = await Promise.race([deferred1.promise, deferred2.promise]);
expect(res).toBe(2);
});
it("should not fire callback which was added during a dispatch", () => {
const fn2 = jest.fn();
defaultDispatcher.register(() => {
defaultDispatcher.register(fn2);
});
defaultDispatcher.dispatch({ action: Action.OnLoggedIn }, true);
expect(fn2).not.toHaveBeenCalled();
});
it("should handle AsyncActionPayload", () => {
const fn = jest.fn();
defaultDispatcher.register(fn);
const readyFn = jest.fn((dispatch) => {
dispatch({ action: "test" });
});
defaultDispatcher.dispatch(new AsyncActionPayload(readyFn), true);
expect(fn).toHaveBeenLastCalledWith(expect.objectContaining({ action: "test" }));
});
});
@@ -1,186 +0,0 @@
/*
Copyright 2025 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { type Mocked } from "jest-mock";
import {
Direction,
type MatrixClient,
type IEvent,
MatrixEvent,
type Room,
ClientEvent,
SyncState,
} from "matrix-js-sdk/src/matrix";
import EventIndex from "../../../src/indexing/EventIndex.ts";
import { emitPromise, getMockClientWithEventEmitter, mockClientMethodsRooms, mockPlatformPeg } from "../../test-utils";
import type BaseEventIndexManager from "../../../src/indexing/BaseEventIndexManager.ts";
import { type ICrawlerCheckpoint } from "../../../src/indexing/BaseEventIndexManager.ts";
import SettingsStore from "../../../src/settings/SettingsStore.ts";
afterEach(() => {
jest.restoreAllMocks();
});
describe("EventIndex", () => {
it("crawls through the loaded checkpoints", async () => {
const mockIndexingManager = {
loadCheckpoints: jest.fn(),
removeCrawlerCheckpoint: jest.fn(),
isEventIndexEmpty: jest.fn().mockResolvedValue(false),
} as any as Mocked<BaseEventIndexManager>;
mockPlatformPeg({ getEventIndexingManager: () => mockIndexingManager });
const room1 = { roomId: "!room1:id" } as any as Room;
const room2 = { roomId: "!room2:id" } as any as Room;
const mockClient = getMockClientWithEventEmitter({
getEventMapper: () => (obj: Partial<IEvent>) => new MatrixEvent(obj),
createMessagesRequest: jest.fn(),
...mockClientMethodsRooms([room1, room2]),
});
jest.spyOn(SettingsStore, "getValueAt").mockImplementation((_level, settingName): any => {
if (settingName === "crawlerSleepTime") return 0;
return undefined;
});
mockIndexingManager.loadCheckpoints.mockResolvedValue([
{ roomId: "!room1:id", token: "token1", direction: Direction.Backward } as ICrawlerCheckpoint,
{ roomId: "!room2:id", token: "token2", direction: Direction.Forward } as ICrawlerCheckpoint,
]);
const indexer = new EventIndex();
await indexer.init();
let changedCheckpointPromise = emitPromise(indexer, "changedCheckpoint") as Promise<Room>;
indexer.startCrawler();
// Mock out the /messags request, and wait for the crawler to hit the first room
const mock1 = mockCreateMessagesRequest(mockClient);
let changedCheckpoint = await changedCheckpointPromise;
expect(changedCheckpoint.roomId).toEqual("!room1:id");
await mock1.called;
expect(mockClient.createMessagesRequest).toHaveBeenCalledWith("!room1:id", "token1", 100, "b");
// Continue, and wait for the crawler to hit the second room
changedCheckpointPromise = emitPromise(indexer, "changedCheckpoint") as Promise<Room>;
mock1.resolve({ chunk: [] });
changedCheckpoint = await changedCheckpointPromise;
expect(changedCheckpoint.roomId).toEqual("!room2:id");
// Mock out the /messages request again, and wait for it to be called
const mock2 = mockCreateMessagesRequest(mockClient);
await mock2.called;
expect(mockClient.createMessagesRequest).toHaveBeenCalledWith("!room2:id", "token2", 100, "f");
});
it("adds checkpoints for the encrypted rooms after the first sync", async () => {
const mockIndexingManager = {
loadCheckpoints: jest.fn().mockResolvedValue([]),
isEventIndexEmpty: jest.fn().mockResolvedValue(true),
addCrawlerCheckpoint: jest.fn(),
removeCrawlerCheckpoint: jest.fn(),
commitLiveEvents: jest.fn(),
} as any as Mocked<BaseEventIndexManager>;
mockPlatformPeg({ getEventIndexingManager: () => mockIndexingManager });
const room1 = {
roomId: "!room1:id",
getLiveTimeline: () => ({
getPaginationToken: () => "token1",
}),
} as any as Room;
const room2 = {
roomId: "!room2:id",
getLiveTimeline: () => ({
getPaginationToken: () => "token2",
}),
} as any as Room;
const mockCrypto = {
isEncryptionEnabledInRoom: jest.fn().mockResolvedValue(true),
};
const mockClient = getMockClientWithEventEmitter({
getEventMapper: () => (obj: Partial<IEvent>) => new MatrixEvent(obj),
createMessagesRequest: jest.fn(),
getCrypto: () => mockCrypto as any,
...mockClientMethodsRooms([room1, room2]),
});
const commitLiveEventsCalled = Promise.withResolvers<void>();
mockIndexingManager.commitLiveEvents.mockImplementation(async () => {
commitLiveEventsCalled.resolve();
});
const indexer = new EventIndex();
await indexer.init();
// During the first sync, some events are added to the index, meaning that `isEventIndexEmpty` will now be false.
mockIndexingManager.isEventIndexEmpty.mockResolvedValue(false);
// The first sync completes:
mockClient.emit(ClientEvent.Sync, SyncState.Syncing, null, {});
// Wait for `commitLiveEvents` to be called, by which time the checkpoints should have been added.
await commitLiveEventsCalled.promise;
expect(mockIndexingManager.addCrawlerCheckpoint).toHaveBeenCalledTimes(4);
expect(mockIndexingManager.addCrawlerCheckpoint).toHaveBeenCalledWith({
roomId: "!room1:id",
token: "token1",
direction: Direction.Backward,
fullCrawl: true,
});
expect(mockIndexingManager.addCrawlerCheckpoint).toHaveBeenCalledWith({
roomId: "!room1:id",
token: "token1",
direction: Direction.Forward,
});
expect(mockIndexingManager.addCrawlerCheckpoint).toHaveBeenCalledWith({
roomId: "!room2:id",
token: "token2",
direction: Direction.Backward,
fullCrawl: true,
});
expect(mockIndexingManager.addCrawlerCheckpoint).toHaveBeenCalledWith({
roomId: "!room2:id",
token: "token2",
direction: Direction.Forward,
});
});
});
/**
* Mock out the `createMessagesRequest` method on the client, with an implementation that will block until a resolver is called.
*
* @returns An object with the following properties:
* * `called`: A promise that resolves when `createMessagesRequest` is called.
* * `resolve`: A function that can be called to allow `createMessagesRequest` to complete.
*/
function mockCreateMessagesRequest(mockClient: Mocked<MatrixClient>): {
called: Promise<void>;
resolve: (result: any) => void;
} {
const messagesCalledPromise = Promise.withResolvers<void>();
const messagesResultPromise = Promise.withResolvers();
mockClient.createMessagesRequest.mockImplementationOnce(() => {
messagesCalledPromise.resolve();
return messagesResultPromise.promise as any;
});
return {
called: messagesCalledPromise.promise,
resolve: messagesResultPromise.resolve,
};
}
@@ -13,7 +13,7 @@ import { DialogContent, type DialogProps } from "@matrix-org/react-sdk-module-ap
import { screen, within } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { type Mocked } from "jest-mock";
import { type Mocked } from "jest-mock-vitest-adapter";
import { ProxiedModuleApi } from "../../../src/modules/ProxiedModuleApi";
import { getMockClientWithEventEmitter, mkRoom, stubClient } from "../../test-utils";
@@ -1,167 +0,0 @@
/*
Copyright 2024, 2025 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { ClientEvent, type MatrixClient, type Room, SyncState } from "matrix-js-sdk/src/matrix";
import { waitFor } from "jest-matrix-react";
import type BasePlatform from "../../../src/BasePlatform";
import SdkConfig from "../../../src/SdkConfig";
import { SettingLevel } from "../../../src/settings/SettingLevel";
import SettingsStore from "../../../src/settings/SettingsStore";
import { mkStubRoom, mockPlatformPeg, stubClient } from "../../test-utils";
import { SETTINGS, type SettingKey } from "../../../src/settings/Settings.tsx";
import MatrixClientBackedController from "../../../src/settings/controllers/MatrixClientBackedController.ts";
const TEST_DATA = [
{
name: "Electron.showTrayIcon" as SettingKey,
level: SettingLevel.PLATFORM,
value: true,
},
];
/**
* An existing setting that has {@link IBaseSetting#supportedLevelsAreOrdered} set to true.
*/
const SETTING_NAME_WITH_CONFIG_OVERRIDE = "feature_msc3531_hide_messages_pending_moderation";
describe("SettingsStore", () => {
let platformSettings: Record<string, any>;
beforeAll(() => {
jest.clearAllMocks();
platformSettings = {};
mockPlatformPeg({
isLevelSupported: jest.fn().mockReturnValue(true),
supportsSetting: jest.fn().mockReturnValue(true),
setSettingValue: jest.fn().mockImplementation((settingName: string, value: any) => {
platformSettings[settingName] = value;
}),
getSettingValue: jest.fn().mockImplementation((settingName: string) => {
return platformSettings[settingName];
}),
reload: jest.fn(),
} as unknown as BasePlatform);
TEST_DATA.forEach((d) => {
SettingsStore.setValue(d.name, null, d.level, d.value);
});
});
beforeEach(() => {
SdkConfig.reset();
SettingsStore.reset();
});
describe("getValueAt", () => {
TEST_DATA.forEach((d) => {
it(`should return the value "${d.level}"."${d.name}"`, () => {
expect(SettingsStore.getValueAt(d.level, d.name)).toBe(d.value);
// regression test #22545
expect(SettingsStore.getValueAt(d.level, d.name)).toBe(d.value);
});
});
it(`supportedLevelsAreOrdered correctly overrides setting`, async () => {
SdkConfig.put({
features: {
[SETTING_NAME_WITH_CONFIG_OVERRIDE]: false,
},
});
await SettingsStore.setValue(SETTING_NAME_WITH_CONFIG_OVERRIDE, null, SettingLevel.DEVICE, true);
expect(SettingsStore.getValue(SETTING_NAME_WITH_CONFIG_OVERRIDE)).toBe(false);
});
it(`supportedLevelsAreOrdered doesn't incorrectly override setting`, async () => {
await SettingsStore.setValue(SETTING_NAME_WITH_CONFIG_OVERRIDE, null, SettingLevel.DEVICE, true);
expect(SettingsStore.getValueAt(SettingLevel.DEVICE, SETTING_NAME_WITH_CONFIG_OVERRIDE)).toBe(true);
});
});
describe("exportForRageshake", () => {
it("should not export settings marked as non-exportable", async () => {
await SettingsStore.setValue("userTimezone", null, SettingLevel.DEVICE, "Europe/London");
const values = JSON.parse(SettingsStore.exportForRageshake()) as Record<SettingKey, unknown>;
for (const exportedKey of Object.keys(values) as SettingKey[]) {
expect(SETTINGS[exportedKey].shouldExportToRageshake).not.toEqual(false);
}
});
});
describe("runMigrations", () => {
let client: MatrixClient;
let room: Room;
beforeEach(() => {
client = stubClient();
room = mkStubRoom("!room:example.org", "Room", client);
client.getRooms = jest.fn().mockReturnValue([room]);
client.getRoom = jest.fn().mockReturnValue(room);
});
afterEach(() => {
jest.restoreAllMocks();
});
describe("Migrate media preview configuration", () => {
beforeEach(() => {
MatrixClientBackedController.matrixClient = client;
client.getAccountData = jest.fn().mockImplementation((type) => {
if (type === "im.vector.web.settings") {
return {
getContent: jest.fn().mockReturnValue({
showImages: false,
showAvatarsOnInvites: false,
}),
};
} else {
return undefined;
}
});
});
it("migrates media preview configuration immediately", async () => {
client.setAccountData = jest.fn();
SettingsStore.runMigrations(false);
expect(client.setAccountData).toHaveBeenCalledWith("io.element.msc4278.media_preview_config", {
invite_avatars: "off",
media_previews: "off",
});
});
it("migrates media preview configuration once client is ready", async () => {
client.setAccountData = jest.fn();
const mockInitialSync = (client.isInitialSyncComplete = jest.fn().mockReturnValue(false));
SettingsStore.runMigrations(false);
mockInitialSync.mockReturnValue(true);
client.emit(ClientEvent.Sync, SyncState.Prepared, null);
// Update is asynchronous
waitFor(() => {
expect(client.setAccountData).toHaveBeenCalledWith("io.element.msc4278.media_preview_config", {
invite_avatars: "off",
media_previews: "off",
});
});
});
it("does not migrate media preview configuration if the session is fresh", async () => {
client.setAccountData = jest.fn();
SettingsStore.runMigrations(true);
client.emit(ClientEvent.Sync, SyncState.Prepared, null);
expect(client.setAccountData).not.toHaveBeenCalled();
});
it("does not migrate media preview configuration if the account data is already set", async () => {
client.setAccountData = jest.fn();
client.getAccountData = jest.fn().mockReturnValue({});
SettingsStore.runMigrations(false);
client.emit(ClientEvent.Sync, SyncState.Prepared, null);
expect(client.setAccountData).not.toHaveBeenCalled();
});
});
});
});
@@ -1,38 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { ImageSize, suggestedSize } from "../../../../src/settings/enums/ImageSize";
describe("ImageSize", () => {
describe("suggestedSize", () => {
it("constrains width", () => {
const size = suggestedSize(ImageSize.Normal, { w: 648, h: 162 });
expect(size).toStrictEqual({ w: 324, h: 81 });
});
it("constrains height", () => {
const size = suggestedSize(ImageSize.Normal, { w: 162, h: 648 });
expect(size).toStrictEqual({ w: 81, h: 324 });
});
it("constrains width in large mode", () => {
const size = suggestedSize(ImageSize.Large, { w: 2400, h: 1200 });
expect(size).toStrictEqual({ w: 800, h: 400 });
});
it("returns max values if content size is not specified", () => {
const size = suggestedSize(ImageSize.Normal, {});
expect(size).toStrictEqual({ w: 324, h: 324 });
});
it("returns integer values", () => {
const size = suggestedSize(ImageSize.Normal, { w: 642, h: 350 }); // does not divide evenly
expect(size).toStrictEqual({ w: 324, h: 176 });
});
it("returns integer values for portrait images", () => {
const size = suggestedSize(ImageSize.Normal, { w: 720, h: 1280 });
expect(size).toStrictEqual({ w: 182, h: 324 });
});
});
});
@@ -1,208 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import SettingsStore from "../../../../src/settings/SettingsStore";
import ThemeWatcher from "../../../../src/settings/watchers/ThemeWatcher";
import { type SettingLevel } from "../../../../src/settings/SettingLevel";
import { type SettingKey, type Settings } from "../../../../src/settings/Settings.tsx";
function makeMatchMedia(values: any) {
class FakeMediaQueryList {
matches: false;
media?: null;
onchange?: null;
addListener() {}
removeListener() {}
addEventListener() {}
removeEventListener() {}
dispatchEvent() {
return true;
}
constructor(query: string) {
this.matches = values[query];
}
}
return function matchMedia(query: string) {
return new FakeMediaQueryList(query) as unknown as MediaQueryList;
};
}
function makeGetValue(values: any): any {
return function getValue<S extends SettingKey>(
settingName: S,
_roomId: string | null = null,
_excludeDefault = false,
): Settings[S] {
return values[settingName];
};
}
function makeGetValueAt(values: any) {
return function getValueAt(
_level: SettingLevel,
settingName: string,
_roomId: string | null = null,
_explicit = false,
_excludeDefault = false,
): any {
return values[settingName];
};
}
describe("ThemeWatcher", function () {
it("should choose a light theme by default", () => {
// Given no system settings
global.matchMedia = makeMatchMedia({});
// Then getEffectiveTheme returns light
const themeWatcher = new ThemeWatcher();
expect(themeWatcher.getEffectiveTheme()).toBe("light");
});
it("should choose default theme if system settings are inconclusive", () => {
// Given no system settings but we asked to use them
global.matchMedia = makeMatchMedia({});
SettingsStore.getValue = makeGetValue({
use_system_theme: true,
theme: "light",
});
// Then getEffectiveTheme returns light
const themeWatcher = new ThemeWatcher();
expect(themeWatcher.getEffectiveTheme()).toBe("light");
});
it("should choose a dark theme if that is selected", () => {
// Given system says light high contrast but theme is set to dark
global.matchMedia = makeMatchMedia({
"(prefers-contrast: more)": true,
"(prefers-color-scheme: light)": true,
});
SettingsStore.getValueAt = makeGetValueAt({ theme: "dark" });
// Then getEffectiveTheme returns dark
const themeWatcher = new ThemeWatcher();
expect(themeWatcher.getEffectiveTheme()).toBe("dark");
});
it("should choose a light theme if that is selected", () => {
// Given system settings say dark high contrast but theme set to light
global.matchMedia = makeMatchMedia({
"(prefers-contrast: more)": true,
"(prefers-color-scheme: dark)": true,
});
SettingsStore.getValueAt = makeGetValueAt({ theme: "light" });
// Then getEffectiveTheme returns light
const themeWatcher = new ThemeWatcher();
expect(themeWatcher.getEffectiveTheme()).toBe("light");
});
it("should choose a light-high-contrast theme if that is selected", () => {
// Given system settings say dark and theme set to light-high-contrast
global.matchMedia = makeMatchMedia({ "(prefers-color-scheme: dark)": true });
SettingsStore.getValueAt = makeGetValueAt({ theme: "light-high-contrast" });
// Then getEffectiveTheme returns light-high-contrast
const themeWatcher = new ThemeWatcher();
expect(themeWatcher.getEffectiveTheme()).toBe("light-high-contrast");
});
it("should choose a light theme if system prefers it (via default)", () => {
// Given system prefers lightness, even though we did not
// click "Use system theme" or choose a theme explicitly
global.matchMedia = makeMatchMedia({ "(prefers-color-scheme: light)": true });
SettingsStore.getValueAt = makeGetValueAt({});
SettingsStore.getValue = makeGetValue({ use_system_theme: true });
// Then getEffectiveTheme returns light
const themeWatcher = new ThemeWatcher();
expect(themeWatcher.getEffectiveTheme()).toBe("light");
});
it("should choose a dark theme if system prefers it (via default)", () => {
// Given system prefers darkness, even though we did not
// click "Use system theme" or choose a theme explicitly
global.matchMedia = makeMatchMedia({ "(prefers-color-scheme: dark)": true });
SettingsStore.getValueAt = makeGetValueAt({});
SettingsStore.getValue = makeGetValue({ use_system_theme: true });
// Then getEffectiveTheme returns dark
const themeWatcher = new ThemeWatcher();
expect(themeWatcher.getEffectiveTheme()).toBe("dark");
});
it("should choose a light theme if system prefers it (explicit)", () => {
// Given system prefers lightness
global.matchMedia = makeMatchMedia({ "(prefers-color-scheme: light)": true });
SettingsStore.getValueAt = makeGetValueAt({ use_system_theme: true });
SettingsStore.getValue = makeGetValue({ use_system_theme: true });
// Then getEffectiveTheme returns light
const themeWatcher = new ThemeWatcher();
expect(themeWatcher.getEffectiveTheme()).toBe("light");
});
it("should choose a dark theme if system prefers it (explicit)", () => {
// Given system prefers darkness
global.matchMedia = makeMatchMedia({ "(prefers-color-scheme: dark)": true });
SettingsStore.getValueAt = makeGetValueAt({ use_system_theme: true });
SettingsStore.getValue = makeGetValue({ use_system_theme: true });
// Then getEffectiveTheme returns dark
const themeWatcher = new ThemeWatcher();
expect(themeWatcher.getEffectiveTheme()).toBe("dark");
});
it("should choose a high-contrast theme if system prefers it", () => {
// Given system prefers high contrast and light
global.matchMedia = makeMatchMedia({
"(prefers-contrast: more)": true,
"(prefers-color-scheme: light)": true,
});
SettingsStore.getValueAt = makeGetValueAt({ use_system_theme: true });
SettingsStore.getValue = makeGetValue({ use_system_theme: true });
// Then getEffectiveTheme returns light-high-contrast
const themeWatcher = new ThemeWatcher();
expect(themeWatcher.getEffectiveTheme()).toBe("light-high-contrast");
});
it("should not choose a high-contrast theme if not available", () => {
// Given system prefers high contrast and dark, but we don't (yet)
// have a high-contrast dark theme
global.matchMedia = makeMatchMedia({
"(prefers-contrast: more)": true,
"(prefers-color-scheme: dark)": true,
});
SettingsStore.getValueAt = makeGetValueAt({ use_system_theme: true });
SettingsStore.getValue = makeGetValue({ use_system_theme: true });
// Then getEffectiveTheme returns dark
const themeWatcher = new ThemeWatcher();
expect(themeWatcher.getEffectiveTheme()).toBe("dark");
});
it("should identify custom dark themes as dark", () => {
SettingsStore.getValueAt = makeGetValueAt({ use_system_theme: false, theme: "custom-darkula" });
SettingsStore.getValue = makeGetValue({
custom_themes: [
{
name: "darkula",
is_dark: true,
},
],
});
const themeWatcher = new ThemeWatcher();
expect(themeWatcher.getEffectiveTheme()).toBe("custom-darkula");
expect(themeWatcher.isUserOnDarkTheme()).toBe(true);
});
});
@@ -19,7 +19,7 @@ import {
} from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { logger } from "matrix-js-sdk/src/logger";
import { type Mocked } from "jest-mock";
import { type Mocked } from "jest-mock-vitest-adapter";
import { OwnBeaconStore, OwnBeaconStoreEvent } from "../../../src/stores/OwnBeaconStore";
import {
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { mocked } from "jest-mock";
import { mocked } from "jest-mock-vitest-adapter";
import { KnownMembership, MatrixError, Room } from "matrix-js-sdk/src/matrix";
import { sleep } from "matrix-js-sdk/src/utils";
import {
@@ -181,7 +181,7 @@ describe("RoomViewStore", function () {
jest.clearAllMocks();
mockClient.credentials = { userId: userId };
mockClient.joinRoom.mockResolvedValue(room);
mockClient.getRoom.mockImplementation((roomId: string): Room | null => {
mockClient.getRoom.mockImplementation((roomId?: string): Room | null => {
if (roomId === room.roomId) return room;
if (roomId === room2.roomId) return room2;
return null;
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { type Mocked, mocked } from "jest-mock";
import { type Mocked, mocked } from "jest-mock-vitest-adapter";
import {
type HttpApiEvent,
type HttpApiEventHandlerMap,
@@ -1,295 +0,0 @@
/*
* Copyright 2024 New Vector Ltd.
* Copyright 2024 The Matrix.org Foundation C.I.C.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import { EventTimeline, EventType, type IEvent, type MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
import { mocked } from "jest-mock";
import { createTestClient } from "../../test-utils";
import PinningUtils from "../../../src/utils/PinningUtils";
import SettingsStore from "../../../src/settings/SettingsStore";
import { isContentActionable } from "../../../src/utils/EventUtils";
import { ReadPinsEventId } from "../../../src/components/views/right_panel/types";
jest.mock("../../../src/utils/EventUtils", () => {
return {
isContentActionable: jest.fn(),
canPinEvent: jest.fn(),
};
});
describe("PinningUtils", () => {
const roomId = "!room:example.org";
const userId = "@alice:example.org";
const mockedIsContentActionable = mocked(isContentActionable);
let matrixClient: MatrixClient;
let room: Room;
/**
* Create a pinned event with the given content.
* @param content
*/
function makePinEvent(content?: Partial<IEvent>) {
return new MatrixEvent({
type: EventType.RoomMessage,
sender: userId,
content: {
body: "First pinned message",
msgtype: "m.text",
},
room_id: roomId,
origin_server_ts: 0,
event_id: "$eventId",
...content,
});
}
beforeEach(() => {
// Enable feature pinning
jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
mockedIsContentActionable.mockImplementation(() => true);
matrixClient = createTestClient();
room = new Room(roomId, matrixClient, userId);
matrixClient.getRoom = jest.fn().mockReturnValue(room);
jest.spyOn(
matrixClient.getRoom(roomId)!.getLiveTimeline().getState(EventTimeline.FORWARDS)!,
"mayClientSendStateEvent",
).mockReturnValue(true);
});
describe("isUnpinnable", () => {
test.each(PinningUtils.PINNABLE_EVENT_TYPES)("should return true for pinnable event types", (eventType) => {
const event = makePinEvent({ type: eventType });
expect(PinningUtils.isUnpinnable(event)).toBe(true);
});
test("should return false for a non pinnable event type", () => {
const event = makePinEvent({ type: EventType.RoomCreate });
expect(PinningUtils.isUnpinnable(event)).toBe(false);
});
test("should return true for a redacted event", () => {
const event = makePinEvent({ unsigned: { redacted_because: "because" as unknown as IEvent } });
expect(PinningUtils.isUnpinnable(event)).toBe(true);
});
});
describe("isPinnable", () => {
test.each(PinningUtils.PINNABLE_EVENT_TYPES)("should return true for pinnable event types", (eventType) => {
const event = makePinEvent({ type: eventType });
expect(PinningUtils.isPinnable(event)).toBe(true);
});
test("should return false for a redacted event", () => {
const event = makePinEvent({ unsigned: { redacted_because: "because" as unknown as IEvent } });
expect(PinningUtils.isPinnable(event)).toBe(false);
});
});
describe("isPinned", () => {
test("should return false if no room", () => {
matrixClient.getRoom = jest.fn().mockReturnValue(undefined);
const event = makePinEvent();
expect(PinningUtils.isPinned(matrixClient, event)).toBe(false);
});
test("should return false if no pinned event", () => {
jest.spyOn(
matrixClient.getRoom(roomId)!.getLiveTimeline().getState(EventTimeline.FORWARDS)!,
"getStateEvents",
).mockReturnValue(null);
const event = makePinEvent();
expect(PinningUtils.isPinned(matrixClient, event)).toBe(false);
});
test("should return false if pinned events do not contain the event id", () => {
jest.spyOn(
matrixClient.getRoom(roomId)!.getLiveTimeline().getState(EventTimeline.FORWARDS)!,
"getStateEvents",
).mockReturnValue({
// @ts-ignore
getContent: () => ({ pinned: ["$otherEventId"] }),
});
const event = makePinEvent();
expect(PinningUtils.isPinned(matrixClient, event)).toBe(false);
});
test("should return true if pinned events contains the event id", () => {
const event = makePinEvent();
jest.spyOn(
matrixClient.getRoom(roomId)!.getLiveTimeline().getState(EventTimeline.FORWARDS)!,
"getStateEvents",
).mockReturnValue({
// @ts-ignore
getContent: () => ({ pinned: [event.getId()] }),
});
expect(PinningUtils.isPinned(matrixClient, event)).toBe(true);
});
});
describe("canPin & canUnpin", () => {
describe("canPin", () => {
test("should return false if event is not actionable", () => {
mockedIsContentActionable.mockImplementation(() => false);
const event = makePinEvent();
expect(PinningUtils.canPin(matrixClient, event)).toBe(false);
});
test("should return false if no room", () => {
matrixClient.getRoom = jest.fn().mockReturnValue(undefined);
const event = makePinEvent();
expect(PinningUtils.canPin(matrixClient, event)).toBe(false);
});
test("should return false if client cannot send state event", () => {
jest.spyOn(
matrixClient.getRoom(roomId)!.getLiveTimeline().getState(EventTimeline.FORWARDS)!,
"mayClientSendStateEvent",
).mockReturnValue(false);
const event = makePinEvent();
expect(PinningUtils.canPin(matrixClient, event)).toBe(false);
});
test("should return false if event is not pinnable", () => {
const event = makePinEvent({ type: EventType.RoomCreate });
expect(PinningUtils.canPin(matrixClient, event)).toBe(false);
});
test("should return true if all conditions are met", () => {
const event = makePinEvent();
expect(PinningUtils.canPin(matrixClient, event)).toBe(true);
});
});
describe("canUnpin", () => {
test("should return false if event is not unpinnable", () => {
const event = makePinEvent({ type: EventType.RoomCreate });
expect(PinningUtils.canUnpin(matrixClient, event)).toBe(false);
});
test("should return true if all conditions are met", () => {
const event = makePinEvent();
expect(PinningUtils.canUnpin(matrixClient, event)).toBe(true);
});
test("should return true if the event is redacted", () => {
const event = makePinEvent({ unsigned: { redacted_because: "because" as unknown as IEvent } });
expect(PinningUtils.canUnpin(matrixClient, event)).toBe(true);
});
});
});
describe("pinOrUnpinEvent", () => {
test("should do nothing if no room", async () => {
matrixClient.getRoom = jest.fn().mockReturnValue(undefined);
const event = makePinEvent();
await PinningUtils.pinOrUnpinEvent(matrixClient, event);
expect(matrixClient.sendStateEvent).not.toHaveBeenCalled();
});
test("should do nothing if no event id", async () => {
const event = makePinEvent({ event_id: undefined });
await PinningUtils.pinOrUnpinEvent(matrixClient, event);
expect(matrixClient.sendStateEvent).not.toHaveBeenCalled();
});
test("should pin the event if not pinned", async () => {
jest.spyOn(
matrixClient.getRoom(roomId)!.getLiveTimeline().getState(EventTimeline.FORWARDS)!,
"getStateEvents",
).mockReturnValue({
// @ts-ignore
getContent: () => ({ pinned: ["$otherEventId"] }),
});
jest.spyOn(room, "getAccountData").mockReturnValue({
getContent: jest.fn().mockReturnValue({
event_ids: ["$otherEventId"],
}),
} as unknown as MatrixEvent);
const event = makePinEvent();
await PinningUtils.pinOrUnpinEvent(matrixClient, event);
expect(matrixClient.setRoomAccountData).toHaveBeenCalledWith(roomId, ReadPinsEventId, {
event_ids: ["$otherEventId", event.getId()],
});
expect(matrixClient.sendStateEvent).toHaveBeenCalledWith(
roomId,
EventType.RoomPinnedEvents,
{ pinned: ["$otherEventId", event.getId()] },
"",
);
});
test("should unpin the event if already pinned", async () => {
const event = makePinEvent();
jest.spyOn(
matrixClient.getRoom(roomId)!.getLiveTimeline().getState(EventTimeline.FORWARDS)!,
"getStateEvents",
).mockReturnValue({
// @ts-ignore
getContent: () => ({ pinned: [event.getId(), "$otherEventId"] }),
});
await PinningUtils.pinOrUnpinEvent(matrixClient, event);
expect(matrixClient.sendStateEvent).toHaveBeenCalledWith(
roomId,
EventType.RoomPinnedEvents,
{ pinned: ["$otherEventId"] },
"",
);
});
});
describe("userHasPinOrUnpinPermission", () => {
test("should return true if user can pin or unpin", () => {
expect(PinningUtils.userHasPinOrUnpinPermission(matrixClient, room)).toBe(true);
});
test("should return false if client cannot send state event", () => {
jest.spyOn(
matrixClient.getRoom(roomId)!.getLiveTimeline().getState(EventTimeline.FORWARDS)!,
"mayClientSendStateEvent",
).mockReturnValue(false);
expect(PinningUtils.userHasPinOrUnpinPermission(matrixClient, room)).toBe(false);
});
});
describe("unpinAllEvents", () => {
it("should unpin all events in the given room", async () => {
await PinningUtils.unpinAllEvents(matrixClient, roomId);
expect(matrixClient.sendStateEvent).toHaveBeenCalledWith(
roomId,
EventType.RoomPinnedEvents,
{ pinned: [] },
"",
);
});
});
});
@@ -1,100 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { Singleflight } from "../../../src/utils/Singleflight";
describe("Singleflight", () => {
afterEach(() => {
Singleflight.forgetAll();
});
it("should throw for bad context variables", () => {
const permutations: [object | null, string | null][] = [
[null, null],
[{}, null],
[null, "test"],
];
for (const p of permutations) {
expect(() => Singleflight.for(p[0], p[1])).toThrow("An instance and key must be supplied");
}
});
it("should execute the function once", () => {
const instance = {};
const key = "test";
const val = {}; // unique object for reference check
const fn = jest.fn().mockReturnValue(val);
const sf = Singleflight.for(instance, key);
const r1 = sf.do(fn);
expect(r1).toBe(val);
expect(fn.mock.calls.length).toBe(1);
const r2 = sf.do(fn);
expect(r2).toBe(val);
expect(fn.mock.calls.length).toBe(1);
});
it("should execute the function once, even with new contexts", () => {
const instance = {};
const key = "test";
const val = {}; // unique object for reference check
const fn = jest.fn().mockReturnValue(val);
let sf = Singleflight.for(instance, key);
const r1 = sf.do(fn);
expect(r1).toBe(val);
expect(fn.mock.calls.length).toBe(1);
sf = Singleflight.for(instance, key); // RESET FOR TEST
const r2 = sf.do(fn);
expect(r2).toBe(val);
expect(fn.mock.calls.length).toBe(1);
});
it("should execute the function twice if the result was forgotten", () => {
const instance = {};
const key = "test";
const val = {}; // unique object for reference check
const fn = jest.fn().mockReturnValue(val);
const sf = Singleflight.for(instance, key);
const r1 = sf.do(fn);
expect(r1).toBe(val);
expect(fn.mock.calls.length).toBe(1);
sf.forget();
const r2 = sf.do(fn);
expect(r2).toBe(val);
expect(fn.mock.calls.length).toBe(2);
});
it("should execute the function twice if the instance was forgotten", () => {
const instance = {};
const key = "test";
const val = {}; // unique object for reference check
const fn = jest.fn().mockReturnValue(val);
const sf = Singleflight.for(instance, key);
const r1 = sf.do(fn);
expect(r1).toBe(val);
expect(fn.mock.calls.length).toBe(1);
Singleflight.forgetAllFor(instance);
const r2 = sf.do(fn);
expect(r2).toBe(val);
expect(fn.mock.calls.length).toBe(2);
});
it("should execute the function twice if everything was forgotten", () => {
const instance = {};
const key = "test";
const val = {}; // unique object for reference check
const fn = jest.fn().mockReturnValue(val);
const sf = Singleflight.for(instance, key);
const r1 = sf.do(fn);
expect(r1).toBe(val);
expect(fn.mock.calls.length).toBe(1);
Singleflight.forgetAll();
const r2 = sf.do(fn);
expect(r2).toBe(val);
expect(fn.mock.calls.length).toBe(2);
});
});
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import { logger } from "matrix-js-sdk/src/logger";
import { type Mocked } from "jest-mock";
import { type Mocked } from "jest-mock-vitest-adapter";
import {
type GenericPosition,
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { type Mocked, mocked } from "jest-mock";
import { type Mocked, mocked } from "jest-mock-vitest-adapter";
import { type Device, type MatrixClient } from "matrix-js-sdk/src/matrix";
import { getDeviceCryptoInfo, getUserDeviceIds } from "../../../../src/utils/crypto/deviceInfo";
@@ -1,18 +0,0 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { onSubmitPreventDefault } from "../../../src/utils/form.ts";
describe("onSubmitPreventDefault", () => {
it("should preventDefault", () => {
const event = new SubmitEvent("submit");
const spy = jest.spyOn(event, "preventDefault");
onSubmitPreventDefault(event);
expect(spy).toHaveBeenCalled();
});
});
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { mocked, type Mocked } from "jest-mock";
import { mocked, type Mocked } from "jest-mock-vitest-adapter";
import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
import { sleep } from "matrix-js-sdk/src/utils";
-219
View File
@@ -1,219 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { EnhancedMap, mapDiff } from "../../../src/utils/maps";
describe("maps", () => {
describe("mapDiff", () => {
it("should indicate no differences when the pointers are the same", () => {
const a = new Map([
[1, 1],
[2, 2],
[3, 3],
]);
const result = mapDiff(a, a);
expect(result).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.changed).toBeDefined();
expect(result.added).toHaveLength(0);
expect(result.removed).toHaveLength(0);
expect(result.changed).toHaveLength(0);
});
it("should indicate no differences when there are none", () => {
const a = new Map([
[1, 1],
[2, 2],
[3, 3],
]);
const b = new Map([
[1, 1],
[2, 2],
[3, 3],
]);
const result = mapDiff(a, b);
expect(result).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.changed).toBeDefined();
expect(result.added).toHaveLength(0);
expect(result.removed).toHaveLength(0);
expect(result.changed).toHaveLength(0);
});
it("should indicate added properties", () => {
const a = new Map([
[1, 1],
[2, 2],
[3, 3],
]);
const b = new Map([
[1, 1],
[2, 2],
[3, 3],
[4, 4],
]);
const result = mapDiff(a, b);
expect(result).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.changed).toBeDefined();
expect(result.added).toHaveLength(1);
expect(result.removed).toHaveLength(0);
expect(result.changed).toHaveLength(0);
expect(result.added).toEqual([4]);
});
it("should indicate removed properties", () => {
const a = new Map([
[1, 1],
[2, 2],
[3, 3],
]);
const b = new Map([
[1, 1],
[2, 2],
]);
const result = mapDiff(a, b);
expect(result).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.changed).toBeDefined();
expect(result.added).toHaveLength(0);
expect(result.removed).toHaveLength(1);
expect(result.changed).toHaveLength(0);
expect(result.removed).toEqual([3]);
});
it("should indicate changed properties", () => {
const a = new Map([
[1, 1],
[2, 2],
[3, 3],
]);
const b = new Map([
[1, 1],
[2, 2],
[3, 4],
]); // note change
const result = mapDiff(a, b);
expect(result).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.changed).toBeDefined();
expect(result.added).toHaveLength(0);
expect(result.removed).toHaveLength(0);
expect(result.changed).toHaveLength(1);
expect(result.changed).toEqual([3]);
});
it("should indicate changed, added, and removed properties", () => {
const a = new Map([
[1, 1],
[2, 2],
[3, 3],
]);
const b = new Map([
[1, 1],
[2, 8],
[4, 4],
]); // note change
const result = mapDiff(a, b);
expect(result).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.changed).toBeDefined();
expect(result.added).toHaveLength(1);
expect(result.removed).toHaveLength(1);
expect(result.changed).toHaveLength(1);
expect(result.added).toEqual([4]);
expect(result.removed).toEqual([3]);
expect(result.changed).toEqual([2]);
});
it("should indicate changes for difference in pointers", () => {
const a = new Map([[1, {}]]); // {} always creates a new object
const b = new Map([[1, {}]]);
const result = mapDiff(a, b);
expect(result).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.changed).toBeDefined();
expect(result.added).toHaveLength(0);
expect(result.removed).toHaveLength(0);
expect(result.changed).toHaveLength(1);
expect(result.changed).toEqual([1]);
});
});
describe("EnhancedMap", () => {
// Most of these tests will make sure it implements the Map<K, V> class
it("should be empty by default", () => {
const result = new EnhancedMap();
expect(result.size).toBe(0);
});
it("should use the provided entries", () => {
const obj = { a: 1, b: 2 };
const result = new EnhancedMap(Object.entries(obj));
expect(result.size).toBe(2);
expect(result.get("a")).toBe(1);
expect(result.get("b")).toBe(2);
});
it("should create keys if they do not exist", () => {
const key = "a";
const val = {}; // we'll check pointers
const result = new EnhancedMap<string, any>();
expect(result.size).toBe(0);
let get = result.getOrCreate(key, val);
expect(get).toBeDefined();
expect(get).toBe(val);
expect(result.size).toBe(1);
get = result.getOrCreate(key, 44); // specifically change `val`
expect(get).toBeDefined();
expect(get).toBe(val);
expect(result.size).toBe(1);
get = result.get(key); // use the base class function
expect(get).toBeDefined();
expect(get).toBe(val);
expect(result.size).toBe(1);
});
it("should proxy remove to delete and return it", () => {
const val = {};
const result = new EnhancedMap<string, any>();
result.set("a", val);
expect(result.size).toBe(1);
const removed = result.remove("a");
expect(result.size).toBe(0);
expect(removed).toBeDefined();
expect(removed).toBe(val);
});
it("should support removing unknown keys", () => {
const val = {};
const result = new EnhancedMap<string, any>();
result.set("a", val);
expect(result.size).toBe(1);
const removed = result.remove("not-a");
expect(result.size).toBe(1);
expect(removed).not.toBeDefined();
});
});
});
@@ -14,7 +14,7 @@ import {
ReceiptType,
type AccountDataEvents,
} from "matrix-js-sdk/src/matrix";
import { type Mocked, mocked } from "jest-mock";
import { type Mocked, mocked } from "jest-mock-vitest-adapter";
import {
localNotificationsAreSilenced,
@@ -382,10 +382,10 @@ describe("Permalinks", function () {
});
it("should generate a room permalink for room IDs with some candidate servers", function () {
mockClient.getRoom.mockImplementation((roomId: Room["roomId"]) => {
return mockRoom(roomId, [
makeMemberWithPL(roomId, "@alice:first", 100),
makeMemberWithPL(roomId, "@bob:second", 0),
mockClient.getRoom.mockImplementation((roomId?: string) => {
return mockRoom(roomId!, [
makeMemberWithPL(roomId!, "@alice:first", 100),
makeMemberWithPL(roomId!, "@bob:second", 0),
]);
});
const result = makeRoomPermalink(mockClient, "!somewhere:example.org");
@@ -399,10 +399,10 @@ describe("Permalinks", function () {
});
it("should generate a room permalink for room aliases without candidate servers", function () {
mockClient.getRoom.mockImplementation((roomId: Room["roomId"]) => {
return mockRoom(roomId, [
makeMemberWithPL(roomId, "@alice:first", 100),
makeMemberWithPL(roomId, "@bob:second", 0),
mockClient.getRoom.mockImplementation((roomId?: string) => {
return mockRoom(roomId!, [
makeMemberWithPL(roomId!, "@alice:first", 100),
makeMemberWithPL(roomId!, "@bob:second", 0),
]);
});
const result = makeRoomPermalink(mockClient, "#somewhere:example.org");
@@ -7,7 +7,7 @@
import { MatrixError, type MatrixClient } from "matrix-js-sdk/src/matrix";
import { waitFor } from "jest-matrix-react";
import type { MockedObject } from "jest-mock";
import type { MockedObject } from "jest-mock-vitest-adapter";
import { UserMenuViewModel } from "../../../src/viewmodels/menus/UserMenuViewModel";
import { getMockClientWithEventEmitter, mockClientMethodsServer, mockClientMethodsUser } from "../../test-utils";
import { MatrixDispatcher } from "../../../src/dispatcher/dispatcher";
@@ -7,7 +7,7 @@
import { expect } from "@jest/globals";
import type { MockedObject } from "jest-mock";
import type { MockedObject } from "jest-mock-vitest-adapter";
import type { MatrixClient, IPreviewUrlResponse } from "matrix-js-sdk/src/matrix";
import {
BUNDLED_LINK_PREVIEWS,
@@ -15,7 +15,7 @@ import {
EventStatus,
} from "matrix-js-sdk/src/matrix";
import { RoomStatusBarState } from "@element-hq/web-shared-components";
import { type MockedObject } from "jest-mock";
import { type MockedObject } from "jest-mock-vitest-adapter";
import { mkEvent, mkRoom, stubClient } from "../../test-utils";
import { RoomStatusBarViewModel } from "../../../src/viewmodels/room/RoomStatusBar";
@@ -6,7 +6,7 @@
*/
import { type MatrixClient, type Room, RoomEvent } from "matrix-js-sdk/src/matrix";
import { type MockedObject } from "jest-mock";
import { type MockedObject } from "jest-mock-vitest-adapter";
import { createRef } from "react";
import { mkRoom, stubClient } from "../../test-utils";