> ## Documentation Index
> Fetch the complete documentation index at: https://86d.store/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Test the Store Runtime

> Run the health gates, write Vitest coverage that catches real failures, and use focused browser smoke plus direct Chrome review.

<Warning>
  **In development.** 86d is being built in the open. Every capability is Experimental until it earns evidence, so check [maturity levels](/docs/resources/versioning) before you rely on anything here.
</Warning>

Vitest covers packages, [Modules](/docs/concepts/modules), and deterministic rendered states. A small Playwright suite covers real-browser seams against a running [Store Runtime](/docs/concepts/architecture). Direct Chrome review owns qualitative UI judgment.

## The six health gates

From the repository root:

```bash theme={null}
bun run generate:modules -- --frozen  # verify the generated Module registry
bun run typecheck                     # TypeScript across the workspace
bun run check                         # Biome lint and format
bun run test                          # unit and integration tests
bun run docker:build                  # production Store Runtime image
bun run docker:verify                 # container boot and health smoke
```

All six pass in that order before you hand off a change. `bun run build` remains available for package and Module authoring. Browser smoke runs separately when a change reaches a browser-specific seam.

## Unit tests

Every Module ships tests under `modules/<name>/src/__tests__/`. The one `86d module create` scaffolds checks the factory and nothing else:

```ts theme={null}
import { describe, expect, it } from "vitest";
import myFeature from "../index.js";

describe("my-feature", () => {
  it("creates a module with correct id", () => {
    const mod = myFeature();
    expect(mod.id).toBe("my-feature");
  });

  it("creates a module with version", () => {
    const mod = myFeature();
    expect(mod.version).toBe("0.0.1");
  });
});
```

### What is worth testing

* **The factory.** It returns the right `id` and takes its options.
* **Controllers.** Every state transition, guard, and calculation gets one happy path and one failure path. The failure path is the one that matters.
* **Endpoint handlers.** Input validation, auth checks, response shape.
* **Contracts.** When your Module publishes something through `exports.read`, test that a consumer receives what you think it does.

For a Module that talks to an outside API, use fixtures that match the provider's real JSON. A test that passes against a shape you invented tells you your invention is consistent. It tells you nothing about the integration.

### One Module at a time

```bash theme={null}
bun run --filter @86d-store/cart test
```

## Browser smoke

Playwright lives in `tests/browser/`. One Chromium project runs with zero retries. The suite is deliberately small: it covers authentication and session boundaries, catalog-to-checkout navigation, cart persistence, browser request construction, keyboard focus, mobile overflow, and recovery behavior.

Point `BROWSER_STORE_URL` at an already running, migrated, seeded Store. It defaults to `http://localhost:3000` and fails closed when the target or authenticated session is unavailable.

```bash theme={null}
bun run test:browser       # focused browser smoke
bun run test:browser:ui    # interactive Playwright runner
```

Set `BROWSER_START_SERVER=1` if Playwright should start the development Store for a local run. CI installs Chromium, builds and starts the production Store, and runs the same suite in its path-filtered workflow.

Do not turn browser smoke into a route inventory, a wall-clock benchmark, or a screenshot matrix. Navigation, storage, focus, responsive layout, browser request construction, and similar browser boundaries belong here. State reducers, validation, calculations, endpoint envelopes, and broad route coverage belong in faster tests.

### Direct visual review

For affected UI, inspect the running Store directly in Chrome at 1280 x 720 and 375 x 667, in light and dark appearance. Use the browser-addressable fixture routes to exercise required loading, empty, error, permission, unavailable, and populated states without depending on live provider data. Check keyboard focus, overflow, responsive composition, errors, and recovery paths relevant to the change. Inspect browser console and network failures too.

Screenshots can support a dated review, but they are not pixel-diff baselines and never advance a launch or capability gate.

### Selectors

Prefer role and label selectors. Use `data-testid` when the element has no stable accessible locator. A CSS class selector breaks the next time someone touches the styling.

Use web-first assertions. Do not add fixed delays or wait for `networkidle`; both couple the test to timing that is unrelated to the behavior under test.

## Checking a whole change

1. Run the focused tests for the package or Module you touched.
2. Run all six health gates in repository order.
3. Seed the target Store with `bun run db:seed` (curated Modules with compiled tables only; requires `DATABASE_URL`).
4. Run `bun run test:browser` when the change touches a real-browser seam.
5. For UI work, inspect the affected states in the required Chrome matrix.

## Writing a Module that is testable

* **Take [`ModuleDataService`](/docs/resources/glossary#moduledataservice) as a constructor argument.** The runtime hands it to `init` as `ctx.data`. Pass it explicitly into your controllers rather than reaching for a global, and your tests get to substitute it.
* **Keep the logic that does not need I/O separate.** A function that takes inputs and returns outputs is trivial to test.
* **Mock at the boundary.** Swap `fetch` itself, not your own wrapper around it. Mocking your wrapper tests your wrapper.
* **Use `@86d-store/core/test-utils`** for an in-memory data service.

## Coverage

```bash theme={null}
bun run test:coverage
```

There is no enforced threshold. The convention for first-party Modules is that every endpoint handler and every controller method has one happy path plus a failure path for each documented error.

## Related pages

* [Troubleshooting](/docs/operations/troubleshooting)
* [Build a custom Module](/docs/guides/building-a-module)
* [Contributing](/docs/resources/contributing)
* [Versioning and maturity](/docs/resources/versioning)
* [Quickstart](/docs/quickstart)
