115 lines
8.9 KiB
Markdown
115 lines
8.9 KiB
Markdown
# QA: Integration images and variants, and CodeSnippet as the only code block
|
||
|
||
SPA only. No Go test, no live server, no SMTP, no real SQLite.
|
||
|
||
## Automated (Vitest + Vue Test Utils, `task test:web`)
|
||
|
||
Common setup: mount IntegrationPage inside DashboardShell at /integration with a test router, a resolved
|
||
verified session whose role is set per case, and `window.location.origin` stubbed to
|
||
`https://cms.renew.example`. Mock `/v1` through the same `useApi` seam as `variantsTestKit.ts`.
|
||
Stub `navigator.clipboard.writeText` per case and use fake timers. A spec fails on any `/v1` call
|
||
that is not listed for the case.
|
||
|
||
Variants fixture: the twelve board variants of the feature plus "print" (enabled false), in the board order, with
|
||
full item fields (fit, quality, generate_on_upload, generated_files, cache_size).
|
||
|
||
CodeSnippet component spec (no router, no `/v1`):
|
||
1. Three tabs: header with tablist aria-label "Language", the first tab selected with an underline class,
|
||
the Copy button named "Copy code".
|
||
2. A click on a tab switches the pre content. The Copy button writes the active tab's code.
|
||
3. One tab renders a muted label, not a role tab.
|
||
4. Copy: clipboard called with the code, "Copied" and icon check inside an aria-live "polite" element,
|
||
back to "Copy" after 2000 ms (advance 1999 and 2000). Two instances copy independently.
|
||
5. The pre has tabindex "0" and no syntax-highlight markup.
|
||
|
||
Integration images specs, one per design state, each with the mocked `/v1` call:
|
||
1. **Many variants** (owner): GET /v1/image-variants -> 200 with the fixture. Assert the section order (Media
|
||
group, Images and variants, Session only, Errors), anchor "images", the section index entry, "12 enabled variants",
|
||
12 table rows with "print" absent, the 264 px max-height class on the table body, the disabled note, exact curl,
|
||
HTML and JavaScript strings, HTML tab active, the Responses rows and bodies.
|
||
2. **One variant**: 200 with only "thumb" enabled. "1 enabled variant", one row, no max-height limit, the three
|
||
snippet slots all use "thumb", no disabled note.
|
||
3. **No variants, admin**: role admin, 200 with {"items": []}. Empty block, text, the "Go to image variants" link with
|
||
href /settings/media/variants, placeholder <variant-name> in the snippets, no table, no builder, Responses present.
|
||
Repeat with a fixture containing only disabled variants.
|
||
4. **No variants, not an admin**: role "member" (or no role). Assert no GET /v1/image-variants call on mount or on a
|
||
window focus event, the non-admin text, no link and no button. Add: role owner with a 403 answer renders the same state.
|
||
5. **Variant removed**: load the fixture, select "hero", make the mock return a list without "hero", then dispatch
|
||
`window.dispatchEvent(new Event('focus'))`. Assert a second GET, "hero (removed)", the warning alert title and text
|
||
with the exact 400 body, the URL snippet ends with ?v=hero, the preview label "No preview: unknown variant", 4 / 3
|
||
aspect and 50 % opacity. Repeat with the reload triggered by "Try again" after a failed first load.
|
||
6. **Loading**: GET /v1/image-variants pending. Assert three skeletons inside an aria-busy region, and no snippet,
|
||
no table, no builder, no Responses table.
|
||
7. **Error loading**: 500, then a rejected fetch. Assert the alert copy, the snippet present with
|
||
<variant-name>, no table and no builder, no Responses table. Click "Try again" and assert a second call
|
||
and the success state replaces the alert.
|
||
8. **Snippet copied**: click Copy on the "Request an image" snippet. Clipboard receives the active (HTML) code, button
|
||
reads "Copied" in an aria-live polite region, reverts after 2 s, and the builder's button is unaffected.
|
||
9. **URL builder copied**: the clipboard receives "https://cms.renew.example/media/north-wall.jpg?v=thumb".
|
||
Pick "square" first and assert the URL, preview label "square · 600×600 · JPEG" and the 600 / 600 aspect ratio.
|
||
|
||
Behaviour specs:
|
||
10. **Reload triggers**: mount sends one GET; each focus event sends one more; "Try again" sends one more; a reload with
|
||
the selected variant still enabled keeps the active tab and the selection. A non-admin sends none on any trigger.
|
||
11. **Responses table**: the two 400 bodies are the exact JSON strings of the feature; the 404 body is
|
||
"404 page not found"; the 413 body is "The source image is too large to generate this variant." and the 500 body is
|
||
"Unable to read the file"; no 409 or 422 row.
|
||
12. **Error sample label**: the Errors sample tab reads "Error response · text/plain" and no snippet in the images
|
||
section carries a text/plain label.
|
||
|
||
Migration specs (update or replace the existing assertions of `IntegrationView.spec.ts`, the EndpointCard spec
|
||
and the ApiKeys specs):
|
||
13. Base URL is a CodeSnippet with the single tab "Base URL", origin in the code, Copy -> "Copied" -> "Copy" after 2 s.
|
||
14. Authentication example: tabs curl and fetch for GET /v1/auth/me, no "Shell" or "JavaScript" label.
|
||
15. Expanded EndpointCard (POST /v1/article/): tabs fetch, curl, Response in one CodeSnippet, fetch active,
|
||
Copy copies the active tab, only that button reads "Copied". The Errors list is unchanged.
|
||
16. Errors sample is a CodeSnippet containing the four sample lines.
|
||
17. ApiKeyReveal: the key is in a CodeSnippet with the single tab "API key", the Copy button is focused when the
|
||
dialog opens, and Copy writes the full key and reads "Copied" for 2 s.
|
||
18. Source guard: nothing imports `@/components/CodeBlock.vue` (EndpointCard.vue and IntegrationView.vue included),
|
||
the file is deleted, and `CodeText.vue` still exists.
|
||
19. The Integration page makes the calls listed in the feature for owner and admin, and none for other roles. The old
|
||
"page stays static" assertions in `IntegrationView.spec.ts` are changed accordingly.
|
||
20. Every new string has a French entry (the existing i18n completeness spec must pass).
|
||
|
||
## Visual (`task visual:check -- integration`, desktop and mobile, light and dark)
|
||
|
||
Every capture must be within 10 %. Group "Integration: images" (board `IntegrationImages.dc.html` through AppFrame),
|
||
setups in `web/visual/states.ts`, with the board's sample data: origin `https://cms.renew.example`, image
|
||
`north-wall.jpg`, the twelve variants of the feature (plus disabled "print" so the note shows in Many), role admin
|
||
unless stated.
|
||
|
||
| State | Mock |
|
||
|---|---|
|
||
| Many variants | GET /v1/image-variants -> 12 enabled + "print" disabled, HTML tab active, builder on "thumb" |
|
||
| One variant | -> only "thumb" |
|
||
| No variants, admin | -> {"items": []}, role admin |
|
||
| No variants, not an admin | role without owner/admin, no call |
|
||
| Variant removed | builder on "hero", list without "hero" after the reload (first 5 as the board) |
|
||
| Loading | request never resolves |
|
||
| Error loading | status 500 |
|
||
| Snippet copied | Many with the snippet in the Copied state (clipboard stubbed) |
|
||
| URL builder copied | Many with the URL snippet in the Copied state |
|
||
|
||
Approved visual exceptions (user, own turn), each with `approvedBy` "Florian Sylvain":
|
||
- I2 One variant, desktop light, `maxPercent` just above the measured value (about 16 %), reason "board draws the striped preview offset by 32 px".
|
||
- I8 Snippet copied, desktop light, `maxPercent` just above the measured value (about 12 %), same reason.
|
||
- Errors, mobile, `maxPercent` just above the measured value (about 18.5 %), reason "the board changed; already 17.33 % at HEAD". Listed as a design-pass item.
|
||
- The `integration-api-keys` mobile failures (14 to 24 %) are NOT excepted. They predate this slice (floating inset dialog versus the board's flush bottom sheet, an overlay-pattern slice). They stay red and documented, and this slice's gate is scoped to the Integration screen.
|
||
|
||
Also re-run the existing groups affected by the migration and keep them within 10 %: Integration ("1. Default",
|
||
"2. Group scrolled, one card expanded", "3a. Copied: base URL", "3b. Copied: CodeSnippet", "4. Authentication",
|
||
"6. Errors") and API keys ("One-time reveal", "One-time reveal: copied"). The board names stay as the design gives
|
||
them. Existing pre-approved exceptions stay as recorded. Skipped: none are clock-sensitive.
|
||
|
||
## Manual checks (not coverable by screenshots)
|
||
- Keyboard: tab order inside a CodeSnippet is tabs, then Copy, then the `pre` (tabindex 0). Arrow keys move
|
||
between tabs. Enter and Space activate Copy. The focus ring is 2 px primary.
|
||
- A screen reader announces "Copied" politely after Copy, and not again on "Copy" returning.
|
||
- Hover and motion: Copy and tabs hover states, no motion on copy other than the label swap.
|
||
- Find-in-page and the index entry "Images and variants" scroll to `#images`; opening `/integration#images` lands on the section.
|
||
- The Variants table scrolls inside its 264 px body with the mouse wheel and keyboard, and the header stays visible.
|
||
- Real clipboard in a browser (secure context) copies the exact snippet text.
|
||
- Against a running server: `?v=<disabled>`, `?v=<unknown>` and `?width=400` answer 400 application/json with the
|
||
documented bodies, and a missing image answers 404 text/plain "404 page not found".
|