feat: integration page (PART 3/3)
This commit is contained in:
1 parent
514a082c96
commit
acd392c29d
20 files changed
+3115
-409
No files matched your search
+182
-52
@@ -1,63 +1,193 @@
|
||||
Feature: Integration screen
|
||||
Feature: Integration screen (API reference)
|
||||
|
||||
As a verified RenewCMS owner
|
||||
I want /integration to show my site's base API URL and ready-to-use endpoint examples
|
||||
So that I, or the developer who builds my public site, can wire it up without reading the
|
||||
source
|
||||
As a developer building a frontend or an API-key client for a RenewCMS site
|
||||
I want /integration to be a complete, searchable API reference
|
||||
So that I can call every route a key may call without reading the source
|
||||
|
||||
Background:
|
||||
Given /integration is a verified route (meta.auth "verified") rendered as IntegrationPage
|
||||
inside DashboardShell, per docs/design/routes.md
|
||||
And the screen makes no /v1 calls; api: none (static)
|
||||
And the PageHeader shows title "Connect your website", description "A guide for the developer
|
||||
who builds your public site.", breadcrumb "Integration" (mobile title "Integration"), and no
|
||||
actions
|
||||
And an introductory paragraph reads "RenewCMS is headless: it stores and publishes your
|
||||
articles but doesn't show them to visitors. Your website fetches published articles from the
|
||||
two addresses below and displays them in its own design."
|
||||
And a "Base URL" row shows window.location.origin in a monospace box, with a "Copy" button
|
||||
(icon copy) beside it
|
||||
And two EndpointCard entries follow, in this order:
|
||||
| method | path | description |
|
||||
| GET | /v1/article/ | Lists every online article. |
|
||||
| GET | /v1/article/{id} | Returns one online article. Drafts and unknown ids both return 404. |
|
||||
And each EndpointCard's fetch example uses the current origin as the base of its URL:
|
||||
| path | fetch example |
|
||||
| /v1/article/ | const res = await fetch('{origin}/v1/article/')\nconst articles = await res.json() |
|
||||
| /v1/article/{id} | const res = await fetch('{origin}/v1/article/12')\nif (res.status === 404) return notFound()\nconst article = await res.json() |
|
||||
And each EndpointCard has fetch, curl and Response tabs, with fetch active by default, and its
|
||||
CodeBlock is labelled "JavaScript" with its own ghost "Copy" button
|
||||
And the curl tab's content is a plain curl command built from the current origin (e.g.
|
||||
"curl {origin}/v1/article/" and "curl {origin}/v1/article/12"), and the Response tab's
|
||||
content is a JSON sample built from the real domain Article fields; neither tab is active by
|
||||
default, and neither is visually checked, since the fetch tab is the one shown in the board
|
||||
And three info notes follow the endpoint cards, in this order:
|
||||
- "Anonymous callers only ever get online articles. Drafts stay private until you publish
|
||||
them."
|
||||
- "Images in body use paths like /static/uploadedImages/<uuid>.png. Prefix them with the
|
||||
base URL before rendering."
|
||||
- "Browsers can only call the API from origins listed in the server's CORS setting. Ask
|
||||
whoever runs the server to add your site's address."
|
||||
And the PageHeader shows breadcrumb "Integration", title "API reference" and description
|
||||
"Everything a developer needs to build on RenewCMS."
|
||||
And the endpoint catalogue is a typed static data file at web/src/data/apiCatalogue.ts, and the view only renders it
|
||||
And the catalogue holds 20 key-callable endpoints in 4 expandable groups, in this order:
|
||||
| group | routes | endpoints |
|
||||
| Articles | 7 | GET /v1/article/, GET /v1/article/{id}, POST /v1/article/, PATCH /v1/article/{id}, PATCH /v1/article/{id}/publish, PATCH /v1/article/{id}/unpublish, DELETE /v1/article/{id} |
|
||||
| Media | 6 | GET /v1/media/, POST /v1/media/, PATCH /v1/media/{id}, DELETE /v1/media/{id}, POST /v1/media/merge, GET /v1/media/{id}/usage |
|
||||
| Users and invites | 6 | GET /v1/user/, PATCH /v1/user/me, POST /v1/user/invite, DELETE /v1/user/invite/{id}, POST /v1/user/invite/{id}/renew, GET /v1/auth/me |
|
||||
| Settings | 1 | GET /v1/settings |
|
||||
And the "Session only" group has 13 non-expandable rows in 3 sub-groups:
|
||||
| sub-group | rows |
|
||||
| System · owner only | GET /v1/system/update, POST /v1/system/update/check, POST /v1/system/update/download, POST /v1/system/update/restart |
|
||||
| Sign in and account | POST /v1/auth/login, POST /v1/auth/logout, POST /v1/auth/register, POST /v1/auth/verify, DELETE /v1/auth/register, POST /v1/auth/password-reset |
|
||||
| API key management | GET /v1/api-key, POST /v1/api-key, DELETE /v1/api-key/{id} |
|
||||
And a "Public auth flow" sub-list follows with 6 non-expandable rows and the chip "Public":
|
||||
| method | path | description |
|
||||
| GET | /v1/ | Hello world. |
|
||||
| GET | /v1/auth/registration | Whether registration is open. |
|
||||
| POST | /v1/auth/password-reset/confirm | Set a new password with the emailed code. |
|
||||
| GET | /v1/auth/invite | Check an invite code. |
|
||||
| POST | /v1/auth/invite/accept | Accept an invite. |
|
||||
| GET | /healthz | Plain text "ok". Outside /v1. |
|
||||
And every catalogue endpoint has a non-empty description, method, path, access chip, key chip,
|
||||
query-parameter table (or "No query parameters."), request-body table (or "No request body."),
|
||||
fetch, curl and Response examples, and error captions
|
||||
|
||||
Scenario: Default — the page loads with the app's own origin
|
||||
Given the app is served from origin "{origin}"
|
||||
Scenario: Default, every card collapsed
|
||||
When the user navigates to /integration
|
||||
Then the Base URL row shows exactly "{origin}"
|
||||
And the Base URL "Copy" button reads "Copy", not "Copied"
|
||||
And both EndpointCards' fetch examples use "{origin}" as the base of their URL
|
||||
Then the intro reads "RenewCMS is headless: it stores your content and your own code displays it. There are two ways in. Public read-only access lets a website read online articles. API keys let a script or service manage articles, media and users without the back-office."
|
||||
And a "Base URL" row shows window.location.origin in a monospace box with a "Copy" button (icon copy)
|
||||
And every EndpointCard is collapsed as a 44 px row: method badge, mono path, one-line description, chips, chevron
|
||||
And the sticky section index lists the sections on desktop and a "Jump to" select lists them on mobile
|
||||
|
||||
Scenario: Copied — the Base URL copy button
|
||||
Given the user is on /integration, served from origin "{origin}"
|
||||
When the user activates the Base URL row's "Copy" button
|
||||
Scenario Outline: Method badge and chips per endpoint
|
||||
Then <method> <path> shows the <method> badge, access chip "<access>" and key chip "API key OK"
|
||||
Examples:
|
||||
| method | path | access |
|
||||
| GET | /v1/article/ | Public |
|
||||
| GET | /v1/article/{id} | Public |
|
||||
| POST | /v1/article/ | Verified user |
|
||||
| DELETE | /v1/article/{id} | Verified user |
|
||||
| GET | /v1/media/ | Verified user |
|
||||
| PATCH | /v1/user/me | Verified user |
|
||||
| GET | /v1/auth/me | Verified user |
|
||||
| POST | /v1/user/invite | Owner only |
|
||||
| DELETE | /v1/user/invite/{id} | Owner only |
|
||||
| POST | /v1/user/invite/{id}/renew | Owner only |
|
||||
| GET | /v1/settings | Verified user |
|
||||
|
||||
Scenario: Owner-only invite cards, user-approved divergence from the board
|
||||
Then the three invite cards show "Owner only", where the board shows "Verified user"
|
||||
And each lists a 403 error "Only the owner can do this."
|
||||
And each says it is rate limited and that too many requests answer 429, with no number
|
||||
|
||||
Scenario: Group scrolled, one card expanded
|
||||
When the user activates the "POST /v1/article/" row
|
||||
Then the row's button reports aria-expanded "true"
|
||||
And a "Query parameters" table lists "format", string, optional, "markdown, html or json. Default markdown."
|
||||
And a "Request body" table lists:
|
||||
| Name | Type | Required | Rule |
|
||||
| title | string | Yes | 3 to 200 characters. Can't be changed later. |
|
||||
| body | string | Yes | Up to 10 000 characters. |
|
||||
And tabs fetch, curl and Response show, fetch active, with a CodeBlock labelled "JavaScript" and a ghost "Copy" button
|
||||
And an "Errors" list shows a status chip with a one-line caption for 400, 401
|
||||
|
||||
Scenario: Query parameters table, new UI beyond the board, user-approved
|
||||
Then the "Query parameters" table sits above the "Request body" table in each card
|
||||
And it lists:
|
||||
| route | name | type | required | rule |
|
||||
| every /v1/article route | format | string | no | markdown, html or json. Default markdown. |
|
||||
| GET /v1/media/ | q | string | no | Search text. |
|
||||
| GET /v1/media/ | hash | string | no | SHA-256 of the content, lowercased. |
|
||||
| GET /v1/media/ | cursor | string | no | The next_cursor of the previous page. |
|
||||
| GET /v1/media/ | filter | string | no | all, unused or duplicates. Default all. |
|
||||
| GET /v1/media/ | sort | string | no | newest, oldest, name or largest. Default newest. |
|
||||
| GET /v1/media/ | limit | number | no | 1 to 100. Default 24. |
|
||||
And a card without query parameters shows "No query parameters."
|
||||
|
||||
Scenario Outline: Request body fields come from the handler validate tags
|
||||
Then <method> <path> lists these fields: <fields>
|
||||
Examples:
|
||||
| method | path | fields |
|
||||
| POST | /v1/article/ | title string required 3-200; body string required max 10000 |
|
||||
| PATCH | /v1/article/{id} | body string required max 10000 |
|
||||
| PATCH | /v1/media/{id} | filename string required |
|
||||
| POST | /v1/media/merge | keep_id number required >0; merge_ids number[] required min 1 each >0 |
|
||||
| POST | /v1/media/ | multipart/form-data, 1 file field named "file" |
|
||||
| PATCH | /v1/user/me | display_name string required max 60 |
|
||||
| POST | /v1/user/invite | email string required valid email |
|
||||
| GET | /v1/article/ | "No request body." |
|
||||
|
||||
Scenario: Response samples use the real domain JSON field names
|
||||
Then the GET /v1/article/{id} Response sample has exactly the keys id, title, body, body_format, is_online, created_at, updated_at
|
||||
And the sample has no "images" key and no "status" key
|
||||
And POST /v1/media/merge has the keys articles_updated and merged
|
||||
And GET /v1/media/ has the keys items, next_cursor and counts
|
||||
And GET /v1/media/{id}/usage items have the keys id, title, published
|
||||
And PATCH /v1/article/{id}/publish and /unpublish samples are articles with is_online true and false
|
||||
|
||||
Scenario: Article body format
|
||||
Then a note reads "Article routes accept `?format=markdown`, `?format=html` or `?format=json` to choose how `body` is returned. With `json`, `body` is the stored JSON structure. Articles have `body_format: markdown` by default."
|
||||
And every article card's Errors list includes 400 "The format must be one of: markdown, html, json."
|
||||
|
||||
Scenario: Authentication section
|
||||
Then card "Public" reads "No key needed. Two read-only routes, and only online articles are returned." and names GET /v1/article/ and GET /v1/article/{id}
|
||||
And card "API key" reads "Send `Authorization: Bearer rcms_…`. Requests act as the user who created the key, with that user's role (owner or admin). A key never has more rights than its creator."
|
||||
And its CodeBlock has tabs curl and fetch for GET /v1/auth/me, using the current origin
|
||||
And it says "Keys start with `rcms_` and are shown in full once, when you create them."
|
||||
And a "Manage API keys" link navigates to /integration/api-keys
|
||||
And Note 1 reads "A key can't create, list or revoke API keys, verify or cancel a registration, or call anything under `/v1/system`. Those routes answer 403 "This action needs a real session." An invalid, unknown, expired or revoked key answers 401, even on public routes, and so does a key whose user was deleted or unverified."
|
||||
And Note 2 reads "Each key can make 120 requests per minute. Beyond that the API answers 429 with a `Retry-After` header."
|
||||
|
||||
Scenario: Session only group
|
||||
Then the banner reads "These routes are made for a signed-in browser session. With an API key, verify, DELETE /auth/register, /api-key/* and /system/* answer 403 "This action needs a real session.""
|
||||
And each row has a dashed border and muted fill, uses no method colour and cannot be expanded
|
||||
And rows that answer 403 to a key show the lock icon and the chip "Session only"
|
||||
And Log in, Log out, Register and Reset password show the caption "Made for the browser. A key is ignored here, with no 403." and no 403 claim
|
||||
And no /v1/system route appears in any expandable group
|
||||
And the "Public auth flow" rows carry the chip "Public" and no lock icon
|
||||
|
||||
Scenario: Errors section
|
||||
Then the intro reads "Every error is a plain-text body with a single sentence. The status code is in the HTTP status line, and the Content-Type is text/plain."
|
||||
And the table lists:
|
||||
| status | message |
|
||||
| 400 | The request cannot be processed due to a mismatch in the format of the body. / The expiry must be a date in the future. |
|
||||
| 401 | You must be logged in to access this resource. |
|
||||
| 403 | This action needs a real session. / Only the owner can do this. / You must verify your account to access this resource. |
|
||||
| 404 | Article not found / Image not found / Invite not found / API key not found |
|
||||
| 409 | There's already a pending invite for this address. / This invite has already been accepted. |
|
||||
| 429 | Too many requests. Please try again later. |
|
||||
| 500 | Internal server error |
|
||||
And a sample shows "HTTP/1.1 403 Forbidden", "Content-Type: text/plain", a blank line, and "This action needs a real session."
|
||||
And the table does not use the SPA copy of docs/design/states.md
|
||||
|
||||
Scenario Outline: Per-endpoint error captions use the real strings
|
||||
Then <method> <path> lists <status> "<caption>"
|
||||
Examples:
|
||||
| method | path | status | caption |
|
||||
| POST | /v1/media/ | 400 | the image could not be read |
|
||||
| POST | /v1/media/ | 413 | the file exceeds the maximum upload size |
|
||||
| POST | /v1/media/ | 415 | the file must be a PNG, JPEG, WEBP, or SVG image |
|
||||
| GET | /v1/media/ | 400 | The media query is invalid. |
|
||||
| POST | /v1/media/merge | 400 | Choose an image to keep and at least one other image to merge into it. |
|
||||
| POST | /v1/media/merge | 409 | Some of the images to merge changed. Refresh and try again. |
|
||||
| PATCH | /v1/media/{id} | 400 | Enter a filename. |
|
||||
| POST | /v1/article/ | 400 | Article title must be between 3 and 200 characters. |
|
||||
| GET | /v1/article/{id} | 400 | The server expects the ID to be in the format of an unsigned 32-bit integer (uint32). |
|
||||
| PATCH | /v1/user/me | 400 | Display name must be 60 characters or fewer. |
|
||||
| POST | /v1/user/invite | 409 | This address already has an account. |
|
||||
| POST | /v1/user/invite | 409 | There's already a pending invite for this address. |
|
||||
| POST | /v1/user/invite/{id}/renew | 409 | This invite has already been accepted. |
|
||||
| POST | /v1/user/invite | 403 | Only the owner can do this. |
|
||||
|
||||
Scenario: Notes
|
||||
Then the image-prefix note reads "Images in body use paths like /static/uploadedImages/<uuid>.png. Prefix them with the base URL before rendering."
|
||||
And the CORS note reads "Browsers can only call the API from origins listed in the server's CORS setting. Ask whoever runs the server to add your site's address."
|
||||
|
||||
Scenario: Copied, Base URL
|
||||
Given the app is served from origin "{origin}"
|
||||
When the user activates the Base URL "Copy" button
|
||||
Then "{origin}" is written to the clipboard
|
||||
And the button's icon changes to check and its text changes to "Copied"
|
||||
And the change is announced to assistive technology
|
||||
When 2 seconds elapse without a further click
|
||||
Then the button reverts to icon copy and text "Copy"
|
||||
And the button shows icon check and "Copied", announced to assistive technology
|
||||
When 2 seconds elapse
|
||||
Then the button reverts to icon copy and "Copy"
|
||||
|
||||
Scenario: Each EndpointCard's own Copy button is independent of the Base URL's
|
||||
Given the user is on /integration
|
||||
When the user activates the "/v1/article/" EndpointCard's "JavaScript" code block's "Copy"
|
||||
button
|
||||
Then that card's own code is written to the clipboard, and only that button reads "Copied"
|
||||
And the Base URL row's "Copy" button and the other EndpointCard's "Copy" button are unaffected
|
||||
Scenario: Copied, CodeBlock is independent of Base URL
|
||||
Given a card is expanded
|
||||
When the user activates that CodeBlock's "Copy JavaScript example" button
|
||||
Then that card's exact code string is written to the clipboard and only that button reads "Copied"
|
||||
And the Base URL button and other CodeBlocks are unaffected
|
||||
|
||||
Scenario: Examples use the current origin and the API key header
|
||||
Then every fetch and curl example starts from "{origin}" and carries no ?format= query
|
||||
And examples for non-public routes carry "Authorization: Bearer rcms_…"
|
||||
And examples for public routes carry no Authorization header
|
||||
|
||||
Scenario: Navigation
|
||||
When the user activates a section in the index (desktop)
|
||||
Then the page scrolls to that anchor and the item reports aria-current "true"
|
||||
And on mobile the "Jump to" select opens and lists the same sections
|
||||
|
||||
Scenario: The page stays static
|
||||
Then no /v1 request is made while the page renders or while any card is expanded
|
||||
+86
-54
@@ -1,66 +1,98 @@
|
||||
# QA: Integration screen
|
||||
# QA: Integration screen (API reference)
|
||||
|
||||
## Automated (Vitest + Vue Test Utils, `task test:web`)
|
||||
|
||||
Mount IntegrationPage inside DashboardShell with a resolved verified session and a test router at
|
||||
/integration. Stub `window.location.origin` (or the equivalent composable/helper the coder uses to
|
||||
read it) to a fixed value for the test, and stub `navigator.clipboard.writeText` per case. No
|
||||
`/v1` mocks are needed; the screen makes no calls.
|
||||
/integration. Stub `window.location.origin` to `https://cms.renew.example` (the board's sample) and
|
||||
`navigator.clipboard.writeText` per case. No `/v1` mocks are needed. A spec fails if any `/v1` call
|
||||
is made.
|
||||
|
||||
1. **Default** — mount at /integration with origin stubbed to a fixed value (e.g.
|
||||
`https://cms.renew.example`, matching the design board's sample so the assertions read the same
|
||||
as the visual reference); assert breadcrumb "Integration"; assert the PageHeader title
|
||||
"Connect your website" and description "A guide for the developer who builds your public
|
||||
site."; assert the intro paragraph text; assert the Base URL row shows exactly the stubbed
|
||||
origin; assert the Base URL "Copy" button reads "Copy"; assert both EndpointCards render in
|
||||
order (GET /v1/article/, GET /v1/article/{id}) with their descriptions and their fetch examples
|
||||
containing the stubbed origin; assert the fetch tab is active by default on both cards; assert
|
||||
the three info notes render in order.
|
||||
2. **Copied** — from Default, use fake timers; click the Base URL row's "Copy" button; assert
|
||||
`navigator.clipboard.writeText` was called with the stubbed origin; assert the button's icon is
|
||||
check and its text is "Copied"; assert the change is exposed to assistive technology (e.g. an
|
||||
aria-live region or a status role holds "Copied"); advance the fake timer by 2 s; assert the
|
||||
button reverts to icon copy and text "Copy".
|
||||
3. **EndpointCard's own Copy button is independent** — from Default, click the "/v1/article/"
|
||||
card's "JavaScript" CodeBlock's "Copy" button; assert `navigator.clipboard.writeText` was called
|
||||
with that card's exact code string; assert only that button reads "Copied"; assert the Base URL
|
||||
button still reads "Copy" and the other card's Copy button still reads "Copy". This behavior is
|
||||
Vitest-only; it has no visual-check state of its own (see Visual below).
|
||||
4. **curl and Response tabs render, unchecked visually** — from Default, switch the "/v1/article/"
|
||||
card to its curl tab; assert its content is a plain curl command built from the stubbed origin
|
||||
(e.g. "curl https://cms.renew.example/v1/article/"); switch to its Response tab; assert its
|
||||
content is a JSON sample built from the real domain Article fields. Neither tab's content is
|
||||
asserted pixel-for-pixel anywhere, since the design board only ever shows the fetch tab active.
|
||||
Catalogue spec (pure data, no mount, imports web/src/data/apiCatalogue.ts):
|
||||
1. The catalogue holds exactly the 20 expected key-callable routes from an explicit hand-maintained
|
||||
list in the spec (Articles 7, Media 6, Users and invites 6, Settings 1), each once, with no other
|
||||
expandable route. Session only has the 13 rows and Public auth flow the 6 rows listed in the
|
||||
feature, from the same kind of explicit list.
|
||||
2. Every catalogue route has a non-empty `description`. An empty or missing one fails the spec and
|
||||
names the route.
|
||||
3. Every route has method, path, access chip, key chip, a query-parameter list and a body-field list
|
||||
(possibly empty), and fetch, curl and Response examples.
|
||||
4. The Response sample of each route parses as JSON and uses only keys from a fixture of the real
|
||||
fields:
|
||||
- article: id, title, body, body_format, is_online, created_at, updated_at;
|
||||
- media item: id, filename, url, size, content_type, width, height, sha256, uploaded_by,
|
||||
created_at, used_in, duplicate;
|
||||
- media list: items, next_cursor, counts (all, unused, duplicates);
|
||||
- merge: articles_updated, merged;
|
||||
- usage: id, title, published;
|
||||
- user list item: id, kind, username, display_name, email, role, status, created_at;
|
||||
- invite: id, kind, email, status, created_at, expires_at;
|
||||
- settings: version.
|
||||
Any other key fails the spec. "images" and "status" are explicitly rejected on articles.
|
||||
5. Request-body tables equal the validate tags (title 3-200, body max 10000, filename, keep_id,
|
||||
merge_ids, display_name, email). The multipart field is "file". Query tables: `format` on every
|
||||
article route, and q, hash, cursor, filter, sort, limit on GET /v1/media/.
|
||||
6. The three invite routes carry "Owner only", a 403 "Only the owner can do this." caption and the
|
||||
rate-limit sentence. No /v1/system route is in an expandable group. Login, Log out, Register and
|
||||
Reset password rows carry no 403 claim. Verify, DELETE /auth/register, /api-key/* and
|
||||
/system/* are the only rows that claim 403.
|
||||
7. Error captions equal fixtures copied from api/pkg/apperr/messages.go and the image domain
|
||||
errors (413, 415, 400 on upload).
|
||||
|
||||
Component specs, one per design state:
|
||||
1. **Default**: breadcrumb, title "API reference", description, intro, Base URL equals the stubbed
|
||||
origin, all cards collapsed (aria-expanded "false"), section index entries present, the three
|
||||
invite rows show "Owner only".
|
||||
2. **Group scrolled, one card expanded**: click the POST /v1/article/ row. Assert the Query
|
||||
parameters table above the Request body table, the body rows, tabs fetch/curl/Response with fetch
|
||||
active, the "JavaScript" CodeBlock, and the Errors list with 400 and 401.
|
||||
3. **Copied: base URL**: fake timers; click Copy. Clipboard gets the origin; check icon and "Copied";
|
||||
announced; reverts after 2 s.
|
||||
4. **Copied: CodeBlock**: expand a card; click "Copy JavaScript example". Clipboard gets that code,
|
||||
only that button reads "Copied", and it reverts after 2 s.
|
||||
5. **Authentication**: both cards, curl/fetch tabs for GET /v1/auth/me, the "Manage API keys" link
|
||||
href /integration/api-keys, Note 1, Note 2.
|
||||
6. **Session only**: the corrected banner; lock icon and "Session only" chip only on rows that
|
||||
answer 403; the caption on the login family; no expand control; the "Public auth flow" rows with
|
||||
the "Public" chip.
|
||||
7. **Errors**: table rows 400, 401, 403, 404, 409, 429, 500 with the real strings and the 403
|
||||
sample block.
|
||||
8. **Navigation**: clicking an index item sets aria-current and scrolls to its anchor; the mobile
|
||||
"Jump to" lists the same sections.
|
||||
9. **Tabs**: curl and Response render for a card. curl contains the stubbed origin and the
|
||||
Authorization header only on non-public routes. Not visually checked.
|
||||
10. **Notes**: format note, image-prefix note, CORS note.
|
||||
|
||||
## Visual (`task visual:check -- integration`, desktop and mobile, light and dark)
|
||||
|
||||
Reference: `docs/design/screens/integration.md` and
|
||||
`docs/design/source/RenewCMS Screens Back-office.dc.html`, group "Integration"; layout detail from
|
||||
`docs/design/source/AppFrame.dc.html`'s `isInteg` block.
|
||||
Reference: docs/design/screens/integration.md; board "Integration" drawn by
|
||||
docs/design/source/IntegrationPage.dc.html. Serve the SPA under `https://cms.renew.example`. Mocks
|
||||
reuse the board's script data: BASE, KEY, group and endpoint descriptions, the request bodies
|
||||
(for example the "Choosing tiles for a small bathroom" article). Response samples are not compared;
|
||||
the Response tab is never active in a board frame.
|
||||
|
||||
The visual-check setup for this screen serves the SPA under `https://cms.renew.example` via
|
||||
Playwright request routing, so the real `window.location.origin` the running app reads matches the
|
||||
board's sample text exactly; no production test hook or origin stub is needed in app code.
|
||||
Pre-approved exceptions: the user pre-approved visual exceptions for Session only, Group scrolled
|
||||
one card expanded, Errors and Default. For each of these four states the coder measures the real
|
||||
diff, sets the smallest `maxPercent` that is sufficient, writes the reason, and records
|
||||
`approvedBy` "Florian Sylvain". The other states must still pass 10 %.
|
||||
|
||||
- [ ] Default state: PageHeader ("Connect your website" / description), intro paragraph, Base URL
|
||||
row with monospace text and a "Copy" button (icon copy), two EndpointCards in order with
|
||||
their GET badge, path, description, fetch/curl/Response tabs (fetch active), JavaScript
|
||||
CodeBlock and its own "Copy" button, and the three info notes below — matches the board
|
||||
exactly at desktop and mobile, light and dark. Sample data for the mocked run: origin
|
||||
`https://cms.renew.example`, served as above.
|
||||
- [ ] Copied state: the Base URL button shows the check icon and "Copied" text, in the success
|
||||
color, matching the board's `copied: true` frame. This state is timer-driven, not
|
||||
date-driven, so it is not clock-sensitive and gets a real visual check, not a skip.
|
||||
- [ ] Keyboard/focus (manual, not a screenshot check): Tab order reaches the Base URL "Copy"
|
||||
button, then each EndpointCard's tab strip (fetch/curl/Response, reachable and operable with
|
||||
arrow keys per UTabs), then that card's own CodeBlock "Copy" button, then the pre's
|
||||
`tabindex="0"` for scrolling; Enter/Space activates each Copy button.
|
||||
- [ ] Hover (manual): ghost Copy buttons show a hover state consistent with other ghost buttons
|
||||
elsewhere in the app (UserMenu, ArticleTable row menu).
|
||||
- [ ] Default (exception: owner-only chips and rate-limit line)
|
||||
- [ ] Group scrolled, one card expanded (exception: Query parameters table)
|
||||
- [ ] Copied: base URL (must pass 10 %)
|
||||
- [ ] Copied: CodeBlock (must pass 10 %)
|
||||
- [ ] Authentication (must pass 10 %; Note 1 text changed)
|
||||
- [ ] Session only (exception: banner, 13th row, captions, Public auth flow block)
|
||||
- [ ] Errors (exception: 409 row)
|
||||
- [ ] Navigation (must pass 10 %)
|
||||
|
||||
## Manual verification
|
||||
None of these states is clock-sensitive, so none is skipped.
|
||||
|
||||
- Confirm the real running app's `window.location.origin` (e.g. `https://your-domain.example` in
|
||||
production, or `http://localhost:8080` under `task dev`) renders correctly in the Base URL row
|
||||
and in both fetch examples, since Vitest only ever exercises a stubbed origin, and
|
||||
`visual:check` only ever exercises the routed `https://cms.renew.example` origin.
|
||||
## Manual
|
||||
- Keyboard and focus order: index, Base URL Copy, each row (Enter/Space), tab strip (arrow keys),
|
||||
CodeBlock Copy, the pre (tabindex 0), Jump to on mobile.
|
||||
- Hover and motion on ghost Copy buttons and rows.
|
||||
- Find-in-page and a deep link to an `#ep-...` anchor.
|
||||
- Real origin in a production build and under `task dev`.
|
||||
- Run each documented example against a running server with a real `rcms_` key, including the
|
||||
403 on a session-only route and the owner-only 403 for an admin key on the invite routes. This
|
||||
needs a real server and is not automated.
|
||||
- docs/api.md matches the catalogue (hand check).
|
||||
Reference in new issue
Block a user