# S016 - Web UI behavior tests One isolated place: `app0/web/tests/`. Vitest + happy-dom — no real browser. ## Layout - `tests/server.ts` - **FakeBff**: the production `linji serve` API contract in memory (invite gate, bare-name normalization, sessions, member-only space ACL, push endpoints). EventSource listeners are fed `update` events when writes land in their space, mirroring the server's zds watch. - `tests/setup.ts` - environment wiring: `fetch` -> FakeBff, EventSource mock fed by the fake, Notification / PushManager / serviceWorker mocks, `resetEnv()` between tests. - `tests/app.test.tsx` - 19 tests over the rendered App: login (hint, invite field visibility, wrong invite, wrong password, bare name), spaces (empty state, invalid URI, join, non-member denial, thread expansion), posting (disabled button, post + composer clear, new thread), SSE live update, open-app notifications (hidden tab fires, own posts and visible tab don't), the bell (permission + push subscription registration), logout. ## Run `cd app0/web && bun run test` (NOT `bun test` - that is Bun's own runner, which can't transform the Solid JSX). ~1.5s. ## Gotchas found (test-side) - Vitest resolves solid-js's `node` export condition (server build, renders nothing). `vitest.config.ts` aliases `solid-js` and `solid-js/web` to their browser `dev.js` builds. - `` keys by reference: a refresh replaces the whole thread section (new JSON objects), so assertions must re-query elements after a refresh. - Seed/mutation state in the fake must be reset between tests (`resetEnv` restores `inviteCode` too — one leaked mutation hid the invite field for every later test).