Files
RenewCMS/features/integration_search.feature
T

249 lines
12 KiB
Gherkin

Feature: Integration page search, a jump-to command palette on /integration
As a developer reading the API reference
I want to find an endpoint, a body field or an error and jump to it
So that I do not have to scroll or use the browser's find-in-page
Background:
Given the SPA route /integration of the verified back-office (static page, no /v1 call)
And search is client-side only, over a static index built from API_GROUPS, SESSION_GROUPS,
PUBLIC_AUTH_ROWS, ERROR_ROWS and the page sections, with these fields per endpoint:
| field | indexed text |
| method | GET, POST, PATCH, DELETE |
| path | for example /v1/media/merge |
| description | the first sentence of the endpoint description |
| access chip | Public, Verified user, Owner or admin, Owner only, Session only |
| request field | body field names and query parameter names |
| error | each error code and message of the endpoint |
And code samples, prose and response examples are never searched
And a term matches as a case-insensitive substring; "mdia" does not match "media"
And a query is split on spaces and every term must match somewhere in the same endpoint
And there are no recent searches: an empty query shows only the "Suggested" group
And the search never hides, filters or reorders the page; find-in-page keeps working
And the components are Nuxt UI v4 (UModal with a custom listbox, UInput, UButton, UKbd, UBadge,
UIcon) and not UCommandPalette, with design tokens only
And every count and sample row below follows the real catalogue, not the board's sample data
And the palette is the board of docs/design/screens/integration.md "Search (jump-to palette)",
states 8a to 8h, in light and dark, at 1440x900 and 390x844
And no state is date dependent
Rule: Trigger and shortcuts
Scenario: Desktop trigger
Given the viewport is 1440x900
Then the page header holds a field-style outline neutral button, 320x36, right of the title block
And it shows the icon, the placeholder "Search the API reference…" and the key hint "/"
And its aria-label is "Search the API reference"
And it has aria-keyshortcuts "/ Control+K Meta+K"
And it is not sticky: it scrolls away with the header
Scenario: Mobile trigger
Given the viewport is 390x844
Then the sticky "Jump to" bar holds a 44x44 icon-only button right of the select
And its aria-label is "Search the API reference"
And the desktop trigger is not displayed
Scenario Outline: Opening the palette with a shortcut
Given focus is on <focus>
When the user presses <keys>
Then the palette <result>
Examples:
| focus | keys | result |
| the page body | / | opens and the "/" is not typed |
| a button | / | opens |
| the page body | Ctrl+K | opens |
| the page body | Cmd+K | opens |
| a text input of the page | / | does not open and "/" is typed |
| a textarea | / | does not open |
| a text input | Ctrl+K | opens |
Scenario: Ctrl+K and Cmd+K replace the browser behaviour on this page
When the palette opens with Ctrl+K or Cmd+K
Then the default browser action is prevented
Scenario: Shortcuts only exist on /integration
When the user leaves /integration
Then "/", Ctrl+K and Cmd+K no longer open a palette
And Ctrl/Cmd+S on the editor route is unchanged
Scenario: Opening focuses the input and closing restores focus
Given the user opened the palette from the desktop trigger
Then focus is in the input
When the user closes the palette
Then focus returns to the trigger
And when it was opened with "/" or Ctrl+K, focus returns to the element that had it
Rule: Palette layout
Scenario: Desktop modal
Given the viewport is 1440x900
When the palette opens
Then it is a modal 720 px wide, 96 px from the top, on the existing overlay
And the input has placeholder "Search the API reference…"
And the footer reads "↑↓ Move", "↵ Open", "Esc Close" and the count
Scenario: Mobile full-screen sheet
Given the viewport is 390x844
When the palette opens
Then it is a full-screen sheet
And the input row holds a 44 px "Cancel" button
And pressing "Cancel" closes the palette and returns focus to the trigger
Rule: Content per state
Scenario: 8b Open, empty query
When the palette opens with an empty query
Then one group "Suggested" shows these 5 rows in order:
| row | description | access |
| Authentication | Public access and API keys | section |
| POST /v1/media/ | Upload an image (multipart). | Verified user |
| Errors | Status codes and messages | section |
| GET /v1/article/ | List one page of articles, newest update first, with the total and the tab counts (all, online, draft and suggestions). | Public |
| PATCH /v1/user/me | Update my profile. | Verified user |
And the first row is selected
And the footer count and the live region read "5 suggestions"
Scenario: 8c Typing "media"
When the user types "media"
Then group "Endpoints" lists the first 6 of 11 media routes
And group "Sections" lists "Media" with description "11 routes"
And the line "5 more endpoints. Keep typing to narrow the list." follows the rows
And the count reads "12 results"
And the live region reads "12 results, showing 7"
And "media" is highlighted with --rc-sand on --rc-on-sand in paths and descriptions
Scenario: 8d More endpoints than the cap
When the user types "404"
Then group "Endpoints" lists 6 rows, each with a second line "Error" and the match, for example "404 Article not found"
And group "Sections" lists "Errors" with description "Status codes and messages"
And the line "8 more endpoints. Keep typing to narrow the list." follows the endpoint rows
And the count reads "15 results"
And the live region reads "15 results, showing 7"
Scenario: 8e Match on a body field
When the user types "keep_id"
Then group "Endpoints" lists exactly POST /v1/media/merge
And its description is "Merge identical copies into one image." and its chip is "Verified user"
And a second line reads "Body field keep_id"
And the count reads "1 result"
Scenario: 8f Session-only route
When the user types "login"
Then group "Endpoints" lists POST /v1/auth/login, "Log in", with a dashed "Session only" chip and a lock icon
And its path weight is 400 and its text is --ui-text-muted
And group "Sections" lists "Session only" with a lock icon and "25 routes, not available to API keys"
And the count reads "2 results"
Scenario: 8g No results
When the user types "xyz"
Then the empty slot reads "No match for "xyz". Try a path, a method or a status code like 404."
And the count reads "0 results"
And the live region reads "No results"
Rule: Matching
Scenario Outline: Multi-term and field matching
When the user types "<query>"
Then the endpoint rows are those whose indexed text contains every term
And the second line is "<second line>"
Examples:
| query | example hit | second line |
| post media | POST /v1/media/ | none |
| PATCH user | PATCH /v1/user/me | none |
| keep_id | POST /v1/media/merge | Body field keep_id |
| page | GET /v1/site/{key}/sitemap | Query parameter page |
| 404 | several routes | Error 404 … |
| OWNER ONLY | owner-only routes | none |
| mdia | none | 0 results |
Scenario: Ordering
Given a route matches "token" only in a body field and another matches in its path
Then the path match is listed first
And routes that tie keep the order of the catalogue
Scenario: A row shows the second line only when matched in a field or error
Then a row matched in method, path, description or chip has no second line
Scenario: Highlighting
Then every term occurrence in the label and description is wrapped in a highlight
And terms inside the second line are highlighted too
Rule: Keyboard navigation
Scenario Outline: Moving the selection
Given the palette shows 7 rows and row <from> is selected
When the user presses <key>
Then row <to> is selected and the input keeps focus
Examples:
| from | key | to |
| 1 | ArrowDown | 2 |
| 7 | ArrowDown | 1 |
| 1 | ArrowUp | 7 |
| 3 | Home | 1 |
| 3 | End | 7 |
Scenario: Typing resets the selection
Given row 3 is selected
When the user types another character
Then row 1 is selected
Scenario: Enter opens and Escape closes
When the user presses Enter on a selected row
Then that row is selected as in the "Selecting a result" rule
When the user presses Esc
Then the palette closes, nothing scrolls and the hash is unchanged
Rule: Accessibility
Scenario: Roles and live region
Then the input has role "combobox", aria-expanded, aria-controls, aria-autocomplete "list"
and aria-activedescendant pointing at the selected row
And the list has role "listbox"; each group has role "group" and aria-labelledby its title
And each row has role "option", aria-selected and an aria-label with method, path, description and access
And a visually hidden polite live region announces the count of the current state
Rule: Selecting a result
Scenario: 8h Selecting an endpoint
Given the palette shows POST /v1/media/merge
When the user presses Enter, or clicks the row
Then the palette closes
And the page scrolls to the card inside its own scroll container, below the sticky offset
And the card is expanded
And focus is on the card's button
And the row flashes: --rc-sand fill and a 2 px --ui-primary ring for 1.5 s, then the open-card fill
And the section index and "Jump to" read "Media" (existing scroll-spy)
And the URL hash is #ep-<id>, written with history.replaceState so no history entry is added
And the other cards keep their open or closed state
Scenario: Selecting a section
When the user selects the "Errors" row
Then the palette closes, the page scrolls to the Errors heading, focus is on the heading,
the hash is #errors and nothing is flashed or expanded
Scenario: Selecting a session-only route
When the user selects POST /v1/auth/login
Then the page scrolls to the "Session only" section and focus goes to its heading
And no card expands
Scenario: Reduced motion
Given the user prefers reduced motion
When a card is selected
Then the flash is a static --rc-sand fill and ring with no fade
Scenario: Shared link
When /integration#ep-<id> is loaded
Then the card is scrolled to, expanded, focused and flashed as above
And the palette stays closed
Scenario: Unknown hash
When /integration#ep-nope is loaded
Then the page behaves as today, nothing is expanded and no error shows
Scenario: The page is never filtered
Given a query is typed
Then every section and card of the page stays in the DOM, unchanged