Use vitest for some EW unit tests (#33816)

* Use vitest for some EW unit tests

* Ensure library builds are done before unit tests

* Stabilise jest tests

* Move more tests over

* Make sonar happier

* Update types/node for happy-dom compat again

* Decrease max-workers to stabilise jest tests

* Split jest over 3 runners to alleviate memory woes

* Switch jest to runInBand

* Attempt to deflake jest tests

* tweak coverage

* tweak coverage
This commit is contained in:
Michael Telatynski
2026-06-15 14:24:33 +01:00
committed by GitHub
parent 7949980a7e
commit 2f4e6a4ec4
37 changed files with 475 additions and 326 deletions
+1 -1
View File
@@ -232,7 +232,7 @@ module.exports = {
},
},
{
files: ["test/**/*.{ts,tsx}", "playwright/**/*.ts"],
files: ["src/**/*.test.{ts,tsx}", "test/**/*.{ts,tsx}", "playwright/**/*.ts"],
extends: ["plugin:matrix-org/jest"],
rules: {
// We don't need super strict typing in test utilities
+4 -1
View File
@@ -52,16 +52,19 @@ const config: Config = {
],
collectCoverageFrom: [
"<rootDir>/src/**/*.{js,ts,tsx}",
"<rootDir>/packages/**/*.{js,ts,tsx}",
// getSessionLock is piped into a different JS context via stringification, and the coverage functionality is
// not available in that contest. So, turn off coverage instrumentation for it.
"!<rootDir>/src/utils/SessionLock.ts",
// Coverage chokes on type definition files
"!<rootDir>/src/**/*.d.ts",
// Ignore vitest tests
"!<rootDir>/src/**/*.test.{ts,tsx}",
"!<rootDir>/src/test/**",
],
coverageReporters: ["text-summary", ["lcov", { projectRoot: "../../" }]],
prettierPath: null,
moduleDirectories: ["node_modules", "test/test-utils"],
workerIdleMemoryLimit: "512MB",
};
// if we're running under GHA, enable relevant reporters
+4
View File
@@ -30,6 +30,7 @@
"lint:types": "nx lint:types",
"lint:style": "stylelint \"res/css/**/*.pcss\"",
"test": "nx test:unit",
"test:vitest": "nx test:vitest",
"test:playwright": "nx test:playwright --",
"test:playwright:open": "nx test:playwright -- --ui",
"test:playwright:screenshots": "nx test:playwright:screenshots --",
@@ -129,6 +130,7 @@
"@element-hq/element-call-embedded": "0.20.1",
"@element-hq/element-web-playwright-common": "workspace:*",
"@fetch-mock/jest": "^0.2.20",
"@fetch-mock/vitest": "^0.2.18",
"@jest/globals": "^30.2.0",
"@peculiar/webcrypto": "^1.4.3",
"@playwright/test": "catalog:",
@@ -190,6 +192,7 @@
"express": "^5.0.0",
"fake-indexeddb": "^6.0.0",
"file-loader": "^6.0.0",
"happy-dom": "^20.10.2",
"html-webpack-plugin": "^5.5.3",
"identity-obj-proxy": "^3.0.0",
"jest": "^30.0.0",
@@ -226,6 +229,7 @@
"testcontainers": "^12.0.0",
"typescript": "catalog:",
"util": "^0.12.5",
"vitest": "catalog:",
"web-streams-polyfill": "^4.0.0",
"webpack": "^5.89.0",
"webpack-bundle-analyzer": "^5.0.0",
+10 -1
View File
@@ -46,11 +46,20 @@
},
"dependsOn": ["^build", "^build:playwright"]
},
"test:unit:prepare": {
"executor": "nx:noop",
"dependsOn": ["^build"]
},
"test:unit": {
// We avoid the jest executor because it doesn't seem to give any benefit, and it mangles the summary of failing tests.
"command": "jest",
"options": { "cwd": "apps/web" },
"dependsOn": ["^build"]
"dependsOn": ["test:unit:prepare"]
},
"test:vitest": {
"command": "vitest run",
"options": { "cwd": "apps/web" },
"dependsOn": ["test:unit:prepare"]
},
"test:playwright": {
"command": "playwright test",
@@ -5,9 +5,12 @@ 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 EventEmitter from "events";
// @vitest-environment happy-dom
import UserActivity from "../../src/UserActivity";
import { vi, describe, it, expect, beforeEach, afterEach } from "vitest";
import EventEmitter from "node:events";
import UserActivity from "./UserActivity";
class FakeDomEventEmitter extends EventEmitter {
addEventListener(what: string, l: (...args: any[]) => void) {
@@ -29,7 +32,7 @@ describe("UserActivity", function () {
fakeDocument = new FakeDomEventEmitter();
userActivity = new UserActivity(fakeWindow as unknown as Window, fakeDocument as unknown as Document);
userActivity.start();
jest.useFakeTimers();
vi.useFakeTimers();
});
afterEach(function () {
@@ -49,7 +52,7 @@ describe("UserActivity", function () {
});
it("should not consider user active after activity if no window focus", function () {
fakeDocument.hasFocus = jest.fn().mockReturnValue(false);
fakeDocument.hasFocus = vi.fn().mockReturnValue(false);
userActivity.onUserActivity({ type: "event" } as Event);
expect(userActivity.userActiveNow()).toBe(false);
@@ -57,62 +60,62 @@ describe("UserActivity", function () {
});
it("should consider user active shortly after activity", function () {
fakeDocument.hasFocus = jest.fn().mockReturnValue(true);
fakeDocument.hasFocus = vi.fn().mockReturnValue(true);
userActivity.onUserActivity({ type: "event" } as Event);
expect(userActivity.userActiveNow()).toBe(true);
expect(userActivity.userActiveRecently()).toBe(true);
jest.advanceTimersByTime(200);
vi.advanceTimersByTime(200);
expect(userActivity.userActiveNow()).toBe(true);
expect(userActivity.userActiveRecently()).toBe(true);
});
it("should consider user not active after 10s of no activity", function () {
fakeDocument.hasFocus = jest.fn().mockReturnValue(true);
fakeDocument.hasFocus = vi.fn().mockReturnValue(true);
userActivity.onUserActivity({ type: "event" } as Event);
jest.advanceTimersByTime(10000);
vi.advanceTimersByTime(10000);
expect(userActivity.userActiveNow()).toBe(false);
});
it("should consider user passive after 10s of no activity", function () {
fakeDocument.hasFocus = jest.fn().mockReturnValue(true);
fakeDocument.hasFocus = vi.fn().mockReturnValue(true);
userActivity.onUserActivity({ type: "event" } as Event);
jest.advanceTimersByTime(10000);
vi.advanceTimersByTime(10000);
expect(userActivity.userActiveRecently()).toBe(true);
});
it("should not consider user passive after 10s if window un-focused", function () {
fakeDocument.hasFocus = jest.fn().mockReturnValue(true);
fakeDocument.hasFocus = vi.fn().mockReturnValue(true);
userActivity.onUserActivity({ type: "event" } as Event);
jest.advanceTimersByTime(10000);
vi.advanceTimersByTime(10000);
fakeDocument.hasFocus = jest.fn().mockReturnValue(false);
fakeDocument.hasFocus = vi.fn().mockReturnValue(false);
fakeWindow.emit("blur", {});
expect(userActivity.userActiveRecently()).toBe(false);
});
it("should not consider user passive after 3 mins", function () {
fakeDocument.hasFocus = jest.fn().mockReturnValue(true);
fakeDocument.hasFocus = vi.fn().mockReturnValue(true);
userActivity.onUserActivity({ type: "event" } as Event);
jest.advanceTimersByTime(3 * 60 * 1000);
vi.advanceTimersByTime(3 * 60 * 1000);
expect(userActivity.userActiveRecently()).toBe(false);
});
it("should extend timer on activity", function () {
fakeDocument.hasFocus = jest.fn().mockReturnValue(true);
fakeDocument.hasFocus = vi.fn().mockReturnValue(true);
userActivity.onUserActivity({ type: "event" } as Event);
jest.advanceTimersByTime(1 * 60 * 1000);
vi.advanceTimersByTime(1 * 60 * 1000);
userActivity.onUserActivity({ type: "event" } as Event);
jest.advanceTimersByTime(1 * 60 * 1000);
vi.advanceTimersByTime(1 * 60 * 1000);
userActivity.onUserActivity({ type: "event" } as Event);
jest.advanceTimersByTime(1 * 60 * 1000);
vi.advanceTimersByTime(1 * 60 * 1000);
expect(userActivity.userActiveRecently()).toBe(true);
});
@@ -6,11 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { WorkerManager } from "../../src/WorkerManager";
import { vi, describe, it, expect } from "vitest";
import { WorkerManager } from "./WorkerManager";
describe("WorkerManager", () => {
it("should generate consecutive sequence numbers for each call", () => {
const postMessage = jest.fn();
const postMessage = vi.fn();
const manager = new WorkerManager({ postMessage } as unknown as Worker);
manager.call({ data: "One" });
@@ -27,7 +29,7 @@ describe("WorkerManager", () => {
});
it("should support resolving out of order", async () => {
const postMessage = jest.fn();
const postMessage = vi.fn();
const worker = { postMessage } as unknown as Worker;
const manager = new WorkerManager(worker);
@@ -6,7 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { looksValid } from "../../src/email";
import { describe, it, expect } from "vitest";
import { looksValid } from "./email";
describe("looksValid", () => {
it.each([
+18
View File
@@ -0,0 +1,18 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { beforeEach } from "vitest";
import fetchMock, { manageFetchMockGlobally } from "@fetch-mock/vitest";
manageFetchMockGlobally();
beforeEach(() => {
// set up fetch API mock
fetchMock.hardReset();
fetchMock.catch(404);
fetchMock.mockGlobal();
});
@@ -6,7 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { lerp } from "../../../src/utils/AnimationUtils";
import { describe, it, expect } from "vitest";
import { lerp } from "./AnimationUtils";
describe("lerp", () => {
it("correctly interpolates", () => {
@@ -6,7 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { FixedRollingArray } from "../../../src/utils/FixedRollingArray";
import { describe, it, expect } from "vitest";
import { FixedRollingArray } from "./FixedRollingArray";
describe("FixedRollingArray", () => {
it("should seed the array with the given value", () => {
@@ -6,10 +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 fs from "fs";
import path from "path";
import { describe, it, expect } from "vitest";
import fs from "node:fs";
import path from "node:path";
import { blobIsAnimated, mayBeAnimated } from "../../src/utils/Image";
import { blobIsAnimated, mayBeAnimated } from "./Image";
const imagesDir = path.resolve(__dirname, "../../test/unit-tests/images");
describe("Image", () => {
describe("mayBeAnimated", () => {
@@ -32,28 +35,28 @@ describe("Image", () => {
describe("blobIsAnimated", () => {
it("Animated GIF", async () => {
const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "animated-logo.gif")).slice()], {
const img = new Blob([fs.readFileSync(path.resolve(imagesDir, "animated-logo.gif")).slice()], {
type: "image/gif",
});
expect(await blobIsAnimated(img)).toBeTruthy();
});
it("Static GIF", async () => {
const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "static-logo.gif")).slice()], {
const img = new Blob([fs.readFileSync(path.resolve(imagesDir, "static-logo.gif")).slice()], {
type: "image/gif",
});
expect(await blobIsAnimated(img)).toBeFalsy();
});
it("Animated WEBP", async () => {
const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "animated-logo.webp")).slice()], {
const img = new Blob([fs.readFileSync(path.resolve(imagesDir, "animated-logo.webp")).slice()], {
type: "image/webp",
});
expect(await blobIsAnimated(img)).toBeTruthy();
});
it("Static WEBP", async () => {
const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "static-logo.webp")).slice()], {
const img = new Blob([fs.readFileSync(path.resolve(imagesDir, "static-logo.webp")).slice()], {
type: "image/webp",
});
expect(await blobIsAnimated(img)).toBeFalsy();
@@ -61,14 +64,14 @@ describe("Image", () => {
it("Static WEBP in extended file format", async () => {
const img = new Blob(
[fs.readFileSync(path.resolve(__dirname, "images", "static-logo-extended-file-format.webp")).slice()],
[fs.readFileSync(path.resolve(imagesDir, "static-logo-extended-file-format.webp")).slice()],
{ type: "image/webp" },
);
expect(await blobIsAnimated(img)).toBeFalsy();
});
it("Animated PNG", async () => {
const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "animated-logo.apng")).slice()]);
const img = new Blob([fs.readFileSync(path.resolve(imagesDir, "animated-logo.apng")).slice()]);
const pngBlob = img.slice(0, img.size, "image/png");
const apngBlob = img.slice(0, img.size, "image/apng");
expect(await blobIsAnimated(pngBlob)).toBeTruthy();
@@ -76,7 +79,7 @@ describe("Image", () => {
});
it("Static PNG", async () => {
const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "static-logo.png")).slice()]);
const img = new Blob([fs.readFileSync(path.resolve(imagesDir, "static-logo.png")).slice()]);
const pngBlob = img.slice(0, img.size, "image/png");
const apngBlob = img.slice(0, img.size, "image/apng");
expect(await blobIsAnimated(pngBlob)).toBeFalsy();
@@ -6,21 +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.
*/
import { vi, describe, it, expect, beforeEach } from "vitest";
import { logger } from "matrix-js-sdk/src/logger";
import { LruCache } from "../../../src/utils/LruCache";
import { LruCache } from "./LruCache";
describe("LruCache", () => {
it("when creating a cache with negative capacity it should raise an error", () => {
expect(() => {
new LruCache(-23);
}).toThrow("Cache capacity must be at least 1");
expect(() => new LruCache(-23)).toThrow("Cache capacity must be at least 1");
});
it("when creating a cache with 0 capacity it should raise an error", () => {
expect(() => {
new LruCache(0);
}).toThrow("Cache capacity must be at least 1");
expect(() => new LruCache(0)).toThrow("Cache capacity must be at least 1");
});
describe("when there is a cache with a capacity of 3", () => {
@@ -72,7 +69,7 @@ describe("LruCache", () => {
});
it("when an error occurs while setting an item the cache should be cleard", () => {
jest.spyOn(logger, "warn");
vi.spyOn(logger, "warn");
const err = new Error("Something weng wrong :(");
// @ts-ignore
@@ -6,7 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { doesRoomVersionSupport, PreferredRoomVersions } from "../../src/utils/PreferredRoomVersions";
import { describe, it, expect } from "vitest";
import { doesRoomVersionSupport, PreferredRoomVersions } from "./PreferredRoomVersions";
describe("doesRoomVersionSupport", () => {
it("should detect unstable as unsupported", () => {
@@ -6,7 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { SnakedObject, snakeToCamel } from "../../../src/utils/SnakedObject";
import { describe, it, expect } from "vitest";
import { SnakedObject, snakeToCamel } from "./SnakedObject";
describe("snakeToCamel", () => {
it("should convert snake_case to camelCase in simple scenarios", () => {
@@ -6,7 +6,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.
*/
import { abbreviateUrl, parseUrl, unabbreviateUrl } from "../../../src/utils/UrlUtils";
// @vitest-environment happy-dom
import { describe, it, expect } from "vitest";
import { abbreviateUrl, parseUrl, unabbreviateUrl } from "./UrlUtils";
describe("abbreviateUrl", () => {
it("should return empty string if passed falsey", () => {
@@ -6,6 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { vi, describe, it, expect } from "vitest";
import {
arrayDiff,
arrayFastClone,
@@ -25,7 +27,7 @@ import {
asyncSome,
asyncSomeParallel,
asyncFilter,
} from "../../../src/utils/arrays";
} from "./arrays";
type TestParams = { input: number[]; output: number[] };
type TestCase = [string, TestParams];
@@ -412,11 +414,11 @@ describe("arrays", () => {
describe("asyncEvery", () => {
it("when called with an empty array, it should return true", async () => {
expect(await asyncEvery([], jest.fn().mockResolvedValue(true))).toBe(true);
expect(await asyncEvery([], vi.fn().mockResolvedValue(true))).toBe(true);
});
it("when called with some items and the predicate resolves to true for all of them, it should return true", async () => {
const predicate = jest.fn().mockResolvedValue(true);
const predicate = vi.fn().mockResolvedValue(true);
expect(await asyncEvery([1, 2, 3], predicate)).toBe(true);
expect(predicate).toHaveBeenCalledTimes(3);
expect(predicate).toHaveBeenCalledWith(1);
@@ -425,14 +427,14 @@ describe("arrays", () => {
});
it("when called with some items and the predicate resolves to false for all of them, it should return false", async () => {
const predicate = jest.fn().mockResolvedValue(false);
const predicate = vi.fn().mockResolvedValue(false);
expect(await asyncEvery([1, 2, 3], predicate)).toBe(false);
expect(predicate).toHaveBeenCalledTimes(1);
expect(predicate).toHaveBeenCalledWith(1);
});
it("when called with some items and the predicate resolves to false for one of them, it should return false", async () => {
const predicate = jest.fn().mockResolvedValueOnce(true).mockResolvedValueOnce(false);
const predicate = vi.fn().mockResolvedValueOnce(true).mockResolvedValueOnce(false);
expect(await asyncEvery([1, 2, 3], predicate)).toBe(false);
expect(predicate).toHaveBeenCalledTimes(2);
expect(predicate).toHaveBeenCalledWith(1);
@@ -442,11 +444,11 @@ describe("arrays", () => {
describe("asyncSome", () => {
it("when called with an empty array, it should return false", async () => {
expect(await asyncSome([], jest.fn().mockResolvedValue(true))).toBe(false);
expect(await asyncSome([], vi.fn().mockResolvedValue(true))).toBe(false);
});
it("when called with some items and the predicate resolves to false for all of them, it should return false", async () => {
const predicate = jest.fn().mockResolvedValue(false);
const predicate = vi.fn().mockResolvedValue(false);
expect(await asyncSome([1, 2, 3], predicate)).toBe(false);
expect(predicate).toHaveBeenCalledTimes(3);
expect(predicate).toHaveBeenCalledWith(1);
@@ -455,7 +457,7 @@ describe("arrays", () => {
});
it("when called with some items and the predicate resolves to true, it should short-circuit and return true", async () => {
const predicate = jest.fn().mockResolvedValueOnce(false).mockResolvedValueOnce(true);
const predicate = vi.fn().mockResolvedValueOnce(false).mockResolvedValueOnce(true);
expect(await asyncSome([1, 2, 3], predicate)).toBe(true);
expect(predicate).toHaveBeenCalledTimes(2);
expect(predicate).toHaveBeenCalledWith(1);
@@ -465,30 +467,30 @@ describe("arrays", () => {
describe("asyncSomeParallel", () => {
it("when called with an empty array, it should return false", async () => {
expect(await asyncSomeParallel([], jest.fn().mockResolvedValue(true))).toBe(false);
expect(await asyncSomeParallel([], vi.fn().mockResolvedValue(true))).toBe(false);
});
it("when all the predicates return false", async () => {
expect(await asyncSomeParallel([1, 2, 3], jest.fn().mockResolvedValue(false))).toBe(false);
expect(await asyncSomeParallel([1, 2, 3], vi.fn().mockResolvedValue(false))).toBe(false);
});
it("when all the predicates return true", async () => {
expect(await asyncSomeParallel([1, 2, 3], jest.fn().mockResolvedValue(true))).toBe(true);
expect(await asyncSomeParallel([1, 2, 3], vi.fn().mockResolvedValue(true))).toBe(true);
});
it("when one of the predicate return true", async () => {
const predicate = jest.fn().mockImplementation((value) => Promise.resolve(value === 2));
const predicate = vi.fn().mockImplementation((value) => Promise.resolve(value === 2));
expect(await asyncSomeParallel([1, 2, 3], predicate)).toBe(true);
});
});
describe("asyncFilter", () => {
it("when called with an empty array, it should return an empty array", async () => {
expect(await asyncFilter([], jest.fn().mockResolvedValue(true))).toEqual([]);
expect(await asyncFilter([], vi.fn().mockResolvedValue(true))).toEqual([]);
});
it("should filter the content", async () => {
const predicate = jest.fn().mockImplementation((value) => Promise.resolve(value === 2));
const predicate = vi.fn().mockImplementation((value) => Promise.resolve(value === 2));
expect(await asyncFilter([1, 2, 3], predicate)).toEqual([2]);
});
});
@@ -6,7 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { textToHtmlRainbow } from "../../../src/utils/colour";
import { describe, it, expect } from "vitest";
import { textToHtmlRainbow } from "./colour";
describe("textToHtmlRainbow", () => {
it("correctly transform text to html without splitting the emoji in two", () => {
@@ -6,16 +6,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 { vi, describe, it, expect, beforeEach, type Mock } from "vitest";
import { type ClientEvent, type ClientEventHandlerMap, SyncState } from "matrix-js-sdk/src/matrix";
import { createReconnectedListener } from "../../../src/utils/connection";
import { createReconnectedListener } from "./connection";
describe("createReconnectedListener", () => {
let reconnectedListener: ClientEventHandlerMap[ClientEvent.Sync];
let onReconnect: jest.Mock;
let onReconnect: Mock;
beforeEach(() => {
onReconnect = jest.fn();
onReconnect = vi.fn();
reconnectedListener = createReconnectedListener(onReconnect);
});
@@ -6,7 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { getEnumValues, isEnumValue } from "../../../src/utils/enums";
import { describe, it, expect } from "vitest";
import { getEnumValues, isEnumValue } from "./enums";
enum TestStringEnum {
First = "__first__",
@@ -6,7 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { iterableDiff, iterableIntersection } from "../../../src/utils/iterables";
import { describe, it, expect } from "vitest";
import { iterableDiff, iterableIntersection } from "./iterables";
describe("iterables", () => {
describe("iterableIntersection", () => {
@@ -5,7 +5,9 @@
* Please see LICENSE files in the repository root for full details.
*/
import { keepIfSame } from "../../../src/utils/keepIfSame";
import { describe, it, expect } from "vitest";
import { keepIfSame } from "./keepIfSame";
describe("keepIfSame", () => {
it("returns the next value if the current and next values are not deeply equal", () => {
@@ -6,6 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { describe, it, expect } from "vitest";
import {
objectClone,
objectDiff,
@@ -14,7 +16,7 @@ import {
objectKeyChanges,
objectShallowClone,
objectWithOnly,
} from "../../../src/utils/objects";
} from "./objects";
describe("objects", () => {
describe("objectExcluding", () => {
@@ -6,9 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
// @vitest-environment happy-dom
import { vi, describe, it, expect, beforeEach } from "vitest";
import { type IdTokenClaims } from "oidc-client-ts";
import { decodeIdToken } from "matrix-js-sdk/src/matrix";
import { mocked } from "jest-mock";
import {
getStoredOidcClientId,
@@ -16,13 +18,13 @@ import {
getStoredOidcIdTokenClaims,
getStoredOidcTokenIssuer,
persistOidcAuthenticatedSettings,
} from "../../../../src/utils/oidc/persistOidcSettings";
} from "./persistOidcSettings";
jest.mock("matrix-js-sdk/src/matrix");
vi.mock("matrix-js-sdk/src/matrix");
describe("persist OIDC settings", () => {
jest.spyOn(Storage.prototype, "getItem");
jest.spyOn(Storage.prototype, "setItem");
vi.spyOn(localStorage, "getItem");
vi.spyOn(localStorage, "setItem");
beforeEach(() => {
localStorage.clear();
@@ -101,7 +103,7 @@ describe("persist OIDC settings", () => {
it("should return claims extracted from id_token in localStorage", () => {
localStorage.setItem("mx_oidc_id_token", idToken);
mocked(decodeIdToken).mockReturnValue(idTokenClaims);
vi.mocked(decodeIdToken).mockReturnValue(idTokenClaims);
expect(getStoredOidcIdTokenClaims()).toEqual(idTokenClaims);
expect(decodeIdToken).toHaveBeenCalledWith(idToken);
expect(localStorage.getItem).toHaveBeenCalledWith("mx_oidc_id_token_claims");
@@ -5,7 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { getManageDeviceUrl } from "../../../../src/utils/oidc/urls";
import { describe, it, expect } from "vitest";
import { getManageDeviceUrl } from "./urls";
describe("OIDC urls", () => {
const accountManagementEndpoint = "https://auth.com/manage";
@@ -5,11 +5,13 @@
* Please see LICENSE files in the repository root for full details.
*/
import { batch } from "../../../src/utils/promise.ts";
import { vi, describe, it, expect, afterEach } from "vitest";
import { batch } from "./promise.ts";
describe("promise.ts", () => {
describe("batch", () => {
afterEach(() => jest.useRealTimers());
afterEach(() => vi.useRealTimers());
it("should batch promises into groups of a given size", async () => {
const promises = [() => Promise.resolve(1), () => Promise.resolve(2), () => Promise.resolve(3)];
@@ -19,7 +21,7 @@ describe("promise.ts", () => {
});
it("should wait for the current batch to finish to request the next one", async () => {
jest.useFakeTimers();
vi.useFakeTimers();
let promise1Called = false;
const promise1 = () =>
@@ -49,7 +51,7 @@ describe("promise.ts", () => {
expect(promise2Called).toBe(true);
expect(promise3Called).toBe(false);
jest.advanceTimersByTime(11);
vi.advanceTimersByTime(11);
expect(await batchPromise).toEqual([1, 2, 3]);
});
});
@@ -6,7 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { setHasDiff } from "../../../src/utils/sets";
import { describe, it, expect } from "vitest";
import { setHasDiff } from "./sets";
describe("sets", () => {
describe("setHasDiff", () => {
@@ -6,10 +6,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.
*/
import { describe, it, expect } from "vitest";
import { sortBy } from "lodash";
import { averageBetweenStrings, DEFAULT_ALPHABET } from "matrix-js-sdk/src/utils";
import { midPointsBetweenStrings, reorderLexicographically } from "../../../src/utils/stringOrderField";
import { midPointsBetweenStrings, reorderLexicographically } from "./stringOrderField";
const moveLexicographicallyTest = (
orders: Array<string | undefined>,
@@ -6,7 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { validateNumberInRange } from "../../../../src/utils/validate";
import { describe, it, expect } from "vitest";
import { validateNumberInRange } from "./numberInRange";
describe("validateNumberInRange", () => {
const min = 1;
@@ -6,9 +6,12 @@ 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 { getVectorConfig } from "../../../src/vector/getconfig";
import { vi, describe, it, expect, beforeEach, afterAll } from "vitest";
import fetchMock from "@fetch-mock/vitest";
import { getVectorConfig } from "./getconfig";
describe("getVectorConfig()", () => {
const elementDomain = "app.element.io";
@@ -27,13 +30,13 @@ describe("getVectorConfig()", () => {
});
// stable value for cachebuster
jest.spyOn(Date, "now").mockReturnValue(now);
jest.clearAllMocks();
vi.spyOn(Date, "now").mockReturnValue(now);
vi.clearAllMocks();
fetchMock.removeRoutes();
});
afterAll(() => {
jest.spyOn(Date, "now").mockRestore();
vi.spyOn(Date, "now").mockRestore();
});
it("requests specific config for document domain", async () => {
@@ -6,16 +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.
*/
import { getInitialScreenAfterLogin, init, onNewScreen } from "../../../src/vector/routing";
import type MatrixChat from "../../../src/components/structures/MatrixChat.tsx";
// @vitest-environment happy-dom
import { vi, describe, it, expect, beforeEach, afterAll } from "vitest";
import { getInitialScreenAfterLogin, init, onNewScreen } from "./routing";
import type MatrixChat from "../components/structures/MatrixChat.tsx";
describe("onNewScreen", () => {
it("should replace history if stripping via fields", () => {
Object.defineProperty(window, "location", {
value: {
hash: "#/room/!room:server?via=abc",
replace: jest.fn(),
assign: jest.fn(),
replace: vi.fn(),
assign: vi.fn(),
},
writable: true,
});
@@ -28,8 +32,8 @@ describe("onNewScreen", () => {
Object.defineProperty(window, "location", {
value: {
hash: "#/room/!room1:server?via=abc",
replace: jest.fn(),
assign: jest.fn(),
replace: vi.fn(),
assign: vi.fn(),
},
writable: true,
});
@@ -41,8 +45,8 @@ describe("onNewScreen", () => {
describe("getInitialScreenAfterLogin", () => {
beforeEach(() => {
jest.spyOn(sessionStorage.__proto__, "getItem").mockClear().mockReturnValue(null);
jest.spyOn(sessionStorage.__proto__, "setItem").mockClear();
vi.spyOn(sessionStorage, "getItem").mockClear().mockReturnValue(null);
vi.spyOn(sessionStorage, "setItem").mockClear();
});
const makeMockLocation = (hash = "") => {
@@ -65,7 +69,7 @@ describe("getInitialScreenAfterLogin", () => {
const screen = {
screen: "/room/!test",
};
jest.spyOn(sessionStorage.__proto__, "getItem").mockReturnValue(JSON.stringify(screen));
vi.spyOn(sessionStorage, "getItem").mockReturnValue(JSON.stringify(screen));
expect(getInitialScreenAfterLogin(makeMockLocation())).toEqual(screen);
});
});
@@ -111,7 +115,7 @@ describe("init", () => {
});
window.matrixChat = {
showScreen: jest.fn(),
showScreen: vi.fn(),
} as unknown as MatrixChat;
init();
@@ -5,7 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { parseAppUrl, parseQsFromFragment, searchParamsToQueryDict } from "../../../src/vector/url_utils";
import { describe, it, expect } from "vitest";
import { parseAppUrl, parseQsFromFragment, searchParamsToQueryDict } from "./url_utils";
// @ts-ignore
const location: Location = {
+18
View File
@@ -0,0 +1,18 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { defineProject } from "vitest/config";
export default defineProject({
test: {
include: ["src/**/*.test.{ts,tsx}"],
environment: "node",
pool: "threads",
globals: false,
setupFiles: ["src/test/setupTests.ts"],
},
});