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