Tests are routed to a Vitest project by the folder they live in, not by the
extension name.
Pick the folder by how the test behaves, so a new extension needs no change to
vitest.config.ts.
Folder
Project
Isolation
Put a test here when it…
__tests__/
unit
shared process, parallel
is pure and deterministic: no module mocks, no global/env mutation left behind. Most tests.
__tests__/contract.test.ts
contract
per-file process.env sandbox
is the required owned-fixture contract test (see AGENTS.md). One per extension.
__serial__/
isolated-serial
fresh module state, one worker
calls vi.mock(...), or needs a single worker for real subprocesses / fake-command PATH / process.chdir.
__e2e__/
Playwright
browser
drives rendered browser assets.
__fuzz__/
campaign
—
hostile-input fuzz targets (<name>.fuzz.ts) plus their regressions/.
__bench__/
bench
—
benchmarks.
The rule that matters
The unit project runs non-isolated across files in a shared worker, so a
vi.mock(...) persists in the module registry and leaks into the next file.
//:check fails any __tests__/*.test.ts (except contract.test.ts) that calls
vi.mock, with a pointer to __serial__/.
Global or env stubs (vi.stubGlobal, vi.stubEnv) are fine in __tests__/only if restored in cleanup; otherwise move them to __serial__/ too.
Spawning real subprocesses is allowed in __tests__/ (scope cwd/env per
call); move to __serial__/ if contention under parallelism is a problem.
Coverage, mutation, fuzz
mise run //extensions/<name>:coverage — V8 coverage over the deterministic
projects, scoped to the extension.
mise run //extensions/<name>:mutate <file> — Stryker, per-extension cache.
mise run //extensions/<name>:fuzz <target> — fuzz campaign; minimized
findings become replay tests next to the target.
# Extension test tiers
Tests are routed to a Vitest project by the **folder** they live in, not by the
extension name.
Pick the folder by how the test behaves, so a new extension needs no change to
`vitest.config.ts`.
| Folder | Project | Isolation | Put a test here when it… |
| --- | --- | --- | --- |
| `__tests__/` | `unit` | shared process, parallel | is pure and deterministic: no module mocks, no global/env mutation left behind. Most tests. |
| `__tests__/contract.test.ts` | `contract` | per-file process.env sandbox | is the required owned-fixture contract test (see `AGENTS.md`). One per extension. |
| `__serial__/` | `isolated-serial` | fresh module state, one worker | calls `vi.mock(...)`, or needs a single worker for real subprocesses / fake-command `PATH` / `process.chdir`. |
| `__e2e__/` | Playwright | browser | drives rendered browser assets. |
| `__fuzz__/` | campaign | — | hostile-input fuzz targets (`<name>.fuzz.ts`) plus their `regressions/`. |
| `__bench__/` | bench | — | benchmarks. |
## The rule that matters
The `unit` project runs **non-isolated** across files in a shared worker, so a
`vi.mock(...)` persists in the module registry and leaks into the next file.
`//:check` fails any `__tests__/*.test.ts` (except `contract.test.ts`) that calls
`vi.mock`, with a pointer to `__serial__/`.
Global or env stubs (`vi.stubGlobal`, `vi.stubEnv`) are fine in `__tests__/`
**only if** restored in cleanup; otherwise move them to `__serial__/` too.
Spawning real subprocesses is allowed in `__tests__/` (scope `cwd`/`env` per
call); move to `__serial__/` if contention under parallelism is a problem.
## Coverage, mutation, fuzz
- `mise run //extensions/<name>:coverage` — V8 coverage over the deterministic
projects, scoped to the extension.
- `mise run //extensions/<name>:mutate <file>` — Stryker, per-extension cache.
- `mise run //extensions/<name>:fuzz <target>` — fuzz campaign; minimized
findings become replay tests next to the target.