diff --git a/packages/shared-components/.gitignore b/packages/shared-components/.gitignore new file mode 100644 index 0000000000..89873c0f5e --- /dev/null +++ b/packages/shared-components/.gitignore @@ -0,0 +1,2 @@ +# Ignore test failure screenshots +/src/**/__screenshots__/ diff --git a/packages/shared-components/package.json b/packages/shared-components/package.json index fc8187472d..47f1c0eeb5 100644 --- a/packages/shared-components/package.json +++ b/packages/shared-components/package.json @@ -38,7 +38,7 @@ "i18n": "matrix-gen-i18n src && yarn i18n:sort && yarn i18n:lint", "i18n:sort": "matrix-sort-i18n src/i18n/strings/en_EN.json", "i18n:lint": "matrix-i18n-lint && prettier --log-level=silent --write src/i18n/strings/ --ignore-path /dev/null", - "test": "jest", + "test:unit": "vitest --project=unit", "prepare": "patch-package && vite build", "storybook": "storybook dev -p 6007", "build-storybook": "storybook build", @@ -63,7 +63,6 @@ "temporal-polyfill": "^0.3.0" }, "devDependencies": { - "@casualbot/jest-sonar-reporter": "^2.5.0", "@element-hq/element-web-playwright-common": "2.2.4", "@fetch-mock/vitest": "^0.2.18", "@matrix-org/react-sdk-module-api": "^2.5.0", @@ -76,7 +75,9 @@ "@storybook/test-runner": "^0.24.1", "@stylistic/eslint-plugin": "^5.7.0", "@testing-library/dom": "^10.4.1", - "@testing-library/react": "^16.3.0", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", "@types/counterpart": "^0.18.4", "@types/jest-image-snapshot": "^6.4.0", "@types/lodash": "^4.17.20", @@ -86,6 +87,10 @@ "@typescript-eslint/parser": "^8.53.1", "@vector-im/compound-web": "^8.3.5", "concurrently": "^9.2.1", + "@vitejs/plugin-react": "^5.1.2", + "@vitest/browser-playwright": "^4.0.17", + "@vitest/coverage-v8": "^4.0.17", + "@vitest/ui": "^4.0.17", "eslint": "8", "eslint-config-google": "^0.14.0", "eslint-config-prettier": "^10.1.8", @@ -98,18 +103,17 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-storybook": "^10.0.7", "eslint-plugin-unicorn": "^56.0.0", - "identity-obj-proxy": "^3.0.0", - "jest": "^30.2.0", - "jest-environment-jsdom": "^30.2.0", - "jest-fixed-jsdom": "^0.0.11", "jest-image-snapshot": "^6.5.1", "patch-package": "^8.0.1", "prettier": "^3.6.2", "storybook": "^10.0.7", "typescript": "^5.9.3", - "vite": "^7.1.9", + "vite": "^7.3.1", "vite-plugin-dts": "^4.5.4", - "vite-plugin-node-polyfills": "^0.25.0" + "vite-plugin-node-polyfills": "^0.25.0", + "vitest": "^4.0.17", + "vitest-browser-react": "^2.0.2", + "vitest-sonar-reporter": "^3.0.0" }, "engines": { "node": ">=20.0.0" diff --git a/packages/shared-components/src/test/setupTests.ts b/packages/shared-components/src/test/setupTests.ts index e89a40af3b..9b048d272b 100644 --- a/packages/shared-components/src/test/setupTests.ts +++ b/packages/shared-components/src/test/setupTests.ts @@ -5,13 +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 "@testing-library/vitest"; -import fetchMock from "@fetch-mock/jest"; +import fetchMock from "@fetch-mock/vitest"; +import { cleanup } from "@test-utils"; +import { afterEach } from "vitest"; import { setLanguage } from "../../src/utils/i18n"; import en from "../i18n/strings/en_EN.json"; -export function setupLanguageMock(): void { +function setupLanguageMock(): void { fetchMock .get("end:/i18n/languages.json", { en: "en_EN.json", @@ -22,3 +23,7 @@ setupLanguageMock(); fetchMock.mockGlobal(); setLanguage("en"); + +afterEach(() => { + cleanup(); +}); diff --git a/packages/shared-components/src/test/utils/jest-matrix-react.tsx b/packages/shared-components/src/test/utils/index.tsx similarity index 100% rename from packages/shared-components/src/test/utils/jest-matrix-react.tsx rename to packages/shared-components/src/test/utils/index.tsx diff --git a/packages/shared-components/tsconfig.json b/packages/shared-components/tsconfig.json index 6044db4d92..479670c124 100644 --- a/packages/shared-components/tsconfig.json +++ b/packages/shared-components/tsconfig.json @@ -14,9 +14,10 @@ "declaration": true, "jsx": "react", "lib": ["es2022", "es2024.promise", "dom", "dom.iterable"], + "types": [], "strict": true, "paths": { - "jest-matrix-react": ["./src/test/utils/jest-matrix-react"] + "@test-utils": ["./src/test/utils/index"] } }, "include": ["./src/**/*.ts", "./src/**/*.tsx", ".storybook/*.ts", ".storybook/*.tsx"], diff --git a/packages/shared-components/tsconfig.node.json b/packages/shared-components/tsconfig.node.json index 8434738150..7d4eb224bc 100644 --- a/packages/shared-components/tsconfig.node.json +++ b/packages/shared-components/tsconfig.node.json @@ -9,5 +9,5 @@ "types": [], "allowSyntheticDefaultImports": true }, - "include": ["vite.config.ts"] + "include": ["vite.config.ts", "vitest.config.ts"] } diff --git a/packages/shared-components/vitest.config.ts b/packages/shared-components/vitest.config.ts new file mode 100644 index 0000000000..90139a635f --- /dev/null +++ b/packages/shared-components/vitest.config.ts @@ -0,0 +1,101 @@ +/* +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 { defineConfig } from "vitest/config"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { playwright } from "@vitest/browser-playwright"; +import { nodePolyfills } from "vite-plugin-node-polyfills"; +import { InlineConfig } from "vite"; +import { Reporter } from "vitest/reporters"; +import { env } from "process"; + +const reporters: NonNullable["reporters"] = [["default"]]; +const slowTestReporter: Reporter = { + onTestRunEnd(testModules, unhandledErrors, reason) { + const tests = testModules + .flatMap((m) => Array.from(m.children.allTests())) + .filter((test) => test.diagnostic()?.slow); + tests.sort((x, y) => x.diagnostic()?.duration! - y.diagnostic()?.duration!); + tests.reverse(); + if (tests.length > 0) { + console.warn("Slowest 10 tests:"); + } + for (const t of tests.slice(0, 10)) { + console.warn(`${t.module.moduleId} > ${t.fullName}: ${t.diagnostic()?.duration.toFixed(0)}ms`); + } + }, +}; + +// if we're running under GHA, enable the GHA & Sonar reporters +if (env["GITHUB_ACTIONS"] !== undefined) { + reporters.push([ + "github-actions", + { + silent: false, + }, + ]); + + reporters.push([ + "vitest-sonar-reporter", + { + outputFile: "coverage/sonar-report.xml", + onWritePath: (path) => `packages/shared-components/${path}`, + }, + ]); + + // if we're running against the develop branch, also enable the slow test reporter + if (env["GITHUB_REF"] == "refs/heads/develop") { + reporters.push(slowTestReporter); + } +} + +export default defineConfig({ + css: { + modules: { + // Stabilise snapshots by stripping the hash component of the CSS module class name + generateScopedName: (name) => name, + }, + }, + test: { + coverage: { + provider: "v8", + include: ["src/**/*.{ts,tsx}"], + exclude: ["src/**/*.stories.tsx"], + reporter: [["lcov", { projectRoot: "../../" }]], + }, + reporters, + globals: false, + pool: "threads", + projects: [ + { + extends: true, + plugins: [nodePolyfills({ include: ["util"], globals: { global: false } })], + test: { + name: "unit", + browser: { + enabled: true, + headless: true, + provider: playwright({}), + instances: [{ browser: "chromium" }], + }, + setupFiles: ["src/test/setupTests.ts"], + }, + }, + ], + }, + optimizeDeps: { + include: ["vite-plugin-node-polyfills/shims/buffer", "vite-plugin-node-polyfills/shims/process"], + }, + resolve: { + alias: { + "@test-utils": path.resolve(__dirname, "./src/test/utils/index.tsx"), + }, + }, +});