mirror of
https://github.com/vernu/textbee.git
synced 2026-08-01 09:59:02 -04:00
The screen opened with a tall filter card, so messages started below the fold, and each message was a bordered card capped at max-w-sm, fitting three or four per mobile screen. List: - Messages are now rows grouped under day headings (Today, Yesterday, then the date). Roughly triples what fits on a phone, and scanning by time is how people actually look for a message. - Rows are buttons, so the list is keyboard navigable, with relative times that reveal the exact timestamp on hover. - Direction now comes from the `type` the API returns. It was inferred from whether `sender` was present, which is only a proxy; the old check survives as a fallback for rows written before `type` existed. - Pagination renders only when there is more than one page. - A search that matches nothing is a different state from a device with no messages, and offers a way back. Filter bar collapsed into one compact row: search, device, refresh, and auto refresh moved into a dropdown instead of four inline buttons occupying a whole row for a rarely-changed setting. Details dialog reordered around why it is opened: the message body first, then metadata, then errors, then actions. The exact timestamp is text here because the list tooltip is hover-only and unreachable on touch. Composer dialog gains real field labels (it used placeholders as labels, which vanish on focus and are not reliably announced) and the same segment counter bulk send has. Two fixes found along the way: - Reply passed message.device?._id with no fallback. The endpoint does populate device, but if it were ever absent the composer would open with no device and could not send. It now falls back to the device being viewed. - The device-defaulting effect became derived state, so the list no longer renders once with no device selected before correcting itself. Segment helpers moved to lib/sms.ts now that the send page, bulk send and the composer all use them. Fixtures now carry the populated `device` and relative dates, so the mocked path matches production and exercises day grouping. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Testing
The web app has two test layers. Both run against mocked API data. Tests must never point at a real backend.
Unit / component tests (Vitest + React Testing Library + MSW)
- Runner: Vitest with jsdom.
- API mocking: MSW node server (
test/msw/server.ts) with handlers intest/msw/handlers.ts. Unhandled requests throw (onUnhandledRequest: 'error'), so a test can never silently reach a live backend. - Render helper:
test/render.tsxexposesrenderWithProviders/render, which wrap components in the app providers (react-query with retries off, a mockSessionProvider). Importrender,screen,userEventfrom@/test/render. - Fixtures:
test/fixtures.tsis the single source of truth for mocked API data, shared with the e2e layer.
Commands:
pnpm test # run once
pnpm test:watch # watch mode
pnpm coverage # with coverage report
Place tests next to the code as *.test.tsx or under __tests__/.
End-to-end tests (Playwright)
- Runner: Playwright, specs in
e2e/. - The real Next dev server boots on port 3100 with a fixed
NEXTAUTH_SECRETand a backend URL (localhost:3999) that nothing listens on. e2e/mock-api.tsintercepts every/api/v1/**request and serves the shared fixtures, so no request can reach a real backend.e2e/session.tsmints a valid NextAuth session cookie so tests run authenticated without the real login flow.
Commands:
pnpm test:e2e # headless
pnpm test:e2e:ui # interactive UI
First-time setup downloads the browser: pnpm exec playwright install chromium.
The rule
If a new screen calls a new endpoint, add a handler in test/msw/handlers.ts and a branch in e2e/mock-api.ts, both backed by a fixture in test/fixtures.ts. Never let a test hit the network.