You've already forked element-web
mirror of
https://github.com/element-hq/element-web.git
synced 2026-10-24 23:19:26 +03:00
* Strip CSS module hashes from apps/web snapshots shared-components and compound-web build their CSS module class names with postcss-modules' default `_<class>_<hash of the stylesheet>_<line>`, so any edit to a stylesheet renames every class in it and changes every apps/web snapshot that renders it. Two unrelated PRs touching the same stylesheet then always conflict in those snapshots. Add a snapshot serializer, alongside the React useId one, that reduces those names to `_<class>` in DOM and string snapshots. The shipped class names keep their content hash, so two copies of the library loaded at once (e.g. by a module) stay isolated. Signed-off-by: David Langley <davidl@element.io> * Update snapshots for the CSS module hash serializer Signed-off-by: David Langley <davidl@element.io> * Only strip CSS module hashes inside class attributes The serializer ran its regex over the whole snapshot, so text or other attributes shaped like a CSS module name would have been shortened too. Restrict it to entries of `class="…"` lists, which is where DOM and HTML string snapshots put class names, and match each entry whole. String snapshots are now picked up by a cheap `class="` check, which also removes the `lastIndex` reset the global regex needed. Signed-off-by: David Langley <davidl@element.io> * Explain the CSS module class regexes Signed-off-by: David Langley <davidl@element.io> * Strip CSS module hashes by splitting on underscores Rather than matching the exact `_<class>_<hash>_<line>` shape, drop the last two `_`-separated parts of any class that starts with `_` and has all three parts. Simpler to read, and produces the same snapshots. Signed-off-by: David Langley <davidl@element.io> --------- Signed-off-by: David Langley <davidl@element.io>
172 lines
7.3 KiB
TypeScript
172 lines
7.3 KiB
TypeScript
/*
|
|
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 { fileURLToPath } from "node:url";
|
|
import { defineProject } from "vitest/config";
|
|
import svgr from "vite-plugin-svgr";
|
|
import { playwright } from "@vitest/browser-playwright";
|
|
import { env, platform } from "node:process";
|
|
|
|
import rootConfig from "../../vitest.config.ts";
|
|
|
|
function resolve(specifier: string): string {
|
|
return fileURLToPath(import.meta.resolve(specifier));
|
|
}
|
|
|
|
// Tests that need a real browser rather than the happy-dom environment used by
|
|
// `vitest.config.ts` — currently those which rasterise to a <canvas> and assert
|
|
// on the pixels, which needs a real 2D context and real font metrics.
|
|
|
|
// Screenshot baselines only mean anything where the rendering is pinned: the
|
|
// generic `sans-serif` the badges use resolves to a different typeface on each
|
|
// OS, so a macOS baseline would never match CI. We therefore only commit linux
|
|
// baselines, and only assert against them when running somewhere that renders
|
|
// the same way — CI, or the pinned container that `playwright-screenshots`
|
|
// starts (it sets PW_TEST_CONNECT_WS_ENDPOINT). Everywhere else the screenshot
|
|
// assertions skip; the rest of the suite still runs.
|
|
const wsEndpoint = env["PW_TEST_CONNECT_WS_ENDPOINT"];
|
|
const canCompareScreenshots = Boolean(wsEndpoint || env["CI"]);
|
|
const browserPlatform = canCompareScreenshots ? "linux" : platform;
|
|
|
|
export default defineProject({
|
|
resolve: {
|
|
alias: [
|
|
{ find: "test-utils-rtl", replacement: resolve("./src/test/test-utils/vitest-matrix-react") },
|
|
{ find: "test-utils", replacement: resolve("./src/test/test-utils") },
|
|
// Stub out workers as they do not play well under test
|
|
{
|
|
find: /.*workers\/(.+)Factory/,
|
|
replacement: resolve("./__mocks__/workerFactoryMock.js"),
|
|
},
|
|
{
|
|
find: /.*waveWorker\.min\.js$/,
|
|
replacement: resolve("./__mocks__/empty.js"),
|
|
},
|
|
{
|
|
find: /.*decoderWorker\.min\.js$/,
|
|
replacement: resolve("./__mocks__/empty.js"),
|
|
},
|
|
{
|
|
find: /.*decoderWorker\.min\.wasm$/,
|
|
replacement: resolve("./__mocks__/empty.js"),
|
|
},
|
|
// Stub this out as we lack AudioWorkletProcessor in the test env
|
|
{
|
|
find: "./recorderWorkletFactory",
|
|
replacement: resolve("./__mocks__/empty.js"),
|
|
},
|
|
// Stub out legacy modules so we don't need to build them first
|
|
{
|
|
find: "../modules.js",
|
|
replacement: resolve("./__mocks__/empty.js"),
|
|
},
|
|
],
|
|
},
|
|
test: {
|
|
coverage: {
|
|
exclude: ["src/**/*.stories.tsx"],
|
|
provider: "v8",
|
|
include: ["src/**/*.{ts,tsx}"],
|
|
reporter: [["lcov", { projectRoot: "../../" }]],
|
|
},
|
|
reporters: rootConfig.test?.reporters,
|
|
globals: false,
|
|
environment: "node",
|
|
pool: "threads",
|
|
projects: [
|
|
{
|
|
extends: true,
|
|
test: {
|
|
name: "unit",
|
|
include: ["src/**/*.test.{ts,tsx}"],
|
|
setupFiles: ["src/test/setupJsdomPolyfills.ts", "src/test/setupTests.ts"],
|
|
environmentOptions: {
|
|
happyDOM: {
|
|
url: "http://localhost/",
|
|
settings: {
|
|
navigation: {
|
|
// Nothing is served at these URLs under test; an iframe's `src` is just an attribute.
|
|
disableChildFrameNavigation: true,
|
|
},
|
|
},
|
|
},
|
|
jsdom: {
|
|
url: "http://localhost/",
|
|
},
|
|
},
|
|
snapshotSerializers: [
|
|
resolve("./src/test/react-use-id-serializer.ts"),
|
|
resolve("./src/test/css-module-class-serializer.ts"),
|
|
],
|
|
},
|
|
plugins: [
|
|
svgr({
|
|
svgrOptions: {
|
|
ref: true,
|
|
svgProps: { "role": "presentation", "aria-hidden": "true" },
|
|
expandProps: "end",
|
|
},
|
|
}),
|
|
],
|
|
},
|
|
{
|
|
extends: true,
|
|
test: {
|
|
name: "browser",
|
|
include: ["src/**/*.test.browser.{ts,tsx}"],
|
|
browser: {
|
|
enabled: true,
|
|
headless: true,
|
|
viewport: { width: 960, height: 720 },
|
|
provider: playwright({
|
|
contextOptions: {
|
|
reducedMotion: "reduce",
|
|
colorScheme: "light",
|
|
deviceScaleFactor: 1,
|
|
viewport: { width: 960, height: 720 },
|
|
},
|
|
launchOptions: {
|
|
// Force consistent font rendering, as per packages/shared-components
|
|
args: [
|
|
"--font-render-hinting=none",
|
|
"--disable-font-subpixel-positioning",
|
|
"--disable-lcd-text",
|
|
],
|
|
},
|
|
connectOptions: wsEndpoint ? { wsEndpoint, exposeNetwork: "<loopback>" } : undefined,
|
|
}),
|
|
instances: [{ browser: "chromium" }],
|
|
expect: {
|
|
toMatchScreenshot: {
|
|
comparatorName: "pixelmatch",
|
|
comparatorOptions: {
|
|
// Enough to absorb sub-pixel antialiasing noise, but far less
|
|
// than a clipped or misplaced glyph moves.
|
|
allowedMismatchedPixelRatio: 0.02,
|
|
},
|
|
// Baselines are grouped by platform, and only the linux ones are
|
|
// committed — see apps/web/.gitignore.
|
|
resolveScreenshotPath: ({
|
|
root,
|
|
testFileDirectory,
|
|
screenshotDirectory,
|
|
testFileName,
|
|
arg,
|
|
ext,
|
|
browserName,
|
|
}) =>
|
|
`${root}/${testFileDirectory}/${screenshotDirectory}/${browserPlatform}/${testFileName}/${arg}-${browserName}${ext}`,
|
|
},
|
|
},
|
|
},
|
|
provide: { canCompareScreenshots },
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|