249 lines
12 KiB
Gherkin
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 the 16 matching routes (GET /v1/builtin-type/ first, because its description names Media, then the Media routes)
|
|
And group "Sections" lists "Media" with description "11 routes"
|
|
And the line "10 more endpoints. Keep typing to narrow the list." follows the rows
|
|
And the count reads "17 results"
|
|
And the live region reads "17 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 "17 more endpoints. Keep typing to narrow the list." follows the endpoint rows
|
|
And the count reads "24 results"
|
|
And the live region reads "24 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 "37 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
|