Migrate more tests to vitest (#34349)

This commit is contained in:
Michael Telatynski
2026-07-20 13:07:35 +00:00
committed by GitHub
parent 6c2c962588
commit b719f531dd
20 changed files with 310 additions and 271 deletions
@@ -6,12 +6,16 @@ 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. Please see LICENSE files in the repository root for full details.
*/ */
import { HTTPError, type MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix"; // @vitest-environment happy-dom
import { mocked } from "jest-mock";
import { createCrossSigning } from "../src/CreateCrossSigning"; import { vi, describe, it, expect, beforeEach } from "vitest";
import { createTestClient } from "./test-utils"; import { HTTPError, type MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
import Modal from "../src/Modal"; import { createTestClient } from "test-utils";
import { createCrossSigning } from "./CreateCrossSigning";
import Modal from "./Modal";
vi.mock("./Modal");
describe("CreateCrossSigning", () => { describe("CreateCrossSigning", () => {
let client: MatrixClient; let client: MatrixClient;
@@ -29,7 +33,7 @@ describe("CreateCrossSigning", () => {
}); });
it("should upload", async () => { it("should upload", async () => {
client.uploadDeviceSigningKeys = jest.fn().mockRejectedValueOnce( client.uploadDeviceSigningKeys = vi.fn().mockRejectedValueOnce(
new MatrixError({ new MatrixError({
flows: [ flows: [
{ {
@@ -41,20 +45,20 @@ describe("CreateCrossSigning", () => {
await createCrossSigning(client); await createCrossSigning(client);
const { authUploadDeviceSigningKeys } = mocked(client.getCrypto()!).bootstrapCrossSigning.mock.calls[0][0]; const { authUploadDeviceSigningKeys } = vi.mocked(client.getCrypto()!).bootstrapCrossSigning.mock.calls[0][0];
const makeRequest = jest.fn(); const makeRequest = vi.fn();
await authUploadDeviceSigningKeys!(makeRequest); await authUploadDeviceSigningKeys!(makeRequest);
expect(makeRequest).toHaveBeenCalledWith(null); expect(makeRequest).toHaveBeenCalledWith(null);
}); });
it("should prompt user if upload failed with UIA", async () => { it("should prompt user if upload failed with UIA", async () => {
const createDialog = jest.spyOn(Modal, "createDialog").mockReturnValue({ const createDialog = vi.spyOn(Modal, "createDialog").mockReturnValue({
finished: Promise.resolve([true]), finished: Promise.resolve([true]),
close: jest.fn(), close: vi.fn(),
}); });
client.uploadDeviceSigningKeys = jest.fn().mockRejectedValueOnce( client.uploadDeviceSigningKeys = vi.fn().mockRejectedValueOnce(
new MatrixError({ new MatrixError({
flows: [ flows: [
{ {
@@ -66,9 +70,9 @@ describe("CreateCrossSigning", () => {
await createCrossSigning(client); await createCrossSigning(client);
const { authUploadDeviceSigningKeys } = mocked(client.getCrypto()!).bootstrapCrossSigning.mock.calls[0][0]; const { authUploadDeviceSigningKeys } = vi.mocked(client.getCrypto()!).bootstrapCrossSigning.mock.calls[0][0];
const makeRequest = jest.fn().mockRejectedValue( const makeRequest = vi.fn().mockRejectedValue(
new MatrixError({ new MatrixError({
flows: [ flows: [
{ {
@@ -85,10 +89,10 @@ describe("CreateCrossSigning", () => {
it("should throw error if server fails with something other than UIA", async () => { it("should throw error if server fails with something other than UIA", async () => {
await createCrossSigning(client); await createCrossSigning(client);
const { authUploadDeviceSigningKeys } = mocked(client.getCrypto()!).bootstrapCrossSigning.mock.calls[0][0]; const { authUploadDeviceSigningKeys } = vi.mocked(client.getCrypto()!).bootstrapCrossSigning.mock.calls[0][0];
const error = new HTTPError("Internal Server Error", 500); const error = new HTTPError("Internal Server Error", 500);
const makeRequest = jest.fn().mockRejectedValue(error); const makeRequest = vi.fn().mockRejectedValue(error);
await expect(authUploadDeviceSigningKeys!(makeRequest)).rejects.toThrow(error); await expect(authUploadDeviceSigningKeys!(makeRequest)).rejects.toThrow(error);
expect(makeRequest).not.toHaveBeenCalledWith(); expect(makeRequest).not.toHaveBeenCalledWith();
}); });
@@ -5,7 +5,9 @@ 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. Please see LICENSE files in the repository root for full details.
*/ */
import { mocked, type Mocked, type MockedObject } from "jest-mock"; // @vitest-environment happy-dom
import { vi, describe, it, expect, afterEach, type Mocked, type MockedObject } from "vitest";
import { HttpApiEvent, type MatrixClient, type MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/matrix"; import { HttpApiEvent, type MatrixClient, type MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/matrix";
import { decryptExistingEvent, mkDecryptionFailureMatrixEvent } from "matrix-js-sdk/src/testing"; import { decryptExistingEvent, mkDecryptionFailureMatrixEvent } from "matrix-js-sdk/src/testing";
import { import {
@@ -15,10 +17,10 @@ import {
CryptoEvent, CryptoEvent,
} from "matrix-js-sdk/src/crypto-api"; } from "matrix-js-sdk/src/crypto-api";
import { sleep } from "matrix-js-sdk/src/utils"; import { sleep } from "matrix-js-sdk/src/utils";
import { stubClient } from "test-utils";
import { DecryptionFailureTracker, type ErrorProperties } from "../../src/DecryptionFailureTracker"; import { DecryptionFailureTracker, type ErrorProperties } from "./DecryptionFailureTracker";
import { stubClient } from "../test-utils"; import * as Lifecycle from "./Lifecycle";
import * as Lifecycle from "../../src/Lifecycle";
async function createFailedDecryptionEvent(opts: { sender?: string; code?: DecryptionFailureCode } = {}) { async function createFailedDecryptionEvent(opts: { sender?: string; code?: DecryptionFailureCode } = {}) {
return await mkDecryptionFailureMatrixEvent({ return await mkDecryptionFailureMatrixEvent({
@@ -592,7 +594,7 @@ describe("DecryptionFailureTracker", function () {
// Calling .start will start some intervals. This test shouldn't run // Calling .start will start some intervals. This test shouldn't run
// long enough for the timers to fire, but we'll use fake timers just // long enough for the timers to fire, but we'll use fake timers just
// to be safe. // to be safe.
jest.useFakeTimers(); vi.useFakeTimers();
await tracker.start(client); await tracker.start(client);
// If the client fails to decrypt, it should get tracked // If the client fails to decrypt, it should get tracked
@@ -616,7 +618,7 @@ describe("DecryptionFailureTracker", function () {
expect(errorCount).toEqual(1); expect(errorCount).toEqual(1);
jest.useRealTimers(); vi.useRealTimers();
}); });
it("tracks client information", async () => { it("tracks client information", async () => {
@@ -657,7 +659,7 @@ describe("DecryptionFailureTracker", function () {
const now = Date.now(); const now = Date.now();
eventDecrypted(tracker, federatedDecryption, now); eventDecrypted(tracker, federatedDecryption, now);
mocked(client.getCrypto()!.getUserVerificationStatus).mockResolvedValue( vi.mocked(client.getCrypto()!.getUserVerificationStatus).mockResolvedValue(
new UserVerificationStatus(true, true, false), new UserVerificationStatus(true, true, false),
); );
client.emit(CryptoEvent.KeysChanged, {}); client.emit(CryptoEvent.KeysChanged, {});
@@ -677,7 +679,7 @@ describe("DecryptionFailureTracker", function () {
// change client params, and make sure the reports the right values // change client params, and make sure the reports the right values
client.getDomain.mockReturnValue("example.com"); client.getDomain.mockReturnValue("example.com");
mocked(client.getCrypto()!.getVersion).mockReturnValue("Olm 0.0.0"); vi.mocked(client.getCrypto()!.getVersion).mockReturnValue("Olm 0.0.0");
// @ts-ignore access to private method // @ts-ignore access to private method
await tracker.calculateClientProperties(client); await tracker.calculateClientProperties(client);
@@ -725,19 +727,19 @@ describe("DecryptionFailureTracker", function () {
}); });
function mockClient(): MockedObject<MatrixClient> { function mockClient(): MockedObject<MatrixClient> {
const client = mocked(stubClient()); const client = vi.mocked(stubClient());
const mockCrypto = { const mockCrypto = {
getVersion: jest.fn().mockReturnValue("Rust SDK 0.7.0 (61b175b), Vodozemac 0.5.1"), getVersion: vi.fn().mockReturnValue("Rust SDK 0.7.0 (61b175b), Vodozemac 0.5.1"),
getUserVerificationStatus: jest.fn().mockResolvedValue(new UserVerificationStatus(false, false, false)), getUserVerificationStatus: vi.fn().mockResolvedValue(new UserVerificationStatus(false, false, false)),
} as unknown as Mocked<CryptoApi>; } as unknown as Mocked<CryptoApi>;
client.getCrypto.mockReturnValue(mockCrypto); client.getCrypto.mockReturnValue(mockCrypto);
// @ts-ignore // @ts-ignore
client.stopClient = jest.fn(() => {}); client.stopClient = vi.fn(() => {});
// @ts-ignore // @ts-ignore
client.removeAllListeners = jest.fn(() => {}); client.removeAllListeners = vi.fn(() => {});
client.store = { destroy: jest.fn(() => {}) } as any; client.store = { destroy: vi.fn(() => {}) } as any;
return client; return client;
} }
@@ -6,7 +6,9 @@ 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. Please see LICENSE files in the repository root for full details.
*/ */
import { type Mocked, mocked } from "jest-mock-vitest-adapter"; // @vitest-environment happy-dom
import { vi, describe, it, expect, beforeEach, type Mocked } from "vitest";
import { import {
MatrixEvent, MatrixEvent,
type Room, type Room,
@@ -24,38 +26,40 @@ import {
type SecretStorageStatus, type SecretStorageStatus,
} from "matrix-js-sdk/src/crypto-api"; } from "matrix-js-sdk/src/crypto-api";
import { type CryptoSessionStateChange } from "@matrix-org/analytics-events/types/typescript/CryptoSessionStateChange"; import { type CryptoSessionStateChange } from "@matrix-org/analytics-events/types/typescript/CryptoSessionStateChange";
import { getMockClientWithEventEmitter, mockPlatformPeg } from "test-utils";
import { import {
DeviceListener, DeviceListener,
ACCOUNT_DATA_KEY_M_KEY_BACKUP, ACCOUNT_DATA_KEY_M_KEY_BACKUP,
ACCOUNT_DATA_KEY_M_KEY_BACKUP_DISABLED_UNSTABLE, ACCOUNT_DATA_KEY_M_KEY_BACKUP_DISABLED_UNSTABLE,
RECOVERY_ACCOUNT_DATA_KEY, RECOVERY_ACCOUNT_DATA_KEY,
} from "../../src/device-listener"; } from "./device-listener";
import { MatrixClientPeg } from "../../src/MatrixClientPeg"; import { MatrixClientPeg } from "./MatrixClientPeg";
import * as SetupEncryptionToast from "../../src/toasts/SetupEncryptionToast"; import * as SetupEncryptionToast from "./toasts/SetupEncryptionToast";
import * as UnverifiedSessionToast from "../../src/toasts/UnverifiedSessionToast"; import * as UnverifiedSessionToast from "./toasts/UnverifiedSessionToast";
import * as BulkUnverifiedSessionsToast from "../../src/toasts/BulkUnverifiedSessionsToast"; import * as BulkUnverifiedSessionsToast from "./toasts/BulkUnverifiedSessionsToast";
import { isSecretStorageBeingAccessed } from "../../src/SecurityManager"; import { isSecretStorageBeingAccessed } from "./SecurityManager";
import { Action } from "../../src/dispatcher/actions"; import { Action } from "./dispatcher/actions";
import SettingsStore from "../../src/settings/SettingsStore"; import SettingsStore from "./settings/SettingsStore";
import { SettingLevel } from "../../src/settings/SettingLevel"; import { SettingLevel } from "./settings/SettingLevel";
import { getMockClientWithEventEmitter, mockPlatformPeg } from "../test-utils"; import { isBulkUnverifiedDeviceReminderSnoozed } from "./utils/device/snoozeBulkUnverifiedDeviceReminder";
import { isBulkUnverifiedDeviceReminderSnoozed } from "../../src/utils/device/snoozeBulkUnverifiedDeviceReminder"; import { PosthogAnalytics } from "./PosthogAnalytics";
import { PosthogAnalytics } from "../../src/PosthogAnalytics";
jest.mock("../../src/dispatcher/dispatcher", () => ({ vi.mock("./dispatcher/dispatcher", () => ({
dispatch: jest.fn(), default: {
register: jest.fn(), dispatch: vi.fn(),
unregister: jest.fn(), register: vi.fn(),
unregister: vi.fn(),
},
})); }));
jest.mock("../../src/SecurityManager", () => ({ vi.mock("./SecurityManager", () => ({
isSecretStorageBeingAccessed: jest.fn(), isSecretStorageBeingAccessed: vi.fn(),
accessSecretStorage: jest.fn(), accessSecretStorage: vi.fn(),
})); }));
jest.mock("../../src/utils/device/snoozeBulkUnverifiedDeviceReminder", () => ({ vi.mock("./utils/device/snoozeBulkUnverifiedDeviceReminder", () => ({
isBulkUnverifiedDeviceReminderSnoozed: jest.fn(), isBulkUnverifiedDeviceReminderSnoozed: vi.fn(),
})); }));
const userId = "@user:server"; const userId = "@user:server";
@@ -79,36 +83,36 @@ describe("DeviceListener", () => {
let mockCrypto: Mocked<CryptoApi>; let mockCrypto: Mocked<CryptoApi>;
beforeEach(() => { beforeEach(() => {
jest.resetAllMocks(); vi.resetAllMocks();
// don't litter the console with logs // don't litter the console with logs
jest.spyOn(console, "debug").mockImplementation(() => {}); vi.spyOn(console, "debug").mockImplementation(() => {});
jest.spyOn(console, "info").mockImplementation(() => {}); vi.spyOn(console, "info").mockImplementation(() => {});
jest.spyOn(console, "warn").mockImplementation(() => {}); vi.spyOn(console, "warn").mockImplementation(() => {});
jest.spyOn(console, "error").mockImplementation(() => {}); vi.spyOn(console, "error").mockImplementation(() => {});
// spy on various toasts' hide and show functions // spy on various toasts' hide and show functions
// easier than mocking // easier than mocking
jest.spyOn(SetupEncryptionToast, "showToast").mockReturnValue(undefined); vi.spyOn(SetupEncryptionToast, "showToast").mockReturnValue(undefined);
jest.spyOn(SetupEncryptionToast, "hideToast").mockReturnValue(undefined); vi.spyOn(SetupEncryptionToast, "hideToast").mockReturnValue(undefined);
jest.spyOn(BulkUnverifiedSessionsToast, "showToast").mockReturnValue(undefined); vi.spyOn(BulkUnverifiedSessionsToast, "showToast").mockReturnValue(undefined);
jest.spyOn(BulkUnverifiedSessionsToast, "hideToast").mockReturnValue(undefined); vi.spyOn(BulkUnverifiedSessionsToast, "hideToast").mockReturnValue(undefined);
jest.spyOn(UnverifiedSessionToast, "showToast").mockResolvedValue(undefined); vi.spyOn(UnverifiedSessionToast, "showToast").mockResolvedValue(undefined);
jest.spyOn(UnverifiedSessionToast, "hideToast").mockReturnValue(undefined); vi.spyOn(UnverifiedSessionToast, "hideToast").mockReturnValue(undefined);
mockPlatformPeg({ mockPlatformPeg({
getAppVersion: jest.fn().mockResolvedValue("1.2.3"), getAppVersion: vi.fn().mockResolvedValue("1.2.3"),
}); });
mockCrypto = { mockCrypto = {
getDeviceVerificationStatus: jest.fn().mockResolvedValue({ getDeviceVerificationStatus: vi.fn().mockResolvedValue({
crossSigningVerified: false, crossSigningVerified: false,
}), }),
getUserDeviceInfo: jest.fn().mockResolvedValue(new Map()), getUserDeviceInfo: vi.fn().mockResolvedValue(new Map()),
isCrossSigningReady: jest.fn().mockResolvedValue(true), isCrossSigningReady: vi.fn().mockResolvedValue(true),
getSecretStorageStatus: jest.fn().mockResolvedValue(readySecretStorageStatus), getSecretStorageStatus: vi.fn().mockResolvedValue(readySecretStorageStatus),
userHasCrossSigningKeys: jest.fn(), userHasCrossSigningKeys: vi.fn(),
getActiveSessionBackupVersion: jest.fn(), getActiveSessionBackupVersion: vi.fn(),
getCrossSigningStatus: jest.fn().mockReturnValue({ getCrossSigningStatus: vi.fn().mockReturnValue({
publicKeysOnDevice: true, publicKeysOnDevice: true,
privateKeysInSecretStorage: true, privateKeysInSecretStorage: true,
privateKeysCachedLocally: { privateKeysCachedLocally: {
@@ -117,33 +121,33 @@ describe("DeviceListener", () => {
userSigningKey: true, userSigningKey: true,
}, },
}), }),
getSessionBackupPrivateKey: jest.fn(), getSessionBackupPrivateKey: vi.fn(),
isEncryptionEnabledInRoom: jest.fn(), isEncryptionEnabledInRoom: vi.fn(),
getKeyBackupInfo: jest.fn().mockResolvedValue(null), getKeyBackupInfo: vi.fn().mockResolvedValue(null),
} as unknown as Mocked<CryptoApi>; } as unknown as Mocked<CryptoApi>;
mockClient = getMockClientWithEventEmitter({ mockClient = getMockClientWithEventEmitter({
isGuest: jest.fn(), isGuest: vi.fn(),
getUserId: jest.fn().mockReturnValue(userId), getUserId: vi.fn().mockReturnValue(userId),
getSafeUserId: jest.fn().mockReturnValue(userId), getSafeUserId: vi.fn().mockReturnValue(userId),
getRooms: jest.fn().mockReturnValue([]), getRooms: vi.fn().mockReturnValue([]),
isVersionSupported: jest.fn().mockResolvedValue(true), isVersionSupported: vi.fn().mockResolvedValue(true),
isInitialSyncComplete: jest.fn().mockReturnValue(true), isInitialSyncComplete: vi.fn().mockReturnValue(true),
isKeyBackupKeyStored: jest.fn(), isKeyBackupKeyStored: vi.fn(),
waitForClientWellKnown: jest.fn(), waitForClientWellKnown: vi.fn(),
getClientWellKnown: jest.fn(), getClientWellKnown: vi.fn(),
getDeviceId: jest.fn().mockReturnValue(deviceId), getDeviceId: vi.fn().mockReturnValue(deviceId),
setAccountData: jest.fn(), setAccountData: vi.fn(),
getAccountData: jest.fn(), getAccountData: vi.fn(),
getAccountDataFromServer: jest.fn(), getAccountDataFromServer: vi.fn(),
deleteAccountData: jest.fn(), deleteAccountData: vi.fn(),
getCrypto: jest.fn().mockReturnValue(mockCrypto), getCrypto: vi.fn().mockReturnValue(mockCrypto),
secretStorage: { secretStorage: {
isStored: jest.fn().mockReturnValue(null), isStored: vi.fn().mockReturnValue(null),
}, },
}); });
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient); vi.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false); vi.spyOn(SettingsStore, "getValue").mockReturnValue(false);
mocked(isBulkUnverifiedDeviceReminderSnoozed).mockClear().mockReturnValue(false); vi.mocked(isBulkUnverifiedDeviceReminderSnoozed).mockClear().mockReturnValue(false);
}); });
const createAndStart = async (): Promise<DeviceListener> => { const createAndStart = async (): Promise<DeviceListener> => {
@@ -155,8 +159,8 @@ describe("DeviceListener", () => {
describe("client information", () => { describe("client information", () => {
it("watches device client information setting", async () => { it("watches device client information setting", async () => {
const watchSettingSpy = jest.spyOn(SettingsStore, "watchSetting"); const watchSettingSpy = vi.spyOn(SettingsStore, "watchSetting");
const unwatchSettingSpy = jest.spyOn(SettingsStore, "unwatchSetting"); const unwatchSettingSpy = vi.spyOn(SettingsStore, "unwatchSetting");
const deviceListener = await createAndStart(); const deviceListener = await createAndStart();
expect(watchSettingSpy).toHaveBeenCalledWith("deviceClientInformationOptIn", null, expect.any(Function)); expect(watchSettingSpy).toHaveBeenCalledWith("deviceClientInformationOptIn", null, expect.any(Function));
@@ -168,7 +172,7 @@ describe("DeviceListener", () => {
it("responds to KeyBackupDecryptionKeyCached events", async () => { it("responds to KeyBackupDecryptionKeyCached events", async () => {
// Given a Device Listener // Given a Device Listener
const recheck = jest.fn(); const recheck = vi.fn();
const deviceListener = await createAndStart(); const deviceListener = await createAndStart();
deviceListener.recheck = recheck; deviceListener.recheck = recheck;
@@ -179,7 +183,7 @@ describe("DeviceListener", () => {
expect(recheck).toHaveBeenCalled(); expect(recheck).toHaveBeenCalled();
// And when we stop our device listener // And when we stop our device listener
const removeListener = jest.fn(() => {}); const removeListener = vi.fn(() => {});
// @ts-ignore overwriting with a mock // @ts-ignore overwriting with a mock
mockClient.removeListener = removeListener; mockClient.removeListener = removeListener;
deviceListener.stop(); deviceListener.stop();
@@ -190,7 +194,7 @@ describe("DeviceListener", () => {
describe("when device client information feature is enabled", () => { describe("when device client information feature is enabled", () => {
beforeEach(() => { beforeEach(() => {
jest.spyOn(SettingsStore, "getValue").mockImplementation( vi.spyOn(SettingsStore, "getValue").mockImplementation(
(settingName) => settingName === "deviceClientInformationOptIn", (settingName) => settingName === "deviceClientInformationOptIn",
); );
}); });
@@ -241,7 +245,7 @@ describe("DeviceListener", () => {
}); });
const emptyClientInfoEvent = new MatrixEvent({ type: `io.element.matrix_client_information.${deviceId}` }); const emptyClientInfoEvent = new MatrixEvent({ type: `io.element.matrix_client_information.${deviceId}` });
beforeEach(() => { beforeEach(() => {
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false); vi.spyOn(SettingsStore, "getValue").mockReturnValue(false);
mockClient!.getAccountData.mockReturnValue(undefined); mockClient!.getAccountData.mockReturnValue(undefined);
}); });
@@ -280,7 +284,7 @@ describe("DeviceListener", () => {
}); });
it("saves client information after setting is enabled", async () => { it("saves client information after setting is enabled", async () => {
const watchSettingSpy = jest.spyOn(SettingsStore, "watchSetting"); const watchSettingSpy = vi.spyOn(SettingsStore, "watchSetting");
await createAndStart(); await createAndStart();
const [settingName, roomId, callback] = watchSettingSpy.mock.calls[0]; const [settingName, roomId, callback] = watchSettingSpy.mock.calls[0];
@@ -376,7 +380,7 @@ describe("DeviceListener", () => {
mockCrypto!.isCrossSigningReady.mockResolvedValue(false); mockCrypto!.isCrossSigningReady.mockResolvedValue(false);
mockCrypto!.getSecretStorageStatus.mockResolvedValue(unreadySecretStorageStatus); mockCrypto!.getSecretStorageStatus.mockResolvedValue(unreadySecretStorageStatus);
mockClient!.getRooms.mockReturnValue(rooms); mockClient!.getRooms.mockReturnValue(rooms);
jest.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true); vi.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
}); });
it("hides setup encryption toast when it is dismissed", async () => { it("hides setup encryption toast when it is dismissed", async () => {
@@ -392,27 +396,27 @@ describe("DeviceListener", () => {
const instance = await createAndStart(); const instance = await createAndStart();
expect(SetupEncryptionToast.showToast).toHaveBeenCalledTimes(1); expect(SetupEncryptionToast.showToast).toHaveBeenCalledTimes(1);
jest.useFakeTimers({ advanceTimers: true }); vi.useFakeTimers();
instance.dismissEncryptionSetup(); instance.dismissEncryptionSetup();
await flushPromises(); await flushPromises();
expect(SetupEncryptionToast.hideToast).toHaveBeenCalled(); expect(SetupEncryptionToast.hideToast).toHaveBeenCalled();
// 1.5 days after the toast was dismissed, we don't re-show the // 1.5 days after the toast was dismissed, we don't re-show the
// toast yet. // toast yet.
jest.advanceTimersByTime(1.5 * 24 * 60 * 60 * 1000); vi.advanceTimersByTime(1.5 * 24 * 60 * 60 * 1000);
expect(SetupEncryptionToast.showToast).toHaveBeenCalledTimes(1); expect(SetupEncryptionToast.showToast).toHaveBeenCalledTimes(1);
// 2 days after the toast was dismissed, we re-show the toast. // 2 days after the toast was dismissed, we re-show the toast.
jest.advanceTimersByTime(0.5 * 24 * 60 * 60 * 1000); vi.advanceTimersByTime(0.5 * 24 * 60 * 60 * 1000);
expect(SetupEncryptionToast.showToast).toHaveBeenCalledTimes(2); expect(SetupEncryptionToast.showToast).toHaveBeenCalledTimes(2);
jest.useRealTimers(); vi.useRealTimers();
}); });
it("doesn't re-show toast if the device is now verified", async () => { it("doesn't re-show toast if the device is now verified", async () => {
const instance = await createAndStart(); const instance = await createAndStart();
expect(SetupEncryptionToast.showToast).toHaveBeenCalledTimes(1); expect(SetupEncryptionToast.showToast).toHaveBeenCalledTimes(1);
jest.useFakeTimers({ advanceTimers: true }); vi.useFakeTimers();
instance.dismissEncryptionSetup(); instance.dismissEncryptionSetup();
await flushPromises(); await flushPromises();
expect(SetupEncryptionToast.hideToast).toHaveBeenCalled(); expect(SetupEncryptionToast.hideToast).toHaveBeenCalled();
@@ -427,20 +431,20 @@ describe("DeviceListener", () => {
); );
instance.recheck(); instance.recheck();
await flushPromises(); await flushPromises();
jest.advanceTimersByTime(2 * 24 * 60 * 60 * 1000); vi.advanceTimersByTime(2 * 24 * 60 * 60 * 1000);
expect(SetupEncryptionToast.showToast).toHaveBeenCalledTimes(1); expect(SetupEncryptionToast.showToast).toHaveBeenCalledTimes(1);
jest.useRealTimers(); vi.useRealTimers();
}); });
it("does not show any toasts when secret storage is being accessed", async () => { it("does not show any toasts when secret storage is being accessed", async () => {
mocked(isSecretStorageBeingAccessed).mockReturnValue(true); vi.mocked(isSecretStorageBeingAccessed).mockReturnValue(true);
await createAndStart(); await createAndStart();
expect(SetupEncryptionToast.showToast).not.toHaveBeenCalled(); expect(SetupEncryptionToast.showToast).not.toHaveBeenCalled();
}); });
it("shows toasts even when no rooms are encrypted", async () => { it("shows toasts even when no rooms are encrypted", async () => {
jest.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(false); vi.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(false);
await createAndStart(); await createAndStart();
expect(SetupEncryptionToast.showToast).toHaveBeenCalled(); expect(SetupEncryptionToast.showToast).toHaveBeenCalled();
@@ -609,7 +613,7 @@ describe("DeviceListener", () => {
// And we have run the checks once (and we were told to verify) // And we have run the checks once (and we were told to verify)
const instance = await createAndStart(); const instance = await createAndStart();
expect(SetupEncryptionToast.showToast).toHaveBeenCalledWith("verify_this_session"); expect(SetupEncryptionToast.showToast).toHaveBeenCalledWith("verify_this_session");
mocked(SetupEncryptionToast.showToast).mockClear(); vi.mocked(SetupEncryptionToast.showToast).mockClear();
mockCrypto.getDeviceVerificationStatus.mockClear(); mockCrypto.getDeviceVerificationStatus.mockClear();
// When we dismiss the dialog telling us to set up encryption // When we dismiss the dialog telling us to set up encryption
@@ -672,7 +676,7 @@ describe("DeviceListener", () => {
beforeEach(() => { beforeEach(() => {
// Encryption is in use // Encryption is in use
mockClient.getRooms.mockReturnValue([{ roomId: "!room1" }, { roomId: "!room2" }] as unknown as Room[]); mockClient.getRooms.mockReturnValue([{ roomId: "!room1" }, { roomId: "!room2" }] as unknown as Room[]);
jest.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true); vi.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
// The device is verified // The device is verified
mockCrypto.getDeviceVerificationStatus.mockResolvedValue( mockCrypto.getDeviceVerificationStatus.mockResolvedValue(
@@ -839,7 +843,7 @@ describe("DeviceListener", () => {
}); });
it("hides toast when reminder is snoozed", async () => { it("hides toast when reminder is snoozed", async () => {
mocked(isBulkUnverifiedDeviceReminderSnoozed).mockReturnValue(true); vi.mocked(isBulkUnverifiedDeviceReminderSnoozed).mockReturnValue(true);
// currentDevice, device2 are verified, device3 is unverified // currentDevice, device2 are verified, device3 is unverified
mockCrypto!.getDeviceVerificationStatus.mockImplementation(async (_userId, deviceId) => { mockCrypto!.getDeviceVerificationStatus.mockImplementation(async (_userId, deviceId) => {
switch (deviceId) { switch (deviceId) {
@@ -931,12 +935,12 @@ describe("DeviceListener", () => {
}); });
describe("Report verification and recovery state to Analytics", () => { describe("Report verification and recovery state to Analytics", () => {
let setPropertySpy: jest.SpyInstance; let setPropertySpy: Mocked<PosthogAnalytics["setProperty"]>;
let trackEventSpy: jest.SpyInstance; let trackEventSpy: Mocked<PosthogAnalytics["trackEvent"]>;
beforeEach(() => { beforeEach(() => {
setPropertySpy = jest.spyOn(PosthogAnalytics.instance, "setProperty"); setPropertySpy = vi.spyOn(PosthogAnalytics.instance, "setProperty");
trackEventSpy = jest.spyOn(PosthogAnalytics.instance, "trackEvent"); trackEventSpy = vi.spyOn(PosthogAnalytics.instance, "trackEvent");
}); });
describe("Report crypto verification state to analytics", () => { describe("Report crypto verification state to analytics", () => {
@@ -1301,7 +1305,7 @@ describe("DeviceListener", () => {
mockCrypto!.isCrossSigningReady.mockResolvedValue(true); mockCrypto!.isCrossSigningReady.mockResolvedValue(true);
mockCrypto!.getSecretStorageStatus.mockResolvedValue(unreadySecretStorageStatus); mockCrypto!.getSecretStorageStatus.mockResolvedValue(unreadySecretStorageStatus);
mockClient!.getRooms.mockReturnValue(rooms); mockClient!.getRooms.mockReturnValue(rooms);
jest.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true); vi.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
}); });
it("shows the 'set up recovery' toast if user has not set up 4S", async () => { it("shows the 'set up recovery' toast if user has not set up 4S", async () => {
@@ -1320,7 +1324,7 @@ describe("DeviceListener", () => {
}); });
it("does not show the 'set up recovery' toast if user has no encrypted rooms", async () => { it("does not show the 'set up recovery' toast if user has no encrypted rooms", async () => {
jest.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(false); vi.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(false);
await createAndStart(); await createAndStart();
expect(SetupEncryptionToast.showToast).not.toHaveBeenCalledWith("set_up_recovery"); expect(SetupEncryptionToast.showToast).not.toHaveBeenCalledWith("set_up_recovery");
@@ -6,12 +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. Please see LICENSE files in the repository root for full details.
*/ */
import { vi, describe, it, expect, beforeEach } from "vitest";
import React from "react"; import React from "react";
import { formatList, formatCount, formatCountLong } from "../../../src/utils/FormattingUtils"; import { formatList, formatCount, formatCountLong } from "./FormattingUtils";
import SettingsStore from "../../../src/settings/SettingsStore"; import SettingsStore from "../settings/SettingsStore";
jest.mock("../../../src/dispatcher/dispatcher"); vi.mock("../dispatcher/dispatcher");
describe("FormattingUtils", () => { describe("FormattingUtils", () => {
describe("formatCount", () => { describe("formatCount", () => {
@@ -37,8 +38,8 @@ describe("FormattingUtils", () => {
describe("formatList", () => { describe("formatList", () => {
beforeEach(() => { beforeEach(() => {
jest.resetAllMocks(); vi.resetAllMocks();
jest.spyOn(SettingsStore, "getValue").mockReturnValue("en-GB"); vi.spyOn(SettingsStore, "getValue").mockReturnValue("en-GB");
}); });
it("should return empty string when given empty list", () => { it("should return empty string when given empty list", () => {
@@ -54,7 +55,7 @@ describe("FormattingUtils", () => {
}); });
it("should return expected sentence in German without item limit", () => { it("should return expected sentence in German without item limit", () => {
jest.spyOn(SettingsStore, "getValue").mockReturnValue("de"); vi.spyOn(SettingsStore, "getValue").mockReturnValue("de");
expect(formatList(["abc", "def", "ghi"])).toEqual("abc, def und ghi"); expect(formatList(["abc", "def", "ghi"])).toEqual("abc, def und ghi");
}); });
@@ -6,11 +6,14 @@ 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. Please see LICENSE files in the repository root for full details.
*/ */
import { render } from "jest-matrix-react"; // @vitest-environment happy-dom
import { describe, it, expect } from "vitest";
import { render } from "test-utils-rtl";
import type { IContent } from "matrix-js-sdk/src/matrix"; import type { IContent } from "matrix-js-sdk/src/matrix";
import type React from "react"; import type React from "react";
import { editBodyDiffToHtml } from "../../../src/utils/MessageDiffUtils"; import { editBodyDiffToHtml } from "./MessageDiffUtils";
describe("editBodyDiffToHtml", () => { describe("editBodyDiffToHtml", () => {
function buildContent(message: string): IContent { function buildContent(message: string): IContent {
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`FormattingUtils formatList should return expected sentence in ReactNode when given 2 React children 1`] = ` exports[`FormattingUtils > formatList > should return expected sentence in ReactNode when given 2 React children 1`] = `
<React.Fragment> <React.Fragment>
<React.Fragment> <React.Fragment>
<span> <span>
@@ -18,7 +18,7 @@ exports[`FormattingUtils formatList should return expected sentence in ReactNode
</React.Fragment> </React.Fragment>
`; `;
exports[`FormattingUtils formatList should return expected sentence in ReactNode when given more React children 1`] = ` exports[`FormattingUtils > formatList > should return expected sentence in ReactNode when given more React children 1`] = `
<React.Fragment> <React.Fragment>
<React.Fragment> <React.Fragment>
<span> <span>
@@ -52,7 +52,7 @@ exports[`FormattingUtils formatList should return expected sentence in ReactNode
</React.Fragment> </React.Fragment>
`; `;
exports[`FormattingUtils formatList should return expected sentence in ReactNode when using itemLimit 1`] = ` exports[`FormattingUtils > formatList > should return expected sentence in ReactNode when using itemLimit 1`] = `
<span> <span>
<React.Fragment> <React.Fragment>
<React.Fragment> <React.Fragment>
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`editBodyDiffToHtml deduplicates diff steps 1`] = ` exports[`editBodyDiffToHtml > deduplicates diff steps 1`] = `
<div> <div>
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body"
@@ -28,7 +28,7 @@ exports[`editBodyDiffToHtml deduplicates diff steps 1`] = `
</div> </div>
`; `;
exports[`editBodyDiffToHtml handles complex transformations 1`] = ` exports[`editBodyDiffToHtml > handles complex transformations 1`] = `
<div> <div>
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body"
@@ -76,7 +76,7 @@ exports[`editBodyDiffToHtml handles complex transformations 1`] = `
</div> </div>
`; `;
exports[`editBodyDiffToHtml handles non-html input 1`] = ` exports[`editBodyDiffToHtml > handles non-html input 1`] = `
<div> <div>
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body"
@@ -95,7 +95,7 @@ exports[`editBodyDiffToHtml handles non-html input 1`] = `
</div> </div>
`; `;
exports[`editBodyDiffToHtml renders attribute additions 1`] = ` exports[`editBodyDiffToHtml > renders attribute additions 1`] = `
<div> <div>
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body"
@@ -159,7 +159,7 @@ exports[`editBodyDiffToHtml renders attribute additions 1`] = `
</div> </div>
`; `;
exports[`editBodyDiffToHtml renders attribute deletions 1`] = ` exports[`editBodyDiffToHtml > renders attribute deletions 1`] = `
<div> <div>
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body"
@@ -225,7 +225,7 @@ exports[`editBodyDiffToHtml renders attribute deletions 1`] = `
</div> </div>
`; `;
exports[`editBodyDiffToHtml renders attribute modifications 1`] = ` exports[`editBodyDiffToHtml > renders attribute modifications 1`] = `
<div> <div>
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body"
@@ -259,7 +259,7 @@ exports[`editBodyDiffToHtml renders attribute modifications 1`] = `
</div> </div>
`; `;
exports[`editBodyDiffToHtml renders block element additions 1`] = ` exports[`editBodyDiffToHtml > renders block element additions 1`] = `
<div> <div>
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body"
@@ -284,7 +284,7 @@ exports[`editBodyDiffToHtml renders block element additions 1`] = `
</div> </div>
`; `;
exports[`editBodyDiffToHtml renders block element deletions 1`] = ` exports[`editBodyDiffToHtml > renders block element deletions 1`] = `
<div> <div>
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body"
@@ -309,7 +309,7 @@ exports[`editBodyDiffToHtml renders block element deletions 1`] = `
</div> </div>
`; `;
exports[`editBodyDiffToHtml renders central word changes 1`] = ` exports[`editBodyDiffToHtml > renders central word changes 1`] = `
<div> <div>
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body"
@@ -340,7 +340,7 @@ exports[`editBodyDiffToHtml renders central word changes 1`] = `
</div> </div>
`; `;
exports[`editBodyDiffToHtml renders element replacements 1`] = ` exports[`editBodyDiffToHtml > renders element replacements 1`] = `
<div> <div>
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body"
@@ -367,7 +367,7 @@ exports[`editBodyDiffToHtml renders element replacements 1`] = `
</div> </div>
`; `;
exports[`editBodyDiffToHtml renders handles empty tags 1`] = ` exports[`editBodyDiffToHtml > renders handles empty tags 1`] = `
<div> <div>
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body"
@@ -398,7 +398,7 @@ exports[`editBodyDiffToHtml renders handles empty tags 1`] = `
</div> </div>
`; `;
exports[`editBodyDiffToHtml renders inline element additions 1`] = ` exports[`editBodyDiffToHtml > renders inline element additions 1`] = `
<div> <div>
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body"
@@ -416,7 +416,7 @@ exports[`editBodyDiffToHtml renders inline element additions 1`] = `
</div> </div>
`; `;
exports[`editBodyDiffToHtml renders inline element deletions 1`] = ` exports[`editBodyDiffToHtml > renders inline element deletions 1`] = `
<div> <div>
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body"
@@ -441,7 +441,7 @@ exports[`editBodyDiffToHtml renders inline element deletions 1`] = `
</div> </div>
`; `;
exports[`editBodyDiffToHtml renders simple word changes 1`] = ` exports[`editBodyDiffToHtml > renders simple word changes 1`] = `
<div> <div>
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body"
@@ -463,7 +463,7 @@ exports[`editBodyDiffToHtml renders simple word changes 1`] = `
</div> </div>
`; `;
exports[`editBodyDiffToHtml renders text additions 1`] = ` exports[`editBodyDiffToHtml > renders text additions 1`] = `
<div> <div>
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body"
@@ -481,7 +481,7 @@ exports[`editBodyDiffToHtml renders text additions 1`] = `
</div> </div>
`; `;
exports[`editBodyDiffToHtml renders text deletions 1`] = ` exports[`editBodyDiffToHtml > renders text deletions 1`] = `
<div> <div>
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body"
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`createVoiceMessageContent should create a voice message content 1`] = ` exports[`createVoiceMessageContent > should create a voice message content 1`] = `
{ {
"body": "Voice message", "body": "Voice message",
"file": {}, "file": {},
@@ -6,9 +6,10 @@ 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. Please see LICENSE files in the repository root for full details.
*/ */
import { describe, it, expect } from "vitest";
import { type EncryptedFile } from "matrix-js-sdk/src/types"; import { type EncryptedFile } from "matrix-js-sdk/src/types";
import { createVoiceMessageContent } from "../../../src/utils/createVoiceMessageContent"; import { createVoiceMessageContent } from "./createVoiceMessageContent";
describe("createVoiceMessageContent", () => { describe("createVoiceMessageContent", () => {
it("should create a voice message content", () => { it("should create a voice message content", () => {
@@ -6,8 +6,10 @@ 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. Please see LICENSE files in the repository root for full details.
*/ */
import { PermalinkParts } from "../../../../src/utils/permalinks/PermalinkConstructor"; import { describe, it, expect } from "vitest";
import MatrixSchemePermalinkConstructor from "../../../../src/utils/permalinks/MatrixSchemePermalinkConstructor";
import { PermalinkParts } from "./PermalinkConstructor";
import MatrixSchemePermalinkConstructor from "./MatrixSchemePermalinkConstructor";
describe("MatrixSchemePermalinkConstructor", () => { describe("MatrixSchemePermalinkConstructor", () => {
const peramlinkConstructor = new MatrixSchemePermalinkConstructor(); const peramlinkConstructor = new MatrixSchemePermalinkConstructor();
@@ -6,8 +6,10 @@ 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. Please see LICENSE files in the repository root for full details.
*/ */
import MatrixToPermalinkConstructor from "../../../../src/utils/permalinks/MatrixToPermalinkConstructor"; import { describe, it, expect } from "vitest";
import { PermalinkParts } from "../../../../src/utils/permalinks/PermalinkConstructor";
import MatrixToPermalinkConstructor from "./MatrixToPermalinkConstructor";
import { PermalinkParts } from "./PermalinkConstructor";
describe("MatrixToPermalinkConstructor", () => { describe("MatrixToPermalinkConstructor", () => {
const peramlinkConstructor = new MatrixToPermalinkConstructor(); const peramlinkConstructor = new MatrixToPermalinkConstructor();
@@ -7,27 +7,24 @@ 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. Please see LICENSE files in the repository root for full details.
*/ */
import { vi, describe, it, expect, afterAll, beforeEach } from "vitest";
import { getMockClientWithEventEmitter } from "test-utils/client";
import { type EventEmitter } from "events"; import { type EventEmitter } from "events";
import { Room, RoomMember, EventType, MatrixEvent } from "matrix-js-sdk/src/matrix"; import { Room, RoomMember, EventType, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types"; import { KnownMembership } from "matrix-js-sdk/src/types";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import { MatrixClientPeg } from "../../MatrixClientPeg";
import { PermalinkParts } from "../../../../src/utils/permalinks/PermalinkConstructor"; import { PermalinkParts } from "./PermalinkConstructor";
import { import { makeRoomPermalink, makeUserPermalink, parsePermalink, RoomPermalinkCreator } from "./Permalinks";
makeRoomPermalink, import { type IConfigOptions } from "../../IConfigOptions";
makeUserPermalink, import SdkConfig from "../../SdkConfig";
parsePermalink,
RoomPermalinkCreator,
} from "../../../../src/utils/permalinks/Permalinks";
import { type IConfigOptions } from "../../../../src/IConfigOptions";
import SdkConfig from "../../../../src/SdkConfig";
import { getMockClientWithEventEmitter } from "../../../test-utils";
describe("Permalinks", function () { describe("Permalinks", function () {
const userId = "@test:example.com"; const userId = "@test:example.com";
const mockClient = getMockClientWithEventEmitter({ const mockClient = getMockClientWithEventEmitter({
getUserId: jest.fn().mockReturnValue(userId), getUserId: vi.fn().mockReturnValue(userId),
getRoom: jest.fn(), getRoom: vi.fn(),
}); });
mockClient.credentials = { userId }; mockClient.credentials = { userId };
@@ -72,18 +69,18 @@ describe("Permalinks", function () {
const stateEvents = serverACL ? [powerLevels, serverACL] : [powerLevels]; const stateEvents = serverACL ? [powerLevels, serverACL] : [powerLevels];
room.currentState.setStateEvents(stateEvents); room.currentState.setStateEvents(stateEvents);
jest.spyOn(room, "getCanonicalAlias").mockReturnValue(null); vi.spyOn(room, "getCanonicalAlias").mockReturnValue(null);
jest.spyOn(room, "getJoinedMembers").mockReturnValue(members); vi.spyOn(room, "getJoinedMembers").mockReturnValue(members);
jest.spyOn(room, "getMember").mockImplementation((userId) => members.find((m) => m.userId === userId) || null); vi.spyOn(room, "getMember").mockImplementation((userId) => members.find((m) => m.userId === userId) || null);
return room; return room;
} }
beforeEach(function () { beforeEach(function () {
jest.clearAllMocks(); vi.clearAllMocks();
}); });
afterAll(() => { afterAll(() => {
jest.spyOn(MatrixClientPeg, "get").mockRestore(); vi.spyOn(MatrixClientPeg, "get").mockRestore();
}); });
it("should not clean up listeners even if start was called multiple times", () => { it("should not clean up listeners even if start was called multiple times", () => {
@@ -416,7 +413,7 @@ describe("Permalinks", function () {
it("should use permalink_prefix for permalinks", function () { it("should use permalink_prefix for permalinks", function () {
const sdkConfigGet = SdkConfig.get; const sdkConfigGet = SdkConfig.get;
jest.spyOn(SdkConfig, "get").mockImplementation((key: keyof IConfigOptions, altCaseName?: string) => { vi.spyOn(SdkConfig, "get").mockImplementation((key: keyof IConfigOptions, altCaseName?: string) => {
if (key === "permalink_prefix") { if (key === "permalink_prefix") {
return "https://element.fs.tld"; return "https://element.fs.tld";
} else return sdkConfigGet(key, altCaseName); } else return sdkConfigGet(key, altCaseName);
@@ -6,12 +6,14 @@ 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. Please see LICENSE files in the repository root for full details.
*/ */
import { type Mocked } from "jest-mock"; // @vitest-environment happy-dom
import { type IIdentityServerProvider, type MatrixClient } from "matrix-js-sdk/src/matrix";
import { DirectoryMember, ThreepidMember } from "../../../src/utils/direct-messages"; import { vi, describe, it, expect, beforeEach, type Mocked } from "vitest";
import { lookupThreePids, resolveThreePids } from "../../../src/utils/threepids"; import { type IIdentityServerProvider, type MatrixClient } from "matrix-js-sdk/src/matrix";
import { stubClient } from "../../test-utils"; import { stubClient } from "test-utils";
import { DirectoryMember, ThreepidMember } from "./direct-messages";
import { lookupThreePids, resolveThreePids } from "./threepids";
describe("threepids", () => { describe("threepids", () => {
let client: Mocked<MatrixClient>; let client: Mocked<MatrixClient>;
@@ -21,7 +23,7 @@ describe("threepids", () => {
beforeEach(() => { beforeEach(() => {
client = stubClient() as Mocked<MatrixClient>; client = stubClient() as Mocked<MatrixClient>;
identityServer = { identityServer = {
getAccessToken: jest.fn().mockResolvedValue(accessToken), getAccessToken: vi.fn().mockResolvedValue(accessToken),
} as unknown as Mocked<IIdentityServerProvider>; } as unknown as Mocked<IIdentityServerProvider>;
}); });
@@ -5,8 +5,11 @@ 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. Please see LICENSE files in the repository root for full details.
*/ */
// @vitest-environment happy-dom
import { vi, describe, it, expect, beforeEach } from "vitest";
import { type MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix"; import { type MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
import { mocked } from "jest-mock"; import { stubClient } from "test-utils";
import { import {
clearUserStatus, clearUserStatus,
@@ -14,8 +17,7 @@ import {
setUserStatus, setUserStatus,
userStatusFromProfile, userStatusFromProfile,
userStatusTextWithinMaxLength, userStatusTextWithinMaxLength,
} from "../../../src/utils/userStatus"; } from "./userStatus";
import { stubClient } from "../../test-utils";
describe("userStatus utils", () => { describe("userStatus utils", () => {
describe("userStatusFromProfile", () => { describe("userStatusFromProfile", () => {
@@ -88,19 +90,22 @@ describe("userStatus utils", () => {
beforeEach(() => { beforeEach(() => {
client = stubClient(); client = stubClient();
client.doesServerSupportExtendedProfiles = jest.fn(); client.doesServerSupportExtendedProfiles = vi.fn();
}); });
it("returns undefined if the server does not support extended profiles", async () => { it("returns undefined if the server does not support extended profiles", async () => {
mocked(client.doesServerSupportExtendedProfiles).mockResolvedValue(false); vi.mocked(client.doesServerSupportExtendedProfiles).mockResolvedValue(false);
await expect(fetchUserStatus(client, "@alice:example.com")).resolves.toBeUndefined(); await expect(fetchUserStatus(client, "@alice:example.com")).resolves.toBeUndefined();
expect(client.getExtendedProfileProperty).not.toHaveBeenCalled(); expect(client.getExtendedProfileProperty).not.toHaveBeenCalled();
}); });
it("returns the validated status if the server supports extended profiles and has a status set", async () => { it("returns the validated status if the server supports extended profiles and has a status set", async () => {
mocked(client.doesServerSupportExtendedProfiles).mockResolvedValue(true); vi.mocked(client.doesServerSupportExtendedProfiles).mockResolvedValue(true);
mocked(client.getExtendedProfileProperty).mockResolvedValue({ emoji: "🐳", text: "Feeling a little blue" }); vi.mocked(client.getExtendedProfileProperty).mockResolvedValue({
emoji: "🐳",
text: "Feeling a little blue",
});
await expect(fetchUserStatus(client, "@alice:example.com")).resolves.toEqual({ await expect(fetchUserStatus(client, "@alice:example.com")).resolves.toEqual({
emoji: "🐳", emoji: "🐳",
@@ -113,15 +118,15 @@ describe("userStatus utils", () => {
}); });
it("returns undefined if the status is invalid", async () => { it("returns undefined if the status is invalid", async () => {
mocked(client.doesServerSupportExtendedProfiles).mockResolvedValue(true); vi.mocked(client.doesServerSupportExtendedProfiles).mockResolvedValue(true);
mocked(client.getExtendedProfileProperty).mockResolvedValue({ text: "Feeling a little blue" }); vi.mocked(client.getExtendedProfileProperty).mockResolvedValue({ text: "Feeling a little blue" });
await expect(fetchUserStatus(client, "@alice:example.com")).resolves.toBeUndefined(); await expect(fetchUserStatus(client, "@alice:example.com")).resolves.toBeUndefined();
}); });
it("returns undefined if the user has no status set", async () => { it("returns undefined if the user has no status set", async () => {
mocked(client.doesServerSupportExtendedProfiles).mockResolvedValue(true); vi.mocked(client.doesServerSupportExtendedProfiles).mockResolvedValue(true);
mocked(client.getExtendedProfileProperty).mockRejectedValue( vi.mocked(client.getExtendedProfileProperty).mockRejectedValue(
new MatrixError({ errcode: "M_NOT_FOUND" }, 404), new MatrixError({ errcode: "M_NOT_FOUND" }, 404),
); );
@@ -129,9 +134,9 @@ describe("userStatus utils", () => {
}); });
it("returns undefined and logs a warning if fetching the status fails unexpectedly", async () => { it("returns undefined and logs a warning if fetching the status fails unexpectedly", async () => {
mocked(client.doesServerSupportExtendedProfiles).mockResolvedValue(true); vi.mocked(client.doesServerSupportExtendedProfiles).mockResolvedValue(true);
const error = new Error("network error"); const error = new Error("network error");
mocked(client.getExtendedProfileProperty).mockRejectedValue(error); vi.mocked(client.getExtendedProfileProperty).mockRejectedValue(error);
await expect(fetchUserStatus(client, "@alice:example.com")).resolves.toBeUndefined(); await expect(fetchUserStatus(client, "@alice:example.com")).resolves.toBeUndefined();
}); });
@@ -5,13 +5,15 @@ 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. Please see LICENSE files in the repository root for full details.
*/ */
import fetchMock from "@fetch-mock/jest"; // @vitest-environment happy-dom
import type { Mocked } from "jest-mock"; import { vi, describe, it, expect, beforeEach, afterEach, type Mocked } from "vitest";
import type { ConsoleLogger } from "../../../src/rageshake/rageshake"; import fetchMock from "@fetch-mock/vitest";
import SdkConfig from "../../../src/SdkConfig";
import "../../../src/vector/rageshakesetup"; import type { ConsoleLogger } from "../rageshake/rageshake";
import { BugReportEndpointURLLocal } from "../../../src/IConfigOptions"; import SdkConfig from "../SdkConfig";
import "./rageshakesetup";
import { BugReportEndpointURLLocal } from "../IConfigOptions";
const RAGESHAKE_URL = "https://logs.example.org/logtome"; const RAGESHAKE_URL = "https://logs.example.org/logtome";
@@ -23,9 +25,9 @@ describe("mxSendRageshake", () => {
fetchMock.postOnce(RAGESHAKE_URL, { status: 200, body: {} }); fetchMock.postOnce(RAGESHAKE_URL, { status: 200, body: {} });
const mockConsoleLogger = { const mockConsoleLogger = {
flush: jest.fn(), flush: vi.fn(),
consume: jest.fn(), consume: vi.fn(),
warn: jest.fn(), warn: vi.fn(),
} as unknown as Mocked<ConsoleLogger>; } as unknown as Mocked<ConsoleLogger>;
prevLogger = global.mx_rage_logger; prevLogger = global.mx_rage_logger;
mockConsoleLogger.flush.mockReturnValue("line 1\nline 2\n"); mockConsoleLogger.flush.mockReturnValue("line 1\nline 2\n");
@@ -34,7 +36,7 @@ describe("mxSendRageshake", () => {
afterEach(() => { afterEach(() => {
global.mx_rage_logger = prevLogger; global.mx_rage_logger = prevLogger;
jest.restoreAllMocks(); vi.restoreAllMocks();
fetchMock.unmockGlobal(); fetchMock.unmockGlobal();
SdkConfig.reset(); SdkConfig.reset();
}); });
@@ -57,7 +59,7 @@ describe("mxSendRageshake", () => {
it("Provides a rageshake locally", async () => { it("Provides a rageshake locally", async () => {
SdkConfig.put({ bug_report_endpoint_url: BugReportEndpointURLLocal }); SdkConfig.put({ bug_report_endpoint_url: BugReportEndpointURLLocal });
const urlSpy = jest.spyOn(URL, "createObjectURL"); const urlSpy = vi.spyOn(URL, "createObjectURL");
await window.mxSendRageshake("Hello world"); await window.mxSendRageshake("Hello world");
expect(fetchMock).not.toHaveFetched(RAGESHAKE_URL); expect(fetchMock).not.toHaveFetched(RAGESHAKE_URL);
expect(urlSpy).toHaveBeenCalledTimes(1); expect(urlSpy).toHaveBeenCalledTimes(1);
@@ -5,27 +5,28 @@
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
import { vi, describe, it, expect, beforeEach, afterEach } from "vitest";
import React from "react"; import React from "react";
import { mocked } from "jest-mock";
import { ConnectionError, Direction } from "matrix-js-sdk/src/matrix"; import { ConnectionError, Direction } from "matrix-js-sdk/src/matrix";
import { flushPromisesWithFakeTimers } from "test-utils/utilities";
import dispatcher from "../../../src/dispatcher/dispatcher"; import dispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../src/dispatcher/actions"; import { Action } from "../../../dispatcher/actions";
import { formatFullDateNoTime } from "../../../src/DateUtils"; import { formatFullDateNoTime } from "../../../DateUtils";
import Modal from "../../../src/Modal"; import Modal from "../../../Modal";
import { MatrixClientPeg } from "../../../src/MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import SettingsStore from "../../../src/settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import { UIFeature } from "../../../src/settings/UIFeature"; import { UIFeature } from "../../../settings/UIFeature";
import { SDKContextClass } from "../../../src/contexts/SDKContextClass"; import { SDKContextClass } from "../../../contexts/SDKContextClass";
import { DateSeparatorViewModel } from "../../../src/viewmodels/room/timeline/DateSeparatorViewModel"; import { DateSeparatorViewModel } from "./DateSeparatorViewModel";
import { flushPromisesWithFakeTimers } from "../../test-utils/utilities";
jest.mock("../../../src/settings/SettingsStore"); vi.mock("../../../Modal");
jest.mock("../../../src/contexts/SDKContextClass", () => ({ vi.mock("../../../settings/SettingsStore");
vi.mock("../../../contexts/SDKContextClass", () => ({
SDKContextClass: { SDKContextClass: {
instance: { instance: {
roomViewStore: { roomViewStore: {
getRoomId: jest.fn(), getRoomId: vi.fn(),
}, },
}, },
}, },
@@ -56,7 +57,7 @@ describe("DateSeparatorViewModel", () => {
]; ];
const watchCallbacks = new Map<string, (...args: any[]) => void>(); const watchCallbacks = new Map<string, (...args: any[]) => void>();
const mockTimestampToEvent = jest.fn(); const mockTimestampToEvent = vi.fn();
const hasTestId = (node: React.ReactNode, testId: string): boolean => { const hasTestId = (node: React.ReactNode, testId: string): boolean => {
if (!React.isValidElement<{ children?: React.ReactNode }>(node)) return false; if (!React.isValidElement<{ children?: React.ReactNode }>(node)) return false;
@@ -78,35 +79,36 @@ describe("DateSeparatorViewModel", () => {
}; };
beforeEach(() => { beforeEach(() => {
jest.useFakeTimers(); vi.useFakeTimers();
jest.setSystemTime(nowDate.getTime()); vi.setSystemTime(nowDate.getTime());
watchCallbacks.clear(); watchCallbacks.clear();
mocked(SettingsStore).getValue.mockImplementation((key): any => { vi.mocked(SettingsStore).getValue.mockImplementation((key): any => {
if (String(key) === UIFeature.TimelineEnableRelativeDates) return true; if (String(key) === UIFeature.TimelineEnableRelativeDates) return true;
if (key === "feature_jump_to_date") return false; if (key === "feature_jump_to_date") return false;
return undefined; return undefined;
}); });
mocked(SettingsStore).watchSetting.mockImplementation((settingName, _roomId, cb): any => { vi.mocked(SettingsStore).watchSetting.mockImplementation((settingName, _roomId, cb): any => {
watchCallbacks.set(String(settingName), cb); watchCallbacks.set(String(settingName), cb);
return `${String(settingName)}-watch-ref`; return `${String(settingName)}-watch-ref`;
}); });
mocked(SettingsStore).unwatchSetting.mockImplementation(() => {}); vi.mocked(SettingsStore).unwatchSetting.mockImplementation(() => {});
mockTimestampToEvent.mockReset(); mockTimestampToEvent.mockReset();
jest.spyOn(MatrixClientPeg, "safeGet").mockReturnValue({ vi.spyOn(MatrixClientPeg, "safeGet").mockReturnValue({
timestampToEvent: mockTimestampToEvent, timestampToEvent: mockTimestampToEvent,
} as any); } as any);
jest.spyOn(dispatcher, "dispatch").mockImplementation(() => {}); vi.spyOn(dispatcher, "dispatch").mockImplementation(() => {});
jest.spyOn(Modal, "createDialog").mockImplementation(() => ({ close: jest.fn() }) as any); vi.spyOn(Modal, "createDialog").mockImplementation(() => ({ close: vi.fn() }) as any);
mocked(SDKContextClass.instance.roomViewStore.getRoomId).mockReturnValue(roomId); vi.mocked(SDKContextClass.instance.roomViewStore.getRoomId).mockReturnValue(roomId);
}); });
afterEach(() => { afterEach(() => {
jest.restoreAllMocks(); vi.restoreAllMocks();
jest.useRealTimers(); vi.clearAllMocks();
vi.useRealTimers();
}); });
it("computes relative label for today", () => { it("computes relative label for today", () => {
@@ -133,7 +135,7 @@ describe("DateSeparatorViewModel", () => {
}); });
it("exposes jumpToDateMenu when feature is enabled", () => { it("exposes jumpToDateMenu when feature is enabled", () => {
mocked(SettingsStore).getValue.mockImplementation((key): any => { vi.mocked(SettingsStore).getValue.mockImplementation((key): any => {
if (String(key) === UIFeature.TimelineEnableRelativeDates) return true; if (String(key) === UIFeature.TimelineEnableRelativeDates) return true;
if (key === "feature_jump_to_date") return true; if (key === "feature_jump_to_date") return true;
return undefined; return undefined;
@@ -150,7 +152,7 @@ describe("DateSeparatorViewModel", () => {
}); });
it("does not expose jumpToDateMenu when exporting", () => { it("does not expose jumpToDateMenu when exporting", () => {
mocked(SettingsStore).getValue.mockImplementation((key): any => { vi.mocked(SettingsStore).getValue.mockImplementation((key): any => {
if (String(key) === UIFeature.TimelineEnableRelativeDates) return true; if (String(key) === UIFeature.TimelineEnableRelativeDates) return true;
if (key === "feature_jump_to_date") return true; if (key === "feature_jump_to_date") return true;
return undefined; return undefined;
@@ -197,7 +199,7 @@ describe("DateSeparatorViewModel", () => {
event_id: "$event", event_id: "$event",
origin_server_ts: nowDate.getTime(), origin_server_ts: nowDate.getTime(),
}); });
mocked(SDKContextClass.instance.roomViewStore.getRoomId).mockReturnValue("!other:example.org"); vi.mocked(SDKContextClass.instance.roomViewStore.getRoomId).mockReturnValue("!other:example.org");
const vm = createViewModel(); const vm = createViewModel();
await vm.pickDate(nowDate.getTime() - HOUR_MS); await vm.pickDate(nowDate.getTime() - HOUR_MS);
@@ -212,7 +214,7 @@ describe("DateSeparatorViewModel", () => {
await vm.pickDate(nowDate.getTime() - HOUR_MS); await vm.pickDate(nowDate.getTime() - HOUR_MS);
expect(Modal.createDialog).toHaveBeenCalled(); expect(Modal.createDialog).toHaveBeenCalled();
const [, params] = mocked(Modal.createDialog).mock.calls.at(-1)!; const [, params] = vi.mocked(Modal.createDialog).mock.calls.at(-1)!;
expect(hasTestId((params as any).description, "jump-to-date-error-submit-debug-logs-button")).toBe(true); expect(hasTestId((params as any).description, "jump-to-date-error-submit-debug-logs-button")).toBe(true);
}); });
@@ -223,7 +225,7 @@ describe("DateSeparatorViewModel", () => {
await vm.pickDate(nowDate.getTime() - HOUR_MS); await vm.pickDate(nowDate.getTime() - HOUR_MS);
expect(Modal.createDialog).toHaveBeenCalled(); expect(Modal.createDialog).toHaveBeenCalled();
const [, params] = mocked(Modal.createDialog).mock.calls.at(-1)!; const [, params] = vi.mocked(Modal.createDialog).mock.calls.at(-1)!;
expect(hasTestId((params as any).description, "jump-to-date-error-submit-debug-logs-button")).toBe(false); expect(hasTestId((params as any).description, "jump-to-date-error-submit-debug-logs-button")).toBe(false);
}); });
@@ -242,7 +244,7 @@ describe("DateSeparatorViewModel", () => {
describe("when TimelineEnableRelativeDates is false", () => { describe("when TimelineEnableRelativeDates is false", () => {
beforeEach(() => { beforeEach(() => {
mocked(SettingsStore).getValue.mockImplementation((key): any => { vi.mocked(SettingsStore).getValue.mockImplementation((key): any => {
if (String(key) === UIFeature.TimelineEnableRelativeDates) return false; if (String(key) === UIFeature.TimelineEnableRelativeDates) return false;
if (key === "feature_jump_to_date") return false; if (key === "feature_jump_to_date") return false;
return undefined; return undefined;
@@ -257,7 +259,7 @@ describe("DateSeparatorViewModel", () => {
describe("jump actions", () => { describe("jump actions", () => {
beforeEach(() => { beforeEach(() => {
mocked(SettingsStore).getValue.mockImplementation((key): any => { vi.mocked(SettingsStore).getValue.mockImplementation((key): any => {
if (String(key) === UIFeature.TimelineEnableRelativeDates) return true; if (String(key) === UIFeature.TimelineEnableRelativeDates) return true;
if (key === "feature_jump_to_date") return true; if (key === "feature_jump_to_date") return true;
return undefined; return undefined;
@@ -303,7 +305,7 @@ describe("DateSeparatorViewModel", () => {
}); });
it("does not jump when room changed before request resolves", async () => { it("does not jump when room changed before request resolves", async () => {
mocked(SDKContextClass.instance.roomViewStore.getRoomId).mockReturnValue("!some-other-room"); vi.mocked(SDKContextClass.instance.roomViewStore.getRoomId).mockReturnValue("!some-other-room");
mockTimestampToEvent.mockResolvedValue({ mockTimestampToEvent.mockResolvedValue({
event_id: "$abc", event_id: "$abc",
origin_server_ts: 0, origin_server_ts: 0,
@@ -317,7 +319,7 @@ describe("DateSeparatorViewModel", () => {
}); });
it("does not show jump to date error if user switched room", async () => { it("does not show jump to date error if user switched room", async () => {
mocked(SDKContextClass.instance.roomViewStore.getRoomId).mockReturnValue("!some-other-room"); vi.mocked(SDKContextClass.instance.roomViewStore.getRoomId).mockReturnValue("!some-other-room");
mockTimestampToEvent.mockRejectedValue(new Error("Fake error in test")); mockTimestampToEvent.mockRejectedValue(new Error("Fake error in test"));
const vm = createViewModel(); const vm = createViewModel();
@@ -335,7 +337,7 @@ describe("DateSeparatorViewModel", () => {
await flushPromisesWithFakeTimers(); await flushPromisesWithFakeTimers();
expect(Modal.createDialog).toHaveBeenCalled(); expect(Modal.createDialog).toHaveBeenCalled();
const [, params] = mocked(Modal.createDialog).mock.calls.at(-1)!; const [, params] = vi.mocked(Modal.createDialog).mock.calls.at(-1)!;
expect(hasTestId((params as any).description, "jump-to-date-error-submit-debug-logs-button")).toBe(true); expect(hasTestId((params as any).description, "jump-to-date-error-submit-debug-logs-button")).toBe(true);
}); });
@@ -347,7 +349,7 @@ describe("DateSeparatorViewModel", () => {
await flushPromisesWithFakeTimers(); await flushPromisesWithFakeTimers();
expect(Modal.createDialog).toHaveBeenCalled(); expect(Modal.createDialog).toHaveBeenCalled();
const [, params] = mocked(Modal.createDialog).mock.calls.at(-1)!; const [, params] = vi.mocked(Modal.createDialog).mock.calls.at(-1)!;
expect(hasTestId((params as any).description, "jump-to-date-error-submit-debug-logs-button")).toBe(false); expect(hasTestId((params as any).description, "jump-to-date-error-submit-debug-logs-button")).toBe(false);
}); });
}); });
@@ -5,7 +5,9 @@
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
import { DisambiguatedProfileViewModel } from "../../../src/viewmodels/room/timeline/event-tile/DisambiguatedProfileViewModel"; import { vi, describe, it, expect } from "vitest";
import { DisambiguatedProfileViewModel } from "./DisambiguatedProfileViewModel";
describe("DisambiguatedProfileViewModel", () => { describe("DisambiguatedProfileViewModel", () => {
const member = { const member = {
@@ -53,14 +55,14 @@ describe("DisambiguatedProfileViewModel", () => {
}); });
it("should delegate onClick without emitting a snapshot update", () => { it("should delegate onClick without emitting a snapshot update", () => {
const onClick = jest.fn(); const onClick = vi.fn();
const vm = new DisambiguatedProfileViewModel({ const vm = new DisambiguatedProfileViewModel({
member, member,
fallbackName: "Fallback", fallbackName: "Fallback",
onClick, onClick,
}); });
const prevSnapshot = vm.getSnapshot(); const prevSnapshot = vm.getSnapshot();
const subscriber = jest.fn(); const subscriber = vi.fn();
vm.subscribe(subscriber); vm.subscribe(subscriber);
vm.onClick?.({} as never); vm.onClick?.({} as never);
@@ -71,7 +73,7 @@ describe("DisambiguatedProfileViewModel", () => {
}); });
it("should keep onClick bound when extracted as a callback", () => { it("should keep onClick bound when extracted as a callback", () => {
const onClick = jest.fn(); const onClick = vi.fn();
const vm = new DisambiguatedProfileViewModel({ const vm = new DisambiguatedProfileViewModel({
member, member,
fallbackName: "Fallback", fallbackName: "Fallback",
@@ -89,7 +91,7 @@ describe("DisambiguatedProfileViewModel", () => {
member: null, member: null,
fallbackName: "Fallback", fallbackName: "Fallback",
}); });
const subscriber = jest.fn(); const subscriber = vi.fn();
vm.subscribe(subscriber); vm.subscribe(subscriber);
vm.setMember("Updated"); vm.setMember("Updated");
@@ -103,7 +105,7 @@ describe("DisambiguatedProfileViewModel", () => {
member: null, member: null,
fallbackName: "Fallback", fallbackName: "Fallback",
}); });
const subscriber = jest.fn(); const subscriber = vi.fn();
vm.subscribe(subscriber); vm.subscribe(subscriber);
vm.setMember("Fallback"); vm.setMember("Fallback");
@@ -136,7 +138,7 @@ describe("DisambiguatedProfileViewModel", () => {
member: null, member: null,
fallbackName: "Fallback", fallbackName: "Fallback",
}); });
const subscriber = jest.fn(); const subscriber = vi.fn();
vm.subscribe(subscriber); vm.subscribe(subscriber);
vm.setMember("Fallback", member); vm.setMember("Fallback", member);
@@ -150,7 +152,7 @@ describe("DisambiguatedProfileViewModel", () => {
member, member,
fallbackName: "Fallback", fallbackName: "Fallback",
}); });
const subscriber = jest.fn(); const subscriber = vi.fn();
vm.subscribe(subscriber); vm.subscribe(subscriber);
vm.setMember("Fallback", member); vm.setMember("Fallback", member);
@@ -5,12 +5,16 @@
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
// @vitest-environment happy-dom
import { vi, describe, it, expect, beforeEach } from "vitest";
import { type ChangeEvent, type KeyboardEvent as ReactKeyboardEvent } from "react"; import { type ChangeEvent, type KeyboardEvent as ReactKeyboardEvent } from "react";
import { waitFor } from "@testing-library/dom"; import { waitFor } from "@testing-library/dom";
import { type Playback, PlaybackState } from "../../../src/audio/Playback"; import { type Playback, PlaybackState } from "../../../../../audio/Playback";
import { AudioPlayerViewModel } from "../../../src/viewmodels/room/timeline/event-tile/body/AudioPlayerViewModel"; import { AudioPlayerViewModel } from "./AudioPlayerViewModel";
import { MockedPlayback } from "../../unit-tests/audio/MockedPlayback"; import { MockedPlayback } from "../../../../../../test/unit-tests/audio/MockedPlayback";
describe("AudioPlayerViewModel", () => { describe("AudioPlayerViewModel", () => {
let playback: Playback; let playback: Playback;
@@ -45,7 +49,7 @@ describe("AudioPlayerViewModel", () => {
}); });
it("should has error=true when playback.prepare fails", async () => { it("should has error=true when playback.prepare fails", async () => {
jest.spyOn(playback, "prepare").mockRejectedValue(new Error("Failed to prepare playback")); vi.spyOn(playback, "prepare").mockRejectedValue(new Error("Failed to prepare playback"));
const vm = new AudioPlayerViewModel({ playback, mediaName: "mediaName" }); const vm = new AudioPlayerViewModel({ playback, mediaName: "mediaName" });
await waitFor(() => expect(vm.getSnapshot().error).toBe(true)); await waitFor(() => expect(vm.getSnapshot().error).toBe(true));
}); });
@@ -68,7 +72,7 @@ describe("AudioPlayerViewModel", () => {
it("does not stop propagation for unhandled key down events", () => { it("does not stop propagation for unhandled key down events", () => {
const vm = new AudioPlayerViewModel({ playback, mediaName: "mediaName" }); const vm = new AudioPlayerViewModel({ playback, mediaName: "mediaName" });
const event = new KeyboardEvent("keydown", { key: "a" }); const event = new KeyboardEvent("keydown", { key: "a" });
const stopPropagationSpy = jest.spyOn(event, "stopPropagation"); const stopPropagationSpy = vi.spyOn(event, "stopPropagation");
vm.onKeyDown(event as unknown as ReactKeyboardEvent<HTMLDivElement>); vm.onKeyDown(event as unknown as ReactKeyboardEvent<HTMLDivElement>);
@@ -5,17 +5,22 @@
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
import { waitFor } from "jest-matrix-react"; // @vitest-environment happy-dom
import { vi, describe, it, expect, afterEach } from "vitest";
import { waitFor } from "test-utils-rtl";
import { type PanelImperativeHandle } from "@element-hq/web-shared-components"; import { type PanelImperativeHandle } from "@element-hq/web-shared-components";
import { ResizerViewModel } from "../../../src/viewmodels/structures/ResizerViewModel"; import { ResizerViewModel } from "./ResizerViewModel";
import SettingsStore from "../../../src/settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
import { SettingLevel } from "../../../src/settings/SettingLevel"; import { SettingLevel } from "../../settings/SettingLevel";
jest.mock("what-input"); vi.mock("what-input");
describe("LeftPanelResizerViewModel", () => { describe("LeftPanelResizerViewModel", () => {
afterEach(() => { afterEach(() => {
localStorage.clear();
SettingsStore.reset(); SettingsStore.reset();
}); });
@@ -72,8 +77,8 @@ describe("LeftPanelResizerViewModel", () => {
const vm = new ResizerViewModel(); const vm = new ResizerViewModel();
SettingsStore.setValue("RoomList.panelSize", null, SettingLevel.DEVICE, 34); SettingsStore.setValue("RoomList.panelSize", null, SettingLevel.DEVICE, 34);
const mockHandle = { const mockHandle = {
resize: jest.fn(), resize: vi.fn(),
isCollapsed: jest.fn().mockReturnValue(true), isCollapsed: vi.fn().mockReturnValue(true),
} as unknown as PanelImperativeHandle; } as unknown as PanelImperativeHandle;
vm.setPanelHandle(mockHandle); vm.setPanelHandle(mockHandle);
@@ -90,8 +95,8 @@ describe("LeftPanelResizerViewModel", () => {
const vm = new ResizerViewModel(); const vm = new ResizerViewModel();
SettingsStore.setValue("RoomList.panelSize", null, SettingLevel.DEVICE, 34); SettingsStore.setValue("RoomList.panelSize", null, SettingLevel.DEVICE, 34);
const mockHandle = { const mockHandle = {
resize: jest.fn(), resize: vi.fn(),
isCollapsed: jest.fn().mockReturnValue(true), isCollapsed: vi.fn().mockReturnValue(true),
} as unknown as PanelImperativeHandle; } as unknown as PanelImperativeHandle;
vm.setPanelHandle(mockHandle); vm.setPanelHandle(mockHandle);
// Simulate click // Simulate click
@@ -103,8 +108,8 @@ describe("LeftPanelResizerViewModel", () => {
it("to maximum size of the panel", () => { it("to maximum size of the panel", () => {
const vm = new ResizerViewModel(); const vm = new ResizerViewModel();
const mockHandle = { const mockHandle = {
resize: jest.fn(), resize: vi.fn(),
isCollapsed: jest.fn().mockReturnValue(true), isCollapsed: vi.fn().mockReturnValue(true),
} as unknown as PanelImperativeHandle; } as unknown as PanelImperativeHandle;
vm.setPanelHandle(mockHandle); vm.setPanelHandle(mockHandle);
// Simulate click // Simulate click
@@ -117,8 +122,8 @@ describe("LeftPanelResizerViewModel", () => {
it("should collapse panel on click when panel is expanded", () => { it("should collapse panel on click when panel is expanded", () => {
const vm = new ResizerViewModel(); const vm = new ResizerViewModel();
const mockHandle = { const mockHandle = {
collapse: jest.fn(), collapse: vi.fn(),
isCollapsed: jest.fn().mockReturnValue(false), isCollapsed: vi.fn().mockReturnValue(false),
} as unknown as PanelImperativeHandle; } as unknown as PanelImperativeHandle;
vm.setPanelHandle(mockHandle); vm.setPanelHandle(mockHandle);
@@ -129,7 +134,7 @@ describe("LeftPanelResizerViewModel", () => {
it("should resize to nearest whole number", () => { it("should resize to nearest whole number", () => {
const vm = new ResizerViewModel(); const vm = new ResizerViewModel();
const mockHandle = { const mockHandle = {
resize: jest.fn(), resize: vi.fn(),
} as unknown as PanelImperativeHandle; } as unknown as PanelImperativeHandle;
vm.setPanelHandle(mockHandle); vm.setPanelHandle(mockHandle);
@@ -9,6 +9,7 @@ import EventEmitter from "events";
import { SimpleObservable } from "matrix-widget-api"; import { SimpleObservable } from "matrix-widget-api";
import { PlaybackState } from "../../../src/audio/Playback"; import { PlaybackState } from "../../../src/audio/Playback";
import { vi } from "../../setup/adapter.ts";
/** /**
* A mocked playback implementation for testing purposes. * A mocked playback implementation for testing purposes.
@@ -51,8 +52,8 @@ export class MockedPlayback extends EventEmitter {
return this.waveformObservable; return this.waveformObservable;
} }
public prepare = jest.fn().mockResolvedValue(undefined); public prepare = vi.fn().mockResolvedValue(undefined);
public skipTo = jest.fn(); public skipTo = vi.fn();
public toggle = jest.fn(); public toggle = vi.fn();
public destroy = jest.fn().mockResolvedValue(undefined); public destroy = vi.fn().mockResolvedValue(undefined);
} }