From 30c02ab5d7ac6e2e68131213c9e3ea62719360b6 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 7 Jul 2026 10:40:22 +0100 Subject: [PATCH] Migrate batch of tests to vitest (#34121) * Migrate batch of tests tro vitest * Iterate * Migrate another test * Migrate batch of tests to vitest * Iterate --- .../MatrixClientPeg.test.ts} | 35 ++--- .../MediaDeviceHandler.test.ts} | 20 +-- .../Searching.test.ts} | 39 +++--- .../SlidingSyncManager.test.ts} | 72 +++++----- .../handlers/AccountSettingsHandler.test.ts} | 16 ++- .../handlers/DeviceSettingsHandler.test.ts} | 18 +-- .../RoomDeviceSettingsHandler.test.ts} | 12 +- .../slash-commands/status.test.ts} | 10 +- .../stores/spaces/SpaceStore.test.ts} | 124 +++++++++--------- .../vector/__snapshots__/init.test.ts.snap} | 6 +- .../app-test.ts => src/vector/app.test.ts} | 58 ++++---- .../init-test.ts => src/vector/init.test.ts} | 25 ++-- .../vector/platform/ElectronPlatform.test.ts} | 67 +++++----- .../vector/platform/PWAPlatform.test.ts} | 18 +-- .../vector/platform/WebPlatform.test.ts} | 64 ++++----- .../widgets/ManagedHybrid.test.ts} | 31 +++-- apps/web/test/setup/adapter.ts | 5 +- apps/web/test/test-utils/utilities.ts | 13 +- apps/web/vitest.config.ts | 5 + 19 files changed, 337 insertions(+), 301 deletions(-) rename apps/web/{test/unit-tests/MatrixClientPeg-test.ts => src/MatrixClientPeg.test.ts} (78%) rename apps/web/{test/unit-tests/MediaDeviceHandler-test.ts => src/MediaDeviceHandler.test.ts} (74%) rename apps/web/{test/unit-tests/Searching-test.ts => src/Searching.test.ts} (90%) rename apps/web/{test/unit-tests/SlidingSyncManager-test.ts => src/SlidingSyncManager.test.ts} (81%) rename apps/web/{test/unit-tests/settings/handlers/AccountSettingsHandler-test.ts => src/settings/handlers/AccountSettingsHandler.test.ts} (78%) rename apps/web/{test/unit-tests/settings/handlers/DeviceSettingsHandler-test.ts => src/settings/handlers/DeviceSettingsHandler.test.ts} (83%) rename apps/web/{test/unit-tests/settings/handlers/RoomDeviceSettingsHandler-test.ts => src/settings/handlers/RoomDeviceSettingsHandler.test.ts} (84%) rename apps/web/{test/slash-commands/status-test.ts => src/slash-commands/status.test.ts} (90%) rename apps/web/{test/unit-tests/stores/SpaceStore-test.ts => src/stores/spaces/SpaceStore.test.ts} (94%) rename apps/web/{test/unit-tests/vector/__snapshots__/init-test.ts.snap => src/vector/__snapshots__/init.test.ts.snap} (98%) rename apps/web/{test/unit-tests/vector/app-test.ts => src/vector/app.test.ts} (63%) rename apps/web/{test/unit-tests/vector/init-test.ts => src/vector/init.test.ts} (73%) rename apps/web/{test/unit-tests/vector/platform/ElectronPlatform-test.ts => src/vector/platform/ElectronPlatform.test.ts} (91%) rename apps/web/{test/unit-tests/vector/platform/PWAPlatform-test.ts => src/vector/platform/PWAPlatform.test.ts} (73%) rename apps/web/{test/unit-tests/vector/platform/WebPlatform-test.ts => src/vector/platform/WebPlatform.test.ts} (85%) rename apps/web/{test/unit-tests/widgets/ManagedHybrid-test.ts => src/widgets/ManagedHybrid.test.ts} (72%) diff --git a/apps/web/test/unit-tests/MatrixClientPeg-test.ts b/apps/web/src/MatrixClientPeg.test.ts similarity index 78% rename from apps/web/test/unit-tests/MatrixClientPeg-test.ts rename to apps/web/src/MatrixClientPeg.test.ts index ff8c904c43..bceecdd0c5 100644 --- a/apps/web/test/unit-tests/MatrixClientPeg-test.ts +++ b/apps/web/src/MatrixClientPeg.test.ts @@ -6,26 +6,29 @@ 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, afterEach } from "vitest"; import { logger } from "matrix-js-sdk/src/logger"; import { type MatrixClient } from "matrix-js-sdk/src/matrix"; -import fetchMock from "@fetch-mock/jest"; +import fetchMock from "@fetch-mock/vitest"; +import { advanceDateAndTime, stubClient, createTestClient } from "test-utils"; -import { advanceDateAndTime, createTestClient, stubClient } from "../test-utils"; -import { type IMatrixClientPeg, MatrixClientPeg as peg } from "../../src/MatrixClientPeg"; +import { type IMatrixClientPeg, MatrixClientPeg as peg } from "./MatrixClientPeg"; -jest.useFakeTimers(); +vi.useFakeTimers(); const PegClass = Object.getPrototypeOf(peg).constructor; describe("MatrixClientPeg", () => { beforeEach(() => { // stub out Logger.log which gets called a lot and clutters up the test output - jest.spyOn(logger, "log").mockImplementation(() => {}); + vi.spyOn(logger, "log").mockImplementation(() => {}); }); afterEach(() => { localStorage.clear(); - jest.restoreAllMocks(); + vi.restoreAllMocks(); // some of the tests assign `MatrixClientPeg.matrixClient`: clear it, to prevent leakage between tests peg.unset(); @@ -73,13 +76,13 @@ describe("MatrixClientPeg", () => { fetchMock.get("http://example.com/_matrix/client/versions", {}); const mockClient = createTestClient(); - mockClient.initRustCrypto = jest.fn(); - mockClient.startClient = jest.fn(); + mockClient.initRustCrypto = vi.fn(); + mockClient.startClient = vi.fn(); testPeg.set(mockClient as unknown as MatrixClient); }); it("should initialise the rust crypto library by default", async () => { - const mockInitRustCrypto = jest.spyOn(testPeg.safeGet(), "initRustCrypto").mockResolvedValue(undefined); + const mockInitRustCrypto = vi.spyOn(testPeg.safeGet(), "initRustCrypto").mockResolvedValue(undefined); const cryptoStoreKey = new Uint8Array([1, 2, 3, 4]); await testPeg.start({ rustCryptoStoreKey: cryptoStoreKey }); @@ -87,14 +90,14 @@ describe("MatrixClientPeg", () => { }); it("should try to start dehydration if dehydration is enabled", async () => { - const mockInitRustCrypto = jest.spyOn(testPeg.safeGet(), "initRustCrypto").mockResolvedValue(undefined); - const mockStartDehydration = jest.fn(); - jest.spyOn(testPeg.safeGet(), "getCrypto").mockReturnValue({ - isDehydrationSupported: jest.fn().mockResolvedValue(true), + const mockInitRustCrypto = vi.spyOn(testPeg.safeGet(), "initRustCrypto").mockResolvedValue(undefined); + const mockStartDehydration = vi.fn(); + vi.spyOn(testPeg.safeGet(), "getCrypto").mockReturnValue({ + isDehydrationSupported: vi.fn().mockResolvedValue(true), startDehydration: mockStartDehydration, - setDeviceIsolationMode: jest.fn(), + setDeviceIsolationMode: vi.fn(), } as any); - jest.spyOn(testPeg.safeGet(), "waitForClientWellKnown").mockResolvedValue({ + vi.spyOn(testPeg.safeGet(), "waitForClientWellKnown").mockResolvedValue({ "m.homeserver": { base_url: "http://example.com", }, @@ -108,7 +111,7 @@ describe("MatrixClientPeg", () => { }); it("Should migrate existing login", async () => { - const mockInitRustCrypto = jest.spyOn(testPeg.safeGet(), "initRustCrypto").mockResolvedValue(undefined); + const mockInitRustCrypto = vi.spyOn(testPeg.safeGet(), "initRustCrypto").mockResolvedValue(undefined); await testPeg.start(); expect(mockInitRustCrypto).toHaveBeenCalledTimes(1); diff --git a/apps/web/test/unit-tests/MediaDeviceHandler-test.ts b/apps/web/src/MediaDeviceHandler.test.ts similarity index 74% rename from apps/web/test/unit-tests/MediaDeviceHandler-test.ts rename to apps/web/src/MediaDeviceHandler.test.ts index 975a3853db..5913fcf9a9 100644 --- a/apps/web/test/unit-tests/MediaDeviceHandler-test.ts +++ b/apps/web/src/MediaDeviceHandler.test.ts @@ -6,17 +6,19 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import { mocked } from "jest-mock"; +// @vitest-environment happy-dom -import { SettingLevel } from "../../src/settings/SettingLevel"; -import { MatrixClientPeg } from "../../src/MatrixClientPeg"; -import { stubClient } from "../test-utils"; -import MediaDeviceHandler from "../../src/MediaDeviceHandler"; -import SettingsStore from "../../src/settings/SettingsStore"; +import { vi, describe, it, expect, beforeEach, afterEach } from "vitest"; +import { stubClient } from "test-utils"; -jest.mock("../../src/settings/SettingsStore"); +import { SettingLevel } from "./settings/SettingLevel"; +import { MatrixClientPeg } from "./MatrixClientPeg"; +import MediaDeviceHandler from "./MediaDeviceHandler"; +import SettingsStore from "./settings/SettingsStore"; -const SettingsStoreMock = mocked(SettingsStore); +vi.mock("./settings/SettingsStore"); + +const SettingsStoreMock = vi.mocked(SettingsStore); describe("MediaDeviceHandler", () => { beforeEach(() => { @@ -24,7 +26,7 @@ describe("MediaDeviceHandler", () => { }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it("sets audio settings", async () => { diff --git a/apps/web/test/unit-tests/Searching-test.ts b/apps/web/src/Searching.test.ts similarity index 90% rename from apps/web/test/unit-tests/Searching-test.ts rename to apps/web/src/Searching.test.ts index 60b34fb345..82a8e4c128 100644 --- a/apps/web/test/unit-tests/Searching-test.ts +++ b/apps/web/src/Searching.test.ts @@ -5,21 +5,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 { type IResultRoomEvents } from "matrix-js-sdk/src/matrix"; +// @vitest-environment happy-dom -import eventSearch from "../../src/Searching"; -import EventIndexPeg from "../../src/indexing/EventIndexPeg"; -import { createTestClient } from "../test-utils"; +import { vi, describe, it, expect, beforeEach, afterEach } from "vitest"; +import { type IResultRoomEvents } from "matrix-js-sdk/src/matrix"; +import { createTestClient } from "test-utils"; + +import eventSearch from "./Searching"; +import EventIndexPeg from "./indexing/EventIndexPeg"; describe("Searching", () => { const mockClient = createTestClient(); beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); afterEach(() => { - jest.restoreAllMocks(); + vi.restoreAllMocks(); }); describe("localSearch", () => { @@ -89,13 +92,13 @@ describe("Searching", () => { // Mock EventIndex.search to return results with state_key: null const mockEventIndex = { - search: jest.fn().mockResolvedValue(mockSearchResults), + search: vi.fn().mockResolvedValue(mockSearchResults), }; - jest.spyOn(EventIndexPeg, "get").mockReturnValue(mockEventIndex as any); + vi.spyOn(EventIndexPeg, "get").mockReturnValue(mockEventIndex as any); // Mock crypto to indicate room is encrypted - jest.spyOn(mockClient, "getCrypto").mockReturnValue({ - isEncryptionEnabledInRoom: jest.fn().mockResolvedValue(true), + vi.spyOn(mockClient, "getCrypto").mockReturnValue({ + isEncryptionEnabledInRoom: vi.fn().mockResolvedValue(true), } as any); // Perform search in an encrypted room @@ -152,12 +155,12 @@ describe("Searching", () => { }; const mockEventIndex = { - search: jest.fn().mockResolvedValue(mockSearchResults), + search: vi.fn().mockResolvedValue(mockSearchResults), }; - jest.spyOn(EventIndexPeg, "get").mockReturnValue(mockEventIndex as any); + vi.spyOn(EventIndexPeg, "get").mockReturnValue(mockEventIndex as any); - jest.spyOn(mockClient, "getCrypto").mockReturnValue({ - isEncryptionEnabledInRoom: jest.fn().mockResolvedValue(true), + vi.spyOn(mockClient, "getCrypto").mockReturnValue({ + isEncryptionEnabledInRoom: vi.fn().mockResolvedValue(true), } as any); const roomId = "!room:example.org"; @@ -222,15 +225,15 @@ describe("Searching", () => { }; const mockEventIndex = { - search: jest + search: vi .fn() .mockResolvedValueOnce(mockSearchResults) .mockResolvedValueOnce({ count: 0, highlights: ["test"] } as IResultRoomEvents), }; - jest.spyOn(EventIndexPeg, "get").mockReturnValue(mockEventIndex as any); + vi.spyOn(EventIndexPeg, "get").mockReturnValue(mockEventIndex as any); - jest.spyOn(mockClient, "getCrypto").mockReturnValue({ - isEncryptionEnabledInRoom: jest.fn().mockResolvedValue(true), + vi.spyOn(mockClient, "getCrypto").mockReturnValue({ + isEncryptionEnabledInRoom: vi.fn().mockResolvedValue(true), } as any); const roomId = "!room:example.org"; diff --git a/apps/web/test/unit-tests/SlidingSyncManager-test.ts b/apps/web/src/SlidingSyncManager.test.ts similarity index 81% rename from apps/web/test/unit-tests/SlidingSyncManager-test.ts rename to apps/web/src/SlidingSyncManager.test.ts index 88c3f5e720..b66ecc5806 100644 --- a/apps/web/test/unit-tests/SlidingSyncManager-test.ts +++ b/apps/web/src/SlidingSyncManager.test.ts @@ -6,30 +6,32 @@ 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 SlidingSync, SlidingSyncEvent, SlidingSyncState } from "matrix-js-sdk/src/sliding-sync"; -import { mocked } from "jest-mock"; -import { ClientEvent, type MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; -import fetchMock from "@fetch-mock/jest"; -import EventEmitter from "events"; -import { waitFor } from "jest-matrix-react"; +// @vitest-environment happy-dom -import { SlidingSyncManager } from "../../src/SlidingSyncManager"; -import { mkStubRoom, stubClient } from "../test-utils"; +import { vi, describe, it, expect, beforeEach } from "vitest"; +import { type SlidingSync, SlidingSyncEvent, SlidingSyncState } from "matrix-js-sdk/src/sliding-sync"; +import { ClientEvent, type MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; +import fetchMock from "@fetch-mock/vitest"; +import EventEmitter from "events"; +import { waitFor } from "test-utils-rtl"; +import { mkStubRoom, stubClient } from "test-utils"; + +import { SlidingSyncManager } from "./SlidingSyncManager"; class MockSlidingSync extends EventEmitter { lists = {}; listModifiedCount = 0; terminated = false; needsResend = false; - modifyRoomSubscriptions = jest.fn(); - getRoomSubscriptions = jest.fn(); - useCustomSubscription = jest.fn(); - getListParams = jest.fn(); - setList = jest.fn(); - setListRanges = jest.fn(); - getListData = jest.fn(); - extensions = jest.fn(); - desiredRoomSubscriptions = jest.fn(); + modifyRoomSubscriptions = vi.fn(); + getRoomSubscriptions = vi.fn(); + useCustomSubscription = vi.fn(); + getListParams = vi.fn(); + setList = vi.fn(); + setListRanges = vi.fn(); + getListData = vi.fn(); + extensions = vi.fn(); + desiredRoomSubscriptions = vi.fn(); } describe("SlidingSyncManager", () => { @@ -42,7 +44,7 @@ describe("SlidingSyncManager", () => { manager = new SlidingSyncManager(); client = stubClient(); // by default the client has no rooms: stubClient magically makes rooms annoyingly. - mocked(client.getRoom).mockReturnValue(null); + vi.mocked(client.getRoom).mockReturnValue(null); (manager as any).configure(client, "invalid"); manager.slidingSync = slidingSync; fetchMock.get("https://proxy/client/server.json", {}); @@ -51,9 +53,9 @@ describe("SlidingSyncManager", () => { describe("setRoomVisible", () => { it("adds a subscription for the room", async () => { const roomId = "!room:id"; - mocked(client.getRoom).mockReturnValue(mkStubRoom(roomId, "foo", client)); + vi.mocked(client.getRoom).mockReturnValue(mkStubRoom(roomId, "foo", client)); const subs = new Set(); - mocked(slidingSync.getRoomSubscriptions).mockReturnValue(subs); + vi.mocked(slidingSync.getRoomSubscriptions).mockReturnValue(subs); await manager.setRoomVisible(roomId); expect(slidingSync.modifyRoomSubscriptions).toHaveBeenCalledWith(new Set([roomId])); }); @@ -72,14 +74,14 @@ describe("SlidingSyncManager", () => { }, }), ]); - mocked(client.getRoom).mockImplementation((r: string): Room | null => { + vi.mocked(client.getRoom).mockImplementation((r?: string): Room | null => { if (roomId === r) { return room; } return null; }); const subs = new Set(); - mocked(slidingSync.getRoomSubscriptions).mockReturnValue(subs); + vi.mocked(slidingSync.getRoomSubscriptions).mockReturnValue(subs); await manager.setRoomVisible(roomId); expect(slidingSync.modifyRoomSubscriptions).toHaveBeenCalledWith(new Set([roomId])); // we aren't prescriptive about what the sub name is. @@ -88,11 +90,11 @@ describe("SlidingSyncManager", () => { it("waits if the room is not yet known", async () => { const roomId = "!room:id"; - mocked(client.getRoom).mockReturnValue(null); + vi.mocked(client.getRoom).mockReturnValue(null); const subs = new Set(); - mocked(slidingSync.getRoomSubscriptions).mockReturnValue(subs); + vi.mocked(slidingSync.getRoomSubscriptions).mockReturnValue(subs); - const setVisibleDone = jest.fn(); + const setVisibleDone = vi.fn(); manager.setRoomVisible(roomId).then(setVisibleDone); await waitFor(() => expect(client.getRoom).toHaveBeenCalledWith(roomId)); @@ -100,7 +102,7 @@ describe("SlidingSyncManager", () => { expect(setVisibleDone).not.toHaveBeenCalled(); const stubRoom = mkStubRoom(roomId, "foo", client); - mocked(client.getRoom).mockReturnValue(stubRoom); + vi.mocked(client.getRoom).mockReturnValue(stubRoom); client.emit(ClientEvent.Room, stubRoom); await waitFor(() => expect(setVisibleDone).toHaveBeenCalled()); @@ -110,7 +112,7 @@ describe("SlidingSyncManager", () => { describe("ensureListRegistered", () => { it("creates a new list based on the key", async () => { const listKey = "key"; - mocked(slidingSync.getListParams).mockReturnValue(null); + vi.mocked(slidingSync.getListParams).mockReturnValue(null); await manager.ensureListRegistered(listKey, { sort: ["by_recency"], }); @@ -124,7 +126,7 @@ describe("SlidingSyncManager", () => { it("updates an existing list based on the key", async () => { const listKey = "key"; - mocked(slidingSync.getListParams).mockReturnValue({ + vi.mocked(slidingSync.getListParams).mockReturnValue({ ranges: [[0, 42]], }); await manager.ensureListRegistered(listKey, { @@ -141,7 +143,7 @@ describe("SlidingSyncManager", () => { it("updates ranges on an existing list based on the key if there's no other changes", async () => { const listKey = "key"; - mocked(slidingSync.getListParams).mockReturnValue({ + vi.mocked(slidingSync.getListParams).mockReturnValue({ ranges: [[0, 42]], }); await manager.ensureListRegistered(listKey, { @@ -153,7 +155,7 @@ describe("SlidingSyncManager", () => { it("no-ops for idential changes", async () => { const listKey = "key"; - mocked(slidingSync.getListParams).mockReturnValue({ + vi.mocked(slidingSync.getListParams).mockReturnValue({ ranges: [[0, 42]], sort: ["by_recency"], }); @@ -170,7 +172,7 @@ describe("SlidingSyncManager", () => { it("requests in expanding batchSizes", async () => { const gapMs = 1; const batchSize = 10; - mocked(slidingSync.getListData).mockImplementation((key) => { + vi.mocked(slidingSync.getListData).mockImplementation((key) => { return { joinedCount: 64, roomIndexToRoomId: {}, @@ -201,7 +203,7 @@ describe("SlidingSyncManager", () => { it("handles accounts with zero rooms", async () => { const gapMs = 1; const batchSize = 10; - mocked(slidingSync.getListData).mockImplementation((key) => { + vi.mocked(slidingSync.getListData).mockImplementation((key) => { return { joinedCount: 0, roomIndexToRoomId: {}, @@ -219,7 +221,7 @@ describe("SlidingSyncManager", () => { SlidingSyncManager.serverSupportsSlidingSync = false; }); it("shorts out if the server has 'native' sliding sync support", async () => { - jest.spyOn(manager, "nativeSlidingSyncSupport").mockResolvedValue(true); + vi.spyOn(manager, "nativeSlidingSyncSupport").mockResolvedValue(true); expect(SlidingSyncManager.serverSupportsSlidingSync).toBeFalsy(); await manager.checkSupport(client); expect(SlidingSyncManager.serverSupportsSlidingSync).toBeTruthy(); @@ -230,8 +232,8 @@ describe("SlidingSyncManager", () => { beforeEach(() => { untypedManager = manager; - jest.spyOn(untypedManager, "configure"); - jest.spyOn(untypedManager, "startSpidering"); + vi.spyOn(untypedManager, "configure"); + vi.spyOn(untypedManager, "startSpidering"); }); it("uses the baseUrl", async () => { await manager.setup(client); diff --git a/apps/web/test/unit-tests/settings/handlers/AccountSettingsHandler-test.ts b/apps/web/src/settings/handlers/AccountSettingsHandler.test.ts similarity index 78% rename from apps/web/test/unit-tests/settings/handlers/AccountSettingsHandler-test.ts rename to apps/web/src/settings/handlers/AccountSettingsHandler.test.ts index e49c0c5fd6..ae02d9d1b5 100644 --- a/apps/web/test/unit-tests/settings/handlers/AccountSettingsHandler-test.ts +++ b/apps/web/src/settings/handlers/AccountSettingsHandler.test.ts @@ -5,12 +5,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 { ClientEvent, MatrixEvent } from "matrix-js-sdk/src/matrix"; -import { mocked } from "jest-mock"; +// @vitest-environment happy-dom -import AccountSettingsHandler from "../../../../src/settings/handlers/AccountSettingsHandler.ts"; -import { WatchManager } from "../../../../src/settings/WatchManager.ts"; -import { stubClient } from "../../../test-utils"; +import { vi, describe, it, expect, beforeEach } from "vitest"; +import { ClientEvent, MatrixEvent } from "matrix-js-sdk/src/matrix"; +import { stubClient } from "test-utils"; + +import AccountSettingsHandler from "./AccountSettingsHandler.ts"; +import { WatchManager } from "../WatchManager.ts"; describe("AccountSettingsHandler", () => { const watchManager = new WatchManager(); @@ -19,7 +21,7 @@ describe("AccountSettingsHandler", () => { beforeEach(stubClient); it("should notify watchers of recent_emoji on account data update", async () => { - const fn = jest.fn(); + const fn = vi.fn(); handler.watchers.watchSetting("recent_emoji", null, fn); const ev = new MatrixEvent({ @@ -28,7 +30,7 @@ describe("AccountSettingsHandler", () => { recent_emoji: [["🤒", 1]], }, }); - mocked(handler.client.getAccountData).mockImplementation((eventType) => + vi.mocked(handler.client.getAccountData).mockImplementation((eventType) => eventType === "io.element.recent_emoji" ? ev : undefined, ); handler.client.emit(ClientEvent.AccountData, ev); diff --git a/apps/web/test/unit-tests/settings/handlers/DeviceSettingsHandler-test.ts b/apps/web/src/settings/handlers/DeviceSettingsHandler.test.ts similarity index 83% rename from apps/web/test/unit-tests/settings/handlers/DeviceSettingsHandler-test.ts rename to apps/web/src/settings/handlers/DeviceSettingsHandler.test.ts index d7580adf1c..052c9a8818 100644 --- a/apps/web/test/unit-tests/settings/handlers/DeviceSettingsHandler-test.ts +++ b/apps/web/src/settings/handlers/DeviceSettingsHandler.test.ts @@ -6,13 +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. */ -import { mocked } from "jest-mock"; -import { MatrixClient } from "matrix-js-sdk/src/matrix"; +// @vitest-environment happy-dom -import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; -import DeviceSettingsHandler from "../../../../src/settings/handlers/DeviceSettingsHandler"; -import { type CallbackFn, WatchManager } from "../../../../src/settings/WatchManager"; -import { stubClient } from "../../../test-utils/test-utils"; +import { vi, describe, it, expect, beforeEach, afterEach } from "vitest"; +import { MatrixClient } from "matrix-js-sdk/src/matrix"; +import { stubClient } from "test-utils"; + +import { MatrixClientPeg } from "../../MatrixClientPeg"; +import DeviceSettingsHandler from "./DeviceSettingsHandler"; +import { type CallbackFn, WatchManager } from "../WatchManager"; describe("DeviceSettingsHandler", () => { const ROOM_ID_IS_UNUSED = ""; @@ -27,7 +29,7 @@ describe("DeviceSettingsHandler", () => { beforeEach(() => { watchers = new WatchManager(); handler = new DeviceSettingsHandler([featureKey], watchers); - settingListener = jest.fn(); + settingListener = vi.fn(); }); afterEach(() => { @@ -53,7 +55,7 @@ describe("DeviceSettingsHandler", () => { beforeEach(() => { client = stubClient(); - mocked(client.isGuest).mockReturnValue(true); + vi.mocked(client.isGuest).mockReturnValue(true); }); afterEach(() => { diff --git a/apps/web/test/unit-tests/settings/handlers/RoomDeviceSettingsHandler-test.ts b/apps/web/src/settings/handlers/RoomDeviceSettingsHandler.test.ts similarity index 84% rename from apps/web/test/unit-tests/settings/handlers/RoomDeviceSettingsHandler-test.ts rename to apps/web/src/settings/handlers/RoomDeviceSettingsHandler.test.ts index 1852acaf17..84b2ebc8bd 100644 --- a/apps/web/test/unit-tests/settings/handlers/RoomDeviceSettingsHandler-test.ts +++ b/apps/web/src/settings/handlers/RoomDeviceSettingsHandler.test.ts @@ -6,9 +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 RoomDeviceSettingsHandler from "../../../../src/settings/handlers/RoomDeviceSettingsHandler"; -import { SettingLevel } from "../../../../src/settings/SettingLevel"; -import { type CallbackFn, WatchManager } from "../../../../src/settings/WatchManager"; +// @vitest-environment happy-dom + +import { vi, describe, it, expect, beforeEach, afterEach } from "vitest"; + +import RoomDeviceSettingsHandler from "./RoomDeviceSettingsHandler"; +import { SettingLevel } from "../SettingLevel"; +import { type CallbackFn, WatchManager } from "../WatchManager"; describe("RoomDeviceSettingsHandler", () => { const roomId = "!room:example.com"; @@ -25,7 +29,7 @@ describe("RoomDeviceSettingsHandler", () => { beforeEach(() => { watchers = new WatchManager(); handler = new RoomDeviceSettingsHandler(watchers); - settingListener = jest.fn(); + settingListener = vi.fn(); }); afterEach(() => { diff --git a/apps/web/test/slash-commands/status-test.ts b/apps/web/src/slash-commands/status.test.ts similarity index 90% rename from apps/web/test/slash-commands/status-test.ts rename to apps/web/src/slash-commands/status.test.ts index e2ff04d03f..bffcf5d2fb 100644 --- a/apps/web/test/slash-commands/status-test.ts +++ b/apps/web/src/slash-commands/status.test.ts @@ -5,9 +5,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 { stubClient } from "../test-utils"; -import { statusCommand } from "../../src/slash-commands/status"; -import { UserFriendlyError } from "../../src/languageHandler"; +// @vitest-environment happy-dom + +import { describe, it, expect, beforeEach } from "vitest"; +import { stubClient } from "test-utils"; + +import { statusCommand } from "./status"; +import { UserFriendlyError } from "../i18n"; describe("/status", () => { const roomId = "!room:example.com"; diff --git a/apps/web/test/unit-tests/stores/SpaceStore-test.ts b/apps/web/src/stores/spaces/SpaceStore.test.ts similarity index 94% rename from apps/web/test/unit-tests/stores/SpaceStore-test.ts rename to apps/web/src/stores/spaces/SpaceStore.test.ts index 6c7f53137b..1cf35e0992 100644 --- a/apps/web/test/unit-tests/stores/SpaceStore-test.ts +++ b/apps/web/src/stores/spaces/SpaceStore.test.ts @@ -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. */ +// @vitest-environment happy-dom + +import { vi, describe, it, expect, beforeAll, beforeEach, afterEach } from "vitest"; import { type EventEmitter } from "events"; -import { mocked } from "jest-mock"; import { EventType, RoomMember, @@ -20,30 +22,30 @@ import { type RoomState, } from "matrix-js-sdk/src/matrix"; import { KnownMembership } from "matrix-js-sdk/src/types"; +import * as testUtils from "test-utils"; +import { mkEvent, stubClient } from "test-utils"; -import SpaceStore from "../../../src/stores/spaces/SpaceStore"; +import SpaceStore from "./SpaceStore"; import { MetaSpace, UPDATE_HOME_BEHAVIOUR, UPDATE_INVITED_SPACES, UPDATE_SELECTED_SPACE, UPDATE_TOP_LEVEL_SPACES, -} from "../../../src/stores/spaces"; -import * as testUtils from "../../test-utils"; -import { mkEvent, stubClient } from "../../test-utils"; +} from "."; import DMRoomMap from "../../../src/utils/DMRoomMap"; import defaultDispatcher from "../../../src/dispatcher/dispatcher"; import SettingsStore from "../../../src/settings/SettingsStore"; -import { SettingLevel } from "../../../src/settings/SettingLevel"; -import { Action } from "../../../src/dispatcher/actions"; -import { MatrixClientPeg } from "../../../src/MatrixClientPeg"; -import RoomListStoreV3 from "../../../src/stores/room-list-v3/RoomListStoreV3"; -import { DefaultTagID } from "../../../src/stores/room-list-v3/skip-list/tag"; -import { RoomNotificationStateStore } from "../../../src/stores/notifications/RoomNotificationStateStore"; -import { NotificationLevel } from "../../../src/stores/notifications/NotificationLevel"; -import { storeRoomAliasInCache } from "../../../src/RoomAliasCache.ts"; +import { SettingLevel } from "../../settings/SettingLevel"; +import { Action } from "../../dispatcher/actions"; +import { MatrixClientPeg } from "../../MatrixClientPeg"; +import RoomListStoreV3 from "../room-list-v3/RoomListStoreV3"; +import { DefaultTagID } from "../room-list-v3/skip-list/tag"; +import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore"; +import { NotificationLevel } from "../notifications/NotificationLevel"; +import { storeRoomAliasInCache } from "../../RoomAliasCache.ts"; -jest.useFakeTimers(); +vi.useFakeTimers(); const testUserId = "@test:user"; @@ -74,14 +76,14 @@ const space2 = "!space2:server"; const space3 = "!space3:server"; const space4 = "!space4:server"; -const getUserIdForRoomId = jest.fn((roomId: string) => { +const getUserIdForRoomId = vi.fn((roomId: string) => { return { [dm1]: dm1Partner.userId, [dm2]: dm2Partner.userId, [dm3]: dm3Partner.userId, }[roomId]; }); -const getDMRoomsForUserId = jest.fn((userId) => { +const getDMRoomsForUserId = vi.fn((userId) => { switch (userId) { case dm1Partner.userId: return [dm1]; @@ -101,7 +103,7 @@ describe("SpaceStore", () => { const store = SpaceStore.instance; const client = MatrixClientPeg.safeGet(); - const spyDispatcher = jest.spyOn(defaultDispatcher, "dispatch"); + const spyDispatcher = vi.spyOn(defaultDispatcher, "dispatch"); let rooms: Room[] = []; const mkRoom = (roomId: string) => testUtils.mkRoom(client, roomId, rooms); @@ -109,26 +111,26 @@ describe("SpaceStore", () => { const viewRoom = (roomId: string) => defaultDispatcher.dispatch({ action: Action.ViewRoom, room_id: roomId }, true); const run = async () => { - mocked(client).getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId) || null); - mocked(client).getRoomUpgradeHistory.mockImplementation((roomId) => { + vi.mocked(client).getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId) || null); + vi.mocked(client).getRoomUpgradeHistory.mockImplementation((roomId) => { const room = rooms.find((room) => room.roomId === roomId); return room ? [room] : []; }); await testUtils.setupAsyncStoreWithClient(store, client); - jest.runOnlyPendingTimers(); + vi.runOnlyPendingTimers(); }; const setShowAllRooms = async (value: boolean) => { if (store.allRoomsInHome === value) return; const emitProm = testUtils.emitPromise(store, UPDATE_HOME_BEHAVIOUR); await SettingsStore.setValue("Spaces.allRoomsInHome", null, SettingLevel.DEVICE, value); - jest.runOnlyPendingTimers(); // run async dispatch + vi.runOnlyPendingTimers(); // run async dispatch await emitProm; }; beforeEach(async () => { - jest.runOnlyPendingTimers(); // run async dispatch - mocked(client).getVisibleRooms.mockReturnValue((rooms = [])); + vi.runOnlyPendingTimers(); // run async dispatch + vi.mocked(client).getVisibleRooms.mockReturnValue((rooms = [])); await SettingsStore.setValue("Spaces.enabledMetaSpaces", null, SettingLevel.DEVICE, { [MetaSpace.Home]: true, @@ -321,7 +323,7 @@ describe("SpaceStore", () => { mkSpace(space3, [invite2]); mkSpace(space4, [room4, fav2, space2, space3]); - mocked(client).getRoom.mockImplementation( + vi.mocked(client).getRoom.mockImplementation( (roomId) => rooms.find((room) => room.roomId === roomId) || null, ); @@ -334,20 +336,20 @@ describe("SpaceStore", () => { }); [invite1, invite2].forEach((roomId) => { - mocked(client.getRoom(roomId)!).getMyMembership.mockReturnValue(KnownMembership.Invite); + vi.mocked(client.getRoom(roomId)!).getMyMembership.mockReturnValue(KnownMembership.Invite); }); // have dmPartner1 be in space1 with you const mySpace1Member = new RoomMember(space1, testUserId); mySpace1Member.membership = KnownMembership.Join; - (rooms.find((r) => r.roomId === space1)!.getMembers as jest.Mock).mockReturnValue([ + vi.mocked(rooms.find((r) => r.roomId === space1)!.getMembers).mockReturnValue([ mySpace1Member, dm1Partner, ]); // have dmPartner2 be in space2 with you const mySpace2Member = new RoomMember(space2, testUserId); mySpace2Member.membership = KnownMembership.Join; - (rooms.find((r) => r.roomId === space2)!.getMembers as jest.Mock).mockReturnValue([ + vi.mocked(rooms.find((r) => r.roomId === space2)!.getMembers).mockReturnValue([ mySpace2Member, dm2Partner, ]); @@ -366,9 +368,9 @@ describe("SpaceStore", () => { ts: Date.now(), }) as MatrixEvent, ]); - mocked(cliRoom2!.currentState).getStateEvents.mockImplementation(room2MockStateEvents); + vi.mocked(cliRoom2!.currentState).getStateEvents.mockImplementation(room2MockStateEvents); const cliSpace2 = client.getRoom(space2); - mocked(cliSpace2!.currentState).maySendStateEvent.mockImplementation( + vi.mocked(cliSpace2!.currentState).maySendStateEvent.mockImplementation( (evType: string, userId: string) => { if (evType === EventType.SpaceChild) { return userId === client.getUserId(); @@ -379,7 +381,7 @@ describe("SpaceStore", () => { // room 3 claims to be a child of space3 but is not due to invalid m.space.parent (permissions) const cliRoom3 = client.getRoom(room3); - mocked(cliRoom3!.currentState).getStateEvents.mockImplementation( + vi.mocked(cliRoom3!.currentState).getStateEvents.mockImplementation( testUtils.mockStateEventImplementation([ mkEvent({ event: true, @@ -393,7 +395,7 @@ describe("SpaceStore", () => { ]), ); const cliSpace3 = client.getRoom(space3); - mocked(cliSpace3!.currentState).maySendStateEvent.mockImplementation( + vi.mocked(cliSpace3!.currentState).maySendStateEvent.mockImplementation( (evType: string, userId: string) => { if (evType === EventType.SpaceChild) { return false; @@ -404,10 +406,10 @@ describe("SpaceStore", () => { [videoRoomPrivate, videoRoomPublic].forEach((roomId) => { const videoRoom = client.getRoom(roomId); - (videoRoom!.isCallRoom as jest.Mock).mockReturnValue(true); + vi.mocked(videoRoom!.isCallRoom).mockReturnValue(true); }); const videoRoomPublicRoom = client.getRoom(videoRoomPublic); - (videoRoomPublicRoom!.getJoinRule as jest.Mock).mockReturnValue(JoinRule.Public); + vi.mocked(videoRoomPublicRoom!.getJoinRule).mockReturnValue(JoinRule.Public); await run(); }); @@ -468,7 +470,7 @@ describe("SpaceStore", () => { it("updates the video room space when the room type changes", async () => { expect(store.isRoomInSpace(MetaSpace.VideoRooms, videoRoomPrivate)).toBeTruthy(); - (client.getRoom(videoRoomPublic)!.isCallRoom as jest.Mock).mockReturnValue(false); + vi.mocked(client.getRoom(videoRoomPublic)!.isCallRoom).mockReturnValue(false); client.emit( RoomStateEvent.Events, { @@ -808,7 +810,7 @@ describe("SpaceStore", () => { ts: Date.now(), }); const spaceRoom = client.getRoom(spaceId)!; - mocked(spaceRoom.currentState).getStateEvents.mockImplementation( + vi.mocked(spaceRoom.currentState).getStateEvents.mockImplementation( testUtils.mockStateEventImplementation([childEvent]), ); @@ -826,10 +828,10 @@ describe("SpaceStore", () => { ts: Date.now(), }); const spaceRoom = client.getRoom(spaceId)!; - mocked(spaceRoom.currentState).getStateEvents.mockImplementation( + vi.mocked(spaceRoom.currentState).getStateEvents.mockImplementation( testUtils.mockStateEventImplementation([memberEvent]), ); - mocked(spaceRoom).getMember.mockReturnValue(user); + vi.mocked(spaceRoom).getMember.mockReturnValue(user); client.emit(RoomStateEvent.Members, memberEvent, spaceRoom.currentState, user); }; @@ -838,7 +840,7 @@ describe("SpaceStore", () => { await run(); const room5 = mkRoom("!room5:server"); - const emitSpy = jest.spyOn(store, "emit").mockClear(); + const emitSpy = vi.spyOn(store, "emit").mockClear(); // add room5 into space2 addChildRoom(space2, room5.roomId); @@ -870,7 +872,7 @@ describe("SpaceStore", () => { it("emits events for parent spaces when a member is added", async () => { await run(); - const emitSpy = jest.spyOn(store, "emit").mockClear(); + const emitSpy = vi.spyOn(store, "emit").mockClear(); // add into space2 addMember(space2, dm1Partner); @@ -898,7 +900,7 @@ describe("SpaceStore", () => { }); describe("active space switching tests", () => { - const fn = jest.spyOn(store, "emit"); + const fn = vi.spyOn(store, "emit"); beforeEach(async () => { mkRoom(room1); // not a space @@ -994,9 +996,9 @@ describe("SpaceStore", () => { expect(space.loadMembersIfNeeded).not.toHaveBeenCalled(); store.setActiveSpace(space1, true); - jest.runOnlyPendingTimers(); + vi.runOnlyPendingTimers(); expect(space.loadMembersIfNeeded).toHaveBeenCalled(); - jest.runAllTimers(); + vi.runAllTimers(); expect(store.activeSpace).toBe(space1); expect(getCurrentRoom()).toBe(room1); @@ -1005,7 +1007,7 @@ describe("SpaceStore", () => { expect(store.activeSpace).toBe(space1); expect(getCurrentRoom()).toBe(room1); - jest.runAllTimers(); + vi.runAllTimers(); expect(store.activeSpace).toBe(space1); expect(getCurrentRoom()).toBe(room1); }); @@ -1033,7 +1035,7 @@ describe("SpaceStore", () => { }); const getCurrentRoom = () => { - jest.runOnlyPendingTimers(); + vi.runOnlyPendingTimers(); return currentRoom; }; @@ -1102,7 +1104,7 @@ describe("SpaceStore", () => { mkSpace(space2, [room1, room2]); const cliRoom2 = client.getRoom(room2)!; - mocked(cliRoom2.currentState).getStateEvents.mockImplementation( + vi.mocked(cliRoom2.currentState).getStateEvents.mockImplementation( testUtils.mockStateEventImplementation([ mkEvent({ event: true, @@ -1154,7 +1156,7 @@ describe("SpaceStore", () => { [MetaSpace.Home]: true, [MetaSpace.Orphans]: false, }); - jest.runAllTimers(); + vi.runAllTimers(); expect(store.activeSpace).toBe(space1); }); @@ -1193,7 +1195,7 @@ describe("SpaceStore", () => { }); it("avoids cycles", () => { - const fn = jest.fn(); + const fn = vi.fn(); store.traverseSpace("!b:server", fn); expect(fn).toHaveBeenCalledTimes(3); @@ -1203,7 +1205,7 @@ describe("SpaceStore", () => { }); it("including rooms", () => { - const fn = jest.fn(); + const fn = vi.fn(); store.traverseSpace("!b:server", fn, true); expect(fn).toHaveBeenCalledTimes(8); // twice for shared-child @@ -1217,7 +1219,7 @@ describe("SpaceStore", () => { }); it("excluding rooms", () => { - const fn = jest.fn(); + const fn = vi.fn(); store.traverseSpace("!b:server", fn, false); expect(fn).toHaveBeenCalledTimes(3); @@ -1236,14 +1238,14 @@ describe("SpaceStore", () => { const rootSpace = mkSpace(space1, [room1, room2, space2]); rootSpace.getMyMembership.mockReturnValue(KnownMembership.Invite); client.emit(ClientEvent.Room, rootSpace); - jest.runOnlyPendingTimers(); + vi.runOnlyPendingTimers(); expect(SpaceStore.instance.invitedSpaces).toStrictEqual([rootSpace]); expect(SpaceStore.instance.spacePanelSpaces).toStrictEqual([]); // accept invite to space rootSpace.getMyMembership.mockReturnValue(KnownMembership.Join); client.emit(RoomEvent.MyMembership, rootSpace, KnownMembership.Join, KnownMembership.Invite); - jest.runOnlyPendingTimers(); + vi.runOnlyPendingTimers(); expect(SpaceStore.instance.invitedSpaces).toStrictEqual([]); expect(SpaceStore.instance.spacePanelSpaces).toStrictEqual([rootSpace]); @@ -1252,7 +1254,7 @@ describe("SpaceStore", () => { const rootSpaceRoom1 = mkRoom(room1); rootSpaceRoom1.getMyMembership.mockReturnValue(KnownMembership.Join); client.emit(ClientEvent.Room, rootSpaceRoom1); - jest.runOnlyPendingTimers(); + vi.runOnlyPendingTimers(); expect(SpaceStore.instance.invitedSpaces).toStrictEqual([]); expect(SpaceStore.instance.spacePanelSpaces).toStrictEqual([rootSpace]); expect(SpaceStore.instance.isRoomInSpace(space1, room1)).toBeTruthy(); @@ -1266,7 +1268,7 @@ describe("SpaceStore", () => { const rootSpaceRoom2 = mkRoom(room2); rootSpaceRoom2.getMyMembership.mockReturnValue(KnownMembership.Invite); client.emit(ClientEvent.Room, rootSpaceRoom2); - jest.runOnlyPendingTimers(); + vi.runOnlyPendingTimers(); expect(SpaceStore.instance.invitedSpaces).toStrictEqual([]); expect(SpaceStore.instance.spacePanelSpaces).toStrictEqual([rootSpace]); expect(SpaceStore.instance.isRoomInSpace(space1, room2)).toBeTruthy(); @@ -1302,12 +1304,12 @@ describe("SpaceStore", () => { room: space1, }); client.emit(RoomStateEvent.Members, memberEvent, rootSpace.currentState, dm1Partner); - jest.runOnlyPendingTimers(); + vi.runOnlyPendingTimers(); expect(SpaceStore.instance.getSpaceFilteredUserIds(space1)!.has(dm1Partner.userId)).toBeTruthy(); const dm1Room = mkRoom(dm1); dm1Room.getMyMembership.mockReturnValue(KnownMembership.Join); client.emit(ClientEvent.Room, dm1Room); - jest.runOnlyPendingTimers(); + vi.runOnlyPendingTimers(); expect(SpaceStore.instance.invitedSpaces).toStrictEqual([]); expect(SpaceStore.instance.spacePanelSpaces).toStrictEqual([rootSpace]); expect(SpaceStore.instance.isRoomInSpace(space1, dm1)).toBeTruthy(); @@ -1321,7 +1323,7 @@ describe("SpaceStore", () => { subspace.getMyMembership.mockReturnValue(KnownMembership.Join); const prom = testUtils.emitPromise(SpaceStore.instance, space1); client.emit(ClientEvent.Room, subspace); - jest.runOnlyPendingTimers(); + vi.runOnlyPendingTimers(); expect(SpaceStore.instance.invitedSpaces).toStrictEqual([]); expect(SpaceStore.instance.spacePanelSpaces.map((r) => r.roomId)).toStrictEqual([rootSpace.roomId]); await prom; @@ -1357,7 +1359,7 @@ describe("SpaceStore", () => { describe("when feature_dynamic_room_predecessors is not enabled", () => { beforeAll(() => { - jest.spyOn(SettingsStore, "getValue").mockImplementation( + vi.spyOn(SettingsStore, "getValue").mockImplementation( (settingName) => settingName === "Spaces.allRoomsInHome", ); // @ts-ignore calling a private function @@ -1372,7 +1374,7 @@ describe("SpaceStore", () => { }); beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it("passes that value in calls to getVisibleRooms and getRoomUpgradeHistory during startup", async () => { @@ -1406,7 +1408,7 @@ describe("SpaceStore", () => { describe("when feature_dynamic_room_predecessors is enabled", () => { beforeAll(() => { - jest.spyOn(SettingsStore, "getValue").mockImplementation( + vi.spyOn(SettingsStore, "getValue").mockImplementation( (settingName) => settingName === "Spaces.allRoomsInHome" || settingName === "feature_dynamic_room_predecessors", ); @@ -1422,7 +1424,7 @@ describe("SpaceStore", () => { }); beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it("passes that value in calls to getVisibleRooms and getRoomUpgradeHistory during startup", async () => { @@ -1459,7 +1461,7 @@ describe("SpaceStore", () => { const state = RoomNotificationStateStore.instance.getRoomState(room); // @ts-ignore state._level = NotificationLevel.Notification; - jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({ + vi.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({ spaceId: MetaSpace.Home, sections: [{ tag: DefaultTagID.Untagged, rooms: [room] }], }); diff --git a/apps/web/test/unit-tests/vector/__snapshots__/init-test.ts.snap b/apps/web/src/vector/__snapshots__/init.test.ts.snap similarity index 98% rename from apps/web/test/unit-tests/vector/__snapshots__/init-test.ts.snap rename to apps/web/src/vector/__snapshots__/init.test.ts.snap index 7c5f97eb53..beaaf7d8c4 100644 --- a/apps/web/test/unit-tests/vector/__snapshots__/init-test.ts.snap +++ b/apps/web/src/vector/__snapshots__/init.test.ts.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`showError should match snapshot 1`] = ` +exports[`showError > should match snapshot 1`] = `
@@ -36,7 +36,7 @@ exports[`showError should match snapshot 1`] = `
`; -exports[`showIncompatibleBrowser should match snapshot 1`] = ` +exports[`showIncompatibleBrowser > should match snapshot 1`] = `
diff --git a/apps/web/test/unit-tests/vector/app-test.ts b/apps/web/src/vector/app.test.ts similarity index 63% rename from apps/web/test/unit-tests/vector/app-test.ts rename to apps/web/src/vector/app.test.ts index fa95a0053b..d5478e4cf3 100644 --- a/apps/web/test/unit-tests/vector/app-test.ts +++ b/apps/web/src/vector/app.test.ts @@ -1,8 +1,3 @@ -/** - * @jest-environment jest-fixed-jsdom - * @jest-environment-options {"url": "https://app.element.io/#/room/#room:server"} - */ - /* Copyright 2026 Element Creations Ltd. @@ -10,41 +5,40 @@ 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"; -import { MatrixClient } from "matrix-js-sdk/src/matrix"; -import { Crypto } from "@peculiar/webcrypto"; +// @vitest-environment happy-dom +// @vitest-environment-options {"url": "https://app.element.io/#/room/#room:server"} -import { loadApp } from "../../../src/vector/app.tsx"; -import SdkConfig from "../../../src/SdkConfig.ts"; -import PlatformPeg from "../../../src/PlatformPeg.ts"; -import { mockPlatformPeg, unmockPlatformPeg } from "../../test-utils"; -import { makeDelegatedAuthConfig } from "../../test-utils/oidc"; +import { vi, describe, it, expect, afterAll, beforeEach } from "vitest"; +import fetchMock from "@fetch-mock/vitest"; +import { MatrixClient } from "matrix-js-sdk/src/matrix"; +import { mockPlatformPeg, unmockPlatformPeg } from "test-utils"; +import { makeDelegatedAuthConfig } from "test-utils/oidc"; +import { type RefCallback } from "react"; + +import { loadApp } from "./app.tsx"; +import SdkConfig from "../SdkConfig.ts"; +import PlatformPeg from "../PlatformPeg.ts"; +import type MatrixChat from "../components/structures/MatrixChat.tsx"; const defaultConfig = { default_hs_url: "https://synapse", }; const issuer = "https://auth.org/"; -const webCrypto = new Crypto(); describe("sso_redirect_options", () => { - beforeAll(() => { - Object.defineProperty(window, "crypto", { - value: { - // Stable stub - getRandomValues: (arr: Uint8Array) => { - for (let i = 0; i < arr.length; i++) { - arr[i] = i; - } - return arr; - }, - subtle: webCrypto.subtle, - }, + beforeEach(() => { + // Stable stub + vi.spyOn(window.crypto, "getRandomValues").mockImplementation((arr) => { + for (let i = 0; i < (arr).length; i++) { + (arr)[i] = i; + } + return arr; }); }); beforeEach(() => { SdkConfig.reset(); - mockPlatformPeg({ getDefaultDeviceDisplayName: jest.fn(), startSingleSignOn: jest.fn() }); + mockPlatformPeg({ getDefaultDeviceDisplayName: vi.fn(), startSingleSignOn: vi.fn() }); }); afterAll(() => { @@ -69,9 +63,9 @@ describe("sso_redirect_options", () => { flows: [{ stages: ["m.login.sso"] }], }); - const startSingleSignOnSpy = jest.spyOn(PlatformPeg.get()!, "startSingleSignOn"); + const startSingleSignOnSpy = vi.spyOn(PlatformPeg.get()!, "startSingleSignOn"); - await loadApp({}, jest.fn()); + await loadApp({}, vi.fn() as RefCallback); expect(startSingleSignOnSpy).toHaveBeenCalledWith(expect.any(MatrixClient), "sso", "/room/#room:server"); }); @@ -81,11 +75,11 @@ describe("sso_redirect_options", () => { fetchMock.get(`${authConfig.issuer}.well-known/openid-configuration`, authConfig); fetchMock.get(authConfig.jwks_uri!, { keys: [] }); - const startOidcLoginSpy = jest.spyOn(window.location, "href", "set"); + const startOidcLoginSpy = vi.spyOn(window.location, "href", "set"); - await loadApp({}, jest.fn()); + await loadApp({}, vi.fn() as RefCallback); expect(startOidcLoginSpy).toHaveBeenCalledWith( - "https://auth.org/auth?client_id=12345&redirect_uri=https%3A%2F%2Fapp.element.io%2F%3Fno_universal_links%3Dtrue&response_type=code&scope=openid+urn%3Amatrix%3Aorg.matrix.msc2967.client%3Aapi%3A*+urn%3Amatrix%3Aorg.matrix.msc2967.client%3Adevice%3AwKpa6hpi3Y&nonce=38QgU2Pomx&state=10000000100040008000100000000000&code_challenge=awE81eIsGff70JahvrTqWRbGKLI10ooyo_Xm1sxuZvU&code_challenge_method=S256&response_mode=fragment", + "https://auth.org/auth?client_id=12345&redirect_uri=https%3A%2F%2Fapp.element.io%2F%3Fno_universal_links%3Dtrue&response_type=code&scope=openid+urn%3Amatrix%3Aorg.matrix.msc2967.client%3Aapi%3A*+urn%3Amatrix%3Aorg.matrix.msc2967.client%3Adevice%3AABCDEFGHIJ&nonce=ABCDEFGHIJ&state=10000000100040008000100000000000&code_challenge=awE81eIsGff70JahvrTqWRbGKLI10ooyo_Xm1sxuZvU&code_challenge_method=S256&response_mode=fragment", ); }); }); diff --git a/apps/web/test/unit-tests/vector/init-test.ts b/apps/web/src/vector/init.test.ts similarity index 73% rename from apps/web/test/unit-tests/vector/init-test.ts rename to apps/web/src/vector/init.test.ts index 9c39ba652a..70a9e8f67d 100644 --- a/apps/web/test/unit-tests/vector/init-test.ts +++ b/apps/web/src/vector/init.test.ts @@ -1,8 +1,3 @@ -/** - * @jest-environment jest-fixed-jsdom - * @jest-environment-options {"url": "https://app.element.io/?loginToken=123&no_universal_links&something_else=value#/home?state=abc&code=xyz"} - */ - /* Copyright 2024 New Vector Ltd. @@ -10,13 +5,17 @@ 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"; -import { waitFor, screen } from "jest-matrix-react"; +// @vitest-environment happy-dom +// @vitest-environment-options {"url": "https://app.element.io/?loginToken=123&no_universal_links&something_else=value#/home?state=abc&code=xyz"} -import { loadApp, showError, showIncompatibleBrowser } from "../../../src/vector/init.tsx"; -import SdkConfig from "../../../src/SdkConfig.ts"; -import MatrixChat from "../../../src/components/structures/MatrixChat.tsx"; -import { parseAppUrl } from "../../../src/vector/url_utils.ts"; +import { vi, describe, it, expect, beforeEach } from "vitest"; +import fetchMock from "@fetch-mock/vitest"; +import { waitFor, screen } from "test-utils-rtl"; + +import { loadApp, showError, showIncompatibleBrowser } from "./init.tsx"; +import SdkConfig from "../SdkConfig.ts"; +import MatrixChat from "../components/structures/MatrixChat.tsx"; +import { parseAppUrl } from "./url_utils.ts"; function setUpMatrixChatDiv() { document.getElementById("matrixchat")?.remove(); @@ -29,7 +28,7 @@ describe("showIncompatibleBrowser", () => { beforeEach(setUpMatrixChatDiv); it("should match snapshot", async () => { - await showIncompatibleBrowser(jest.fn()); + await showIncompatibleBrowser(vi.fn()); await screen.findByText("Element does not support this browser"); expect(document.getElementById("matrixchat")).toMatchSnapshot(); }); @@ -59,7 +58,7 @@ describe("loadApp", () => { }); it("should pass onTokenLoginCompleted which strips searchParams & fragment to MatrixChat", async () => { - const spy = jest.spyOn(window.history, "replaceState"); + const spy = vi.spyOn(window.history, "replaceState"); await loadApp({}); await waitFor(() => expect(window.matrixChat).toBeInstanceOf(MatrixChat)); diff --git a/apps/web/test/unit-tests/vector/platform/ElectronPlatform-test.ts b/apps/web/src/vector/platform/ElectronPlatform.test.ts similarity index 91% rename from apps/web/test/unit-tests/vector/platform/ElectronPlatform-test.ts rename to apps/web/src/vector/platform/ElectronPlatform.test.ts index f6ff2db07f..a495773841 100644 --- a/apps/web/test/unit-tests/vector/platform/ElectronPlatform-test.ts +++ b/apps/web/src/vector/platform/ElectronPlatform.test.ts @@ -6,28 +6,31 @@ 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, beforeAll, beforeEach, afterEach, type MockedObject } from "vitest"; import { logger } from "matrix-js-sdk/src/logger"; import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; -import { mocked, type MockedObject } from "jest-mock"; -import { waitFor } from "jest-matrix-react"; +import { waitFor } from "test-utils-rtl"; +import { stubClient } from "test-utils"; +import "vitest-canvas-mock"; -import { UpdateCheckStatus } from "../../../../src/BasePlatform"; -import { Action } from "../../../../src/dispatcher/actions"; -import dispatcher from "../../../../src/dispatcher/dispatcher"; -import * as rageshake from "../../../../src/rageshake/rageshake"; -import { BreadcrumbsStore } from "../../../../src/stores/BreadcrumbsStore"; -import Modal from "../../../../src/Modal"; -import DesktopCapturerSourcePicker from "../../../../src/components/views/elements/DesktopCapturerSourcePicker"; -import ElectronPlatform from "../../../../src/vector/platform/ElectronPlatform"; -import { stubClient } from "../../../test-utils"; -import ToastStore from "../../../../src/stores/ToastStore.ts"; +import { UpdateCheckStatus } from "../../BasePlatform"; +import { Action } from "../../dispatcher/actions"; +import dispatcher from "../../dispatcher/dispatcher"; +import * as rageshake from "../../rageshake/rageshake"; +import { BreadcrumbsStore } from "../../stores/BreadcrumbsStore"; +import Modal from "../../Modal"; +import DesktopCapturerSourcePicker from "../../components/views/elements/DesktopCapturerSourcePicker"; +import ElectronPlatform from "./ElectronPlatform"; +import ToastStore from "../../stores/ToastStore.ts"; -jest.mock("../../../../src/rageshake/rageshake", () => ({ - flush: jest.fn(), +vi.mock("../../rageshake/rageshake", () => ({ + flush: vi.fn(), })); describe("ElectronPlatform", () => { - const initialiseValues = jest.fn().mockReturnValue({ + const initialiseValues = vi.fn().mockReturnValue({ protocol: "io.element.desktop", sessionId: "session-id", config: { _config: true }, @@ -38,23 +41,23 @@ describe("ElectronPlatform", () => { "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 " + "(KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"; const mockElectron = { - on: jest.fn(), - send: jest.fn(), + on: vi.fn(), + send: vi.fn(), initialise: initialiseValues, - setSettingValue: jest.fn().mockResolvedValue(undefined), - getSettingValue: jest.fn().mockResolvedValue(undefined), + setSettingValue: vi.fn().mockResolvedValue(undefined), + getSettingValue: vi.fn().mockResolvedValue(undefined), } as unknown as MockedObject; - const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); - const dispatchFireSpy = jest.spyOn(dispatcher, "fire"); - const logSpy = jest.spyOn(logger, "log").mockImplementation(() => {}); + const dispatchSpy = vi.spyOn(dispatcher, "dispatch"); + const dispatchFireSpy = vi.spyOn(dispatcher, "fire"); + const logSpy = vi.spyOn(logger, "log").mockImplementation(() => {}); const userId = "@alice:server.org"; const deviceId = "device-id"; beforeEach(() => { window.electron = mockElectron; - jest.clearAllMocks(); + vi.clearAllMocks(); Object.defineProperty(window, "navigator", { value: { userAgent: defaultUserAgent }, writable: true }); }); @@ -99,10 +102,10 @@ describe("ElectronPlatform", () => { it("creates a modal on openDesktopCapturerSourcePicker", async () => { const plat = new ElectronPlatform(); - Modal.createDialog = jest.fn(); + Modal.createDialog = vi.fn(); // @ts-ignore mock - mocked(Modal.createDialog).mockReturnValue({ + vi.mocked(Modal.createDialog).mockReturnValue({ finished: new Promise((r) => r(["source"])), }); @@ -111,7 +114,7 @@ describe("ElectronPlatform", () => { res = r; }); // @ts-ignore mock - jest.spyOn(plat.ipc, "call").mockImplementation(() => { + vi.spyOn(plat.ipc, "call").mockImplementation(() => { res(); }); @@ -134,7 +137,7 @@ describe("ElectronPlatform", () => { }, true, ); - const spy = jest.spyOn(ToastStore.sharedInstance(), "addOrReplaceToast"); + const spy = vi.spyOn(ToastStore.sharedInstance(), "addOrReplaceToast"); const [event, handler] = getElectronEventHandlerCall("showToast")!; handler({} as any, { title: "title", description: "description" }); @@ -335,7 +338,7 @@ describe("ElectronPlatform", () => { describe("breadcrumbs", () => { it("should send breadcrumb updates over the IPC", () => { - const spy = jest.spyOn(BreadcrumbsStore.instance, "on"); + const spy = vi.spyOn(BreadcrumbsStore.instance, "on"); new ElectronPlatform(); const cb = spy.mock.calls[0][1]; cb(); @@ -352,9 +355,9 @@ describe("ElectronPlatform", () => { describe("authenticated media", () => { it("should respond to relevant ipc requests", async () => { const cli = stubClient(); - mocked(cli.getAccessToken).mockReturnValue("access_token"); - mocked(cli.getHomeserverUrl).mockReturnValue("homeserver_url"); - mocked(cli.getVersions).mockResolvedValue({ + vi.mocked(cli.getAccessToken).mockReturnValue("access_token"); + vi.mocked(cli.getHomeserverUrl).mockReturnValue("homeserver_url"); + vi.mocked(cli.getVersions).mockResolvedValue({ versions: ["v1.1"], unstable_features: {}, }); @@ -443,7 +446,7 @@ describe("ElectronPlatform", () => { }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it("should send a badge with a notification count", async () => { diff --git a/apps/web/test/unit-tests/vector/platform/PWAPlatform-test.ts b/apps/web/src/vector/platform/PWAPlatform.test.ts similarity index 73% rename from apps/web/test/unit-tests/vector/platform/PWAPlatform-test.ts rename to apps/web/src/vector/platform/PWAPlatform.test.ts index 3ab3e97415..8c6fde8c99 100644 --- a/apps/web/test/unit-tests/vector/platform/PWAPlatform-test.ts +++ b/apps/web/src/vector/platform/PWAPlatform.test.ts @@ -6,21 +6,21 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import { mocked } from "jest-mock"; +import { vi, describe, it, expect, beforeEach } from "vitest"; -import PWAPlatform from "../../../../src/vector/platform/PWAPlatform"; -import WebPlatform from "../../../../src/vector/platform/WebPlatform"; +import PWAPlatform from "./PWAPlatform"; +import WebPlatform from "./WebPlatform"; -jest.mock("../../../../src/vector/platform/WebPlatform"); +vi.mock("./WebPlatform"); describe("PWAPlatform", () => { beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe("setNotificationCount", () => { it("should call Navigator::setAppBadge", () => { - navigator.setAppBadge = jest.fn().mockResolvedValue(undefined); + navigator.setAppBadge = vi.fn().mockResolvedValue(undefined); const platform = new PWAPlatform(); expect(navigator.setAppBadge).not.toHaveBeenCalled(); platform.setNotificationCount(123); @@ -28,7 +28,7 @@ describe("PWAPlatform", () => { }); it("should no-op if the badge count isn't changing", () => { - navigator.setAppBadge = jest.fn().mockResolvedValue(undefined); + navigator.setAppBadge = vi.fn().mockResolvedValue(undefined); const platform = new PWAPlatform(); platform.setNotificationCount(123); expect(navigator.setAppBadge).toHaveBeenCalledTimes(1); @@ -40,14 +40,14 @@ describe("PWAPlatform", () => { // @ts-ignore navigator.setAppBadge = undefined; const platform = new PWAPlatform(); - const superMethod = mocked(WebPlatform.prototype.setNotificationCount); + const superMethod = vi.mocked(WebPlatform.prototype.setNotificationCount); expect(superMethod).not.toHaveBeenCalled(); platform.setNotificationCount(123); expect(superMethod).toHaveBeenCalledWith(123); }); it("should handle Navigator::setAppBadge rejecting gracefully", () => { - navigator.setAppBadge = jest.fn().mockRejectedValue(new Error()); + navigator.setAppBadge = vi.fn().mockRejectedValue(new Error()); const platform = new PWAPlatform(); expect(() => platform.setNotificationCount(123)).not.toThrow(); }); diff --git a/apps/web/test/unit-tests/vector/platform/WebPlatform-test.ts b/apps/web/src/vector/platform/WebPlatform.test.ts similarity index 85% rename from apps/web/test/unit-tests/vector/platform/WebPlatform-test.ts rename to apps/web/src/vector/platform/WebPlatform.test.ts index 438968c78a..9d92f7885c 100644 --- a/apps/web/test/unit-tests/vector/platform/WebPlatform-test.ts +++ b/apps/web/src/vector/platform/WebPlatform.test.ts @@ -6,26 +6,30 @@ 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 { UpdateCheckStatus } from "../../../../src/BasePlatform"; -import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; -import WebPlatform from "../../../../src/vector/platform/WebPlatform"; -import ToastStore from "../../../../src/stores/ToastStore.ts"; -import defaultDispatcher from "../../../../src/dispatcher/dispatcher.ts"; -import { emitPromise } from "../../../test-utils"; -import { Action } from "../../../../src/dispatcher/actions.ts"; +import { vi, describe, it, expect, afterAll, beforeEach } from "vitest"; +import fetchMock from "@fetch-mock/vitest"; +import { emitPromise } from "test-utils/utilities"; +import "vitest-canvas-mock"; + +import { UpdateCheckStatus } from "../../BasePlatform"; +import { MatrixClientPeg } from "../../MatrixClientPeg"; +import WebPlatform from "./WebPlatform"; +import ToastStore from "../../stores/ToastStore.ts"; +import defaultDispatcher from "../../dispatcher/dispatcher.ts"; +import { Action } from "../../dispatcher/actions.ts"; describe("WebPlatform", () => { beforeEach(() => { - jest.spyOn(global, "navigator", "get").mockReturnValue({ + vi.spyOn(global, "navigator", "get").mockReturnValue({ ...navigator, // @ts-expect-error - mocking readonly object serviceWorker: { - register: jest.fn().mockResolvedValue({ - update: jest.fn(), + register: vi.fn().mockResolvedValue({ + update: vi.fn(), }), - addEventListener: jest.fn(), + addEventListener: vi.fn(), }, }); }); @@ -42,7 +46,7 @@ describe("WebPlatform", () => { }); it("handles errors", async () => { - jest.spyOn(global, "navigator", "get").mockReturnValue({ + vi.spyOn(global, "navigator", "get").mockReturnValue({ serviceWorker: { // @ts-expect-error - mocking readonly object register: undefined, @@ -59,7 +63,7 @@ describe("WebPlatform", () => { }); it("should call reload on window location object", () => { - Object.defineProperty(window, "location", { value: { reload: jest.fn() }, writable: true }); + Object.defineProperty(window, "location", { value: { reload: vi.fn() }, writable: true }); const platform = new WebPlatform(); expect(window.location.reload).not.toHaveBeenCalled(); @@ -68,7 +72,7 @@ describe("WebPlatform", () => { }); it("should call reload to install update", () => { - Object.defineProperty(window, "location", { value: { reload: jest.fn() }, writable: true }); + Object.defineProperty(window, "location", { value: { reload: vi.fn() }, writable: true }); const platform = new WebPlatform(); expect(window.location.reload).not.toHaveBeenCalled(); @@ -85,7 +89,7 @@ describe("WebPlatform", () => { "develop.element.io: Chrome on macOS", ], ])("%s & %s = %s", (url, userAgent, result) => { - jest.spyOn(global, "navigator", "get").mockReturnValue({ userAgent } as Navigator); + vi.spyOn(global, "navigator", "get").mockReturnValue({ userAgent } as Navigator); Object.defineProperty(window, "location", { value: { href: url }, writable: true }); const platform = new WebPlatform(); expect(platform.getDefaultDeviceDisplayName()).toEqual(result); @@ -94,7 +98,7 @@ describe("WebPlatform", () => { describe("notification support", () => { const mockNotification = { - requestPermission: jest.fn(), + requestPermission: vi.fn(), permission: "notGranted", }; beforeEach(() => { @@ -136,7 +140,7 @@ describe("WebPlatform", () => { const prodVersion = "1.10.13"; beforeEach(() => { - jest.spyOn(MatrixClientPeg, "userRegisteredWithinLastHours").mockReturnValue(false); + vi.spyOn(MatrixClientPeg, "userRegisteredWithinLastHours").mockReturnValue(false); }); afterAll(() => { @@ -177,8 +181,8 @@ describe("WebPlatform", () => { fetchMock.getOnce("end:/version", prodVersion); const platform = new WebPlatform(); - const showUpdate = jest.fn(); - const showNoUpdate = jest.fn(); + const showUpdate = vi.fn(); + const showNoUpdate = vi.fn(); const result = await platform.pollForUpdate(showUpdate, showNoUpdate); expect(result).toEqual({ status: UpdateCheckStatus.NotAvailable }); @@ -192,8 +196,8 @@ describe("WebPlatform", () => { fetchMock.getOnce("end:/version", `v${prodVersion}`); const platform = new WebPlatform(); - const showUpdate = jest.fn(); - const showNoUpdate = jest.fn(); + const showUpdate = vi.fn(); + const showNoUpdate = vi.fn(); const result = await platform.pollForUpdate(showUpdate, showNoUpdate); // versions only differ by v prefix, no update @@ -210,8 +214,8 @@ describe("WebPlatform", () => { fetchMock.getOnce("end:/version", prodVersion); const platform = new WebPlatform(); - const showUpdate = jest.fn(); - const showNoUpdate = jest.fn(); + const showUpdate = vi.fn(); + const showNoUpdate = vi.fn(); const result = await platform.pollForUpdate(showUpdate, showNoUpdate); expect(result).toEqual({ status: UpdateCheckStatus.Ready }); @@ -223,12 +227,12 @@ describe("WebPlatform", () => { it("should return ready without showing update when user registered in last 24", async () => { // @ts-ignore WebPlatform.VERSION = "0.0.0"; // old version - jest.spyOn(MatrixClientPeg, "userRegisteredWithinLastHours").mockReturnValue(true); + vi.spyOn(MatrixClientPeg, "userRegisteredWithinLastHours").mockReturnValue(true); fetchMock.getOnce("end:/version", prodVersion); const platform = new WebPlatform(); - const showUpdate = jest.fn(); - const showNoUpdate = jest.fn(); + const showUpdate = vi.fn(); + const showNoUpdate = vi.fn(); const result = await platform.pollForUpdate(showUpdate, showNoUpdate); expect(result).toEqual({ status: UpdateCheckStatus.Ready }); @@ -240,8 +244,8 @@ describe("WebPlatform", () => { fetchMock.getOnce("end:/version", { throws: "oups" }); const platform = new WebPlatform(); - const showUpdate = jest.fn(); - const showNoUpdate = jest.fn(); + const showUpdate = vi.fn(); + const showNoUpdate = vi.fn(); const result = await platform.pollForUpdate(showUpdate, showNoUpdate); expect(result).toEqual({ status: UpdateCheckStatus.Error, detail: "Unknown Error" }); @@ -260,7 +264,7 @@ describe("WebPlatform", () => { it("should re-render favicon when setting error status", () => { const platform = new WebPlatform(); - const spy = jest.spyOn(platform.favicon, "badge"); + const spy = vi.spyOn(platform.favicon, "badge"); platform.setErrorStatus(true); expect(spy).toHaveBeenCalledWith(expect.anything(), { bgColor: "#f00" }); }); diff --git a/apps/web/test/unit-tests/widgets/ManagedHybrid-test.ts b/apps/web/src/widgets/ManagedHybrid.test.ts similarity index 72% rename from apps/web/test/unit-tests/widgets/ManagedHybrid-test.ts rename to apps/web/src/widgets/ManagedHybrid.test.ts index 126033f2a5..76b2e2a904 100644 --- a/apps/web/test/unit-tests/widgets/ManagedHybrid-test.ts +++ b/apps/web/src/widgets/ManagedHybrid.test.ts @@ -6,18 +6,21 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ +// @vitest-environment happy-dom + +import { vi, describe, it, expect, beforeEach } from "vitest"; import { Room } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; -import fetchMock from "@fetch-mock/jest"; +import fetchMock from "@fetch-mock/vitest"; +import { stubClient } from "test-utils"; -import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from "../../../src/widgets/ManagedHybrid"; -import { stubClient } from "../../test-utils"; -import SdkConfig from "../../../src/SdkConfig"; -import WidgetUtils from "../../../src/utils/WidgetUtils"; -import { WidgetLayoutStore } from "../../../src/stores/widgets/WidgetLayoutStore"; +import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from "./ManagedHybrid"; +import SdkConfig from "../SdkConfig"; +import WidgetUtils from "../utils/WidgetUtils"; +import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore"; -jest.mock("../../../src/utils/room/getJoinedNonFunctionalMembers", () => ({ - getJoinedNonFunctionalMembers: jest.fn().mockReturnValue([1, 2]), +vi.mock("../utils/room/getJoinedNonFunctionalMembers", () => ({ + getJoinedNonFunctionalMembers: vi.fn().mockReturnValue([1, 2]), })); describe("isManagedHybridWidgetEnabled", () => { @@ -57,8 +60,8 @@ describe("addManagedHybridWidget", () => { }); it("should noop if user lacks permission", async () => { - const logSpy = jest.spyOn(logger, "error").mockImplementation(); - jest.spyOn(WidgetUtils, "canUserModifyWidgets").mockReturnValue(false); + const logSpy = vi.spyOn(logger, "error").mockImplementation(() => {}); + vi.spyOn(WidgetUtils, "canUserModifyWidgets").mockReturnValue(false); fetchMock.mockClear(); await addManagedHybridWidget(room); @@ -67,7 +70,7 @@ describe("addManagedHybridWidget", () => { }); it("should noop if no widget_build_url", async () => { - jest.spyOn(WidgetUtils, "canUserModifyWidgets").mockReturnValue(true); + vi.spyOn(WidgetUtils, "canUserModifyWidgets").mockReturnValue(true); fetchMock.mockClear(); await addManagedHybridWidget(room); @@ -79,9 +82,9 @@ describe("addManagedHybridWidget", () => { widget_id: "WIDGET_ID", widget: { key: "value" }, }); - jest.spyOn(WidgetUtils, "canUserModifyWidgets").mockReturnValue(true); - jest.spyOn(WidgetLayoutStore.instance, "canCopyLayoutToRoom").mockReturnValue(true); - const setRoomWidgetContentSpy = jest.spyOn(WidgetUtils, "setRoomWidgetContent").mockResolvedValue(); + vi.spyOn(WidgetUtils, "canUserModifyWidgets").mockReturnValue(true); + vi.spyOn(WidgetLayoutStore.instance, "canCopyLayoutToRoom").mockReturnValue(true); + const setRoomWidgetContentSpy = vi.spyOn(WidgetUtils, "setRoomWidgetContent").mockResolvedValue(); SdkConfig.put({ widget_build_url: "https://widget-build-url", }); diff --git a/apps/web/test/setup/adapter.ts b/apps/web/test/setup/adapter.ts index 4ec437a5af..34e4dca023 100644 --- a/apps/web/test/setup/adapter.ts +++ b/apps/web/test/setup/adapter.ts @@ -17,7 +17,10 @@ const adapter = { fn: isJest ? (jest.fn as unknown as typeof vi.fn) : vi.fn, spyOn: isJest ? (jest.spyOn as unknown as typeof vi.spyOn) : vi.spyOn, mocked: isJest ? (jestMocked as typeof vi.mocked) : vi.mocked, -} as Pick; + advanceTimersByTime: isJest + ? (jest.advanceTimersByTime as unknown as typeof vi.advanceTimersByTime) + : vi.advanceTimersByTime, +} as Pick; const mocked = adapter.mocked; export { adapter as vi, mocked }; diff --git a/apps/web/test/test-utils/utilities.ts b/apps/web/test/test-utils/utilities.ts index 266b58e0db..6f9f1e68bb 100644 --- a/apps/web/test/test-utils/utilities.ts +++ b/apps/web/test/test-utils/utilities.ts @@ -13,6 +13,7 @@ import { type ActionPayload } from "../../src/dispatcher/payloads"; import defaultDispatcher from "../../src/dispatcher/dispatcher"; import { type DispatcherAction } from "../../src/dispatcher/actions"; import Modal from "../../src/Modal"; +import { vi } from "../setup/adapter.ts"; export const emitPromise = (e: EventEmitter, k: string | symbol) => new Promise((r) => e.once(k, r)); @@ -128,7 +129,7 @@ export const flushPromises = () => act(async () => await new Promise((reso // https://gist.github.com/apieceofbart/e6dea8d884d29cf88cdb54ef14ddbcc4?permalink_comment_id=4018174#gistcomment-4018174 export const flushPromisesWithFakeTimers = async (): Promise => { const promise = new Promise((resolve) => process.nextTick(resolve)); - jest.advanceTimersByTime(1); + vi.advanceTimersByTime(1); await promise; }; @@ -165,8 +166,8 @@ export function waitForUpdate(inst: React.Component, updates = 1): Promise * that also checks timestamps */ export const advanceDateAndTime = (ms: number) => { - jest.spyOn(global.Date, "now").mockReturnValue(Date.now() + ms); - jest.advanceTimersByTime(ms); + vi.spyOn(global.Date, "now").mockReturnValue(Date.now() + ms); + vi.advanceTimersByTime(ms); }; /** @@ -199,8 +200,8 @@ export const clearAllModals = async (): Promise => { export function useMockMediaDevices(): void { // @ts-ignore assignment of a thing that isn't a `MediaDevices` to read-only property navigator["mediaDevices"] = { - enumerateDevices: jest.fn().mockResolvedValue([]), - getUserMedia: jest.fn(), + enumerateDevices: vi.fn().mockResolvedValue([]), + getUserMedia: vi.fn(), }; } @@ -234,7 +235,7 @@ export function resetJsDomAfterEach(): void { // intercept setTimeout and setInterval, and clear them at the end. // - // *Don't* use jest.spyOn for this because it makes the DOM testing library think we are using fake timers. + // *Don't* use vi.spyOn for this because it makes the DOM testing library think we are using fake timers. // ["setTimeout", "setInterval"].forEach((name) => { const originalFn = window[name as keyof Window]; diff --git a/apps/web/vitest.config.ts b/apps/web/vitest.config.ts index 9676745a69..a8a8792b1c 100644 --- a/apps/web/vitest.config.ts +++ b/apps/web/vitest.config.ts @@ -35,6 +35,11 @@ export default defineProject({ find: "./recorderWorkletFactory", replacement: resolve(__dirname, "./__mocks__/empty.js"), }, + // Stub out legacy modules so we don't need to build them first + { + find: "../modules.js", + replacement: resolve(__dirname, "./__mocks__/empty.js"), + }, ], }, test: {