102 lines
7.4 KiB
Markdown
102 lines
7.4 KiB
Markdown
# QA: Home screen
|
|
|
|
## Implementation note for coder (informational, not a QA assertion)
|
|
|
|
The greeting requires `useApi.getMe()` and the `Session` type/store
|
|
(`web/src/composables/useApi.ts`, `web/src/stores/session.ts`) to also expose `username`, which
|
|
`GET /v1/auth/me` already returns in its JSON body. Today `getMe()` only extracts `is_verified` and
|
|
discards the rest, and `Session` is just `{ verified: boolean }`. Widen `MeResult` and `Session` to
|
|
carry `username: string` as well; this is a same-layer frontend response-type widening (the wire
|
|
contract doesn't change), not a backend change.
|
|
|
|
## Automated (Vitest + Vue Test Utils, `task test:web`)
|
|
|
|
Mock `useApi`'s `GET /v1/article/` call per case; no real backend, no httptest server. Mount
|
|
HomePage inside DashboardShell with a resolved session (username "marta") and a test router at
|
|
/home.
|
|
|
|
1. **Skeleton only after 150 ms** — mock `GET /v1/article/` with an unresolved promise; assert no
|
|
skeleton is present before 150 ms elapse (use fake timers); advance past 150 ms; assert skeleton
|
|
placeholders replace the stat tiles and the 5 recent-list rows, and the list container has
|
|
aria-busy="true"; resolve the promise with 200 and a non-empty list; assert the skeletons are
|
|
gone and the Default content is shown. Separately, mock a promise that resolves in under 150 ms;
|
|
assert no skeleton ever renders.
|
|
2. **Default state** — mock `GET /v1/article/` resolving 200 with the 6-article fixture (4 online,
|
|
2 drafts, ordered most-recently-updated first as in the feature); assert subtitle "Here's where
|
|
things stand on your site."; assert stat tiles "6" "articles", "4" "online", "2" "drafts"; assert
|
|
the recent list shows exactly the first 5 articles in order, each with its ArticleStatusBadge and
|
|
updated date, each row linking to /article/:id/edit; assert the 6th (least recently updated)
|
|
article is absent from the recent list; assert the PageHeader's "New article" action opens
|
|
NewArticleModal.
|
|
3. **Empty, first run** — mock `GET /v1/article/` resolving 200 with `[]`; assert subtitle "Welcome
|
|
to RenewCMS."; assert no stat tiles render; assert an empty state renders in place of the recent
|
|
list with icon file-text, title "Write your first article", text "It starts as a draft, so
|
|
nothing appears on your site until you publish it.", and action "New article"; click it; assert
|
|
NewArticleModal opens.
|
|
4. **Error state** — Examples-driven: (a) `GET /v1/article/` resolves 500, (b) it rejects with a
|
|
network error. In each: assert the breadcrumb, greeting and subtitle remain visible and the
|
|
subtitle stays "Here's where things stand on your site."; assert no stat tiles or recent list
|
|
render; assert an inline alert with role="alert" renders in their place, with icon circle-x,
|
|
title "Couldn't load your articles", text "Check your connection and try again.", and a single
|
|
action "Try again"; assert the alert text is identical between the 500 case and the network-
|
|
failure case — there is no separate wording per cause.
|
|
5. **Retry recovers into Default** — start from the Error state (mocked 500); click "Try again"; assert
|
|
`GET /v1/article/` is called a second time; resolve it 200 with the 6-article fixture; assert the
|
|
Alert is replaced by the stat tiles "6"/"4"/"2" and the recent list.
|
|
6. **Single request per navigation** — mock `GET /v1/article/` resolving 200 with the 6-article
|
|
fixture; mount /home; assert the mock was called exactly once, and both the stat tiles and the
|
|
recent list read from that one resolved value (no second call fires for either).
|
|
7. **Greeting matches the hour of day, at every state, for the session username** — stub the clock
|
|
(e.g. `vi.setSystemTime` or a mocked `Date.now`/`Date.prototype.getHours`) to each boundary hour
|
|
in turn (0, 11, 12, 17, 18, 23) and mount with session username "marta"; assert the greeting
|
|
above the subtitle reads exactly "Good morning, marta" for hours 0 and 11, "Good afternoon,
|
|
marta" for hours 12 and 17, and "Good evening, marta" for hours 18 and 23. Then, fixing the hour
|
|
at 9 ("Good morning, marta"), assert the same exact greeting text is shown above the subtitle in
|
|
all four states: while `GET /v1/article/` is unresolved past 150 ms (Loading), once it resolves
|
|
with the 6-article fixture (Default), once it resolves with `[]` (Empty), and once it rejects
|
|
with a 500 (Error).
|
|
8. **"Connect your website" card renders in every state** — assert the static card (icon plug,
|
|
title "Connect your website", text "Your site reads published articles from RenewCMS. Share
|
|
this guide with whoever builds it.", link "Open the guide" navigating to /integration) is
|
|
present and identical while `GET /v1/article/` is unresolved past 150 ms (Loading), once it
|
|
resolves with the 6-article fixture (Default), once it resolves with `[]` (Empty), and once it
|
|
rejects with a 500 (Error). The card is not part of the PageHeader's actions.
|
|
|
|
## Visual (manual verification — desktop and mobile, light and dark)
|
|
|
|
Reference: `docs/design/screens/home.md` and `docs/design/source/RenewCMS Screens Back-office.dc.html`,
|
|
group "Home". Run `task visual:check -- home` for each state and compare against the board frame.
|
|
|
|
- [ ] Loading state: skeleton stat tiles and 5 skeleton recent rows, shaped like the real layout,
|
|
matching the board.
|
|
- [ ] Default state: stat tiles, recent list with ArticleStatusBadge per row, PageHeader primary
|
|
action "New article", breadcrumb "Home", and the "Connect your website" card below — matches
|
|
the board exactly at desktop and mobile.
|
|
- [ ] Empty, first run state: the empty state replaces the stats and recent list, matching the
|
|
board's icon tile, title "Write your first article", text, and single "New article" action.
|
|
- [ ] Error state: the inline alert's icon (circle-x), title "Couldn't load your articles", text
|
|
"Check your connection and try again.", and "Try again" action match the board; the
|
|
breadcrumb, greeting and subtitle stay visible above it.
|
|
- [ ] "Connect your website" card: icon plug, title, text and "Open the guide" link match the
|
|
board, and the card renders identically in every state (Loading, Default, Empty, Error).
|
|
- [ ] Greeting placement: the greeting sits above the subtitle in the PageHeader area (not the note
|
|
board's fixture text, since the greeting is new content — position and type scale should read
|
|
as a lightweight line above the description, not competing with the h1).
|
|
- [ ] Keyboard/focus: Tab order reaches "New article" in the header, then the "Connect your
|
|
website" card's "Open the guide" link, before the recent list; the Error state's "Try again"
|
|
button is reachable and, on activation, focus stays sensible (not lost) while the request
|
|
re-runs; the recent list's rows are reachable and each activates its /article/:id/edit link
|
|
with Enter.
|
|
- [ ] Motion: the skeleton pulse runs at 1.6 s and stops under reduced motion, per the Skeleton
|
|
component spec.
|
|
|
|
## Manual verification
|
|
|
|
- **Real GET /v1/article/ against a running server** — Vitest only mocks `useApi`; confirm the real
|
|
200/500 round-trip and the real article ordering (by updated_at) against `task dev` with a live
|
|
SQLite file, since these automated tests never exercise the actual Go handler or the
|
|
`ListArticles` use case.
|
|
- **Real session username on a live app-boot** — confirm `GET /v1/auth/me`'s `username` field flows
|
|
through the real session store into the greeting, since Vitest only ever supplies a mocked
|
|
session.
|