Files
RenewCMS/features/image_variants_screens.qa.md

11 KiB
Raw Permalink Blame History

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.