189 lines
12 KiB
Gherkin
189 lines
12 KiB
Gherkin
Feature: Articles list pagination
|
||
Superseded in part by article_list_localization: the request is GET /v1/article/?group=story&format=html&...; page, size, q, status, sort, lang, tab, author, site and seo live in the URL; items are story rows (representative version plus coverage) and total is the unit total (stories, or versions of the selected language). The tab counts follow lang, q, author, seo and site (never status or tab) instead of staying fixed while searching (deliberate behavior change); "No results" shows every count at 0 (board loc.l7.noResults). A change of lang, tab or author, like any filter, drops page. The summary "Showing {from}–{to} of {total}" is unchanged and the UnitLabel sits above it. Scenarios 4, 6 and 7 keep their wording with story rows and the grouped request.
|
||
|
||
As a verified RenewCMS user with many articles
|
||
I want the Articles list split into pages with a page-size menu, server-side search and tabs, and shareable URLs
|
||
So that I can browse a large list quickly and come back to the same page
|
||
|
||
Background:
|
||
Given /article is the verified Articles route (features/articles_list.feature), per docs/design/routes.md, docs/design/screens/articles.md group "Articles: pagination" and the Paginator in docs/design/components.md
|
||
And the route stays /article (the design's /articles is not adopted)
|
||
And the page requests GET /v1/article/?format=html&page=P&size=S&q=Q&status=T and reads {items, total, page, size, counts} (features/articles_pagination_api.feature); the tab counts "All", "Online", "Drafts" come from counts and do not change while searching
|
||
And page, size, q, status (tab) and sort live in the URL: ?page=&size=&q=&status=&sort=; the defaults (page 1, size 25, no q, tab All, sort updated_at:desc) are omitted from the URL and from the request only where the API default applies
|
||
And the sortable columns are Title (title), Status (status), Created (created_at) and Updated (updated_at); a header activation sends sort=<field>:<direction>, the active column shows an arrow icon for its direction, and sorting is done by the server over the whole list, never inside the loaded page
|
||
And an invalid sort in the URL falls back to updated_at:desc and is replaced in the URL
|
||
And the table caption reads "Articles, sorted by last updated, newest first" for the default sort and "Articles, sorted by {column}, {ascending|descending}" otherwise, with a French entry in messages.ts
|
||
And the mobile card list has no sort control; the sort parameter is still honoured from the URL there
|
||
And an invalid or missing size falls back to 25 and an invalid page (not an integer >= 1) falls back to 1, both replaced in the URL
|
||
And a change to search, tab, sort or size resets page to 1 (and drops it from the URL); search keeps its 120 ms debounce
|
||
And the URL changes when a response succeeds, not when the request is sent
|
||
And the summary reads "Showing {from}–{to} of {total}" with locale-formatted numbers, from total (the filtered count)
|
||
And the Paginator has First, Previous, page numbers with ellipses marking skipped ranges, Next, Last, and the page-size select "5 per page", "25 per page", "50 per page", "100 per page"
|
||
And the current page uses --rc-sand fill, --rc-on-sand text and a --ui-primary border and has aria-current="page"; disabled controls are at 40 % opacity; buttons are 32 px (44 px on mobile)
|
||
And on mobile the paginator is Previous, "Page N of M" and Next
|
||
And the paginator is hidden when total fits on one page; the summary and the page-size select stay
|
||
And keyboard: Tab reaches the page-size select, first, previous, each page, next, last; Enter or Space activates; ArrowDown opens the page-size list, Enter selects, Esc closes; every control shows a 2 px --ui-primary focus outline with a 2 px offset
|
||
And this slice amends features/articles_list.feature: search, tabs and sort are no longer computed client-side over the full list; the list, the counts and the empty states come from the server response
|
||
And HomeView, ArticleView and useArticles.listArticles migrate to the envelope: they read items (and counts or total where they count) and send explicit paging; no caller reads a bare array any more
|
||
And the fixture below is total 312, size 25 (13 pages) unless stated; the frames draw at most 10 rows
|
||
|
||
Scenario: 1 First page
|
||
Given GET /v1/article/?format=html&page=1&size=25 resolves 200 with 25 items and total 312
|
||
When the user navigates to /article
|
||
Then the summary reads "Showing 1–25 of 312"
|
||
And First and Previous are disabled, page 1 is current, Next and Last are enabled
|
||
And the URL has no page or size
|
||
|
||
Scenario: 2 Middle page
|
||
Given GET ...&page=7&size=25 resolves 200 with 25 items and total 312
|
||
When the user navigates to /article?page=7
|
||
Then the summary reads "Showing 151–175 of 312"
|
||
And page 7 is current with --rc-sand / --rc-on-sand and aria-current="page"
|
||
And ellipses mark the skipped ranges and every control is enabled
|
||
|
||
Scenario: 3 Last page
|
||
Given GET ...&page=13&size=25 resolves 200 with 12 items and total 312
|
||
When the user navigates to /article?page=13
|
||
Then the summary reads "Showing 301–312 of 312"
|
||
And Next and Last are disabled
|
||
|
||
Scenario: 4 Single page
|
||
Given GET /v1/article/ resolves 200 with 6 items, total 6 and counts {all 6, online 4, draft 2}
|
||
When the user navigates to /article
|
||
Then the summary reads "Showing 1–6 of 6" and the page-size select stays
|
||
And the paginator (First ... Last) is not rendered
|
||
And the tabs read "All 6" "Online 4" "Drafts 2"
|
||
|
||
Scenario: 5 Loading next page
|
||
Given the first page has loaded and GET ...&page=2 has not resolved
|
||
When the user activates Next
|
||
Then skeleton rows keep the table height and the list is aria-busy="true"
|
||
And every paginator control is disabled and the summary reads "Loading page 2…"
|
||
When the request resolves 200
|
||
Then the rows and summary of page 2 appear and the URL becomes /article?page=2
|
||
|
||
Scenario Outline: 6 Error loading a page keeps the previous rows
|
||
Given page 6 is shown
|
||
When the user activates Next and GET ...&page=7 <failure>
|
||
Then the page-6 rows stay, the URL stays /article?page=6 and the controls are re-enabled
|
||
And a notice reads "Couldn't load page 7. Showing page 6." with a "Retry" action
|
||
When the user activates "Retry"
|
||
Then GET ...&page=7 is sent again and, on 200, page 7 replaces the rows, the URL becomes ?page=7 and the notice goes
|
||
|
||
Examples:
|
||
| failure |
|
||
| resolves with a 500 response |
|
||
| never reaches the server |
|
||
|
||
Scenario: 7 Page out of range redirects to the last page with a notice
|
||
Given GET ...&page=99 resolves 200 with items [] and total 312
|
||
When the user navigates to /article?page=99
|
||
Then the URL is replaced (no history entry) by /article?page=13 and GET ...&page=13 is sent
|
||
And the notice reads "That page doesn't exist. Showing the last page."
|
||
And the last-page state (3) is shown
|
||
|
||
Scenario: 7b Out of range with total 0 shows the empty state, no redirect
|
||
Given GET ...&page=5 resolves 200 with items [] and total 0
|
||
When the user navigates to /article?page=5
|
||
Then the "No articles yet" empty state shows and the URL is /article
|
||
|
||
Scenario: 8 Search resets to page 1
|
||
Given the user is on /article?page=7
|
||
When the user types "plaster" in the search field and the debounce elapses
|
||
Then GET ...&q=plaster&page=1 is sent
|
||
And on success the URL is /article?q=plaster (page omitted)
|
||
And the tab counts follow the new search (lang, q, author, seo and site; never status or tab)
|
||
And the same reset happens for a tab change, a sort change, a page-size change, and a lang, author, site or seo change
|
||
|
||
Scenario: 9 Empty after search
|
||
Given GET ...&q=tiles resolves 200 with items [], total 0 and counts {all 0, online 0, draft 0, suggestions 0, missing 0}
|
||
When the user searches "tiles"
|
||
Then the paginator and the summary are hidden
|
||
And the no-results state shows "No articles match "tiles"", "Check the spelling, or search for a shorter part of the title." and "Clear search"
|
||
And every tab count reads 0 (board loc.l7.noResults), because counts follow the search
|
||
|
||
Scenario Outline: 10 Page size menu
|
||
Given the user is on /article?page=3
|
||
When the user opens the page-size select (it opens upward) and chooses "<label>"
|
||
Then GET ...&page=1&size=<size> is sent and, on success, the URL is <url>
|
||
|
||
Examples:
|
||
| label | size | url |
|
||
| 5 per page | 5 | /article?size=5 |
|
||
| 25 per page | 25 | /article |
|
||
| 50 per page | 50 | /article?size=50 |
|
||
| 100 per page | 100 | /article?size=100 |
|
||
|
||
Scenario: 10b Invalid URL values fall back
|
||
When the user navigates to /article?size=10&page=abc
|
||
Then GET ... is sent with page=1 and size=25 and the URL is replaced by /article
|
||
|
||
Scenario: 11 Focus-visible
|
||
When the user tabs to Next
|
||
Then Next shows a 2 px --ui-primary outline with a 2 px offset
|
||
And the same ring shows on every paginator control and on the page-size select
|
||
|
||
Scenario: Keyboard, the page-size list
|
||
When the user focuses the page-size select and presses ArrowDown
|
||
Then the list opens; Enter selects the highlighted size; Esc closes it and returns focus to the select
|
||
|
||
Scenario: Tabs filter on the server
|
||
Given the Default list has loaded and the user is on /article?page=3
|
||
When the user selects the "Drafts" tab
|
||
Then GET ...&status=draft&page=1 is sent and, on success, the URL is /article?status=draft
|
||
And the tab counts are unchanged, because counts never follow status or tab
|
||
|
||
Scenario Outline: Sort header, server-side, resets to page 1
|
||
Given the user is on /article?page=7
|
||
When the user activates the "<column>" header (first click)
|
||
Then GET ...&sort=<sort>&page=1 is sent
|
||
And on success the URL is /article?sort=<sort> (page omitted)
|
||
And the header shows the arrow icon for its direction and the tab counts are unchanged
|
||
|
||
Examples:
|
||
| column | sort |
|
||
| Title | title:asc |
|
||
| Status | status:asc |
|
||
| Created | created_at:asc |
|
||
| Updated | updated_at:asc |
|
||
|
||
Scenario: Activating the sorted header again toggles the direction
|
||
Given the list is sorted by Title ascending
|
||
When the user activates the "Title" header
|
||
Then GET ...&sort=title:desc&page=1 is sent and, on success, the URL is /article?sort=title:desc
|
||
|
||
Scenario: Returning to the default sort drops sort from the URL
|
||
Given the URL is /article?sort=title:asc
|
||
When the user activates the "Updated" header until it is updated_at:desc
|
||
Then the request sends sort=updated_at:desc and, on success, the URL has no sort
|
||
|
||
Scenario: Sort is kept across paging, page size, search and tabs
|
||
Given the URL is /article?sort=title:asc
|
||
When the user activates Next, or changes the size, or searches "plaster"
|
||
Then every request carries sort=title:asc and the URL keeps sort=title:asc (search, tab and size also reset the page)
|
||
|
||
Scenario: A sorted URL loads sorted
|
||
When the user navigates to /article?sort=created_at:desc&page=2
|
||
Then GET ... carries sort=created_at:desc&page=2 and the Created header shows the arrow-down icon
|
||
|
||
Scenario: Invalid sort in the URL falls back
|
||
When the user navigates to /article?sort=body:asc
|
||
Then GET ... carries no sort (the default applies), the Updated header shows the arrow-down icon and the URL is replaced by /article
|
||
|
||
Scenario: The caption follows the sort
|
||
Given the list is sorted by Title ascending
|
||
Then the table's visually hidden caption reads "Articles, sorted by title, ascending"
|
||
|
||
Scenario: Mobile, Previous, "Page N of M", Next
|
||
Given the viewport is below md and page 2 of 13 is shown
|
||
Then the paginator reads Previous, "Page 2 of 13", Next with 44 px controls and no page numbers
|
||
|
||
Scenario: Deleting the last row of the last page
|
||
Given the user is on the last page with a single row and deletes it (204)
|
||
Then the list reloads the previous page and the URL is replaced by it
|
||
And the tab counts are recomputed from the new response
|
||
|
||
Scenario: Home and the article view read the envelope
|
||
When HomeView or ArticleView load their articles
|
||
Then they call GET /v1/article/ with explicit paging and read items, never a bare array
|