# The component and store tier (`make ui-test`) 757 tests in a real Chromium with no Wails, no backend, no seeded library and no virtual display. This is the cheapest coverage available and where the bulk of UI regression belongs. ```bash make ui-setup # once: the Vitest provider's own Chromium make ui-test # behaviour only make ui-watch make ui-visual # + toMatchScreenshot baselines (YJ_VISUAL=1) make ui-visual-update # re-record them make ui-test UI_ARGS='store/queue' # filter ``` ## How it works Wails v3 routes every runtime call — bindings, event emits, window, dialogs, clipboard — through one IPC transport, and `setTransport()` is a public seam for replacing it. So `frontend/test/support/wails-fake.ts` replaces **that and nothing else**, and the tests then exercise the *real* generated bindings, the *real* runtime and the *real* store code. No module mocking, and no second description of the Wails layer. A binding call carries a *method ID* (an FNV-1a hash of the Go method's fully-qualified name), not a name, so the fake derives the ID → path map from the generated tree at setup: each package's `index.ts` re-exports its service under the Go type's real name, which is the one place that casing survives. A path that never maps records as `#` and fails the assertion naming it. ```ts emit(Events.QueueChanged, payload); // push a backend event stub('queue.Queue.GetState', state); // a value, or a function of the args stubFailure('queue.Queue.SetQueue'); // reject, as a Go error does calls('queue.Queue.SetQueue'); // what the frontend called back with lastArgs('queue.Queue.SetQueue'); const el = await fixture('now-playing'); // mount; shadow()/text() query it ``` Delivery is not mirrored — `emit()` goes through the runtime's own `window._wails.dispatchWailsEvent`, which is the entry point the backend's push uses, so listener expiry and ordering are the runtime's real code. What *is* mirrored is one line of Go: how `EventManager.Emit` packs variadic data into an event's single `data` field (none is null, one is the value, more is the slice). A frontend `Events.Emit` no longer notifies local listeners before Go — v3 calls the backend, which sends the event back out to every window. The page still sees its own emit, one round trip later rather than synchronously. ## Five things that will cost you time - **Store singletons are constructed at module import**, before any test can stub. `test/setup.ts` therefore carries import-time defaults for the stores that read config in their constructor. Without one, a store caches `undefined` where Go would have sent `[]`, and components crash on `.length` — which reads exactly like a component bug and is not. Adding a store that reads config on construction means adding its default there. - **`vitest.config.mts`, not `.ts`** — it `mergeConfig`s the repo's `vite.config.mts` to reuse the `@go`/`@store`/`@components` aliases, and a `.ts` sibling cannot import it. - **Screenshots need the theme.** The setup file imports `@store/theme-store` for its side effect (it applies the `--yj-*` ramp to `:root`); without it a component renders white-on-white and the baseline is blank. - **`@lit-labs/virtualizer` never produces two identical frames**, so `toMatchScreenshot` on `` fails with "could not capture a stable screenshot" rather than a diff. Assert on its rows instead. - **A v3 binding settles several microtasks after a v2 one did** — it goes through `Call()`, an async `runtimeCallWithID`, the transport and a `CancellablePromise`, where v2's `window.go` proxy resolved one promise. `fixture()` drains microtasks between two renders so a component that loads in `firstUpdated` is loaded when it returns. Microtasks and not a timer, deliberately: a timer hangs forever under the suites that install fake ones. Visual baselines are font-hinting and compositing sensitive, which is why they are opt-in: they only mean anything on the machine that recorded them. ## Bindings `frontend/bindings/` is generated by `wails3`, **not** by `go generate`, so the pre-commit codegen check does not cover it — a renamed Go bound method first shows up at runtime, as a call that never settles. ```bash make bindings-check # ~3.5 s warm, also a pre-commit hook make bindings # regenerate for real ``` No build tags are passed: the generator is a static analyser that sees only the configuration it is told about, and the one that matters is the one users run, which is the default tag set.