Vitest plugin
@soundor/vitest: configuration, generated declarations, isolation, and consumer validation.
Source: packages/vitest/.
Vitest integration for the simulated Soundor runtime. Tests run in Node, without a browser, Hermes, native libraries, or audio hardware. Vitest 5 is the supported runner; Vite 8 is the supported transform backend.
Quick start
pnpm add -D vitest @soundor/vitest vitest-environment-soundor @soundor/testing-libraryimport { soundor } from '@soundor/vitest';
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
plugins: [soundor()],
test: { environment: 'soundor' },
});No aliases or setup file are needed. TS and TSX use Vite's automatic JSX
transform. Published @soundor/react is transformed through Vite so its
soundor:ui imports work too; React remains an application dependency.
import { createTextInput, root } from 'soundor:ui';
import { test, expect } from 'vitest';
test('focuses an input', () => {
const input = createTextInput({ value: 'Soundor' });
root.appendChild(input);
input.focus();
expect(input).toHaveFocus();
});See @soundor/testing-library for rendering, semantic queries, and stateful user interactions.
Configuration and declarations
soundor() discovers soundor.config.ts in the project or its parents. It uses
Soundor's existing configuration loader and declaration generator, and updates
.soundor/generated. Invalid configurations fail with the usual Soundor errors.
The file and imported native declarations describe the simulated API; no native
runtime starts. Include .soundor/generated/*.d.ts in the test TypeScript project
alongside the test files, using the same declarations as the application.
A library without a Soundor project can use soundor({ config: false }), or
supply runtime.parameters, runtime.plugin, and native declarations.
Without a discovered project, omitted options provide an empty test plugin.
config: './soundor.config.ts' selects an explicit file. A configured project
owns its parameter and native declarations; state changes belong in test hooks.
Configuration changes restart the test server in watch mode.
plugins: [soundor({ runtime: { host: { sampleRate: 96000 } } })];Each test starts with this initial state. In tests:
import {
testParameters,
testHost,
mockNative,
} from '@soundor/testing-library/react';
import { beforeEach, vi } from 'vitest';
beforeEach(() => {
testParameters.set('gain', 0.75);
testHost.set({ hostName: 'Test DAW' });
mockNative(
'loadPreset',
vi.fn(async () => ({ name: 'Warm' })),
);
});Use the return shape declared by your native API. Unconfigured native methods
fail clearly; async declarations return promises. Ordinary module mocks use
vi.mock and function mocks use vi.fn.
Isolation and cleanup
The environment owns a runtime per test, explicitly managed by Vitest's
aroundEach hook. Module factories, root bindings, parameter handles, storage,
and host subscriptions follow the active instance. Nodes and subscriptions from
an earlier test cannot be reused. Put state setup in beforeEach, rather than
beforeAll or module initialization. User cleanup hooks run before disposal,
even when a test fails. Soundor cleanup does not reset user mocks: Vitest's
clearMocks, mockReset, and restoreMocks retain their normal behavior.
Parallel test files are supported in standard forks/threads pools. Tests in
one file must run sequentially; test.concurrent is rejected. Keep
test.isolate enabled (the default). VM pools are not supported.
Animation time is explicit: await advanceTime(250). The environment
provides requestAnimationFrame/cancelAnimationFrame, simulating 60 Hz frame
batches on that clock, with native ordering and cancellation semantics. This
also supports React's useAnimationFrame. Frame timestamps use runtime time;
Node timers and performance.now() remain on Node's clock. Vitest fake timers
can control Node timers independently; they do not advance native presentation.
Select toFake explicitly to keep the simulated frame functions when using
Vitest timers. devicePixelRatio defaults to 1; no pixels are rendered.
Simulation limits
UI trees, input, accessibility, parameters, host state, clipboard, JSON storage,
and animation lifecycle are simulated. Layout, rendering, Hermes execution,
audio, and DAW integration are unavailable. soundor:fs resolves, but operations
reject with an explanation; supply a vi.mock('soundor:fs') when needed.
Storage is in memory and resets between tests. See
@soundor/test-runtime for the runtime's full limits.
The root plugin explicitly configures simulation. There is no native entry point or backend switch. The plugin and testing tools do not require an environment package as a peer; install the chosen environment directly.
pnpm's default automatic peer installation supplies the compatible Vite 8 peer. Projects that disable automatic peer installation must install a compatible Vite version as required by the plugin and Vitest (this repository uses Vite 8).
Package consumer validation
pnpm build
pnpm --filter @soundor/vitest test:consumerThe smoke test packs the real packages, installs them in an external project
with isolated node_modules and strict peers, runs React interaction tests, and
checks public TypeScript declarations. No workspace aliases or direct
@soundor/test-runtime dependency are used.