generative-a11y
Core

@generative-a11y/core/testing

Record events, replay fixtures, and assert deterministic runtime output.

ManualClock and createRecorder

ManualClock controls time without global timers. createRecorder bundles that clock with a runtime plus announcement and diagnostic transcripts.

accessibility.test.ts
import { createRecorder } from "@generative-a11y/core";

const recorder = createRecorder({
  preset: "balanced",
  startAt: 10_000,
});

recorder.runtime.dispatch({
  type: "response.started",
  responseId: "r1",
});
recorder.clock.runUntilIdle();

The recorder owns its runtime and clock. runUntilIdle() advances only that clock; dispose recorder.runtime after the test.

Prop

Type

recordRuntime and replayEvents

Recording captures accepted events sent through the returned dispatch target. Replay validates the full fixture before dispatching any event and advances the supplied ManualClock in recorded order.

Replay a recorded lifecycle
import { ManualClock, createRuntime } from "@generative-a11y/core";
import { recordRuntime, replayEvents } from "@generative-a11y/core/testing";

const clock = new ManualClock(0);
const runtime = createRuntime({ clock });
const recording = recordRuntime({ runtime, clock });
recording.runtime.dispatch({
  type: "response.started",
  responseId: "report",
});
const fixture = recording.fixture();

const replayClock = new ManualClock(fixture.startAt);
const replayRuntime = createRuntime({ clock: replayClock });
replayEvents(replayRuntime, replayClock, fixture);

runtime.dispose();
replayRuntime.dispose();

Only dispatches through recording.runtime enter the fixture. Times are relative and non-negative; tied entries keep array order. replayEvents advances the clock before each dispatch but does not run it until idle.

Prop

Type

Opt-in Vitest matchers

Import @generative-a11y/core/testing only in Vitest setup. The root package entry has no Vitest runtime import.

Vitest matcher assertion
import { expect } from "vitest";
import { installVitestMatchers } from "@generative-a11y/core/testing";

const accessibilityExpect = installVitestMatchers(expect);
recorder.runtime.dispatch({
  type: "response.text.delta",
  responseId: "r1",
  delta: "Your answer is ready.",
});
recorder.runtime.dispatch({ type: "response.completed", responseId: "r1" });
recorder.clock.runUntilIdle();

accessibilityExpect(recorder).toHaveAnnounced({
  sourceType: "response.completed",
});

Register matchers once in your Vitest setup. Partial expectations let each test assert only the semantic fields it needs.

Prop

Type

Validation and evidence limits

Replay rejects unsupported formats, versions, event types, backward times, malformed workflow identity, and lifecycle events without required IDs before dispatch. Run and step attempt boundaries remain in recorded fixtures. A passing transcript confirms deterministic core output. It does not prove browser delivery or screen-reader speech.

Public declarations: @generative-a11y/core/testing

All exported values and types for this entry point. Import these names from @generative-a11y/core/testing; the signatures below are reference material, not a replacement for the ownership and lifecycle guidance above.

A11yExpect — type
interface A11yExpect {
    (received: unknown): A11yMatchers;
}
A11yMatchers — type
interface A11yMatchers {
    toHaveAnnouncementTranscript(expected: readonly Partial<AnnouncementIntent>[]): void;
    toHaveAnnounced(expected: Partial<AnnouncementIntent>): void;
    toHaveDiagnostic(expected: Partial<AnnouncementDiagnostic>): void;
}
AnnouncementExpectation — type
type AnnouncementExpectation = Partial<AnnouncementIntent>;
createReplayFixture — value
declare function createReplayFixture(events: readonly RecordedEvent[], options?: ReplayFixtureOptions): ReplayFixtureV1;
DiagnosticExpectation — type
type DiagnosticExpectation = Partial<AnnouncementDiagnostic>;
installVitestMatchers — value
declare function installVitestMatchers(expect: {
    extend(matchers: Record<string, unknown>): void;
}): A11yExpect;
matchesPartial — value
declare function matchesPartial(actual: object, expected: object): boolean;
RecordedEvent — type
interface RecordedEvent {
    readonly at: number;
    readonly event: RuntimeEvent;
}
recordRuntime — value
declare function recordRuntime(options: RecordRuntimeOptions): RuntimeRecording;
RecordRuntimeOptions — type
interface RecordRuntimeOptions {
    readonly runtime: Pick<Runtime, "dispatch">;
    readonly clock: Pick<Clock, "now">;
}
replayEvents — value
declare function replayEvents(runtime: Pick<Runtime, "dispatch">, clock: ManualClock, fixture: ReplayFixtureV1): void;
ReplayFixtureOptions — type
interface ReplayFixtureOptions {
    readonly startAt?: number;
}
ReplayFixtureV1 — type
interface ReplayFixtureV1 {
    readonly format: "generative-a11y/replay";
    readonly version: 1;
    readonly startAt: number;
    readonly events: readonly RecordedEvent[];
}
RuntimeRecording — type
interface RuntimeRecording {
    readonly runtime: Pick<Runtime, "dispatch">;
    events(): readonly RecordedEvent[];
    fixture(): ReplayFixtureV1;
    clear(): void;
}
toHaveAnnounced — value
declare function toHaveAnnounced(received: unknown, expected: Partial<AnnouncementIntent>): MatcherResult;
toHaveAnnouncementTranscript — value
declare function toHaveAnnouncementTranscript(received: unknown, expected: readonly Partial<AnnouncementIntent>[]): MatcherResult;
toHaveDiagnostic — value
declare function toHaveDiagnostic(received: unknown, expected: Partial<AnnouncementDiagnostic>): MatcherResult;
TranscriptRecorder — type
interface TranscriptRecorder {
    transcript(): readonly AnnouncementIntent[];
    diagnosticTranscript(): readonly AnnouncementDiagnostic[];
}

Recorder transcript readers return mutable copies that cannot modify recorded history. clear() removes recorded entries only; use a new recorder to reset runtime state and clock time for a separate test.