first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
# hmr-tester
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`withHMR` round-trips a single Vite HMR edit through an injectable `HmrServerLike` adapter and asserts that the expected events fire in order. It is designed for tests that exercise HMR-driven UI logic without spinning up a real Vite dev server — the adapter can wrap an in-memory event bus, a Playwright page, or a real `ws` connection depending on the testing strategy.
## Features
- Injectable server adapter (`send` + `onEvent`).
- Ordered event matching — events fire in the order specified in `expectEvents`.
- Configurable `timeoutMs` with `TestViteError(TEST_VITE.HMR_TIMEOUT)` on expiry.
- Injectable `Clock` for deterministic duration accounting.
- OTel `test_vite_hmr_round_trip_ms` histogram recorded on every resolution.
## Usage: Basic
```ts
import { withHMR } from "@suaveplan/test-vite/hmr-tester";
const events = await withHMR({
server: viteServerAdapter,
edit: { file: "src/widget.ts", content: "..." },
expectEvents: ["vite:beforeUpdate", "vite:afterUpdate"],
timeoutMs: 5000,
});
```
## Usage: Advanced
Drive a synthetic server in tests:
```ts
import { withHMR } from "@suaveplan/test-vite/hmr-tester";
let listener: ((event: string) => void) | undefined;
const server = {
send: () => setTimeout(() => listener?.("vite:afterUpdate"), 1),
onEvent: (cb: (event: string) => void) => {
listener = cb;
return () => { listener = undefined; };
},
};
await withHMR({
server,
edit: { file: "a.ts", content: "1" },
expectEvents: ["vite:afterUpdate"],
timeoutMs: 100,
clock: () => Date.now(),
});
```
## API Reference
- `withHMR(opts: WithHmrOptions): Promise<readonly string[]>`.
- `WithHmrOptions` — `{ server, edit: { file, content }, expectEvents, timeoutMs?, clock? }`.
- Throws `TestViteError` with `TEST_VITE.INVALID_OPTIONS` or `TEST_VITE.HMR_TIMEOUT`.
## Implementation Notes
`withHMR` matches expected events in order — non-matching events are captured but do not advance the cursor. Once the final expected event fires the promise resolves with the complete capture array, including any non-matching events that arrived along the way. The timer is cleared and the `onEvent` disposer invoked before resolution so the caller does not have to wire teardown. Test consumers can swap the clock to record deterministic durations and exercise edge cases without real wall-clock delays.