mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 22:32:04 +00:00
Merge branch 'feat/v146-ui-browser' into feat/v146-ui
# Conflicts: # src/__tests__/baselines/test-source-reads.json # src/__tests__/baselines/version-stamp-comments.json
This commit is contained in:
commit
a60111cc9d
29 files changed
+2271
-1012
No files matched your search
@@ -11,6 +11,7 @@ is about choosing and writing tests.
|
||||
| Pure logic: statistics, time zones, cadence, parsers, unit conversion, formatting | `src/**/__tests__/*.test.ts` (unit) | `pnpm test` |
|
||||
| Laws of that logic over random inputs | `*.property.test.ts` next to the unit tests | `pnpm test` |
|
||||
| What a component renders for a given state | `src/**/__tests__/*.test.tsx` (unit) | `pnpm test` |
|
||||
| How a component behaves when someone types, clicks or presses keys, and what it sends | `src/**/__tests__/*.browser.test.tsx` | `pnpm test` |
|
||||
| A structural rule about the tree (a guard) | `src/__tests__/` with a stated floor | `pnpm test` |
|
||||
| A route: auth, scope, validation, what it stores and what it answers | `tests/integration/routes/` with the kit | `pnpm test:integration` |
|
||||
| Anything whose truth lives in Postgres: constraints, `where` semantics, transactions, locks, cascades, time zones in SQL, backups, jobs | `tests/integration/` | `pnpm test:integration` |
|
||||
@@ -136,6 +137,43 @@ into a fresh account and reads it back there. A new setting column without a
|
||||
table entry fails the file. A column with no write route says why in its
|
||||
entry.
|
||||
|
||||
## Browser tests
|
||||
|
||||
Interaction belongs in the `browser` project: `*.browser.test.tsx` files run
|
||||
in headless Chromium through Vitest's Playwright provider, as part of
|
||||
`pnpm test`. Run them alone with `pnpm test --project browser`, or one file
|
||||
with `pnpm exec vitest run --project browser <path>`. The Node projects never
|
||||
collect these files and this project collects nothing else. CI installs the
|
||||
browser it needs (`playwright install --only-shell chromium`); locally, run
|
||||
that once.
|
||||
|
||||
Reach for it when the claim is about behaviour a markup test cannot see:
|
||||
focus, keys, an open menu, a pending state, the body a form posts, the URL a
|
||||
filter writes. A `renderToStaticMarkup` grep stays the tool for what a given
|
||||
state renders, and bottom-sheet gestures stay in `e2e/`.
|
||||
|
||||
The helper is `tests/browser/`:
|
||||
|
||||
- `renderWithApp(ui, { locale, user })` mounts the app's own providers: a real
|
||||
TanStack `QueryClient` (no retries) and `I18nProvider` with the real
|
||||
catalog. The signed-in account is seeded where `useAuth` reads it, with
|
||||
`authUser()` defaults under `user`. The app stylesheet and the default
|
||||
theme class are loaded, so layout, visibility and colour tokens resolve.
|
||||
- `stubFetch()` replaces `fetch` and nothing above it. Answer a route with
|
||||
`.on(method, path, { status, data, error, meta })`, hold one with
|
||||
`.deferred(...)` to assert a pending state, and read the parsed bodies from
|
||||
`.calls(method, path)`. An unmatched request gets a 404 envelope and is
|
||||
recorded. Do not mock `@tanstack/react-query` or `@/lib/api/api-fetch`.
|
||||
- `next/navigation` is aliased to a shim over the page's real `location` and
|
||||
`history`; `url.ts` sets the starting query, records pushes and replaces,
|
||||
and simulates Back or an edited address.
|
||||
- `pinDate(iso)` fakes `Date` only, so timers and animation frames still run.
|
||||
|
||||
Every test gets the real `fetch`, clock, address and an empty `localStorage`
|
||||
back afterwards. Locate by role and accessible name, the way a person finds a
|
||||
control; a `Label` adds a colon to its name, so match a labelled combobox with
|
||||
`/^Type/` rather than `"Type"`.
|
||||
|
||||
## The integration database
|
||||
|
||||
Global setup boots one Postgres container and migrates one template database.
|
||||
@@ -174,7 +212,8 @@ node scripts/test-durations.mjs unit unit.json
|
||||
The nightly workflow (`.github/workflows/test-nightly.yml`) uploads a file
|
||||
regenerated from CI timings, which is the better source.
|
||||
|
||||
The unit suite is three projects, decided per file from its source
|
||||
The unit suite is three Node projects and the `browser` project above. The
|
||||
Node ones are decided per file from its source
|
||||
(`tests/unit-projects.ts`): `guards` and `unit` share a module graph within a
|
||||
worker, `isolated` gives each file a fresh one. A file lands in `isolated` when
|
||||
it mocks, spies, stubs env or globals, fakes timers or resets modules. If a
|
||||
|
||||
@@ -101,7 +101,7 @@ No `text-[9px]`. No off-scale sizes (`text-[28px]`); round to the neighbouring s
|
||||
| A sheet from the bottom edge (phones) | `<SheetContent side="bottom">` or `ResponsiveSheet`, both of which render `BottomSheetContent` (`ui/bottom-sheet.tsx`): grip, swipe to close, X on the title row. `preview="58dvh"` for a sheet that opens part-way and pulls up to 90 dvh (its footer `data-sheet-footer`, its scroll body `data-sheet-body`); `draggable={false}` only for a sheet that fills the window | a hand-rolled grab handle or drag; a `fixed bottom-0` panel standing in for a sheet |
|
||||
| Date / time entry | `DateField` / `TimeField` / `DateTimeField` | native `type="date | time | datetime-local"` inputs (browser-locale trap, v1.25.4) |
|
||||
| Small text action inside a line (Open schedule, Select all, View all, Retry, Learn more) | `InlineAction` (`ui/inline-action.tsx`): tone `primary` / `muted` / `foreground`, size `xs` / `sm`, `asChild` for a `Link`; owns the focus ring and `pointer-coarse:min-h-11` | a hand-rolled underlined `<button>` / `<Link>` (`inline-action-guard.test.ts`) |
|
||||
| Mutually exclusive choice on one track (range, zoom, cards vs table, recent vs all) | `Segmented` (`ui/segmented.tsx`): no track, the chosen segment filled in primary (the chart tabs' look; a muted track with a raised segment was tried in 1.43.8 and dropped in 1.43.10 as too heavy); `mode="radio"` (default, roving focus) or `"toggle"` (view switches, form sheets), `iconOnly` for icon segments, `size="sm"` for range rows in a chart header; 44 px per segment on a coarse pointer | a row of ghost/secondary Buttons, a fifth segmented variant (`segmented.test.tsx` holds the definition to one module) |
|
||||
| Mutually exclusive choice on one track (range, zoom, cards vs table, recent vs all) | `Segmented` (`ui/segmented.tsx`): no track, the chosen segment filled in primary (the chart tabs' look; a muted track with a raised segment was tried in 1.43.8 and dropped in 1.43.10 as too heavy); `mode="radio"` (default, roving focus) or `"toggle"` (view switches, form sheets), `iconOnly` for icon segments, `size="sm"` for range rows in a chart header; 44 px per segment on a coarse pointer | a row of ghost/secondary Buttons, a fifth segmented variant (`segmented-single-implementation.test.ts` holds the definition to one module) |
|
||||
| A grade beside a reading (confidence, score gain, band, lab range flag) | `ScorePill` (`ui/score-pill.tsx`): tone `success` / `info` / `warning` / `destructive` / `neutral`, `numeric` for figures; `CONFIDENCE_TONE` maps both confidence scales | a hand-rolled `rounded-full px-2 py-0.5 text-xs bg-*/15` chip (`score-pill-guard.test.ts`); `TagChip` (neutral label) and `Badge` (connection/account state) stay what they are |
|
||||
| "This vital on these days vs those days" board | `MetricCrosstab` (`insights/metric-crosstab.tsx`): caller formats the numbers, the board owns row, neutral signed delta, confidence chip, dismissal | a fourth copy of the crosstab list |
|
||||
| A trend / stat tile in a grid (KPI strip, derived tiles, their skeletons and building states) | `TrendTile` (`ui/trend-tile.tsx`) | the hand-spelled `bg-card border-border … p-4 md:p-6` frame |
|
||||
|
||||
Reference in new issue
Block a user