# @gyral/testing

> Gyral test helpers: pure update stepping, command helpers, fake drivers, virtual time, SSR page mounting, server rendering in Vitest browser tests.

Source: https://gyral.dev/docs/api/testing/

Gyral test helpers: pure update stepping, command helpers, fake drivers, virtual time, SSR page mounting, server rendering in Vitest browser tests.

```sh
npm install @gyral/testing
```

> **Writing code? Start from working code, not from this page.** [Testing](https://gyral.dev/docs/testing/). Adapt the closest example and check it, then look a name up here when you are about to call it.

Generated from the package's type declarations. Entry points: [`@gyral/testing`](#gyraltesting), [`@gyral/testing/vitest`](#gyraltestingvitest).

## `@gyral/testing`

### Functions

| Name | Summary |
| --- | --- |
| [`callsIn`](#testing-callsin) | The element method calls among the commands (`call(selector, method, ...args)`), in order: |
| [`collectErrors`](#testing-collecterrors) | Starts collecting every `GyralError` Gyral reports, so deliberate failures don't fail the test run. |
| [`commandsFor`](#testing-commandsfor) | The commands addressed to `driver` (matched by name, so fakes and real drivers agree). |
| [`fakeDriver`](#testing-fakedriver) | A recording driver for DOM-level tests: `el.drivers = { http: fakeDriver(http) }`. |
| [`focusTargetsIn`](#testing-focustargetsin) | The focus requests among the commands (`focus(selector, options)`), in order: |
| [`hydrated`](#testing-hydrated) | Waits until the page's Gyral components have rendered (see `settled()`): releases lazy islands if asked, then `await settled()`. |
| [`initial`](#testing-initial) | Runs `init` without a DOM. |
| [`inputsFor`](#testing-inputsfor) | The inputs of the commands addressed to `driver`; given a store, the messages the commands `send()` to it, in order (ADR 0013). |
| [`mount`](#testing-mount) | Creates `component` (a class from `define()` or a registered tag name), sets `props`, `attrs`, `drivers` and `stores`, appends it, and waits for its first render. |
| [`mountSsr`](#testing-mountssr) | Mounts server output: a full document (head + body) or a body fragment. |
| [`outputsIn`](#testing-outputsin) | The outputs the commands send to the parent component (`emit`, `outputs<O>()`), in order. |
| [`parse`](#testing-parse) | Runs the parser of intent `name` with `input` (only the fields the parser reads are needed: `{ value: '3' }`, `{ key: 'Enter' }`, `{ detail }`). |
| [`reject`](#testing-reject) | The message a command produces if its driver fails with `error` (already typed, i.e. |
| [`resetDocumentStores`](#testing-resetdocumentstores) | Tests only: forget the document default so each test starts clean. |
| [`resolve`](#testing-resolve) | The message a command produces if its driver succeeds with `output`. |
| [`run`](#testing-run) | Folds messages through `update`, collecting every command (including `init`'s). |
| [`step`](#testing-step) | Feeds one message through `update` without a DOM. |
| [`stepStore`](#testing-stepstore) | Feeds one message through a store's `update` without an instance. |
| [`subscriptionsFor`](#testing-subscriptionsfor) | The commands `spec.subscriptions` asks to keep running for `state` and `props` (ADR 0025), without a DOM: `subscriptionsFor(Clock.spec, { live: true })`. |
| [`viewOf`](#testing-viewof) | Calls a component's pure view without mounting, a DOM shim or consumer casts. |
| [`virtualTime`](#testing-virtualtime) | Replaces timers and `Date` with a virtual clock (ADR 0006, "Testing"). |

#### `callsIn`

```ts
function callsIn(commands: ReadonlyArray<Command<unknown>>): readonly CallInput[];
```

The element method calls among the commands (`call(selector, method, ...args)`), in order:


```ts
expect(callsIn(next.commands)).toEqual([{ selector: 'video', method: 'play', args: [] }]);
```


#### `collectErrors`

```ts
function collectErrors(): CollectedErrors;
```

Starts collecting every `GyralError` Gyral reports, so deliberate failures don't fail the
test run. Errors that aren't Gyral's are left alone. Call `stop()` when the test ends.

#### `commandsFor`

```ts
function commandsFor<I, M>(commands: ReadonlyArray<Command<M>>, driver: Driver<I, unknown> | Named): ReadonlyArray<Command<M> & {
    readonly input: I;
}>;
```

The commands addressed to `driver` (matched by name, so fakes and real drivers agree).

#### `fakeDriver`

```ts
function fakeDriver<I = unknown, O = unknown, E = unknown>(of: string | Driver<I, O, E>, optionsOrRun?: FakeOptions<I, O, E> | FakeRun<I, O>): FakeDriver<I, O, E>;
```

A recording driver for DOM-level tests: `el.drivers = { http: fakeDriver(http) }`.
Pass a real driver to copy its name and concurrency, or just a name. Failures pass
through unchanged unless you give `toError`, so `rejectNext(error)` takes the typed error.
A function as the second argument answers every call (`impl`):


```ts
el.drivers = { 'url-sync': fakeDriver<string | null, void>('url-sync', (q) => void urls.push(q)) };
```

Fakes go into `el.drivers` and `provideDrivers` as they are, with no cast.

#### `focusTargetsIn`

```ts
function focusTargetsIn(commands: ReadonlyArray<Command<unknown>>): readonly FocusTarget[];
```

The focus requests among the commands (`focus(selector, options)`), in order:


```ts
expect(focusTargetsIn(next.commands)).toEqual([{ selector: '#results' }]);
```


#### `hydrated`

```ts
function hydrated(page: MountedSsr | ParentNode, options?: HydratedOptions): Promise<void>;
```

Waits until the page's Gyral components have rendered (see `settled()`): releases lazy islands if asked, then `await settled()`. Islands still
deferred are left alone. It throws when a server-rendered custom element never upgraded
(import its module, or list it in `allowUndefined`). Given a `MountedSsr`, it also throws
when console errors/warnings or uncaught errors were recorded since mounting.

#### `initial`

```ts
function initial<S, M extends Tagged, P>(spec: ComponentSpec<S, M, P>, props?: P): Stepped<S, M>;
```

Runs `init` without a DOM. `props` defaults to `{}`.

#### `inputsFor`

```ts
function inputsFor<S, SM extends Tagged>(commands: ReadonlyArray<Command<unknown>>, store: Store<S, SM>): readonly SM[];
function inputsFor<I, M>(commands: ReadonlyArray<Command<M>>, driver: Driver<I, unknown> | Named): readonly I[];
```

The inputs of the commands addressed to `driver`; given a store, the messages the commands
`send()` to it, in order (ADR 0013).

#### `mount`

```ts
function mount<K extends keyof HTMLElementTagNameMap>(tag: K, options?: MountOptions<HTMLElementTagNameMap[K]>): Promise<Mounted<HTMLElementTagNameMap[K]>>;
function mount<E extends HTMLElement>(component: Constructor<E> | string, options?: MountOptions<E>): Promise<Mounted<E>>;
```

Creates `component` (a class from `define()` or a registered tag name), sets `props`,
`attrs`, `drivers` and `stores`, appends it, and waits for its first render.

#### `mountSsr`

```ts
function mountSsr(html: string, options?: MountSsrOptions): MountedSsr;
```

Mounts server output: a full document (head + body) or a body fragment. Call it before
importing component modules so elements upgrade and hydrate like on a real page load.
- Only `<head>` styles are applied: `<style>` inside Declarative Shadow DOM belongs to its
  shadow root and must not leak into the page.
- The store seed, named `<meta>`s and the managed head are restored, as the browser would
  see them.

#### `outputsIn`

```ts
function outputsIn<C extends WithOutputs>(commands: ReadonlyArray<Command<unknown>>, component: C): ReadonlyArray<OutputsOf<C>>;
function outputsIn<O extends Tagged = Tagged>(commands: ReadonlyArray<Command<unknown>>): readonly O[];
```

The outputs the commands send to the parent component (`emit`, `outputs<O>()`), in order.
Pass the component class to type them by its output union (`OutputsOf<typeof Item>`):


```ts
expect(outputsIn(next.commands, Item)).toEqual([{ _tag: 'Picked', id: 1 }]);
```

The outputs, typed by the union you name: `outputsIn<Out>(commands)`.

#### `parse`

```ts
function parse<S, M extends Tagged, P, N extends string, K extends N>(component: SpecOf<S, M, P, N>, name: K, input?: Partial<IntentInput>, options?: ParseOptions<NoInfer<S>, NoInfer<P>>): Parsed<M, K> | IntentRejected | undefined | Promise<Parsed<M, K> | IntentRejected | undefined>;
```

Runs the parser of intent `name` with `input` (only the fields the parser reads are needed:
`{ value: '3' }`, `{ key: 'Enter' }`, `{ detail }`). Returns what the parser returns: a
message, an `IntentRejected`, `undefined` (ignored or declined), or a promise of one.

#### `reject`

```ts
function reject<M>(command: Command<M>, error: unknown): M | undefined;
```

The message a command produces if its driver fails with `error` (already typed, i.e.
after the driver's `toError`). `undefined` when the command has no `onFailure`.

#### `resetDocumentStores`

```ts
function resetDocumentStores(): void;
```

Tests only: forget the document default so each test starts clean.

#### `resolve`

```ts
function resolve<M>(command: Command<M>, output: unknown): M | undefined;
```

The message a command produces if its driver succeeds with `output`.

#### `run`

```ts
function run<S, M extends Tagged, P>(spec: ComponentSpec<S, M, P>, messages: ReadonlyArray<NoInfer<StepMessage<M, P>>>, options?: NoInfer<RunOptions<S, P>>): Ran<S, M>;
```

Folds messages through `update`, collecting every command (including `init`'s).

#### `step`

```ts
function step<S, M extends Tagged, P>(spec: ComponentSpec<S, M, P>, state: NoInfer<S>, msg: NoInfer<StepMessage<M, P>>, props?: NoInfer<P>, stores?: readonly AnyStoreInstance[]): Stepped<S, M>;
```

Feeds one message through `update` without a DOM. Framework messages (`PropsChanged`,
`IntentRejected`, `StoreChanged`, `Hydrated`, `Errored`) without a reducer leave state unchanged, as in
the element. `stores` are the instances `ctx.read()` sees.

#### `stepStore`

```ts
function stepStore<S, M extends Tagged>(store: Store<S, M>, state: NoInfer<S>, msg: NoInfer<M>): Stepped<S, M>;
```

Feeds one message through a store's `update` without an instance.

#### `subscriptionsFor`

```ts
function subscriptionsFor<S, M extends Tagged, P>(spec: ComponentSpec<S, M, P>, state: NoInfer<S>, props?: NoInfer<P>): ReadonlyArray<Command<M>>;
```

The commands `spec.subscriptions` asks to keep running for `state` and `props` (ADR 0025),
without a DOM: `subscriptionsFor(Clock.spec, { live: true })`. A spec without the field gives
none. Assert their inputs with `inputsFor`, as for any commands.

#### `viewOf`

```ts
function viewOf<S, M extends Tagged, P, N extends string>(component: ComponentSpec<S, M, P, N> | GyralElementClass<S, M, P, Tagged, N>, state: NoInfer<S>, options?: ViewOptions<NoInfer<P>>): ChildValue;
```

Calls a component's pure view without mounting, a DOM shim or consumer casts. Use
`renderToString(viewOf(Component, state, { props, stores }), { dev: false })` for stable
HTML assertions. Hooks, native validity, focus and hydration need real-browser tests.

#### `virtualTime`

```ts
function virtualTime(): VirtualTime;
```

Replaces timers and `Date` with a virtual clock (ADR 0006, "Testing"). It patches the
platform rather than Gyral's runtime, so it covers drivers, debounces and retry delays
alike and does not depend on how the interpreter is implemented. Microtasks stay real,
so promises and the runtime keep working. Animation frames are faked only where they exist,
so model and driver tests can use it in a Node project.

### Types and interfaces

You rarely write most of these yourself: TypeScript infers them from the functions above. Look one up when an example or a compiler error names it.

| Name | Summary |
| --- | --- |
| [`CollectedErrors`](#testing-collectederrors) | The failures collected so far; `stop()` removes the listeners. |
| [`FakeCall`](#testing-fakecall) | One recorded `run` of a fake driver. |
| [`FakeDriver`](#testing-fakedriver) |  |
| [`FakeOptions`](#testing-fakeoptions) |  |
| [`FakeRun`](#testing-fakerun) | What a fake answers each call with (`FakeOptions.impl`). |
| [`FocusTarget`](#testing-focustarget) | What a `focus()` command asks for: the selector and its options. |
| [`HydratedOptions`](#testing-hydratedoptions) |  |
| [`Mounted`](#testing-mounted) |  |
| [`MountedSsr`](#testing-mountedssr) |  |
| [`MountOptions`](#testing-mountoptions) |  |
| [`MountSsrOptions`](#testing-mountssroptions) |  |
| [`ParseOptions`](#testing-parseoptions) | What `parse` passes the parser besides the input. |
| [`Ran`](#testing-ran) |  |
| [`RunOptions`](#testing-runoptions) |  |
| [`StepMessage`](#testing-stepmessage) | Any message `update` accepts: the component's own, or a framework message. |
| [`Stepped`](#testing-stepped) | A reducer result, normalised: the new state plus the commands it asked for. |
| [`ViewOptions`](#testing-viewoptions) |  |
| [`VirtualTime`](#testing-virtualtime) | Controls time in a test. |
| [`WithOutputs`](#testing-withoutputs) | Anything carrying a component's output union: a `define()` class. |

#### `CollectedErrors`

```ts
interface CollectedErrors {
    readonly errors: readonly GyralError[];
    /** Stops collecting; later failures are reported as usual. */
    stop(): void;
}
```

The failures collected so far; `stop()` removes the listeners.

#### `FakeCall`

```ts
interface FakeCall<I, O> {
    readonly input: I;
    readonly signal: AbortSignal;
    /** Settles this call (only for fakes without `impl`). */
    readonly resolve: (output: O) => void;
    readonly reject: (error: unknown) => void;
    /** Streams a value into this call (DriverContext.emit); ignored once settled or aborted. */
    readonly emit: (output: O) => void;
    readonly settled: boolean;
}
```

One recorded `run` of a fake driver.

#### `FakeDriver`

```ts
interface FakeDriver<I, O, E> extends Driver<I, O, E> {
    readonly calls: ReadonlyArray<FakeCall<I, O>>;
    /** Inputs of every call so far. */
    readonly inputs: readonly I[];
    /** Settles the oldest unsettled, non-aborted call. Throws if there is none. */
    resolveNext(output: O): void;
    rejectNext(error: unknown): void;
    /** Streams a value into the newest call that is still running. Throws if there is none. */
    emitNext(output: O): void;
}
```


#### `FakeOptions`

```ts
interface FakeOptions<I, O, E> {
    /** Answer calls immediately. Without it, calls wait for `resolveNext`/`rejectNext`. */
    readonly impl?: FakeRun<I, O>;
    readonly concurrency?: Concurrency;
    readonly toError?: (cause: unknown) => E;
}
```


#### `FakeRun`

```ts
type FakeRun<I, O> = (input: I, ctx: DriverContext<O>) => O | Promise<O>;
```

What a fake answers each call with (`FakeOptions.impl`).

#### `FocusTarget`

```ts
interface FocusTarget extends FocusOptions {
    readonly selector: string;
}
```

What a `focus()` command asks for: the selector and its options.

#### `HydratedOptions`

```ts
interface HydratedOptions {
    /**
     * Custom element tags allowed to stay undefined (never upgraded). `gyral-stores` is always
     * allowed: in the browser it is a plain scoping element.
     */
    readonly allowUndefined?: readonly string[];
    /**
     * Release every lazy island first (`defer-hydration` + `data-gyral-hydrate`), as if each
     * one's trigger had fired. Default `false`: islands keep waiting.
     */
    readonly releaseIslands?: boolean;
}
```


#### `Mounted`

```ts
interface Mounted<E extends HTMLElement> {
    readonly el: E;
    /** The first match in the element's shadow root (or light DOM), or null. */
    $(selector: string): HTMLElement | null;
    /** Every match in the element's shadow root (or light DOM). */
    $$(selector: string): HTMLElement[];
    /** Sets a control's value as typing would, fires `input` and `change`, then waits. */
    type(selector: string, text: string): Promise<void>;
    /** Clicks the first match, then waits. */
    click(selector: string): Promise<void>;
    /** Waits until rendering, commands and messages have settled (`settled()`). */
    settled(): Promise<void>;
    /** Removes the element (and its overrides with it). */
    dispose(): void;
}
```


#### `MountedSsr`

```ts
interface MountedSsr {
    /** The element holding the server body (Declarative Shadow DOM already parsed). */
    readonly root: HTMLElement;
    /** Console errors/warnings and uncaught errors seen since mounting (see `hydrated`). */
    readonly problems: readonly string[];
    readonly unmount: () => void;
}
```


#### `MountOptions`

```ts
interface MountOptions<E extends HTMLElement> {
    /** Props, set as properties before the element connects. */
    readonly props?: PropsOf<E> & Readonly<Record<string, unknown>>;
    /** Attributes, set before the element connects. */
    readonly attrs?: Readonly<Record<string, string>>;
    /** Driver substitutions by name (`el.drivers`), set before the element connects. */
    readonly drivers?: DriverOverrides;
    /** Store instances by name (`el.stores`), set before the element connects. */
    readonly stores?: StoreOverrides;
    /** Query the element's light DOM instead of its shadow root (for `shadow: false`). */
    readonly light?: boolean;
    /** Where to append the element. Default `document.body`. */
    readonly parent?: ParentNode;
}
```


#### `MountSsrOptions`

```ts
interface MountSsrOptions {
    /** Restore the page-level store seed (`data-gyral-stores`). Default `true`. */
    readonly stores?: boolean;
    /** Copy `<meta name=…>` elements from the head (e.g. a CSRF token). Default `true`. */
    readonly metas?: boolean;
    /**
     * Copy the managed head (ADR 0019: elements marked `data-gyral-head`) right after the
     * document's `<title>`, as the server wrote it, so a test can check that the client's first
     * `setHead()` adopts it without writing. Default `true`.
     */
    readonly head?: boolean;
}
```


#### `ParseOptions`

```ts
interface ParseOptions<S, P> {
    /** Props as the component sees them. Default `{}`. */
    readonly props?: P;
    /** The state when the event fires. Default: the component's initial state for `props`. */
    readonly state?: S;
    /** Store instances `ctx.read` resolves (ADR 0013). */
    readonly stores?: readonly AnyStoreInstance[];
}
```

What `parse` passes the parser besides the input.

#### `Ran`

```ts
interface Ran<S, M> extends Stepped<S, M> {
    /** State after each message, in order. */
    readonly states: readonly S[];
}
```


#### `RunOptions`

```ts
interface RunOptions<S, P> {
    readonly props?: P;
    /** Store instances `ctx.read()` sees. */
    readonly stores?: readonly AnyStoreInstance[];
    /** Start from this state instead of `init(props)` (whose commands are then skipped). */
    readonly state?: S;
}
```


#### `StepMessage`

```ts
type StepMessage<M, P> = M | PropsChanged<P> | IntentRejected | StoreChanged | Hydrated | Errored;
```

Any message `update` accepts: the component's own, or a framework message.

#### `Stepped`

```ts
interface Stepped<S, M> {
    readonly state: S;
    readonly commands: ReadonlyArray<Command<M>>;
}
```

A reducer result, normalised: the new state plus the commands it asked for.

#### `ViewOptions`

```ts
interface ViewOptions<P> {
    readonly props?: P;
    readonly stores?: readonly AnyStoreInstance[];
}
```


#### `VirtualTime`

```ts
interface VirtualTime {
    /** Advances the clock, running due timers and the promise work between them. */
    advance(ms: number): Promise<void>;
    /** Runs every pending timer (and timers they schedule) until none remain. */
    runAll(): Promise<void>;
    /** Current virtual time in ms (`Date.now()` while installed). */
    now(): number;
    restore(): void;
}
```

Controls time in a test. Always `restore()` (for example in `afterEach`).

#### `WithOutputs`

```ts
interface WithOutputs {
    /** Type-only, as on `GyralElementClass`. */
    readonly outputs?: Tagged;
}
```

Anything carrying a component's output union: a `define()` class.

## `@gyral/testing/vitest`

### Functions

| Name | Summary |
| --- | --- |
| [`renderOnServer`](#testing-vitest-renderonserver) | The browser command: `browser: { commands: { renderOnServer } }` in vitest.config.ts, then in a browser test: |

#### `renderOnServer`

```ts
const renderOnServer: BrowserCommand<[ServerRenderRequest], string>
```

The browser command: `browser: { commands: { renderOnServer } }` in vitest.config.ts, then in
a browser test:


```ts
import { commands } from 'vitest/browser';
const page = mountSsr(await commands.renderOnServer({ module: '../src/counter.ts',
  export: 'Counter', props: { start: 3 } }));
await import('../src/counter.ts'); // hydrates in place
await hydrated(page);
```


### Types and interfaces

You rarely write most of these yourself: TypeScript infers them from the functions above. Look one up when an example or a compiler error names it.

| Name | Summary |
| --- | --- |
| [`ServerRenderRequest`](#testing-vitest-serverrenderrequest) | What `renderOnServer` renders. |

#### `ServerRenderRequest`

```ts
interface ServerRenderRequest {
    /**
     * The module to load on the server: relative to the test file (`'../src/counter.ts'`), an
     * absolute path, a path from the project root (`'/src/app.ts'`) or a package specifier.
     */
    readonly module: string;
    /**
     * The export to render (default `'default'`):
     * - a `define()` class: renders its element, with `props` set as properties;
     * - a function: called with `props`; it may return (or resolve to) a template result, an
     *   HTML string or a `Response` (a full page from `@gyral/ssr`'s `renderPage`, an app's
     *   `fetch` handler…);
     * - a template result: rendered as is.
     */
    readonly export?: string;
    /** Props for a component class, or the argument of a function export. */
    readonly props?: Readonly<Record<string, unknown>>;
    /**
     * Development markers and checks (`render`'s `dev` option). Default: the build of core the
     * server loads (development in Vitest).
     */
    readonly dev?: boolean;
}
```

What `renderOnServer` renders. Everything is sent from the browser, so it must be JSON.
