feat: integration screen
This commit is contained in:
1 parent
a9a208b80a
commit
ad55137883
13 files changed
+786
-6
No files matched your search
@@ -0,0 +1,63 @@
|
||||
Feature: Integration screen
|
||||
|
||||
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
|
||||
|
||||
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."
|
||||
|
||||
Scenario: Default — the page loads with the app's own origin
|
||||
Given the app is served from origin "{origin}"
|
||||
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
|
||||
|
||||
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
|
||||
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"
|
||||
|
||||
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
|
||||
@@ -0,0 +1,66 @@
|
||||
# QA: Integration screen
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
||||
- [ ] 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).
|
||||
|
||||
## Manual verification
|
||||
|
||||
- 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.
|
||||
Reference in new issue
Block a user