157 lines
11 KiB
Markdown
157 lines
11 KiB
Markdown
# QA: Image variants settings screens
|
||
|
||
## Implementation note for coder (informational, not a QA assertion)
|
||
|
||
The API is built and covered by features/image-variants.qa.md: this slice has no Go change and no HTTP-level
|
||
section. The auth requirement "admin" does not exist in router meta (AuthRequirement is
|
||
public | session | verified | any); the role check lives in VariantsPage via the session role from
|
||
GET /v1/auth/me. Plain-text API errors are read through ApiError.body; never display the raw body.
|
||
Sizes: the new formatter is not formatSize. Every new string has an English and a
|
||
French entry in web/src/i18n/messages.ts.
|
||
|
||
## Automated (Vitest + Vue Test Utils, `task test:web`)
|
||
|
||
Mock useApi's `request` per case (GET /v1/image-variants, POST /v1/image-variants, PUT, PATCH,
|
||
DELETE, POST .../purge); no backend. Mount VariantsPage inside DashboardShell with a resolved verified
|
||
session (id 1, username "marta", role "owner") and a test router at /settings/media/variants.
|
||
Default fixture = the Background's 6 variants in API order. Mock `matchMedia` for the 640 px
|
||
breakpoint; run layout cases at desktop and below-640. Use fake timers for the 150 ms skeleton delay.
|
||
Mock navigator.clipboard. No test freezes the system date (this screen shows no date).
|
||
|
||
1. **Access, Examples-driven** — session without role; role "owner"; role "admin"; list answering
|
||
ApiError(403). Assert the Forbidden copy and "Back to Home" (navigates to /home) and that no list
|
||
request is sent when the role is missing; assert the page header for owner and admin.
|
||
2. **Header and tabs** — title, description, "Add variant", tabs "Variants" (active) and "Cache"
|
||
(aria-disabled, click does nothing), breadcrumb "Settings", "Media", "Variants" and
|
||
mobile title "Variants".
|
||
3. **Loaded, desktop** — column labels, the 6 rows in order with every cell of the table, "—" for
|
||
print, "Disabled" for print, muted text on the disabled row, footer "6 variants · 1,284 generated
|
||
files · 212 MB", the reference card.
|
||
4. **Cell formats, Examples-driven** — the four dimension/quality/format cases.
|
||
5. **Loaded, mobile** — 6 cards, the spec lines (`1200×630 · cover · WebP · q82`, print without a
|
||
quality part), "244 generated · 84 MB", the footer after the cards, the reference card.
|
||
6. **Footer** — Default sums; a one-variant list "1 variant · 1 generated file · 18 KB"; an
|
||
after-delete recompute "5 variants · 1,040 generated files · 128 MB".
|
||
7. **Loading** — unresolved GET: nothing at 149 ms; at 150 ms the region with aria-busy and the label
|
||
"Loading variants", 5 skeleton rows on desktop / 4 cards below 640, no text; a response at 100 ms never
|
||
draws skeletons.
|
||
8. **Empty** — copy, the two template cards, "Add variant", no reference card; each template opens
|
||
the form with its values (thumb 400×300 cover WebP 80; hero 1200×630 cover WebP 82).
|
||
9. **Error, Examples-driven** — ApiError(500) and NetworkError: alert copy, "Try again", no close
|
||
button, no table; Try again re-sends GET and renders the Default fixture.
|
||
10. **Status switch** — accessible name "Enable variant hero"; toggling hero sends `PATCH
|
||
/v1/image-variants/hero {"enabled": false}`, switch disabled while in flight, row shows Disabled,
|
||
no toast; print on sends `{"enabled": true}`; failures 403/404/500 revert the switch and raise
|
||
the toast copy of the table.
|
||
11. **Row menu** — items Edit/Purge cache/Delete in order, ArrowDown/Up and Enter, Escape closes and
|
||
returns focus to the ellipsis button; Edit opens the edit form, the others open their dialog.
|
||
12. **Purge** — dialog copy for hero ("Purge 244 files", "84 MB"); Cancel/Escape send nothing; confirm
|
||
sends `POST /v1/image-variants/hero/purge`, closes the dialog, row dimmed with "Purging…" and
|
||
disabled controls while others stay active; resolve `{purged_files: 244, purged_bytes: 88080384}`
|
||
-> row 0 files, status Enabled, toast "Purged 244 files from “hero”"; failures 404/500 -> row restored + toast.
|
||
13. **Delete** — dialog copy; confirm sends `DELETE /v1/image-variants/hero`; spinner, Cancel
|
||
disabled, Escape and backdrop blocked; 204 -> row removed, toast `Variant “hero” deleted`, footer
|
||
recompute; deleting the last row shows Empty; failures 404 (reloads the list) / 500 -> toast.
|
||
14. **Reference card** — the 4 rows with badge words and icons, the exact JSON block text, the note;
|
||
absent in Loading/Empty/Error/Forbidden; one column below 640.
|
||
15. **Slide-over open/close** — desktop slide-over vs mobile bottom sheet (grabber, x; full-page back arrow superseded by features/variants_visual_conformance.feature), titles
|
||
"Add variant" / "Edit variant: hero", focus on Name, pristine close without dialog, dirty close
|
||
via Cancel/x/back/Escape opens "Discard changes?" with the right text, Keep editing / Discard
|
||
changes behaviours, focus return to the opener.
|
||
16. **Create: pristine** — all labels, placeholders, helps, defaults (Fit Cover, Format Original,
|
||
Quality 80, Generate off, Enabled on), "Add variant" disabled, overlay "Set a width or height to
|
||
see a preview.".
|
||
17. **Fit help, Examples-driven** — three helps.
|
||
18. **Field errors, Examples-driven** — each name/width/height/quality row of the feature; blur and
|
||
submit; revalidation on input; boundary values accepted; several errors at once: no request.
|
||
19. **No dimension** — copy under Width, error border on Height, default help hidden, no request.
|
||
20. **PNG** — Quality hidden, body without "quality", quality restored after switching back.
|
||
21. **Quality slider/number sync**.
|
||
22. **Create request** — exact POST body for the hero values; the `201` flow inserts the row in
|
||
name order without a second GET, footer recomputed, toast `Variant “hero” added`.
|
||
23. **Saving** — fields disabled, "Saving…", Escape/x/backdrop blocked.
|
||
24. **409** — inline message with the name, values kept, no alert and no toast, cleared on edit.
|
||
25. **Save error, Examples-driven** — ApiError(500) and NetworkError: alert copy; Try again repeats the
|
||
request; values kept.
|
||
26. **Other statuses, Examples-driven** — 400, 403, 404 toasts.
|
||
27. **422, Examples-driven** — the four server sentences and their inline/alert mapping.
|
||
28. **Edit: pristine** — locked Name with help, stored values, warning alert text with 244, Save
|
||
disabled.
|
||
29. **Edit: dirty and save** — quality 82 -> 70: estimate "about 71 KB", Save enabled, PUT body
|
||
without "name", 200 with purged_files 244 -> row updated in place, toast "Variant “hero” saved. 244 files
|
||
purged."; purged_files 0 -> "Variant “hero” saved.".
|
||
30. **Preview layout** — heading, sample text, labels, hint, chips "Original" and the name, handle size
|
||
(44 px class on mobile).
|
||
31. **ImageCompare keyboard, Examples-driven** — role/type range, min 0 max 100 step 1, value 50,
|
||
aria-label "Compare original and hero", aria-valuetext "50% hero"; every key row of the feature,
|
||
including clamping; valuetext and clip-path/divider position follow the value; pointer input sets it.
|
||
32. **ImageCompare empty** — no dimensions: no variant layer, divider or handle; overlay text.
|
||
33. **Output size and estimate, Examples-driven** — the six size rows and the four estimate rows;
|
||
monotonic quality; never above 3.9 MB.
|
||
34. **URL** — URL text with and without a name, Copy disabled without a name, click writes the
|
||
displayed URL, label "Copied" with fake timers and back to "Copy", a rejected clipboard keeps "Copy".
|
||
35. **French** — `setLocale('fr')`; every key used on the page exists in messages.ts (a catalogue
|
||
test) and the page renders without missing keys.
|
||
36. **Vocabulary** — no rendered text contains "thumbnail preset", "version" or "rendition".
|
||
|
||
## Visual (task visual:check -- image-variants, desktop and mobile, light and dark)
|
||
|
||
Reference: docs/design/screens/image-variants.md, board "Image variants" in
|
||
`docs/design/source/RenewCMS Screens Image variants.dc.html`, exact values in
|
||
`docs/design/source/VariantsFrame.dc.html` and `AppFrame.dc.html`. Add each state to
|
||
`web/visual/states.ts` with the board's data. The gate is hard: every non-skipped capture within 10 %,
|
||
both viewports, both themes. All states below use the board's sample data; none is clock-sensitive,
|
||
so none is skipped.
|
||
|
||
Sample data (from the board): the 6 variants of the Background (thumb 412 files 21 MB, card 318/58 MB,
|
||
hero 244/84 MB, square 140/19 MB, og 96/9 MB, print 74/21 MB), board row order as listed in
|
||
VariantsFrame (thumb, card, hero, square, og, print); (the board orders by creation, the API by name;
|
||
the mock must return the board order for the visual capture only). Session name "Marta Alves".
|
||
Preview sample: north-wall.jpg, 4032 × 3024, 3.9 MB.
|
||
|
||
- [ ] 1a Loaded (desktop table, mobile cards, reference card on desktop)
|
||
- [ ] 1b Loading
|
||
- [ ] 1c Empty with the two templates
|
||
- [ ] 1d Error loading
|
||
- [ ] 1e Forbidden (session without role, no nav item current)
|
||
- [ ] 1f Row menu (hero)
|
||
- [ ] 1g Row action in progress (hero "Purging…")
|
||
- [ ] 1h Purge confirm
|
||
- [ ] 1i Delete confirm
|
||
- [ ] 1j Delete in progress
|
||
- [ ] 1k Request reference (mobile scrolled 900 px; desktop shows it under the table)
|
||
- [ ] 2a Create: pristine
|
||
- [ ] 2b Create: dirty (hero, slider focused, mobile scrolled to the preview, estimate 96 KB)
|
||
- [ ] 2c Field errors (name "Hero banner", width 9000, height 630, quality 140)
|
||
- [ ] 2d No dimension (name "banner")
|
||
- [ ] 2e Name already taken (name "thumb", 400×300, estimate 18 KB)
|
||
- [ ] 2f Saving
|
||
- [ ] 2g Save error
|
||
- [ ] 2h Edit: pristine
|
||
- [ ] 2i Edit: dirty (quality 70, estimate 71 KB)
|
||
- [ ] 2j Unsaved changes (discard dialog over 2i)
|
||
- [ ] 2k PNG: no quality (estimate 310 KB)
|
||
- [ ] Layout, taken from the board frame (VariantsFrame.dc.html), not components.md: table grid
|
||
`minmax(0,1.1fr) 104px 76px 72px 68px 92px 92px 132px 44px`, 36 px header, 48 px rows; cards 12 px
|
||
padding with the name+switch line, the spec line, then count+menu; slide-over 960 px with a
|
||
440 px form column; compare box 3:2, chips 8 px from the top corners, handle 32 px (44 px on
|
||
mobile) centred on a 2 px divider; footer 64 px (mobile sheet footer: see features/variants_visual_conformance.feature).
|
||
- [ ] Not drawn, no visual state: toggle failures, the purge/delete failure toasts, the 409-free
|
||
status toasts, the 422 mapping, the clipboard states "Copied" (add only if the board draws it: it
|
||
does not, so Vitest only), singular footer.
|
||
- [ ] Not covered by screenshots: keyboard and focus order (Tab reaches Add variant, tabs, each row's
|
||
switch and ellipsis; the slide-over focus starts on Name and is trapped; Escape rules), slider
|
||
keys by hand, hover/active, motion (slide-over, dialogs, skeleton pulse stopped under reduced
|
||
motion).
|
||
|
||
## Manual verification
|
||
|
||
- **Round trips against `task dev`**: create, edit (PUT with a purge), toggle, purge and delete a real
|
||
variant; confirm rows, counts and sizes after a reload match the API (needs generated files from a
|
||
real `/static/uploadedImages/<storedName>?v=<name>` request).
|
||
- **Role**: sign in as an owner and as an admin; confirm both reach the page. A session without a role
|
||
is not reproducible live (only owner/admin exist); covered by Vitest and the visual state.
|
||
- **Real clipboard** in a secure context (localhost): Copy then paste.
|
||
- **French**: switch the language and read every state, the dialogs and the toasts.
|
||
- **Real public URL**: paste the copied URL on a stored image and confirm it serves the variant.
|