Files
skills/docs/devtools/tooling/test-vite/src/hmr-tester/hmr-tester.md
2026-05-29 08:20:56 +01:00

2.4 KiB

hmr-tester

(no submodules)

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

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:

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.