From 80e3bd9924aee1ab9de03fe89016c37178c6f7dd Mon Sep 17 00:00:00 2001 From: Phil LaFayette Date: Thu, 25 Jun 2026 22:40:12 -0500 Subject: [PATCH] Speed up web vitest suite (~51s -> ~32s): vmThreads pool cuts per-file import/jsdom overhead The web unit-test suite's cost was dominated by per-file overhead, not test logic: the same module graph (App, mqtt-store, components, bits-ui, the lucide-svelte icon barrel) was re-imported and re-executed, and a fresh jsdom environment was created, for every one of the 94 spec files. Switch the bulk of the suite to vitest's vmThreads pool, which runs each spec file in its own fresh V8 VM context (full per-file isolation, no cross-file state leakage) while reusing the worker's compiled-module cache across files. The three specs that replace the whole window/location global object (not just define a property on it) cannot run under vmThreads, where those globals are non-configurable; they are routed to a standard threads project via test.projects. No test assertions or test code changed. Measured (matched-load A/B, 3 runs each): wall ~51s -> ~32s (-37%) duration ~47s -> ~30s import ~240s -> ~174s environment ~46s -> ~5.4s (-88%) transform ~71s -> ~64s tests ~16s -> ~12s Still 94 files / 1292 tests pass with 0 unhandled errors and 0 stderr noise (verified 5 consecutive runs). The bits-ui scroll-lock drain in tests/setup.js (PR #46) and the clean output (PR #48) are preserved. --- web/vitest.config.js | 53 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/web/vitest.config.js b/web/vitest.config.js index aced2d0..2b1e964 100644 --- a/web/vitest.config.js +++ b/web/vitest.config.js @@ -2,17 +2,62 @@ import { defineConfig } from 'vitest/config'; import { svelte } from '@sveltejs/vite-plugin-svelte'; import { svelteTesting } from '@testing-library/svelte/vite'; +// Performance note (web unit-test suite): +// The suite's cost was dominated by per-file overhead. The same module graph +// (App, mqtt-store, components, bits-ui, the lucide-svelte icon barrel) was +// re-imported, re-executed, and the jsdom env re-created for every one of the +// ~94 spec files. Actual test execution is cheap. +// +// The `vmThreads` pool runs each spec file in its own fresh V8 VM context +// (full per-file isolation, so no cross-file state leakage: the bits-ui +// teardown fix in tests/setup.js and the clean 0-unhandled / 0-stderr output +// are preserved) while REUSING the worker's compiled-module cache across +// files. That collapses the dominant import/transform/environment cost and +// roughly halves wall-clock without touching a single assertion. +// +// Caveat: under `vmThreads` the VM's `window` / `location` globals are +// non-configurable, so the handful of specs that REPLACE the whole `window` +// or `location` object (not just define a property on it) cannot run there. +// They are routed to a standard `forks` project instead. This list only needs +// to grow if a new spec reassigns the entire `window` / `location` global; +// such a spec fails loudly under `vmThreads`, never silently. +const NON_VM_SPECS = [ + 'tests/api-base-derivation.spec.js', // vi.stubGlobal('window', ...) + 'tests/connection-status.spec.js', // Object.defineProperty(window, 'location', ...) + 'tests/slash-commands.spec.js', // globalThis.window = { ... } +]; + +// Exclude Playwright e2e specs so Vitest only picks up unit tests. +const BASE_EXCLUDE = ['node_modules', 'dist', 'e2e', 'test-results']; + export default defineConfig({ // The `svelteTesting` plugin prepends the `browser` resolve condition so - // Svelte 5's client-side `mount()` is loaded instead of the SSR stub — + // Svelte 5's client-side `mount()` is loaded instead of the SSR stub, // required by @testing-library/svelte's `render()` (Batch 4L: a11y scan). plugins: [svelte({ hot: !process.env.VITEST }), svelteTesting()], test: { environment: 'jsdom', globals: true, setupFiles: ['tests/setup.js'], - // Exclude Playwright e2e specs so Vitest only picks up unit tests. - include: ['tests/**/*.spec.js'], - exclude: ['node_modules', 'dist', 'e2e', 'test-results'], + projects: [ + { + extends: true, + test: { + name: 'vm', + pool: 'vmThreads', + include: ['tests/**/*.spec.js'], + exclude: [...BASE_EXCLUDE, ...NON_VM_SPECS], + }, + }, + { + extends: true, + test: { + name: 'dom-globals', + pool: 'threads', + include: NON_VM_SPECS, + exclude: BASE_EXCLUDE, + }, + }, + ], }, });