Skip to main content
In development. 86d is being built in the open. Every capability is Experimental until it earns evidence, so check maturity levels before you rely on anything here.
Vitest covers packages, Modules, and deterministic rendered states. A small Playwright suite covers real-browser seams against a running Store Runtime. Direct Chrome review owns qualitative UI judgment.

The six health gates

From the repository root:
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:

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

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.
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 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

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.