Files
RenewCMS/features/sites_authors_pagination_screen.qa.md
T

48 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# QA: Sites and Authors list pagination (SPA)
SPA slice only. The Go API (page, size, q, X-Total-Count) already exists (features/sites_backend.feature, features/authors_backend.feature), so no new Go test is requested. If a contract below turns out wrong, report it as a backend defect. Un-run web tests are NOT RUN, never passing.
## Automated (Vitest + Vue Test Utils, `task test:web`)
Mock `useApi` for GET `/v1/site/?...` and `/v1/author/?...` (the route key includes the query) per case, returning the body and the X-Total-Count header through the new headers-aware helper. Mount SitesView (owner session) and AuthorsView (owner, admin and member sessions) inside DashboardShell with a test router at /settings/sites and /authors, as in features/sites_authors_screens.qa.md. Fixtures: 61 sites at 25 per page (3 pages) and 128 authors at 25 per page (6 pages), built by helpers. Mock matchMedia for desktop and below-md. Do not freeze the system date; use fake timers only for the 150 ms skeleton delay and the search debounce. One test per scenario, Sites and Authors in the same file through `it.each`.
1. First page: no page/size in the URL, requests carry page=1&size=25, "Showing 1–25 of 61" and "Showing 1–25 of 128", First and Previous aria-disabled, page 1 aria-current, total read from the header (body length ignored).
2. Middle page (?page=2 on Sites, ?page=3 on Authors): summaries, aria-current on the page, every control enabled.
3. Last page (?page=3, ?page=6): "Showing 51–61 of 61" and "Showing 126–128 of 128", Next and Last disabled.
4. Single page: total 4 sites and 6 authors render the count line "4 sites" / "6 authors" only; no nav, no select, no "Showing" text. 4b: "1 site" and "1 author". 4c: size=5 with total 6 renders the full pager ("Page 1 of 2").
5. Page size menu: 5/25/50/100 -> request page=1&size=N, URL after the response (/settings/sites?size=5, no size for 25); ArrowDown opens, Enter selects, Esc closes and returns focus.
6. Loading a page: unresolved promise -> skeleton rows (8 desktop, 6 below md), aria-busy, pager 40 % and disabled, "Loading page 3…" / "Loading page 4…", page number unchanged, URL unchanged before the response; resolve -> rows and URL.
7. Error loading a page (500 and network rejection): rows and URL kept, "Couldn't load page 3. Showing page 2." (Authors: page 4/3), "Check your connection and try again.", Retry resends page N and success clears the error and sets the URL. A first-load failure still shows "Couldn't load sites" / "Couldn't load authors" with "Try again".
8. Empty after search: "No sites match "blog"" / "No authors match "zoe"", "Clear search" goes back to the bare route and page 1, no pager, summary or count line.
9. Search resets to page 1: from ?page=4 (Sites) and ?page=5 (Authors) type the term (fake timers past the debounce) -> request q=...&page=1, URL /settings/sites?q=renew and /authors?q=mar after the response, "Showing 1–25 of 27" and "Showing 1–25 of 31". A size change resets the page too.
10. Long labels on mobile: 312 sites page 6 and 1,284 authors page 12 -> "Showing 126–150 of 312", "Page 6 of 13", "Showing 276–300 of 1,284", "Page 12 of 52" with the thousands separator; label has no wrap class and Previous/Next carry the flex classes (the pixel layout is the visual check).
11. URL rules: ?size=30&page=abc, ?page=0 and ?page=-2 -> default request and router.replace (assert no extra history entry and no notice). Out of range ?page=99 with total 61 / 128 -> router.replace to the last page, the second request for it and the notice "That page doesn't exist. Showing the last page."; with total 0 -> empty state, no redirect, no notice. ?q=renew&page=2 loads with the field filled. Browser Back returns to page 1.
12. Sites search field: placeholder "Search by name or key" with its French string; q is sent, encoded; matches the API contract of features/sites_backend.feature.
13. Unpaged site list for dependents: with page 2 shown, ?edit=journal, the duplicate-key blur and the ReassignDialog destinations call GET /v1/site/ with no page and no size and never use the paged rows.
14. Roles: member on /authors?page=2 sees the same footer with no Add author button and no row menu; owner and admin see the same footer with the Add author button and the row menu.
15. Delete the last row of the last page reloads the previous page and replaces the URL; adding the 26th item turns the count line into the pager.
16. Authors regression: the old threshold behaviour (pager above 25 only) is gone; a test asserts the count line instead of the pager when total <= size.
17. useApi: a unit test for the new headers-aware helper (header read, missing header treated as a failed load, 401/403/429 behaviour unchanged, `request` untouched).
18. i18n: the new strings have a French entry ("Rechercher par nom ou clé", "{0} sites", "1 site", "{0} auteurs", "1 auteur", 'Aucun site ne correspond à « {0} »'); the existing key-parity spec in web/src/i18n/ must stay green.
19. Existing specs amended (list them in the diff): SitesView and AuthorsView specs and their test kits in web/src/views/__tests__/directoryTestKit.ts (the list mock now answers a page and a total through the header), and the sites_authors_screens specs whose assumptions about the loaded list change (item 14 duplicate-key check, the deep link).
## Visual (`task visual:check -- sites`, `-- authors`, `-- editor`, `-- integration`)
Run all four. The gate is hard: every capture within 10 % at 1440x900 and 390x844, in light and dark. Add no new exception. A failure, a missing setup or an unmocked /v1 call is listed for the user and not hidden.
Sites and Authors: add one setup per state in `web/visual/states.ts`, reusing the board's own sample data from the groups "Sites: pagination" and "Authors: pagination" in `docs/design/source/RenewCMS Screens Back-office.dc.html` (generated rows, totals 61/128, summaries, page labels). Take layout from the frame markup. States for each of the two screens: First page, Middle page, Last page, Single page, Page size menu, Loading a page, Error loading a page, Empty after search, Search resets to page 1, Long labels on mobile (10 each, 20 in total). None of them show a date or time, so none is `skip: 'clock-sensitive'`. The page-size menu opens upward. Mock `GET /v1/site/?...` and `/v1/author/?...` with the right X-Total-Count; the Loading state keeps its request pending.
Existing Sites and Authors states ("Default", "Only the default site", loading, error, row menu, forms, delete, read-only) are re-run: the count line now appears under every loaded list, so some may exceed 10 %. Report any that do, with the diff percentage, to the user.
Editor: remove the `withMobileException` wrapper and its `boardOmitsMobileDialogReason` constant from the four "Create author" states (dialog, for me, slug taken, failed) now that the board draws the mobile dialog; update the setups only where needed to match the frame, and do not hide the dialog. If a mobile capture still fails, list it.
Integration: remove the `maxPercent: { desktop: 16, mobile: 22 }` exception from "6. Errors" (the board now draws the 403 note). Remove no other Integration exception unless the user names it.
## Manual
- Keyboard order and focus return: Tab to the page-size select, then First, Previous, each page, Next, Last; Enter and Space; ArrowDown, Enter, Esc; the 2 px --ui-primary focus ring with a 2 px offset on every control.
- Hover, motion and the reduced-motion behaviour of the skeleton pulse.
- Real round trip with `task dev`: seed more than 25 sites and more than 25 authors; page, size and q survive a reload; browser Back after a size change; the footer count line on a single page.
- Out-of-range fallback against a real shrinking list (delete rows in another tab).
- A small real list: owner and non-owner views of /authors keep the same footer.