feat: pagination in the SPA for the Sites and Authors lists
This commit is contained in:
1 parent
500ad59458
commit
340a0ad461
34 files changed
+2833
-896
No files matched your search
@@ -0,0 +1,307 @@
|
||||
Feature: Sites and Authors list pagination
|
||||
|
||||
As a RenewCMS owner (Sites) or any signed-in role (Authors)
|
||||
I want the Sites and Authors lists split into pages with a page-size menu, server-side search and shareable URLs
|
||||
So that I can browse a long directory quickly and come back to the same page
|
||||
|
||||
Background:
|
||||
Given /settings/sites (SitesView, owner only) and /authors (AuthorsView, every role) are the verified routes of features/sites_authors_screens.feature, per docs/design/routes.md, docs/design/screens/sites.md group "Sites: pagination" and docs/design/screens/authors.md group "Authors: pagination"
|
||||
And the Paginator and the page-size select are the Articles and Media ones (AppPaginator, docs/design/components.md "Paginator"); nothing new is drawn except the single-page variant below
|
||||
And the screens request GET /v1/site/?q=Q&page=P&size=S and GET /v1/author/?q=Q&page=P&size=S through useApi only, with relative URLs, always sending page and size, and q only when it is not empty
|
||||
And the total is read from the X-Total-Count response header, and the body stays the array of sites (Sites) or the envelope {items,total,page,size} (Authors)
|
||||
And a request that fails or carries no X-Total-Count is treated as a failed load
|
||||
And page, size and q live in the URL as ?page=&size=&q=; sizes are 5, 25, 50, 100 and the default is 25; the defaults (page 1, size 25, no q) are omitted from the URL
|
||||
And an invalid page (not a positive integer) or an invalid size (not 5, 25, 50 or 100) is replaced silently by the default with a history replace and no notice, and the request carries the default
|
||||
And a change to search or to size resets page to 1 and drops it from the URL; search keeps its existing debounce
|
||||
And the URL changes when a response succeeds, not when the request is sent, and Browser Back returns to the previous page of results
|
||||
And a page past the last page (a 200 with items [] and total above 0) is replaced in the URL (no history entry) by ?page=<last>, <last> is requested, and the info notice "That page doesn't exist. Showing the last page." is shown
|
||||
And the summary reads "Showing {from}–{to} of {total}" with locale-formatted numbers (thousands separators above 999), computed from total (the filtered count)
|
||||
And the footer sits below the list: summary on the left, the page-size select next to it, the page controls right-aligned; below md the summary and the select share one row (wrapping onto two if needed), then Previous, "Page X of Y" and Next on one row with 44 px targets and a label that never wraps
|
||||
And the page controls are First, Previous, page numbers with ellipses marking skipped ranges once there are more than 7 pages, Next and Last; the current page uses --rc-sand fill, --rc-on-sand text, a --ui-primary border and aria-current="page"; disabled controls are at 40 % opacity with aria-disabled
|
||||
And on a single page (total <= size, total > 0) the controls and the page-size select are not rendered and the footer holds only the count line "{N} sites" or "{N} authors" in --ui-text-muted ("1 site", "1 author"); this replaces the AuthorsView rule that showed the pager only above 25 items
|
||||
And the count line or the footer appears whenever the list has loaded with total above 0, including the existing "Default" frames
|
||||
And the same footer is shown to owner, admin and read-only roles on Authors; Sites stays owner only (403 page for the others)
|
||||
And SitesView gains a search field with the placeholder "Search by name or key", matching name, key and base URL on the server; Authors keeps "Search by name or slug"
|
||||
And the places that need every site (deep link ?edit=<key>, duplicate-key check, ReassignDialog destinations, the form's default-site text) read the unpaged GET /v1/site/ and never the current page
|
||||
And keyboard and aria are those of Articles: Tab reaches the page-size select then the controls, Enter or Space activates, ArrowDown opens the page-size list, Enter selects, Esc closes and returns focus; every control shows a 2 px --ui-primary focus outline with a 2 px offset
|
||||
And every new visible string goes through t(...) with the English text as the key and its French entry in web/src/i18n/messages.ts; the placeholders are written {0}, as in the other messages; the new strings are "Search by name or key" (French "Rechercher par nom ou clé"), "{0} sites" and "1 site" ("{0} sites", "1 site"), "{0} authors" and "1 author" ("{0} auteurs", "1 auteur"), and 'No sites match "{0}"' (French 'Aucun site ne correspond à « {0} »'); the other strings reuse the Articles and Media entries
|
||||
And the sites fixture is 61 sites (3 pages at size 25) and the authors fixture is 128 authors (6 pages at size 25), with generated rows, unless stated
|
||||
|
||||
Rule: Page states, one set per screen
|
||||
|
||||
Scenario Outline: 1 First page
|
||||
Given <screen> resolves GET <path>?page=1&size=25 with 25 rows and X-Total-Count <total>
|
||||
When the user navigates to <route>
|
||||
Then the summary reads "<summary>", the page-size select reads "25 per page" and the controls read "Page 1 of <pages>"
|
||||
And First and Previous are disabled with aria-disabled, page 1 is current, Next and Last are enabled
|
||||
And below md Previous is disabled
|
||||
And the URL has no page and no size
|
||||
|
||||
Examples:
|
||||
| screen | path | route | total | pages | summary |
|
||||
| Sites | /v1/site/ | /settings/sites | 61 | 3 | Showing 1–25 of 61 |
|
||||
| Authors | /v1/author/ | /authors | 128 | 6 | Showing 1–25 of 128 |
|
||||
|
||||
Scenario Outline: 2 Middle page
|
||||
Given <screen> resolves GET <path>?page=<page>&size=25 with 25 rows and X-Total-Count <total>
|
||||
When the user navigates to <route>?page=<page>
|
||||
Then the summary reads "<summary>" and the label reads "Page <page> of <pages>"
|
||||
And page <page> is current with --rc-sand, --rc-on-sand, a --ui-primary border and aria-current="page"
|
||||
And every control is enabled
|
||||
|
||||
Examples:
|
||||
| screen | path | route | page | total | pages | summary |
|
||||
| Sites | /v1/site/ | /settings/sites | 2 | 61 | 3 | Showing 26–50 of 61 |
|
||||
| Authors | /v1/author/ | /authors | 3 | 128 | 6 | Showing 51–75 of 128 |
|
||||
|
||||
Scenario Outline: 3 Last page
|
||||
Given <screen> resolves GET <path>?page=<pages>&size=25 with <rows> rows and X-Total-Count <total>
|
||||
When the user navigates to <route>?page=<pages>
|
||||
Then the summary reads "<summary>" and the label reads "Page <pages> of <pages>"
|
||||
And Next and Last are disabled with aria-disabled and Previous and First are enabled
|
||||
|
||||
Examples:
|
||||
| screen | path | route | pages | rows | total | summary |
|
||||
| Sites | /v1/site/ | /settings/sites | 3 | 11 | 61 | Showing 51–61 of 61 |
|
||||
| Authors | /v1/author/ | /authors | 6 | 3 | 128 | Showing 126–128 of 128 |
|
||||
|
||||
Scenario Outline: 4 Single page
|
||||
Given <screen> resolves GET <path>?page=1&size=25 with <total> rows and X-Total-Count <total>
|
||||
When the user navigates to <route>
|
||||
Then the footer shows only "<line>"
|
||||
And the page controls and the page-size select are not rendered
|
||||
And no "Showing" summary is rendered
|
||||
|
||||
Examples:
|
||||
| screen | path | route | total | line |
|
||||
| Sites | /v1/site/ | /settings/sites | 4 | 4 sites |
|
||||
| Authors | /v1/author/ | /authors | 6 | 6 authors |
|
||||
|
||||
Scenario Outline: 4b Singular count line
|
||||
Given <screen> resolves its list with 1 row and X-Total-Count 1
|
||||
When the user navigates to <route>
|
||||
Then the footer shows only "<line>"
|
||||
|
||||
Examples:
|
||||
| screen | route | line |
|
||||
| Sites | /settings/sites | 1 site |
|
||||
| Authors | /authors | 1 author |
|
||||
|
||||
Scenario Outline: 4c Five per page with a few rows still pages
|
||||
Given <screen> resolves GET <path>?size=5&page=1 with 5 rows and X-Total-Count <total>
|
||||
When the user navigates to <route>?size=5
|
||||
Then the summary "Showing 1–5 of <total>", the page-size select "5 per page" and the controls read "Page 1 of 2"
|
||||
|
||||
Examples:
|
||||
| screen | path | route | total |
|
||||
| Sites | /v1/site/ | /settings/sites | 6 |
|
||||
| Authors | /v1/author/ | /authors | 6 |
|
||||
|
||||
Scenario Outline: 5 Page size menu
|
||||
Given the user is on <route>?page=<page>
|
||||
When the user opens the page-size select (it opens upward) and chooses "<label>"
|
||||
Then <path>?page=1&size=<size> is requested and, on success, the URL is <url>
|
||||
And the list shows page 1
|
||||
|
||||
Examples:
|
||||
| route | path | page | label | size | url |
|
||||
| /settings/sites | /v1/site/ | 2 | 5 per page | 5 | /settings/sites?size=5 |
|
||||
| /settings/sites | /v1/site/ | 2 | 25 per page | 25 | /settings/sites |
|
||||
| /settings/sites | /v1/site/ | 2 | 50 per page | 50 | /settings/sites?size=50 |
|
||||
| /settings/sites | /v1/site/ | 2 | 100 per page | 100 | /settings/sites?size=100 |
|
||||
| /authors | /v1/author/ | 3 | 5 per page | 5 | /authors?size=5 |
|
||||
| /authors | /v1/author/ | 3 | 25 per page | 25 | /authors |
|
||||
| /authors | /v1/author/ | 3 | 50 per page | 50 | /authors?size=50 |
|
||||
| /authors | /v1/author/ | 3 | 100 per page | 100 | /authors?size=100 |
|
||||
|
||||
Scenario Outline: 6 Loading a page
|
||||
Given <route>?page=<page> has loaded and the request for page <next> has not resolved
|
||||
When the user activates Next (or Previous, a page number, or changes the size)
|
||||
Then the rows are replaced by skeleton rows (8 on desktop, 6 below md) and the list is aria-busy="true"
|
||||
And the pager stays, at 40 % opacity, every control and the page-size select are disabled and not focusable
|
||||
And the summary reads "Loading page <next>…" and the page number shown is still <page>
|
||||
And the URL is unchanged until the request resolves
|
||||
When the request resolves 200
|
||||
Then the rows, the summary and the URL of page <next> appear
|
||||
|
||||
Examples:
|
||||
| route | page | next |
|
||||
| /settings/sites | 2 | 3 |
|
||||
| /authors | 3 | 4 |
|
||||
|
||||
Scenario Outline: 7 Error loading a page keeps the previous rows
|
||||
Given <route>?page=<page> is shown
|
||||
When the user activates Next and the request for page <next> <failure>
|
||||
Then the rows of page <page> stay, the pager keeps "Page <page>" and is enabled again, and the URL stays <route>?page=<page>
|
||||
And an inline error above the pager reads "Couldn't load page <next>. Showing page <page>.", "Check your connection and try again." and a "Retry" action
|
||||
When the user activates "Retry"
|
||||
Then the request for page <next> is sent again and, on 200, page <next> replaces the rows, the URL becomes ?page=<next> and the error goes
|
||||
|
||||
Examples:
|
||||
| route | page | next | failure |
|
||||
| /settings/sites | 2 | 3 | resolves with a 500 response |
|
||||
| /settings/sites | 2 | 3 | never reaches the server |
|
||||
| /authors | 3 | 4 | resolves with a 500 response |
|
||||
| /authors | 3 | 4 | never reaches the server |
|
||||
|
||||
Scenario Outline: 8 Empty after search
|
||||
Given <path>?q=<q>&page=1&size=25 resolves 200 with [] and X-Total-Count 0
|
||||
When the user searches "<q>"
|
||||
Then the existing empty state shows "<title>", "Check the spelling, or search for a shorter part of the name." and "Clear search"
|
||||
And the pager, the summary and the count line are not rendered
|
||||
When the user activates "Clear search"
|
||||
Then the URL is <route>, page 1 is requested and the list returns
|
||||
|
||||
Examples:
|
||||
| route | path | q | title |
|
||||
| /settings/sites | /v1/site/ | blog | No sites match "blog" |
|
||||
| /authors | /v1/author/ | zoe | No authors match "zoe" |
|
||||
|
||||
Scenario Outline: 9 Search resets to page 1
|
||||
Given the user is on <route>?page=<page>
|
||||
When the user types "<q>" in the search field and the debounce elapses
|
||||
Then <path>?q=<q>&page=1&size=25 is requested
|
||||
And on success the URL is <route>?q=<q> (page omitted) and the summary reads "<summary>"
|
||||
And a size change resets the page the same way
|
||||
|
||||
Examples:
|
||||
| route | path | page | q | summary |
|
||||
| /settings/sites | /v1/site/ | 4 | renew | Showing 1–25 of 27 |
|
||||
| /authors | /v1/author/ | 5 | mar | Showing 1–25 of 31 |
|
||||
|
||||
Scenario Outline: 10 Long labels on mobile
|
||||
Given the viewport is 390 px wide and <route>?page=<page> resolves with X-Total-Count <total>
|
||||
Then the summary "<summary>" and the page-size select share one row and wrap onto two if needed
|
||||
And Previous, the label "<label>" and Next stay on one row, the two buttons flex and the label never wraps
|
||||
And totals above 999 use thousands separators in both labels
|
||||
|
||||
Examples:
|
||||
| route | page | total | summary | label |
|
||||
| /settings/sites | 6 | 312 | Showing 126–150 of 312 | Page 6 of 13 |
|
||||
| /authors | 12 | 1284 | Showing 276–300 of 1,284 | Page 12 of 52 |
|
||||
|
||||
Rule: URL rules
|
||||
|
||||
Scenario Outline: Invalid values fall back silently
|
||||
When the user navigates to <route>?size=30&page=abc
|
||||
Then <path>?page=1&size=25 is requested and the URL is replaced by <route>
|
||||
And no notice is shown
|
||||
|
||||
Examples:
|
||||
| route | path |
|
||||
| /settings/sites | /v1/site/ |
|
||||
| /authors | /v1/author/ |
|
||||
|
||||
Scenario Outline: Page 0 and negative pages fall back
|
||||
When the user navigates to <route>?page=<page>
|
||||
Then page 1 is requested and the URL is replaced by <route>
|
||||
|
||||
Examples:
|
||||
| route | page |
|
||||
| /settings/sites | 0 |
|
||||
| /settings/sites | -2 |
|
||||
| /authors | 0 |
|
||||
| /authors | -2 |
|
||||
|
||||
Scenario Outline: A page out of range goes to the last page with a notice
|
||||
Given <path>?page=99&size=25 resolves 200 with [] and X-Total-Count <total>
|
||||
When the user navigates to <route>?page=99
|
||||
Then the URL is replaced (no history entry) by <route>?page=<last> and page <last> is requested
|
||||
And the info notice reads "That page doesn't exist. Showing the last page."
|
||||
And the last-page state is shown
|
||||
|
||||
Examples:
|
||||
| route | path | total | last |
|
||||
| /settings/sites | /v1/site/ | 61 | 3 |
|
||||
| /authors | /v1/author/ | 128 | 6 |
|
||||
|
||||
Scenario Outline: A page out of range with a total of 0 shows the empty state
|
||||
Given <path>?page=5&size=25 resolves 200 with [] and X-Total-Count 0
|
||||
When the user navigates to <route>?page=5
|
||||
Then the empty state shows, there is no redirect, no notice, and the URL is <route>
|
||||
|
||||
Examples:
|
||||
| route | path |
|
||||
| /settings/sites | /v1/site/ |
|
||||
| /authors | /v1/author/ |
|
||||
|
||||
Scenario Outline: Combined q and page in the URL
|
||||
When the user navigates to <route>?q=<q>&page=2
|
||||
Then <path>?q=<q>&page=2&size=25 is requested and the search field holds "<q>"
|
||||
|
||||
Examples:
|
||||
| route | path | q |
|
||||
| /settings/sites | /v1/site/ | renew |
|
||||
| /authors | /v1/author/ | mar |
|
||||
|
||||
Scenario Outline: Browser Back returns to the previous page of results
|
||||
Given the user went from <route> to <route>?page=2 with Next
|
||||
When the user uses Browser Back
|
||||
Then the URL is <route> and page 1 is requested
|
||||
|
||||
Examples:
|
||||
| route |
|
||||
| /settings/sites |
|
||||
| /authors |
|
||||
|
||||
Rule: Sites search
|
||||
|
||||
Scenario: The Sites search field
|
||||
Given the owner is on /settings/sites
|
||||
Then a search field with the placeholder "Search by name or key" is shown above the list
|
||||
When the owner types "renew" and the debounce elapses
|
||||
Then GET /v1/site/?q=renew&page=1&size=25 is requested and the list shows the matches
|
||||
|
||||
Scenario: A Sites search that matches the default site only shows what the API returns
|
||||
Given GET /v1/site/?q=journal&page=1&size=25 resolves 200 with only "Journal" and X-Total-Count 1
|
||||
When the owner searches "journal"
|
||||
Then only "Journal" is listed and the footer shows "1 site"
|
||||
|
||||
Rule: Existing behaviour that must not regress
|
||||
|
||||
Scenario Outline: The unpaged site list still feeds the dependent features
|
||||
Given the paged list shows page 2 of <total> sites
|
||||
When the owner opens ?edit=<key>, blurs a duplicate key in the add form, or opens the delete dialog
|
||||
Then GET /v1/site/ is requested without page and size and the result is used, not the paged rows
|
||||
|
||||
Examples:
|
||||
| total | key |
|
||||
| 61 | journal |
|
||||
|
||||
Scenario Outline: Read-only roles see the same Authors pager
|
||||
Given the session role is "<role>"
|
||||
When the user navigates to /authors?page=2
|
||||
Then the same footer, summary and controls are shown, with no Add author button and no row menu
|
||||
|
||||
Examples:
|
||||
| role |
|
||||
| member |
|
||||
|
||||
Scenario Outline: Owner and admin see the same Authors pager with the management actions
|
||||
Given the session role is "<role>"
|
||||
When the user navigates to /authors?page=2
|
||||
Then the same footer, summary and controls are shown, with the Add author button and the row menu
|
||||
|
||||
Examples:
|
||||
| role |
|
||||
| owner |
|
||||
| admin |
|
||||
|
||||
Scenario: Deleting the last row of the last page
|
||||
Given the user is on the last page of /authors with a single row and deletes it (204)
|
||||
Then the previous page is requested and the URL is replaced by it
|
||||
|
||||
Scenario: Adding or deleting refreshes the total
|
||||
Given a list of 25 rows (total 25, count line only)
|
||||
When a row is added and the list reloads with total 26
|
||||
Then the footer shows the pager and the summary "Showing 1–25 of 26"
|
||||
|
||||
Rule: Design conformance of the visual gate
|
||||
|
||||
Scenario: The Create author mobile states pass at the default budget
|
||||
Given the editor states "Create author: dialog", "Create author: for me", "Create author: slug taken" and "Create author: failed" at 390x844
|
||||
Then each capture is within 10 % of the board frame, with no per-state exception in web/visual/states.ts
|
||||
|
||||
Scenario: Integration "6. Errors" passes at the default budget
|
||||
Given the board frame of Integration "6. Errors" is drawn with the 403 note
|
||||
Then every capture of "6. Errors" is within 10 %, desktop and mobile, light and dark, with no maxPercent exception
|
||||
@@ -0,0 +1,47 @@
|
||||
# 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.
|
||||
Reference in new issue
Block a user