Migrate batch of tests to vitest (#34111)

* Migrate batch of tests tro vitest

* Iterate

* Migrate another test

* Fix lockfile
This commit is contained in:
Michael Telatynski
2026-07-06 14:10:50 +00:00
committed by GitHub
parent d4f72dfa69
commit d5164acb50
18 changed files with 285 additions and 258 deletions
+1
View File
@@ -228,6 +228,7 @@
"typescript": "catalog:", "typescript": "catalog:",
"util": "^0.12.5", "util": "^0.12.5",
"vitest": "catalog:", "vitest": "catalog:",
"vitest-canvas-mock": "^1.1.4",
"web-streams-polyfill": "^4.0.0", "web-streams-polyfill": "^4.0.0",
"webpack": "^5.89.0", "webpack": "^5.89.0",
"webpack-bundle-analyzer": "^5.0.0", "webpack-bundle-analyzer": "^5.0.0",
@@ -6,15 +6,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 { mocked } from "jest-mock"; import { vi, describe, it, expect, beforeEach, type Mock } from "vitest";
import { type Room, type RoomMember, RoomType } from "matrix-js-sdk/src/matrix"; import { type Room, type RoomMember, RoomType } from "matrix-js-sdk/src/matrix";
import { avatarUrlForRoom } from "../../src/Avatar"; import { avatarUrlForRoom } from "./Avatar";
import { type Media, mediaFromMxc } from "../../src/customisations/Media"; import { type Media, mediaFromMxc } from "./customisations/Media";
import DMRoomMap from "../../src/utils/DMRoomMap"; import DMRoomMap from "./utils/DMRoomMap";
jest.mock("../../src/customisations/Media", () => ({ vi.mock("./customisations/Media", () => ({
mediaFromMxc: jest.fn(), mediaFromMxc: vi.fn(),
})); }));
const roomId = "!room:example.com"; const roomId = "!room:example.com";
@@ -22,31 +22,31 @@ const avatarUrl1 = "https://example.com/avatar1";
const avatarUrl2 = "https://example.com/avatar2"; const avatarUrl2 = "https://example.com/avatar2";
describe("avatarUrlForRoom", () => { describe("avatarUrlForRoom", () => {
let getThumbnailOfSourceHttp: jest.Mock; let getThumbnailOfSourceHttp: Mock;
let room: Room; let room: Room;
let roomMember: RoomMember; let roomMember: RoomMember;
let dmRoomMap: DMRoomMap; let dmRoomMap: DMRoomMap;
beforeEach(() => { beforeEach(() => {
getThumbnailOfSourceHttp = jest.fn(); getThumbnailOfSourceHttp = vi.fn();
mocked(mediaFromMxc).mockImplementation((): Media => { vi.mocked(mediaFromMxc).mockImplementation((): Media => {
return { return {
getThumbnailOfSourceHttp, getThumbnailOfSourceHttp,
} as unknown as Media; } as unknown as Media;
}); });
room = { room = {
roomId, roomId,
getMxcAvatarUrl: jest.fn(), getMxcAvatarUrl: vi.fn(),
isSpaceRoom: jest.fn(), isSpaceRoom: vi.fn(),
getType: jest.fn(), getType: vi.fn(),
getAvatarFallbackMember: jest.fn(), getAvatarFallbackMember: vi.fn(),
} as unknown as Room; } as unknown as Room;
dmRoomMap = { dmRoomMap = {
getUserIdForRoomId: jest.fn(), getUserIdForRoomId: vi.fn(),
} as unknown as DMRoomMap; } as unknown as DMRoomMap;
DMRoomMap.setShared(dmRoomMap); DMRoomMap.setShared(dmRoomMap);
roomMember = { roomMember = {
getMxcAvatarUrl: jest.fn(), getMxcAvatarUrl: vi.fn(),
} as unknown as RoomMember; } as unknown as RoomMember;
}); });
@@ -55,40 +55,40 @@ describe("avatarUrlForRoom", () => {
}); });
it("should return the HTTP source if the room provides a MXC url", () => { it("should return the HTTP source if the room provides a MXC url", () => {
mocked(room.getMxcAvatarUrl).mockReturnValue(avatarUrl1); vi.mocked(room.getMxcAvatarUrl).mockReturnValue(avatarUrl1);
getThumbnailOfSourceHttp.mockReturnValue(avatarUrl2); getThumbnailOfSourceHttp.mockReturnValue(avatarUrl2);
expect(avatarUrlForRoom(room, 128, 256, "crop")).toEqual(avatarUrl2); expect(avatarUrlForRoom(room, 128, 256, "crop")).toEqual(avatarUrl2);
expect(getThumbnailOfSourceHttp).toHaveBeenCalledWith(128, 256, "crop"); expect(getThumbnailOfSourceHttp).toHaveBeenCalledWith(128, 256, "crop");
}); });
it("should return null for a space room", () => { it("should return null for a space room", () => {
mocked(room.isSpaceRoom).mockReturnValue(true); vi.mocked(room.isSpaceRoom).mockReturnValue(true);
mocked(room.getType).mockReturnValue(RoomType.Space); vi.mocked(room.getType).mockReturnValue(RoomType.Space);
expect(avatarUrlForRoom(room, 128, 128)).toBeNull(); expect(avatarUrlForRoom(room, 128, 128)).toBeNull();
}); });
it("should return null if the room is not a DM", () => { it("should return null if the room is not a DM", () => {
mocked(dmRoomMap).getUserIdForRoomId.mockReturnValue(undefined); vi.mocked(dmRoomMap).getUserIdForRoomId.mockReturnValue(undefined);
expect(avatarUrlForRoom(room, 128, 128)).toBeNull(); expect(avatarUrlForRoom(room, 128, 128)).toBeNull();
expect(dmRoomMap.getUserIdForRoomId).toHaveBeenCalledWith(roomId); expect(dmRoomMap.getUserIdForRoomId).toHaveBeenCalledWith(roomId);
}); });
it("should return null if there is no other member in the room", () => { it("should return null if there is no other member in the room", () => {
mocked(dmRoomMap).getUserIdForRoomId.mockReturnValue("@user:example.com"); vi.mocked(dmRoomMap).getUserIdForRoomId.mockReturnValue("@user:example.com");
mocked(room.getAvatarFallbackMember).mockReturnValue(undefined); vi.mocked(room.getAvatarFallbackMember).mockReturnValue(undefined);
expect(avatarUrlForRoom(room, 128, 128)).toBeNull(); expect(avatarUrlForRoom(room, 128, 128)).toBeNull();
}); });
it("should return null if the other member has no avatar URL", () => { it("should return null if the other member has no avatar URL", () => {
mocked(dmRoomMap).getUserIdForRoomId.mockReturnValue("@user:example.com"); vi.mocked(dmRoomMap).getUserIdForRoomId.mockReturnValue("@user:example.com");
mocked(room.getAvatarFallbackMember).mockReturnValue(roomMember); vi.mocked(room.getAvatarFallbackMember).mockReturnValue(roomMember);
expect(avatarUrlForRoom(room, 128, 128)).toBeNull(); expect(avatarUrlForRoom(room, 128, 128)).toBeNull();
}); });
it("should return the other member's avatar URL", () => { it("should return the other member's avatar URL", () => {
mocked(dmRoomMap).getUserIdForRoomId.mockReturnValue("@user:example.com"); vi.mocked(dmRoomMap).getUserIdForRoomId.mockReturnValue("@user:example.com");
mocked(room.getAvatarFallbackMember).mockReturnValue(roomMember); vi.mocked(room.getAvatarFallbackMember).mockReturnValue(roomMember);
mocked(roomMember.getMxcAvatarUrl).mockReturnValue(avatarUrl2); vi.mocked(roomMember.getMxcAvatarUrl).mockReturnValue(avatarUrl2);
getThumbnailOfSourceHttp.mockReturnValue(avatarUrl2); getThumbnailOfSourceHttp.mockReturnValue(avatarUrl2);
expect(avatarUrlForRoom(room, 128, 256, "crop")).toEqual(avatarUrl2); expect(avatarUrlForRoom(room, 128, 256, "crop")).toEqual(avatarUrl2);
expect(getThumbnailOfSourceHttp).toHaveBeenCalledWith(128, 256, "crop"); expect(getThumbnailOfSourceHttp).toHaveBeenCalledWith(128, 256, "crop");
@@ -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 { isKeyComboMatch, type KeyCombo } from "../../src/KeyBindingsManager"; import { describe, it, expect } from "vitest";
import { isKeyComboMatch, type KeyCombo } from "./KeyBindingsManager";
function mockKeyEvent( function mockKeyEvent(
key: string, key: string,
@@ -6,37 +6,34 @@ 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 { Crypto } from "@peculiar/webcrypto"; // @vitest-environment happy-dom
import { vi, describe, it, expect, beforeEach, afterEach, type MockedObject } from "vitest";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import * as MatrixJs from "matrix-js-sdk/src/matrix"; import * as MatrixJs from "matrix-js-sdk/src/matrix";
import { decodeBase64, encodeUnpaddedBase64 } from "matrix-js-sdk/src/matrix"; import { decodeBase64, encodeUnpaddedBase64 } from "matrix-js-sdk/src/matrix";
import * as encryptAESSecretStorageItemModule from "matrix-js-sdk/src/utils/encryptAESSecretStorageItem"; import * as encryptAESSecretStorageItemModule from "matrix-js-sdk/src/utils/encryptAESSecretStorageItem";
import { mocked, type MockedObject } from "jest-mock-vitest-adapter"; import fetchMock from "@fetch-mock/vitest";
import fetchMock from "@fetch-mock/jest"; import { flushPromises, getMockClientWithEventEmitter, mockClientMethodsUser, mockPlatformPeg } from "test-utils";
import { makeDelegatedAuthConfig } from "test-utils/oidc";
import StorageEvictedDialog from "../../src/components/views/dialogs/StorageEvictedDialog"; import StorageEvictedDialog from "./components/views/dialogs/StorageEvictedDialog";
import * as Lifecycle from "../../src/Lifecycle"; import * as Lifecycle from "./Lifecycle";
import { MatrixClientPeg } from "../../src/MatrixClientPeg"; import { MatrixClientPeg } from "./MatrixClientPeg";
import Modal from "../../src/Modal"; import Modal from "./Modal";
import * as StorageAccess from "../../src/utils/StorageAccess"; import * as StorageAccess from "./utils/StorageAccess";
import { idbSave } from "../../src/utils/StorageAccess"; import { idbSave } from "./utils/StorageAccess";
import { flushPromises, getMockClientWithEventEmitter, mockClientMethodsUser, mockPlatformPeg } from "../test-utils"; import { OidcClientStore } from "./stores/oidc/OidcClientStore";
import { OidcClientStore } from "../../src/stores/oidc/OidcClientStore"; import { Action } from "./dispatcher/actions";
import { makeDelegatedAuthConfig } from "../test-utils/oidc"; import PlatformPeg from "./PlatformPeg";
import { Action } from "../../src/dispatcher/actions"; import { persistAccessTokenInStorage, persistRefreshTokenInStorage } from "./utils/tokens/tokens";
import PlatformPeg from "../../src/PlatformPeg"; import { encryptPickleKey } from "./utils/tokens/pickling";
import { persistAccessTokenInStorage, persistRefreshTokenInStorage } from "../../src/utils/tokens/tokens"; import * as StorageManager from "./utils/StorageManager.ts";
import { encryptPickleKey } from "../../src/utils/tokens/pickling"; import type BasePlatform from "./BasePlatform.ts";
import * as StorageManager from "../../src/utils/StorageManager.ts"; import * as createMatrixClientModule from "./utils/createMatrixClient";
import type BasePlatform from "../../src/BasePlatform.ts";
import * as createMatrixClientModule from "../../src/utils/createMatrixClient";
const { logout, restoreSessionFromStorage, setLoggedIn } = Lifecycle; const { logout, restoreSessionFromStorage, setLoggedIn } = Lifecycle;
const webCrypto = new Crypto();
const windowCrypto = window.crypto;
describe("Lifecycle", () => { describe("Lifecycle", () => {
const homeserverUrl = "https://domain"; const homeserverUrl = "https://domain";
const identityServerUrl = "https://is.org"; const identityServerUrl = "https://is.org";
@@ -46,65 +43,55 @@ describe("Lifecycle", () => {
let mockPlatform: MockedObject<BasePlatform>; let mockPlatform: MockedObject<BasePlatform>;
const realLocalStorage = global.localStorage;
let mockClient!: MockedObject<MatrixJs.MatrixClient>; let mockClient!: MockedObject<MatrixJs.MatrixClient>;
beforeEach(() => { beforeEach(() => {
jest.restoreAllMocks();
mockPlatform = mockPlatformPeg(); mockPlatform = mockPlatformPeg();
mockClient = getMockClientWithEventEmitter({ mockClient = getMockClientWithEventEmitter({
...mockClientMethodsUser(), ...mockClientMethodsUser(),
stopClient: jest.fn(), stopClient: vi.fn(),
removeAllListeners: jest.fn(), removeAllListeners: vi.fn(),
clearStores: jest.fn(), clearStores: vi.fn(),
getAccountData: jest.fn(), getAccountData: vi.fn(),
getDeviceId: jest.fn().mockReturnValue(deviceId), getDeviceId: vi.fn().mockReturnValue(deviceId),
isVersionSupported: jest.fn().mockResolvedValue(true), isVersionSupported: vi.fn().mockResolvedValue(true),
getCrypto: jest.fn(), getCrypto: vi.fn(),
getClientWellKnown: jest.fn(), getClientWellKnown: vi.fn(),
waitForClientWellKnown: jest.fn(), waitForClientWellKnown: vi.fn(),
getThirdpartyProtocols: jest.fn(), getThirdpartyProtocols: vi.fn(),
store: { store: {
destroy: jest.fn(), destroy: vi.fn(),
}, },
getVersions: jest.fn().mockResolvedValue({ versions: ["v1.1"] }), getVersions: vi.fn().mockResolvedValue({ versions: ["v1.1"] }),
logout: jest.fn().mockResolvedValue(undefined), logout: vi.fn().mockResolvedValue(undefined),
getAccessToken: jest.fn(), getAccessToken: vi.fn(),
getRefreshToken: jest.fn(), getRefreshToken: vi.fn(),
setGuest: jest.fn(), isInitialSyncComplete: vi.fn(),
setNotifTimelineSet: jest.fn(), setGuest: vi.fn(),
setNotifTimelineSet: vi.fn(),
}); });
// stub this // stub this
jest.spyOn(MatrixClientPeg, "set").mockImplementation(() => {}); vi.spyOn(MatrixClientPeg, "set").mockImplementation(() => {});
jest.spyOn(MatrixClientPeg, "start").mockResolvedValue(undefined); vi.spyOn(MatrixClientPeg, "start").mockResolvedValue(undefined);
// reset any mocking vi.spyOn(encryptAESSecretStorageItemModule, "default").mockRestore();
// @ts-ignore mocking
delete global.localStorage;
global.localStorage = realLocalStorage;
// @ts-ignore mocking localStorage.clear();
delete window.crypto; sessionStorage.clear();
window.crypto = webCrypto;
jest.spyOn(encryptAESSecretStorageItemModule, "default").mockRestore();
}); });
afterAll(() => { afterEach(() => {
// @ts-ignore unmocking vi.resetAllMocks();
delete window.crypto;
window.crypto = windowCrypto;
}); });
const initIdbMock = (mockStore: Record<string, Record<string, unknown>> = {}): void => { const initIdbMock = (mockStore: Record<string, Record<string, unknown>> = {}): void => {
jest.spyOn(StorageAccess, "idbLoad") vi.spyOn(StorageAccess, "idbLoad")
.mockClear() .mockClear()
.mockImplementation( .mockImplementation(
// @ts-ignore mock type // @ts-ignore mock type
async (table: string, key: string) => mockStore[table]?.[key] ?? null, async (table: string, key: string) => mockStore[table]?.[key] ?? null,
); );
jest.spyOn(StorageAccess, "idbSave") vi.spyOn(StorageAccess, "idbSave")
.mockClear() .mockClear()
.mockImplementation( .mockImplementation(
// @ts-ignore mock type // @ts-ignore mock type
@@ -114,13 +101,13 @@ describe("Lifecycle", () => {
mockStore[tableKey] = table; mockStore[tableKey] = table;
}, },
); );
jest.spyOn(StorageAccess, "idbDelete") vi.spyOn(StorageAccess, "idbDelete")
.mockClear() .mockClear()
.mockImplementation(async (tableKey: string, key: string | string[]) => { .mockImplementation(async (tableKey: string, key: string | string[]) => {
const table = mockStore[tableKey]; const table = mockStore[tableKey];
delete table?.[key as string]; delete table?.[key as string];
}); });
jest.spyOn(StorageAccess, "idbClear") vi.spyOn(StorageAccess, "idbClear")
.mockClear() .mockClear()
.mockImplementation(async (tableKey: string) => { .mockImplementation(async (tableKey: string) => {
mockStore[tableKey] = {}; mockStore[tableKey] = {};
@@ -159,14 +146,14 @@ describe("Lifecycle", () => {
describe("loadSession", () => { describe("loadSession", () => {
beforeEach(() => { beforeEach(() => {
// stub this out // stub this out
jest.spyOn(Modal, "createDialog").mockReturnValue( vi.spyOn(Modal, "createDialog").mockReturnValue(
// @ts-ignore allow bad mock // @ts-ignore allow bad mock
{ finished: Promise.resolve([true]) }, { finished: Promise.resolve([true]) },
); );
}); });
it("should not show any error dialog when checkConsistency throws but abortSignal has triggered", async () => { it("should not show any error dialog when checkConsistency throws but abortSignal has triggered", async () => {
jest.spyOn(StorageManager, "checkConsistency").mockRejectedValue(new Error("test error")); vi.spyOn(StorageManager, "checkConsistency").mockRejectedValue(new Error("test error"));
const abortController = new AbortController(); const abortController = new AbortController();
const prom = Lifecycle.loadSession({ const prom = Lifecycle.loadSession({
@@ -183,27 +170,29 @@ describe("Lifecycle", () => {
}); });
describe("restoreSessionFromStorage()", () => { describe("restoreSessionFromStorage()", () => {
const realLocalStorage = localStorage;
beforeEach(() => { beforeEach(() => {
initIdbMock(); initIdbMock();
jest.clearAllMocks(); vi.spyOn(logger, "log").mockClear();
jest.spyOn(logger, "log").mockClear();
jest.spyOn(MatrixJs, "createClient").mockReturnValue(mockClient); vi.spyOn(MatrixJs, "createClient").mockReturnValue(mockClient);
jest.spyOn(createMatrixClientModule, "createClientWithCreds").mockReturnValue(mockClient); vi.spyOn(createMatrixClientModule, "createClientWithCreds").mockReturnValue(mockClient);
// stub this out // stub this out
jest.spyOn(Modal, "createDialog").mockReturnValue( vi.spyOn(Modal, "createDialog").mockReturnValue(
// @ts-ignore allow bad mock // @ts-ignore allow bad mock
{ finished: Promise.resolve([true]) }, { finished: Promise.resolve([true]) },
); );
}); });
afterEach(() => {
vi.stubGlobal("localStorage", realLocalStorage);
});
it("should return false when localStorage is not available", async () => { it("should return false when localStorage is not available", async () => {
// @ts-ignore dirty mocking vi.stubGlobal("localStorage", undefined);
delete global.localStorage;
// @ts-ignore dirty mocking
global.localStorage = undefined;
expect(await restoreSessionFromStorage()).toEqual(false); expect(await restoreSessionFromStorage()).toEqual(false);
}); });
@@ -272,7 +261,7 @@ describe("Lifecycle", () => {
}); });
it("should persist access token when idb is not available", async () => { it("should persist access token when idb is not available", async () => {
jest.spyOn(StorageAccess, "idbSave").mockRejectedValue("oups"); vi.spyOn(StorageAccess, "idbSave").mockRejectedValue("oups");
expect(await restoreSessionFromStorage()).toEqual(true); expect(await restoreSessionFromStorage()).toEqual(true);
expect(StorageAccess.idbSave).toHaveBeenCalledWith("account", "mx_access_token", accessToken); expect(StorageAccess.idbSave).toHaveBeenCalledWith("account", "mx_access_token", accessToken);
@@ -384,7 +373,7 @@ describe("Lifecycle", () => {
it("should persist access token when idb is not available", async () => { it("should persist access token when idb is not available", async () => {
// dont fail for pickle key persist // dont fail for pickle key persist
jest.spyOn(StorageAccess, "idbSave").mockImplementation( vi.spyOn(StorageAccess, "idbSave").mockImplementation(
async (table: string, key: string | string[]) => { async (table: string, key: string | string[]) => {
if (table === "account" && key === "mx_access_token") { if (table === "account" && key === "mx_access_token") {
throw new Error("oups"); throw new Error("oups");
@@ -406,7 +395,7 @@ describe("Lifecycle", () => {
it("should create and start new matrix client with credentials", async () => { it("should create and start new matrix client with credentials", async () => {
// Check that the rust crypto key is as expected. We have to do this during the call, as // Check that the rust crypto key is as expected. We have to do this during the call, as
// the buffer is cleared afterwards. // the buffer is cleared afterwards.
mocked(MatrixClientPeg.start).mockImplementation(async (opts) => { vi.mocked(MatrixClientPeg.start).mockImplementation(async (opts) => {
expect(opts?.rustCryptoStoreKey).toEqual(decodeBase64(pickleKey)); expect(opts?.rustCryptoStoreKey).toEqual(decodeBase64(pickleKey));
}); });
@@ -559,14 +548,14 @@ describe("Lifecycle", () => {
beforeEach(() => { beforeEach(() => {
initIdbMock(); initIdbMock();
jest.clearAllMocks(); vi.clearAllMocks();
jest.spyOn(logger, "log").mockClear(); vi.spyOn(logger, "log").mockClear();
jest.spyOn(MatrixJs, "createClient").mockReturnValue(mockClient); vi.spyOn(MatrixJs, "createClient").mockReturnValue(mockClient);
// remove any mock implementations // remove any mock implementations
jest.spyOn(mockPlatform, "createPickleKey").mockRestore(); vi.spyOn(mockPlatform, "createPickleKey").mockRestore();
// but still spy and call through // but still spy and call through
jest.spyOn(mockPlatform, "createPickleKey"); vi.spyOn(mockPlatform, "createPickleKey");
}); });
const refreshToken = "test-refresh-token"; const refreshToken = "test-refresh-token";
@@ -617,8 +606,8 @@ describe("Lifecycle", () => {
describe("without a pickle key", () => { describe("without a pickle key", () => {
beforeEach(() => { beforeEach(() => {
jest.spyOn(mockPlatform, "createPickleKey").mockResolvedValue(null); vi.spyOn(mockPlatform, "createPickleKey").mockResolvedValue(null);
jest.spyOn(createMatrixClientModule, "createClientWithCreds").mockReturnValue(mockClient); vi.spyOn(createMatrixClientModule, "createClientWithCreds").mockReturnValue(mockClient);
}); });
it("should persist credentials", async () => { it("should persist credentials", async () => {
@@ -650,7 +639,7 @@ describe("Lifecycle", () => {
}); });
it("should remove any access token from storage when there is none in credentials and idb save fails", async () => { it("should remove any access token from storage when there is none in credentials and idb save fails", async () => {
jest.spyOn(StorageAccess, "idbSave").mockRejectedValue("oups"); vi.spyOn(StorageAccess, "idbSave").mockRejectedValue("oups");
await setLoggedIn({ await setLoggedIn({
...credentials, ...credentials,
// @ts-ignore // @ts-ignore
@@ -726,7 +715,7 @@ describe("Lifecycle", () => {
}); });
it("should persist token when encrypting the token fails", async () => { it("should persist token when encrypting the token fails", async () => {
jest.spyOn(encryptAESSecretStorageItemModule, "default").mockRejectedValue("MOCK REJECT ENCRYPTAES"); vi.spyOn(encryptAESSecretStorageItemModule, "default").mockRejectedValue("MOCK REJECT ENCRYPTAES");
await setLoggedIn(credentials); await setLoggedIn(credentials);
// persist the unencrypted token // persist the unencrypted token
@@ -735,13 +724,11 @@ describe("Lifecycle", () => {
it("should persist token in localStorage when idb fails to save token", async () => { it("should persist token in localStorage when idb fails to save token", async () => {
// dont fail for pickle key persist // dont fail for pickle key persist
jest.spyOn(StorageAccess, "idbSave").mockImplementation( vi.spyOn(StorageAccess, "idbSave").mockImplementation(async (table: string, key: string | string[]) => {
async (table: string, key: string | string[]) => { if (table === "account" && key === "mx_access_token") {
if (table === "account" && key === "mx_access_token") { throw new Error("oups");
throw new Error("oups"); }
} });
},
);
await setLoggedIn(credentials); await setLoggedIn(credentials);
// put plain accessToken in localstorage when we dont have idb // put plain accessToken in localstorage when we dont have idb
@@ -750,13 +737,11 @@ describe("Lifecycle", () => {
it("should remove any access token from storage when there is none in credentials and idb save fails", async () => { it("should remove any access token from storage when there is none in credentials and idb save fails", async () => {
// dont fail for pickle key persist // dont fail for pickle key persist
jest.spyOn(StorageAccess, "idbSave").mockImplementation( vi.spyOn(StorageAccess, "idbSave").mockImplementation(async (table: string, key: string | string[]) => {
async (table: string, key: string | string[]) => { if (table === "account" && key === "mx_access_token") {
if (table === "account" && key === "mx_access_token") { throw new Error("oups");
throw new Error("oups"); }
} });
},
);
await setLoggedIn({ await setLoggedIn({
...credentials, ...credentials,
// @ts-ignore // @ts-ignore
@@ -768,7 +753,7 @@ describe("Lifecycle", () => {
}); });
it("should create new matrix client with credentials", async () => { it("should create new matrix client with credentials", async () => {
jest.spyOn(createMatrixClientModule, "createClientWithCreds").mockReturnValue(mockClient); vi.spyOn(createMatrixClientModule, "createClientWithCreds").mockReturnValue(mockClient);
expect(await setLoggedIn(credentials)).toEqual(mockClient); expect(await setLoggedIn(credentials)).toEqual(mockClient);
expect(createMatrixClientModule.createClientWithCreds).toHaveBeenCalledWith( expect(createMatrixClientModule.createClientWithCreds).toHaveBeenCalledWith(
@@ -903,7 +888,7 @@ describe("Lifecycle", () => {
beforeEach(() => { beforeEach(() => {
oidcClientStore = new OidcClientStore(mockClient); oidcClientStore = new OidcClientStore(mockClient);
// stub // stub
jest.spyOn(oidcClientStore, "revokeTokens").mockResolvedValue(undefined); vi.spyOn(oidcClientStore, "revokeTokens").mockResolvedValue(undefined);
mockClient.getAccessToken.mockReturnValue(accessToken); mockClient.getAccessToken.mockReturnValue(accessToken);
mockClient.getRefreshToken.mockReturnValue(refreshToken); mockClient.getRefreshToken.mockReturnValue(refreshToken);
@@ -918,7 +903,7 @@ describe("Lifecycle", () => {
}); });
it("should call logout on the client when oidcClientStore.isUserAuthenticatedWithOidc is falsy", async () => { it("should call logout on the client when oidcClientStore.isUserAuthenticatedWithOidc is falsy", async () => {
jest.spyOn(oidcClientStore, "isUserAuthenticatedWithOidc", "get").mockReturnValue(false); vi.spyOn(oidcClientStore, "isUserAuthenticatedWithOidc", "get").mockReturnValue(false);
logout(oidcClientStore); logout(oidcClientStore);
await flushPromises(); await flushPromises();
@@ -928,7 +913,7 @@ describe("Lifecycle", () => {
}); });
it("should revoke tokens when user is authenticated with oidc", async () => { it("should revoke tokens when user is authenticated with oidc", async () => {
jest.spyOn(oidcClientStore, "isUserAuthenticatedWithOidc", "get").mockReturnValue(true); vi.spyOn(oidcClientStore, "isUserAuthenticatedWithOidc", "get").mockReturnValue(true);
logout(oidcClientStore); logout(oidcClientStore);
await flushPromises(); await flushPromises();
@@ -940,12 +925,12 @@ describe("Lifecycle", () => {
describe("overwritelogin", () => { describe("overwritelogin", () => {
beforeEach(async () => { beforeEach(async () => {
jest.spyOn(MatrixJs, "createClient").mockReturnValue(mockClient); vi.spyOn(MatrixJs, "createClient").mockReturnValue(mockClient);
}); });
it("should replace the current login with a new one", async () => { it("should replace the current login with a new one", async () => {
const stopSpy = jest.spyOn(mockClient, "stopClient").mockReturnValue(undefined); const stopSpy = vi.spyOn(mockClient, "stopClient").mockReturnValue(undefined);
jest.spyOn(createMatrixClientModule, "createClientWithCreds").mockReturnValue(mockClient); vi.spyOn(createMatrixClientModule, "createClientWithCreds").mockReturnValue(mockClient);
const dis = window.mxDispatcher; const dis = window.mxDispatcher;
const firstLoginEvent: Promise<void> = new Promise((resolve) => { const firstLoginEvent: Promise<void> = new Promise((resolve) => {
@@ -963,7 +948,7 @@ describe("Lifecycle", () => {
expect(stopSpy).toHaveBeenCalledTimes(1); expect(stopSpy).toHaveBeenCalledTimes(1);
// important the overwrite action should not call unset before replacing. // important the overwrite action should not call unset before replacing.
// So spy on it and make sure it's not called. // So spy on it and make sure it's not called.
jest.spyOn(MatrixClientPeg, "unset").mockReturnValue(undefined); vi.spyOn(MatrixClientPeg, "unset").mockReturnValue(undefined);
expect(createMatrixClientModule.createClientWithCreds).toHaveBeenCalledWith( expect(createMatrixClientModule.createClientWithCreds).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
@@ -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 Markdown from "../../src/Markdown"; import { describe, it, expect } from "vitest";
import Markdown from "./Markdown";
describe("Markdown parser test", () => { describe("Markdown parser test", () => {
describe("fixing HTML links", () => { describe("fixing HTML links", () => {
@@ -7,41 +7,38 @@ 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 } from "jest-mock"; // @vitest-environment happy-dom
import { vi, describe, it, expect, beforeEach, afterEach } from "vitest";
import { type PostHog } from "posthog-js"; import { type PostHog } from "posthog-js";
import { type MatrixClient } from "matrix-js-sdk/src/matrix"; import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { type CryptoApi } from "matrix-js-sdk/src/crypto-api"; import { type CryptoApi } from "matrix-js-sdk/src/crypto-api";
import { getMockClientWithEventEmitter } from "test-utils";
import { import { Anonymity, getRedactedCurrentLocation, type IPosthogEvent, PosthogAnalytics } from "./PosthogAnalytics";
Anonymity, import SdkConfig from "./SdkConfig";
getRedactedCurrentLocation, import SettingsStore from "./settings/SettingsStore";
type IPosthogEvent, import { Layout } from "./settings/enums/Layout";
PosthogAnalytics, import defaultDispatcher from "./dispatcher/dispatcher";
} from "../../src/PosthogAnalytics"; import { Action } from "./dispatcher/actions";
import SdkConfig from "../../src/SdkConfig"; import { SettingLevel } from "./settings/SettingLevel";
import { getMockClientWithEventEmitter } from "../test-utils";
import SettingsStore from "../../src/settings/SettingsStore";
import { Layout } from "../../src/settings/enums/Layout";
import defaultDispatcher from "../../src/dispatcher/dispatcher";
import { Action } from "../../src/dispatcher/actions";
import { SettingLevel } from "../../src/settings/SettingLevel";
const getFakePosthog = (): PostHog => const getFakePosthog = (): PostHog =>
({ ({
capture: jest.fn(), capture: vi.fn(),
init: jest.fn(), init: vi.fn(),
identify: jest.fn(), identify: vi.fn(),
reset: jest.fn(), reset: vi.fn(),
register: jest.fn(), register: vi.fn(),
get_distinct_id: jest.fn(), get_distinct_id: vi.fn(),
persistence: { persistence: {
get_property: jest.fn(), get_property: vi.fn(),
}, },
identifyUser: jest.fn(), identifyUser: vi.fn(),
}) as unknown as PostHog; }) as unknown as PostHog;
interface ITestEvent extends IPosthogEvent { interface ITestEvent extends IPosthogEvent {
eventName: "JestTestEvents"; eventName: "TestEvents";
foo?: string; foo?: string;
} }
@@ -121,17 +118,17 @@ describe("PosthogAnalytics", () => {
it("Should pass event to posthog", () => { it("Should pass event to posthog", () => {
analytics.setAnonymity(Anonymity.Pseudonymous); analytics.setAnonymity(Anonymity.Pseudonymous);
analytics.trackEvent<ITestEvent>({ analytics.trackEvent<ITestEvent>({
eventName: "JestTestEvents", eventName: "TestEvents",
foo: "bar", foo: "bar",
}); });
expect(mocked(fakePosthog).capture.mock.calls[0][0]).toBe("JestTestEvents"); expect(vi.mocked(fakePosthog).capture.mock.calls[0][0]).toBe("TestEvents");
expect(mocked(fakePosthog).capture.mock.calls[0][1]!["foo"]).toEqual("bar"); expect(vi.mocked(fakePosthog).capture.mock.calls[0][1]!["foo"]).toEqual("bar");
}); });
it("Should not track events if anonymous", async () => { it("Should not track events if anonymous", async () => {
analytics.setAnonymity(Anonymity.Anonymous); analytics.setAnonymity(Anonymity.Anonymous);
await analytics.trackEvent<ITestEvent>({ await analytics.trackEvent<ITestEvent>({
eventName: "JestTestEvents", eventName: "TestEvents",
foo: "bar", foo: "bar",
}); });
expect(fakePosthog.capture).not.toHaveBeenCalled(); expect(fakePosthog.capture).not.toHaveBeenCalled();
@@ -140,7 +137,7 @@ describe("PosthogAnalytics", () => {
it("Should not track any events if disabled", async () => { it("Should not track any events if disabled", async () => {
analytics.setAnonymity(Anonymity.Disabled); analytics.setAnonymity(Anonymity.Disabled);
analytics.trackEvent<ITestEvent>({ analytics.trackEvent<ITestEvent>({
eventName: "JestTestEvents", eventName: "TestEvents",
foo: "bar", foo: "bar",
}); });
expect(fakePosthog.capture).not.toHaveBeenCalled(); expect(fakePosthog.capture).not.toHaveBeenCalled();
@@ -164,29 +161,29 @@ describe("PosthogAnalytics", () => {
it("Should identify the user to posthog if pseudonymous", async () => { it("Should identify the user to posthog if pseudonymous", async () => {
analytics.setAnonymity(Anonymity.Pseudonymous); analytics.setAnonymity(Anonymity.Pseudonymous);
const client = getMockClientWithEventEmitter({ const client = getMockClientWithEventEmitter({
getAccountDataFromServer: jest.fn().mockResolvedValue(null), getAccountDataFromServer: vi.fn().mockResolvedValue(null),
setAccountData: jest.fn().mockResolvedValue({}), setAccountData: vi.fn().mockResolvedValue({}),
}); });
await analytics.identifyUser(client, () => "analytics_id"); await analytics.identifyUser(client, () => "analytics_id");
expect(mocked(fakePosthog).identify.mock.calls[0][0]).toBe("analytics_id"); expect(vi.mocked(fakePosthog).identify.mock.calls[0][0]).toBe("analytics_id");
}); });
it("Should not identify the user to posthog if anonymous", async () => { it("Should not identify the user to posthog if anonymous", async () => {
analytics.setAnonymity(Anonymity.Anonymous); analytics.setAnonymity(Anonymity.Anonymous);
const client = getMockClientWithEventEmitter({}); const client = getMockClientWithEventEmitter({});
await analytics.identifyUser(client, () => "analytics_id"); await analytics.identifyUser(client, () => "analytics_id");
expect(mocked(fakePosthog).identify.mock.calls.length).toBe(0); expect(vi.mocked(fakePosthog).identify.mock.calls.length).toBe(0);
}); });
it("Should identify using the server's analytics id if present", async () => { it("Should identify using the server's analytics id if present", async () => {
analytics.setAnonymity(Anonymity.Pseudonymous); analytics.setAnonymity(Anonymity.Pseudonymous);
const client = getMockClientWithEventEmitter({ const client = getMockClientWithEventEmitter({
getAccountDataFromServer: jest.fn().mockResolvedValue({ id: "existing_analytics_id" }), getAccountDataFromServer: vi.fn().mockResolvedValue({ id: "existing_analytics_id" }),
setAccountData: jest.fn().mockResolvedValue({}), setAccountData: vi.fn().mockResolvedValue({}),
}); });
await analytics.identifyUser(client, () => "new_analytics_id"); await analytics.identifyUser(client, () => "new_analytics_id");
expect(mocked(fakePosthog).identify.mock.calls[0][0]).toBe("existing_analytics_id"); expect(vi.mocked(fakePosthog).identify.mock.calls[0][0]).toBe("existing_analytics_id");
}); });
}); });
@@ -220,9 +217,9 @@ describe("PosthogAnalytics", () => {
true, true,
); );
analytics.trackEvent<ITestEvent>({ analytics.trackEvent<ITestEvent>({
eventName: "JestTestEvents", eventName: "TestEvents",
}); });
expect(mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toMatchObject({ expect(vi.mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toMatchObject({
WebLayout: "IRC", WebLayout: "IRC",
}); });
}); });
@@ -237,9 +234,9 @@ describe("PosthogAnalytics", () => {
true, true,
); );
analytics.trackEvent<ITestEvent>({ analytics.trackEvent<ITestEvent>({
eventName: "JestTestEvents", eventName: "TestEvents",
}); });
expect(mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toMatchObject({ expect(vi.mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toMatchObject({
WebLayout: "Bubble", WebLayout: "Bubble",
}); });
}); });
@@ -254,9 +251,9 @@ describe("PosthogAnalytics", () => {
true, true,
); );
analytics.trackEvent<ITestEvent>({ analytics.trackEvent<ITestEvent>({
eventName: "JestTestEvents", eventName: "TestEvents",
}); });
expect(mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toMatchObject({ expect(vi.mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toMatchObject({
WebLayout: "Group", WebLayout: "Group",
}); });
}); });
@@ -272,10 +269,10 @@ describe("PosthogAnalytics", () => {
true, true,
); );
analytics.trackEvent<ITestEvent>({ analytics.trackEvent<ITestEvent>({
eventName: "JestTestEvents", eventName: "TestEvents",
}); });
console.log(mocked(fakePosthog).capture.mock.calls[0]); console.log(vi.mocked(fakePosthog).capture.mock.calls[0]);
expect(mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toMatchObject({ expect(vi.mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toMatchObject({
WebLayout: "Compact", WebLayout: "Compact",
}); });
}); });
@@ -311,9 +308,9 @@ describe("PosthogAnalytics", () => {
true, true,
); );
analytics.trackEvent<ITestEvent>({ analytics.trackEvent<ITestEvent>({
eventName: "JestTestEvents", eventName: "TestEvents",
}); });
expect(mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toMatchObject({ expect(vi.mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toMatchObject({
URLPreviewsEnabled: true, URLPreviewsEnabled: true,
}); });
}); });
@@ -323,10 +320,10 @@ describe("PosthogAnalytics", () => {
let analytics: PosthogAnalytics; let analytics: PosthogAnalytics;
const getFakeClient = (): MatrixClient => const getFakeClient = (): MatrixClient =>
({ ({
getCrypto: jest.fn(), getCrypto: vi.fn(),
setAccountData: jest.fn(), setAccountData: vi.fn(),
// just fake return an `im.vector.analytics` content // just fake return an `im.vector.analytics` content
getAccountDataFromServer: jest.fn().mockReturnValue({ getAccountDataFromServer: vi.fn().mockReturnValue({
id: "0000000", id: "0000000",
pseudonymousAnalyticsOptIn: true, pseudonymousAnalyticsOptIn: true,
}), }),
@@ -350,7 +347,7 @@ describe("PosthogAnalytics", () => {
// To simulate a switch we call updateAnonymityFromSettings. // To simulate a switch we call updateAnonymityFromSettings.
// As per documentation this function is called On login. // As per documentation this function is called On login.
const mockClient = getFakeClient(); const mockClient = getFakeClient();
mocked(mockClient.getCrypto).mockReturnValue({ vi.mocked(mockClient.getCrypto).mockReturnValue({
getVersion: () => { getVersion: () => {
return rustBackend ? "Rust SDK 0.6.0 (9c6b550), Vodozemac 0.5.0" : "Olm 3.2.0"; return rustBackend ? "Rust SDK 0.6.0 (9c6b550), Vodozemac 0.5.0" : "Olm 3.2.0";
}, },
@@ -363,7 +360,7 @@ describe("PosthogAnalytics", () => {
await simulateLogin(false); await simulateLogin(false);
expect(mocked(fakePosthog).register.mock.lastCall![0]["cryptoSDK"]).toStrictEqual("Legacy"); expect(vi.mocked(fakePosthog).register.mock.lastCall![0]["cryptoSDK"]).toStrictEqual("Legacy");
}); });
it("should send Legacy cryptoSDK superProperty correctly", async () => { it("should send Legacy cryptoSDK superProperty correctly", async () => {
@@ -373,7 +370,7 @@ describe("PosthogAnalytics", () => {
// Super Properties are properties associated with events that are set once and then sent with every capture call. // Super Properties are properties associated with events that are set once and then sent with every capture call.
// They are set using posthog.register // They are set using posthog.register
expect(mocked(fakePosthog).register.mock.lastCall![0]["cryptoSDK"]).toStrictEqual("Legacy"); expect(vi.mocked(fakePosthog).register.mock.lastCall![0]["cryptoSDK"]).toStrictEqual("Legacy");
}); });
it("should send cryptoSDK superProperty when enabling analytics", async () => { it("should send cryptoSDK superProperty when enabling analytics", async () => {
@@ -383,13 +380,13 @@ describe("PosthogAnalytics", () => {
// This initial call is due to the call to register platformSuperProperties // This initial call is due to the call to register platformSuperProperties
// The important thing is that the cryptoSDK superProperty is not set. // The important thing is that the cryptoSDK superProperty is not set.
expect(mocked(fakePosthog).register.mock.lastCall![0]).toStrictEqual({}); expect(vi.mocked(fakePosthog).register.mock.lastCall![0]).toStrictEqual({});
// switching to pseudonymous should ensure that the cryptoSDK superProperty is set correctly // switching to pseudonymous should ensure that the cryptoSDK superProperty is set correctly
analytics.setAnonymity(Anonymity.Pseudonymous); analytics.setAnonymity(Anonymity.Pseudonymous);
// Super Properties are properties associated with events that are set once and then sent with every capture call. // Super Properties are properties associated with events that are set once and then sent with every capture call.
// They are set using posthog.register // They are set using posthog.register
expect(mocked(fakePosthog).register.mock.lastCall![0]["cryptoSDK"]).toStrictEqual("Rust"); expect(vi.mocked(fakePosthog).register.mock.lastCall![0]["cryptoSDK"]).toStrictEqual("Rust");
}); });
}); });
}); });
@@ -5,16 +5,20 @@ 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 { PosthogAnalytics } from "../../src/PosthogAnalytics"; // @vitest-environment happy-dom
import PosthogTrackers from "../../src/PosthogTrackers";
import { vi, describe, it, expect, afterEach } from "vitest";
import { PosthogAnalytics } from "./PosthogAnalytics";
import PosthogTrackers from "./PosthogTrackers";
describe("PosthogTrackers", () => { describe("PosthogTrackers", () => {
afterEach(() => { afterEach(() => {
jest.resetAllMocks(); vi.resetAllMocks();
}); });
it("tracks URL Previews", () => { it("tracks URL Previews", () => {
jest.spyOn(PosthogAnalytics.instance, "trackEvent"); vi.spyOn(PosthogAnalytics.instance, "trackEvent");
const tracker = new PosthogTrackers(); const tracker = new PosthogTrackers();
tracker.trackUrlPreview("$123456", false, [ tracker.trackUrlPreview("$123456", false, [
{ {
@@ -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 { mocked } from "jest-mock"; // @vitest-environment happy-dom
import { vi, describe, it, expect, beforeEach, type Mocked } from "vitest";
import { import {
PushRuleActionName, PushRuleActionName,
TweakName, TweakName,
@@ -19,19 +21,19 @@ import {
type MatrixClient, type MatrixClient,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types"; import { KnownMembership } from "matrix-js-sdk/src/types";
import { mkEvent, mkRoom, mkRoomMember, muteRoom, stubClient, upsertRoomStateEvents } from "test-utils";
import { mkThread } from "test-utils/threads";
import { mkEvent, mkRoom, mkRoomMember, muteRoom, stubClient, upsertRoomStateEvents } from "../test-utils";
import { import {
getRoomNotifsState, getRoomNotifsState,
RoomNotifState, RoomNotifState,
getUnreadNotificationCount, getUnreadNotificationCount,
determineUnreadState, determineUnreadState,
getUnsentMessages, getUnsentMessages,
} from "../../src/RoomNotifs"; } from "./RoomNotifs";
import { NotificationLevel } from "../../src/stores/notifications/NotificationLevel"; import { NotificationLevel } from "./stores/notifications/NotificationLevel";
import SettingsStore from "../../src/settings/SettingsStore"; import SettingsStore from "./settings/SettingsStore";
import { MatrixClientPeg } from "../../src/MatrixClientPeg"; import { MatrixClientPeg } from "./MatrixClientPeg";
import { mkThread } from "../test-utils/threads";
describe("getUnsentMessages", () => { describe("getUnsentMessages", () => {
const ROOM_ID = "!roomId"; const ROOM_ID = "!roomId";
@@ -95,14 +97,14 @@ describe("getUnsentMessages", () => {
}); });
describe("RoomNotifs test", () => { describe("RoomNotifs test", () => {
let client: jest.Mocked<MatrixClient>; let client: Mocked<MatrixClient>;
beforeEach(() => { beforeEach(() => {
client = stubClient() as jest.Mocked<MatrixClient>; client = stubClient() as Mocked<MatrixClient>;
}); });
it("getRoomNotifsState handles rules with no conditions", () => { it("getRoomNotifsState handles rules with no conditions", () => {
mocked(client).pushRules = { vi.mocked(client).pushRules = {
global: { global: {
override: [ override: [
{ {
@@ -118,7 +120,7 @@ describe("RoomNotifs test", () => {
}); });
it("getRoomNotifsState handles guest users", () => { it("getRoomNotifsState handles guest users", () => {
mocked(client).isGuest.mockReturnValue(true); vi.mocked(client).isGuest.mockReturnValue(true);
expect(getRoomNotifsState(client, "!roomId:server")).toBe(RoomNotifState.AllMessages); expect(getRoomNotifsState(client, "!roomId:server")).toBe(RoomNotifState.AllMessages);
}); });
@@ -258,7 +260,7 @@ describe("RoomNotifs test", () => {
describe("and dynamic room predecessors are enabled", () => { describe("and dynamic room predecessors are enabled", () => {
beforeEach(() => { beforeEach(() => {
jest.spyOn(SettingsStore, "getValue").mockImplementation( vi.spyOn(SettingsStore, "getValue").mockImplementation(
(settingName) => settingName === "feature_dynamic_room_predecessors", (settingName) => settingName === "feature_dynamic_room_predecessors",
); );
}); });
@@ -343,7 +345,7 @@ describe("RoomNotifs test", () => {
}); });
it("indicates the user knock has been denied", async () => { it("indicates the user knock has been denied", async () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((name) => { vi.spyOn(SettingsStore, "getValue").mockImplementation((name) => {
return name === "feature_ask_to_join"; return name === "feature_ask_to_join";
}); });
const roomMember = mkRoomMember( const roomMember = mkRoomMember(
@@ -355,7 +357,7 @@ describe("RoomNotifs test", () => {
membership: KnownMembership.Knock, membership: KnownMembership.Knock,
}, },
); );
jest.spyOn(room, "getMember").mockReturnValue(roomMember); vi.spyOn(room, "getMember").mockReturnValue(roomMember);
const { level, symbol, count } = determineUnreadState(room); const { level, symbol, count } = determineUnreadState(room);
expect(symbol).toBe("!"); expect(symbol).toBe("!");
@@ -6,11 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import { mocked } from "jest-mock"; // @vitest-environment happy-dom
import { EventType, type MatrixClient, type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { setDMRoom } from "../../src/Rooms"; import { vi, describe, it, expect, beforeEach } from "vitest";
import { mkEvent, stubClient } from "../test-utils"; import { EventType, type MatrixClient, type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { mkEvent, stubClient } from "test-utils";
import { setDMRoom } from "./Rooms";
describe("setDMRoom", () => { describe("setDMRoom", () => {
const userId1 = "@user1:example.com"; const userId1 = "@user1:example.com";
@@ -23,8 +25,8 @@ describe("setDMRoom", () => {
let client: MatrixClient; let client: MatrixClient;
beforeEach(() => { beforeEach(() => {
client = mocked(stubClient()); client = vi.mocked(stubClient());
client.getAccountData = jest.fn().mockImplementation((eventType: string): MatrixEvent | undefined => { client.getAccountData = vi.fn().mockImplementation((eventType: string): MatrixEvent | undefined => {
if (eventType === EventType.Direct) { if (eventType === EventType.Direct) {
return mkEvent({ return mkEvent({
event: true, event: true,
@@ -43,7 +45,7 @@ describe("setDMRoom", () => {
describe("when logged in as a guest and marking a room as DM", () => { describe("when logged in as a guest and marking a room as DM", () => {
beforeEach(() => { beforeEach(() => {
mocked(client.isGuest).mockReturnValue(true); vi.mocked(client.isGuest).mockReturnValue(true);
setDMRoom(client, roomId1, userId1); setDMRoom(client, roomId1, userId1);
}); });
@@ -94,7 +96,7 @@ describe("setDMRoom", () => {
describe("when the direct event is undefined", () => { describe("when the direct event is undefined", () => {
beforeEach(() => { beforeEach(() => {
mocked(client.getAccountData).mockReturnValue(undefined); vi.mocked(client.getAccountData).mockReturnValue(undefined);
setDMRoom(client, roomId1, userId1); setDMRoom(client, roomId1, userId1);
}); });
@@ -108,8 +110,8 @@ describe("setDMRoom", () => {
describe("when the current content is undefined", () => { describe("when the current content is undefined", () => {
beforeEach(() => { beforeEach(() => {
// @ts-ignore // @ts-ignore
mocked(client.getAccountData).mockReturnValue({ vi.mocked(client.getAccountData).mockReturnValue({
getContent: jest.fn(), getContent: vi.fn(),
}); });
setDMRoom(client, roomId1, userId1); setDMRoom(client, roomId1, userId1);
}); });
@@ -6,14 +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 { mocked } from "jest-mock"; // @vitest-environment happy-dom
import fetchMock from "@fetch-mock/jest";
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import ScalarAuthClient from "../../src/ScalarAuthClient"; import { vi, describe, it, expect, beforeEach } from "vitest";
import { stubClient } from "../test-utils"; import fetchMock from "@fetch-mock/vitest";
import SdkConfig from "../../src/SdkConfig"; import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { WidgetType } from "../../src/widgets/WidgetType"; import { stubClient } from "test-utils";
import ScalarAuthClient from "./ScalarAuthClient";
import SdkConfig from "./SdkConfig";
import { WidgetType } from "./widgets/WidgetType";
describe("ScalarAuthClient", function () { describe("ScalarAuthClient", function () {
const apiUrl = "https://test.com/api"; const apiUrl = "https://test.com/api";
@@ -27,7 +29,7 @@ describe("ScalarAuthClient", function () {
let client: MatrixClient; let client: MatrixClient;
beforeEach(function () { beforeEach(function () {
jest.clearAllMocks(); vi.clearAllMocks();
client = stubClient(); client = stubClient();
}); });
@@ -42,9 +44,9 @@ describe("ScalarAuthClient", function () {
body: { user_id: client.getUserId() }, body: { user_id: client.getUserId() },
}); });
client.getOpenIdToken = jest.fn().mockResolvedValue(tokenObject); client.getOpenIdToken = vi.fn().mockResolvedValue(tokenObject);
sac.exchangeForScalarToken = jest.fn((arg) => { sac.exchangeForScalarToken = vi.fn((arg) => {
return Promise.resolve(arg === tokenObject ? "wokentoken" : "othertoken"); return Promise.resolve(arg === tokenObject ? "wokentoken" : "othertoken");
}); });
@@ -91,13 +93,13 @@ describe("ScalarAuthClient", function () {
describe("registerForToken", () => { describe("registerForToken", () => {
it("should call `termsInteractionCallback` upon M_TERMS_NOT_SIGNED error", async () => { it("should call `termsInteractionCallback` upon M_TERMS_NOT_SIGNED error", async () => {
const sac = new ScalarAuthClient(apiUrl + 4, uiUrl); const sac = new ScalarAuthClient(apiUrl + 4, uiUrl);
const termsInteractionCallback = jest.fn(); const termsInteractionCallback = vi.fn();
sac.setTermsInteractionCallback(termsInteractionCallback); sac.setTermsInteractionCallback(termsInteractionCallback);
fetchMock.get("https://test.com/api4/account?scalar_token=testtoken1&v=1.1", { fetchMock.get("https://test.com/api4/account?scalar_token=testtoken1&v=1.1", {
body: { errcode: "M_TERMS_NOT_SIGNED" }, body: { errcode: "M_TERMS_NOT_SIGNED" },
}); });
sac.exchangeForScalarToken = jest.fn(() => Promise.resolve("testtoken1")); sac.exchangeForScalarToken = vi.fn(() => Promise.resolve("testtoken1"));
mocked(client.getTerms).mockResolvedValue({ policies: {} }); vi.mocked(client.getTerms).mockResolvedValue({ policies: {} });
await expect(sac.registerForToken()).resolves.toBe("testtoken1"); await expect(sac.registerForToken()).resolves.toBe("testtoken1");
}); });
@@ -108,7 +110,7 @@ describe("ScalarAuthClient", function () {
body: { errcode: "SERVER_IS_SAD" }, body: { errcode: "SERVER_IS_SAD" },
status: 500, status: 500,
}); });
sac.exchangeForScalarToken = jest.fn(() => Promise.resolve("testtoken2")); sac.exchangeForScalarToken = vi.fn(() => Promise.resolve("testtoken2"));
await expect(sac.registerForToken()).rejects.toBeTruthy(); await expect(sac.registerForToken()).rejects.toBeTruthy();
}); });
@@ -118,7 +120,7 @@ describe("ScalarAuthClient", function () {
fetchMock.get("https://test.com/api6/account?scalar_token=testtoken3&v=1.1", { fetchMock.get("https://test.com/api6/account?scalar_token=testtoken3&v=1.1", {
body: {}, body: {},
}); });
sac.exchangeForScalarToken = jest.fn(() => Promise.resolve("testtoken3")); sac.exchangeForScalarToken = vi.fn(() => Promise.resolve("testtoken3"));
await expect(sac.registerForToken()).rejects.toThrow("Missing user_id in response"); await expect(sac.registerForToken()).rejects.toThrow("Missing user_id in response");
}); });
@@ -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 SdkConfig, { DEFAULTS } from "../../src/SdkConfig"; import { describe, it, expect, beforeEach } from "vitest";
import SdkConfig, { DEFAULTS } from "./SdkConfig";
describe("SdkConfig", () => { describe("SdkConfig", () => {
describe("with default values", () => { describe("with default values", () => {
@@ -6,17 +6,20 @@ 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 { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { getBrowserSupport, checkBrowserSupport, LOCAL_STORAGE_KEY } from "../../src/SupportedBrowser"; import { getBrowserSupport, checkBrowserSupport, LOCAL_STORAGE_KEY } from "./SupportedBrowser";
import ToastStore from "../../src/stores/ToastStore"; import ToastStore from "./stores/ToastStore";
import GenericToast from "../../src/components/views/toasts/GenericToast"; import GenericToast from "./components/views/toasts/GenericToast";
jest.mock("matrix-js-sdk/src/logger"); vi.mock("matrix-js-sdk/src/logger");
describe("SupportedBrowser", () => { describe("SupportedBrowser", () => {
beforeEach(() => { beforeEach(() => {
jest.resetAllMocks(); vi.resetAllMocks();
localStorage.clear(); localStorage.clear();
getBrowserSupport.clear(); getBrowserSupport.clear();
}); });
@@ -24,8 +27,8 @@ describe("SupportedBrowser", () => {
const testUserAgentFactory = const testUserAgentFactory =
(expectedWarning?: string) => (expectedWarning?: string) =>
async (userAgent: string): Promise<void> => { async (userAgent: string): Promise<void> => {
const toastSpy = jest.spyOn(ToastStore.sharedInstance(), "addOrReplaceToast"); const toastSpy = vi.spyOn(ToastStore.sharedInstance(), "addOrReplaceToast");
const warnLogSpy = jest.spyOn(logger, "warn"); const warnLogSpy = vi.spyOn(logger, "warn");
Object.defineProperty(window, "navigator", { value: { userAgent: userAgent }, writable: true }); Object.defineProperty(window, "navigator", { value: { userAgent: userAgent }, writable: true });
checkBrowserSupport(); checkBrowserSupport();
if (expectedWarning) { if (expectedWarning) {
@@ -91,8 +94,8 @@ describe("SupportedBrowser", () => {
); );
it("should not warn for unsupported browser if user accepted already", async () => { it("should not warn for unsupported browser if user accepted already", async () => {
const toastSpy = jest.spyOn(ToastStore.sharedInstance(), "addOrReplaceToast"); const toastSpy = vi.spyOn(ToastStore.sharedInstance(), "addOrReplaceToast");
const warnLogSpy = jest.spyOn(logger, "warn"); const warnLogSpy = vi.spyOn(logger, "warn");
const userAgent = const userAgent =
"Mozilla/5.0 (X11; CrOS x86_64 15633.69.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.212 Safari/537.36"; "Mozilla/5.0 (X11; CrOS x86_64 15633.69.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.212 Safari/537.36";
Object.defineProperty(window, "navigator", { value: { userAgent: userAgent }, writable: true }); Object.defineProperty(window, "navigator", { value: { userAgent: userAgent }, writable: true });
@@ -8,7 +8,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.
*/ */
import * as tzh from "../../src/TimezoneHandler"; // @vitest-environment happy-dom
import { describe, it, expect } from "vitest";
import * as tzh from "./TimezoneHandler";
describe("TimezoneHandler", () => { describe("TimezoneHandler", () => {
it("should support setting a user timezone", async () => { it("should support setting a user timezone", async () => {
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Favicon should clear a badge if called with a zero value 1`] = ` exports[`Favicon > should clear a badge if called with a zero value 1`] = `
[ [
{ {
"props": { "props": {
@@ -236,7 +236,7 @@ exports[`Favicon should clear a badge if called with a zero value 1`] = `
] ]
`; `;
exports[`Favicon should draw a badge if called with a non-zero value 1`] = ` exports[`Favicon > should draw a badge if called with a non-zero value 1`] = `
[ [
{ {
"props": { "props": {
@@ -6,15 +6,18 @@ 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 "jest-canvas-mock"; // @vitest-environment happy-dom
import Favicon from "../../src/favicon"; import { vi, describe, it, expect, beforeEach } from "vitest";
import "vitest-canvas-mock";
jest.useFakeTimers(); import Favicon from "./favicon";
vi.useFakeTimers();
describe("Favicon", () => { describe("Favicon", () => {
beforeEach(() => { beforeEach(() => {
jest.restoreAllMocks(); vi.restoreAllMocks();
document.getElementsByTagName("head")[0]?.remove(); document.getElementsByTagName("head")[0]?.remove();
const head = document.createElement("head"); const head = document.createElement("head");
window.document.documentElement.prepend(head); window.document.documentElement.prepend(head);
@@ -30,14 +33,14 @@ describe("Favicon", () => {
it("should draw a badge if called with a non-zero value", () => { it("should draw a badge if called with a non-zero value", () => {
const favicon = new Favicon(); const favicon = new Favicon();
favicon.badge(123); favicon.badge(123);
jest.runAllTimers(); vi.runAllTimers();
expect(favicon["context"].__getDrawCalls()).toMatchSnapshot(); expect(favicon["context"].__getDrawCalls()).toMatchSnapshot();
}); });
it("should clear a badge if called with a zero value", () => { it("should clear a badge if called with a zero value", () => {
const favicon = new Favicon(); const favicon = new Favicon();
favicon.badge(123); favicon.badge(123);
jest.runAllTimers(); vi.runAllTimers();
favicon.badge(0); favicon.badge(0);
expect(favicon["context"].__getDrawCalls()).toMatchSnapshot(); expect(favicon["context"].__getDrawCalls()).toMatchSnapshot();
}); });
@@ -48,7 +51,7 @@ describe("Favicon", () => {
const favicon = new Favicon(); const favicon = new Favicon();
const originalLink = window.document.querySelector("link"); const originalLink = window.document.querySelector("link");
favicon.badge(123); favicon.badge(123);
jest.runAllTimers(); vi.runAllTimers();
const newLink = window.document.querySelector("link"); const newLink = window.document.querySelector("link");
expect(originalLink).not.toStrictEqual(newLink); expect(originalLink).not.toStrictEqual(newLink);
}); });
@@ -60,9 +63,9 @@ describe("Favicon", () => {
link.href = "favicon.png"; link.href = "favicon.png";
head.appendChild(link); head.appendChild(link);
const spy = jest.spyOn(document, "createElement"); const spy = vi.spyOn(document, "createElement");
const favicon = new Favicon(); const favicon = new Favicon();
jest.runAllTimers(); vi.runAllTimers();
const img = spy.mock.results[0].value; const img = spy.mock.results[0].value;
img.onload(); img.onload();
@@ -78,9 +81,9 @@ describe("Favicon", () => {
link.href = "favicon.png"; link.href = "favicon.png";
head.appendChild(link); head.appendChild(link);
const spy = jest.spyOn(document, "createElement"); const spy = vi.spyOn(document, "createElement");
const favicon = new Favicon(); const favicon = new Favicon();
jest.runAllTimers(); vi.runAllTimers();
const img = spy.mock.results[0].value; const img = spy.mock.results[0].value;
img.height = 512; img.height = 512;
+4 -1
View File
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import { vi } from "vitest"; import { vi, expect as viExpect } from "vitest";
import { mocked as jestMocked } from "jest-mock"; import { mocked as jestMocked } from "jest-mock";
export const isJest = typeof jest !== "undefined"; export const isJest = typeof jest !== "undefined";
@@ -22,4 +22,7 @@ const adapter = {
const mocked = adapter.mocked; const mocked = adapter.mocked;
export { adapter as vi, mocked }; export { adapter as vi, mocked };
const _expect = isJest ? (expect as unknown as typeof viExpect) : viExpect;
export { _expect as expect };
export { type Mocked, type MockedObject } from "vitest"; export { type Mocked, type MockedObject } from "vitest";
+1
View File
@@ -16,6 +16,7 @@ import {
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { mkMessage, type MessageEventProps } from "./test-utils"; import { mkMessage, type MessageEventProps } from "./test-utils";
import { expect } from "../setup/adapter.ts";
export const makeThreadEvent = ({ export const makeThreadEvent = ({
rootEventId, rootEventId,
+14
View File
@@ -1067,6 +1067,9 @@ importers:
vitest: vitest:
specifier: 'catalog:' specifier: 'catalog:'
version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@25.9.3)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(happy-dom@20.10.6)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.1.3(@types/node@25.9.3)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.16))(terser@5.48.0)(yaml@2.8.4)) version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@25.9.3)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(happy-dom@20.10.6)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.1.3(@types/node@25.9.3)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.16))(terser@5.48.0)(yaml@2.8.4))
vitest-canvas-mock:
specifier: ^1.1.4
version: 1.1.4(vitest@4.1.9)
web-streams-polyfill: web-streams-polyfill:
specifier: ^4.0.0 specifier: ^4.0.0
version: 4.3.0 version: 4.3.0
@@ -13675,6 +13678,11 @@ packages:
postcss: postcss:
optional: true optional: true
vitest-canvas-mock@1.1.4:
resolution: {integrity: sha512-4boWHY+STwAxGl1+uwakNNoQky5EjPLC8HuponXNoAscYyT1h/F7RUvTkl4IyF/MiWr3V8Q626je3Iel3eArqA==}
peerDependencies:
vitest: ^3.0.0 || ^4.0.0
vitest-plugin-vis@5.1.1: vitest-plugin-vis@5.1.1:
resolution: {integrity: sha512-A/MEvQhDpNAj/5b53GiN139glgWUe0Qh5cDupm25G+06FA/2K85bd3tK+Hi4fTaolNbL5WiLNgnQ8dOL+kC7Pw==} resolution: {integrity: sha512-A/MEvQhDpNAj/5b53GiN139glgWUe0Qh5cDupm25G+06FA/2K85bd3tK+Hi4fTaolNbL5WiLNgnQ8dOL+kC7Pw==}
peerDependencies: peerDependencies:
@@ -27960,6 +27968,12 @@ snapshots:
- typescript - typescript
- universal-cookie - universal-cookie
vitest-canvas-mock@1.1.4(vitest@4.1.9):
dependencies:
cssfontparser: 1.2.1
moo-color: 1.0.3
vitest: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@25.9.3)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(happy-dom@20.10.6)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.1.3(@types/node@25.9.3)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.16))(terser@5.48.0)(yaml@2.8.4))
vitest-plugin-vis@5.1.1(@vitest/browser-playwright@4.1.9)(@vitest/browser@4.1.9)(babel-plugin-macros@3.1.0)(typescript@6.0.3)(vitest@4.1.9): vitest-plugin-vis@5.1.1(@vitest/browser-playwright@4.1.9)(@vitest/browser@4.1.9)(babel-plugin-macros@3.1.0)(typescript@6.0.3)(vitest@4.1.9):
dependencies: dependencies:
'@vitest/browser': 4.1.9(vite@8.1.3(@types/node@25.9.3)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.16))(terser@5.48.0)(yaml@2.8.4))(vitest@4.1.9) '@vitest/browser': 4.1.9(vite@8.1.3(@types/node@25.9.3)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.16))(terser@5.48.0)(yaml@2.8.4))(vitest@4.1.9)