Files
RenewCMS/features/articles_pagination_screen.feature

188 lines
11 KiB
Gherkin
Raw Permalink 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.
Feature: Articles list pagination
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 are unchanged
And the same reset happens for a tab change, a sort change and a page-size change
Scenario: 9 Empty after search
Given GET ...&q=tiles resolves 200 with items [], total 0 and counts {all 6, online 4, draft 2}
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 the tabs still read "All 6" "Online 4" "Drafts 2"
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
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