431 lines
26 KiB
Gherkin
431 lines
26 KiB
Gherkin
Feature: Image variants on the Media page: badge, filter, sort, Variants tab, delete dialogs and activity
|
||
|
||
As an owner or admin of a RenewCMS site
|
||
I want the Media page to show which images have generated variants, let me generate, preview and purge them for one image, and warn me what a delete also removes
|
||
So that I can keep the cache of variants under control without leaving the library
|
||
|
||
Background:
|
||
Given the spec of record is docs/design/screens/image-variants.md sections 3, 4 and 8 (the Generate all and Cache purged: by image rows), docs/design/screens/media.md, docs/design/screens/activity.md, the board docs/design/source/RenewCMS Screens Image variants.dc.html drawn by VariantsFrame.dc.html (frames 3a to 3f and 4a to 4h), and docs/design/routes.md
|
||
And this slice is SPA only: the API is the one of features/image_variants_per_image_api.feature and features/image-variants.feature, and features/media_browse.feature and features/media_upload_selection_duplicates.feature stay as they are except where this file says so
|
||
And the editor picker is in features/image_variants_editor_picker.feature, and the Settings → Media → Cache screen is not in this slice
|
||
And every new English string has a French entry in web/src/i18n/messages.ts, only design copy is verbatim, and French wording is proposed
|
||
And the page uses Nuxt UI components only and design tokens, with no raw hex value and no arbitrary Tailwind colour, and no comment is added to any file
|
||
And the stored url of an image is "/static/uploadedImages/<storedName>" and the url of its variant is that plus "?v=<name>"
|
||
And sizes use the library's formatter ("8 KB", "260 KB", "3.9 MB"), dimensions in the library read "4032 × 3024" and in variants "400×300", "800 × auto", "auto × 2400"
|
||
And the library fixture is the 12-image fixture of features/media_browse.feature, extended with variant_count and variant_size from the board:
|
||
| id | filename | size | variant_count | variant_size | total |
|
||
| 1 | north-wall.jpg | 4089446 | 4 | 266240 | 4.2 MB |
|
||
| 2 | kitchen-after.webp | 1258291 | 5 | 419840 | 1.6 MB |
|
||
| 3 | kitchen-before.webp | 1153434 | 5 | 404480 | 1.5 MB |
|
||
| 4 | logo-renew.svg | 8192 | 0 | 0 | 8 KB |
|
||
| 5 | floorboards-copy.jpg | 2516582 | 3 | 245760 | 2.6 MB |
|
||
| 6 | IMG_2208.jpg | 2516582 | 0 | 0 | 2.4 MB |
|
||
| 7 | IMG_4471.jpg | 3250586 | 2 | 153600 | 3.3 MB |
|
||
| 8 | window-frame.jpg | 1887436 | 0 | 0 | 1.8 MB |
|
||
| 9 | stone-wall-detail.jpg | 3250586 | 1 | 61440 | 3.2 MB |
|
||
| 10 | bathroom-tiles.png | 880640 | 3 | 194560 | 1.1 MB |
|
||
| 11 | lime-plaster.jpg | 2202009 | 4 | 286720 | 2.4 MB |
|
||
| 12 | floorboards.jpg | 2516582 | 5 | 440320 | 2.8 MB |
|
||
And the five enabled variants of the Variants tab, in the order the API returns them (name), are card (inside 800 x auto webp), hero (cover 1200 x 630 webp), og (cover 1200 x 630 jpeg), square (cover 600 x 600 jpeg), thumb (cover 400 x 300 webp), and the disabled variant "print" is never listed
|
||
And the Variants tab of image 1 reads its rows from GET /v1/media/1/variants: output 800×600, 1200×630, 1200×630, 600×600, 400×300 and sizes in bytes thumb 18432, card 65536, hero 98304, square 83968, og 145408
|
||
And the session role comes from GET /v1/auth/me and is "owner" unless a scenario says otherwise
|
||
And every success toast lasts 5 s and toast ids follow the action-plus-id convention
|
||
|
||
Rule: The library badge
|
||
|
||
Scenario: A card with generated variants carries the badge
|
||
Given GET /v1/media/ resolves 200 with the extended fixture
|
||
When the user navigates to /media
|
||
Then the card "north-wall.jpg" shows the badge "4 variants" after "Used in 2"
|
||
And the card "kitchen-after.webp" shows "Used in 3" then "5 variants"
|
||
And the badge has the info-soft fill, the layers icon, 12 px medium text and is 20 px high
|
||
|
||
Scenario Outline: The badge text and its absence
|
||
Given the Default state has loaded
|
||
Then the card for "<filename>" shows the badges "<badges>"
|
||
|
||
Examples:
|
||
| filename | badges |
|
||
| north-wall.jpg | Used in 2, 4 variants |
|
||
| stone-wall-detail.jpg | Duplicate, Used in 1, 1 variant |
|
||
| window-frame.jpg | Unused |
|
||
| logo-renew.svg | Used in 5 |
|
||
| IMG_2208.jpg | Duplicate, Used in 2 |
|
||
|
||
Scenario: The accessible name of a card is unchanged
|
||
Then the card for "north-wall.jpg" is still named "north-wall.jpg, 3.9 MB, used in 2 articles"
|
||
|
||
Scenario: List view shows no variants badge
|
||
Given the Default state has loaded with ?view=list
|
||
Then the table and the card list show no "variants" badge
|
||
|
||
Rule: The variants filter
|
||
|
||
Scenario: The segmented control sits between the search field and the sort menu
|
||
Given the Default state has loaded at desktop width
|
||
Then the toolbar reads, left to right, the tabs, the search field, a segmented control "All", "With variants", "Without" (30 px high, 1 px accented border, 12 px horizontal padding) and the sort menu
|
||
And "All" is selected, with the primary fill and inverted text
|
||
|
||
Scenario: On mobile the control is its own full-width row
|
||
Given the Default state has loaded below md width
|
||
Then the toolbar stacks search, tabs, the segmented control with three equal segments of 44 px and 12 px text, then the sort menu and the grid toggle
|
||
|
||
Scenario Outline: Choosing a segment writes the URL and requests the list
|
||
Given the Default state has loaded
|
||
When the user selects "<segment>"
|
||
Then the URL <url> and GET /v1/media/ is sent with "<request>" and limit=100
|
||
And the list shows a skeleton after 150 ms
|
||
|
||
Examples:
|
||
| segment | url | request |
|
||
| With variants | gains ?variants=with | filter=all&sort=newest&variants=with |
|
||
| Without | gains ?variants=without| filter=all&sort=newest&variants=without |
|
||
|
||
Scenario: All removes the parameter
|
||
Given the URL is /media?variants=with
|
||
When the user selects "All"
|
||
Then ?variants leaves the URL and the request carries no variants parameter
|
||
|
||
Scenario Outline: The query string is the source of truth
|
||
When the user opens /media<url>
|
||
Then GET /v1/media/ is sent with "<request>" and the control shows "<control>"
|
||
|
||
Examples:
|
||
| url | request | control |
|
||
| ?variants=with | filter=all&sort=newest&variants=with | segment "With variants" selected |
|
||
| ?variants=without&filter=unused | filter=unused&sort=newest&variants=without | tab "Unused" and segment "Without" selected |
|
||
| ?variants=all | filter=all&sort=newest | segment "All" selected, URL kept clean |
|
||
| ?variants=zzz | filter=all&sort=newest | segment "All" selected, URL kept clean |
|
||
|
||
Scenario: With variants returns only the images that have cached files
|
||
Given GET /v1/media/?...&variants=with&sort=total_size resolves 200 with ids 1, 7, 9, 12, 5, 11, 2, 3, 10
|
||
Then nine cards show in that order and each carries a variants badge
|
||
|
||
Scenario: Without variants returns the images with no cached file
|
||
Given GET /v1/media/?...&variants=without resolves 200 with ids 4, 6, 8
|
||
Then three cards show and none carries a variants badge
|
||
|
||
Rule: Sorting by total size
|
||
|
||
Scenario: The new option is last in the sort menu
|
||
When the user opens the sort menu
|
||
Then the options read "Newest", "Oldest", "Name", "Largest", "Largest total size (original + variants)" in that order, the last one 340 px wide on desktop and labelled "Largest total size" on mobile
|
||
And the selected option carries a check
|
||
|
||
Scenario: Choosing it sends sort=total_size
|
||
When the user picks "Largest total size (original + variants)"
|
||
Then the URL gains sort=total_size and GET /v1/media/ is sent with sort=total_size
|
||
And the sort button reads "Largest total size (original + variants)" on desktop and "Largest total size" on mobile
|
||
|
||
Scenario Outline: The meta line shows the total while sorted by total size
|
||
Given the list is sorted by total size
|
||
Then the card for "<filename>" shows the meta line "<meta>" in place of "size · dimensions"
|
||
|
||
Examples:
|
||
| filename | meta |
|
||
| north-wall.jpg | 4.2 MB total · 3.9 MB + 260 KB |
|
||
| kitchen-after.webp | 1.6 MB total · 1.2 MB + 410 KB |
|
||
| window-frame.jpg | 1.8 MB total |
|
||
| logo-renew.svg | 8 KB total |
|
||
|
||
Scenario: Other sorts keep the usual meta line
|
||
Given the list is sorted by Newest
|
||
Then the card for "north-wall.jpg" shows "3.9 MB · 4032 × 3024"
|
||
|
||
Scenario: A stale sort value is dropped
|
||
When the user opens /media?sort=total
|
||
Then the sort shows "Newest" and the URL is clean
|
||
|
||
Rule: Nothing to show with the variants filter
|
||
|
||
Scenario: With variants and an empty cache
|
||
Given GET /v1/media/?...&variants=with resolves 200 with no item and no q is set and the tab is All
|
||
Then the layers icon shows with the title "No images with variants", the text "Variants are generated when an image is requested with ?v=<name>, or from its Variants tab." and an outline button "Show all images"
|
||
And the toolbar, including the segmented control, stays visible
|
||
|
||
Scenario: Show all images resets the variants filter
|
||
When the user activates "Show all images"
|
||
Then ?variants leaves the URL, the segmented control reads "All" and the Default cards reappear
|
||
|
||
Rule: Delete dialogs name the generated variants they also remove
|
||
|
||
Scenario: A single delete of an image with variants
|
||
Given the detail panel is open for "north-wall.jpg" (used in 2, 4 variants of 266240 bytes)
|
||
When the user activates "Delete"
|
||
Then the dialog keeps its title "Delete an image that's in use?", its description and its list of the two articles
|
||
And a note shows in the body with the layers icon on a warning-soft background: "Also removes 4 generated variants (260 KB)"
|
||
And no extra request is sent to compute it
|
||
|
||
Scenario Outline: The note is singular for one variant and absent for none
|
||
Given the detail panel is open for "<filename>"
|
||
When the user activates "Delete"
|
||
Then the note reads "<note>"
|
||
|
||
Examples:
|
||
| filename | note |
|
||
| stone-wall-detail.jpg | Also removes 1 generated variant (60 KB) |
|
||
| window-frame.jpg | no note is shown |
|
||
|
||
Scenario: Bulk delete sums the selection
|
||
Given the user selected "north-wall.jpg", "kitchen-after.webp" and "window-frame.jpg"
|
||
When the user activates "Delete" in the bulk bar
|
||
Then the bulk dialog keeps its copy and its one row per image
|
||
And the note under the rows reads "Also removes 9 generated variants (670 KB)"
|
||
|
||
Scenario: A bulk delete with no variant in the selection shows no note
|
||
Given the user selected "window-frame.jpg" and "IMG_2208.jpg"
|
||
Then the dialog shows no note
|
||
|
||
Scenario: The delete flow itself is unchanged
|
||
Then the pending, failure, 404 and success behaviours of features/media_browse.feature apply, and after a successful delete the list is requested again in the background
|
||
|
||
Rule: The Variants tab in the detail panel
|
||
|
||
Scenario: The panel gets two tabs for an owner or an admin
|
||
When the user opens the panel of "north-wall.jpg"
|
||
Then two tabs sit under the 56 px header: "Details" (selected) and "Variants", 40 px high on desktop and 44 px on mobile, 20 px apart, with a 2 px primary underline on the selected one
|
||
And the Details tab is the existing panel body, unchanged
|
||
|
||
Scenario: A session that is not owner or admin sees no tab
|
||
Given the session role is neither "owner" nor "admin"
|
||
When the user opens a panel
|
||
Then no tab strip shows, no request to /v1/media/{id}/variants is sent, and the panel is the existing one
|
||
|
||
Scenario: Selecting the tab requests the variants once
|
||
When the user selects "Variants"
|
||
Then GET /v1/media/1/variants is sent
|
||
And selecting "Details" then "Variants" again does not send it a second time for the same open panel
|
||
|
||
Scenario: Loading
|
||
Given GET /v1/media/1/variants has not answered
|
||
Then nothing is drawn in the tab body for 149 ms
|
||
And at 150 ms a region announces "Loading variants" with aria-busy="true" and holds 5 skeleton rows
|
||
|
||
Scenario Outline: The variants request fails
|
||
Given GET /v1/media/1/variants fails with <failure>
|
||
Then an error alert shows the title "Couldn't load variants", the text "Check your connection and try again." and a "Try again" button with a rotate-cw icon
|
||
When the user activates "Try again"
|
||
Then the request is sent again
|
||
|
||
Examples:
|
||
| failure |
|
||
| status 500 |
|
||
| a network failure |
|
||
|
||
Scenario: Partly generated
|
||
Given GET /v1/media/1/variants resolves with card, hero, square and thumb generated and og not
|
||
Then the tab shows, in order: the Original block, the summary, the list
|
||
And the Original block (72 x 54 thumbnail, 12 px gap, 12 px padding, 1 px border, --ui-bg-muted) reads "Original · kept untouched", "north-wall.jpg", "JPEG · 4032 × 3024 · 3.9 MB"
|
||
And the summary reads "4 of 5 generated" in semibold next to a "Generate all" button with the zap icon
|
||
And the list is a bordered group of five rows in the order card, hero, og, square, thumb
|
||
|
||
Scenario Outline: A generated row
|
||
Then the row "<name>" shows its name in mono 13 px medium, the badge "Generated" (success-soft, circle-check), and the meta "<meta>"
|
||
And the part "<saving> vs original" is in the success colour and the meta before it is toned 12 px text
|
||
|
||
Examples:
|
||
| name | meta | saving |
|
||
| thumb | 400×300 · WebP · 18 KB · -99% | -99% |
|
||
| card | 800×600 · WebP · 64 KB · -98% | -98% |
|
||
| hero | 1200×630 · WebP · 96 KB · -98% | -98% |
|
||
| square | 600×600 · JPEG · 82 KB · -98% | -98% |
|
||
|
||
Scenario: A not generated row
|
||
Then the row "og" shows the badge "Not generated" (outline, circle-dashed) and the meta "1200×630 · JPEG" with no size and no saving
|
||
|
||
Scenario Outline: The saving against the original
|
||
Given the original is 4089446 bytes
|
||
When a variant file is <size> bytes
|
||
Then the saving reads "<text>"
|
||
|
||
Examples:
|
||
| size | text |
|
||
| 18432 | -99% |
|
||
| 65536 | -98% |
|
||
| 98304 | -98% |
|
||
| 145408 | -96% |
|
||
| 3250586 | -21% |
|
||
| 4089446 | 0% |
|
||
| 4500000 | +10% |
|
||
|
||
Scenario: Nothing generated yet
|
||
Given GET /v1/media/1/variants resolves with no row generated
|
||
Then the summary reads "0 of 5 generated" over the muted line "Nothing generated yet."
|
||
And every row shows "Not generated"
|
||
|
||
Scenario: Row actions on desktop are icon buttons
|
||
Given the viewport is 640 px wide or more
|
||
Then a generated row shows three 28 px square icon buttons Copy URL (link), Preview (eye) and Purge (eraser), each with a tooltip and an accessible name equal to its label
|
||
And a not generated row shows Copy URL (link) and Generate now (zap)
|
||
|
||
Scenario: Row actions on mobile are labelled buttons on their own line
|
||
Given the viewport is below 640 px
|
||
Then each action is a 44 px high labelled button on a line under the meta, "Copy URL", "Preview", "Purge" or "Copy URL", "Generate now"
|
||
|
||
Scenario: Generate all is disabled when everything is generated
|
||
Given all five rows are generated
|
||
Then "Generate all" is disabled
|
||
|
||
Scenario: Generate now
|
||
When the user activates "Generate now" on the row "og"
|
||
Then POST /v1/media/1/variants/og is sent
|
||
And the row "og" shows the badge "Generating" (info-soft, loader-circle), its actions are gone, its background is --ui-bg-elevated and the other rows stay as they are
|
||
And the summary shows "Generating variants…" in the info colour and "Generate all" reads "Generating…" with a loader-circle and is disabled
|
||
When the API answers 200 with the item generated
|
||
Then the row shows "Generated" with its meta, the summary reads "5 of 5 generated"
|
||
And a toast shows icon circle-check, id "media-variant-generated-1-og" and title "Generated og"
|
||
|
||
Scenario: Generate all
|
||
Given only thumb is generated
|
||
When the user activates "Generate all"
|
||
Then POST /v1/media/1/variants is sent
|
||
And the four rows not generated show "Generating", the generated row stays, and the summary and button read as above
|
||
When the API answers 200 with "generated" 4 and the five items
|
||
Then every row shows "Generated" and a toast shows id "media-variants-generated-1" and title "Generated 4 variants"
|
||
|
||
Scenario: A single generated variant reads in the singular
|
||
When Generate all answers "generated" 1
|
||
Then the toast title is "Generated 1 variant"
|
||
|
||
Scenario: Generate all with a failure
|
||
When Generate all answers 200 with "generated" 4, "failed" 1, failures [{"name": "hero", "cause": "too_large"}] and the five items
|
||
Then the row "hero" shows the badge "Failed" (error-soft, circle-x), a line with a circle-alert icon in the error colour at 12 px reading "Image too large", and a "Retry" button with a rotate-cw icon (labelled, 28 px high, 12 px text)
|
||
And the summary reads "4 of 5 generated" and the toast reads "Generated 4 variants"
|
||
|
||
Scenario Outline: A single generation fails with a cause
|
||
When POST /v1/media/1/variants/hero answers <status> with the plain text body
|
||
Then the row "hero" shows "Failed" and the line "<cause>" with "Retry"
|
||
And an error toast shows the title "Couldn't generate hero." and the text "<cause>."
|
||
|
||
Examples:
|
||
| status | cause |
|
||
| 413 | Image too large |
|
||
| 415 | Unsupported source format |
|
||
|
||
Scenario: Failed state is not remembered
|
||
Given the row "hero" shows "Failed"
|
||
When the panel is closed and opened again, and the tab is selected
|
||
Then the row "hero" shows "Not generated"
|
||
|
||
Scenario: Retry sends the same request
|
||
When the user activates "Retry" on the row "hero"
|
||
Then POST /v1/media/1/variants/hero is sent and the row shows "Generating"
|
||
|
||
Scenario Outline: Other failures of a generation
|
||
When POST /v1/media/1/variants/og answers <status>
|
||
Then the row "og" returns to "Not generated"
|
||
And <result>
|
||
|
||
Examples:
|
||
| status | result |
|
||
| 500 | a toast shows "Something went wrong" and "The server couldn't finish. Try again. If it keeps happening, check the server log." |
|
||
| 403 | a toast shows "You don't have access" and "Only admins can manage image variants." |
|
||
| 404 | a toast shows "Not found" and "This variant or image no longer exists. Refresh the page." and the tab is requested again |
|
||
| network failure | the same toast as 500 |
|
||
|
||
Scenario: The image is gone
|
||
When the tab is requested again after a 404 and GET /v1/media/1/variants answers 404
|
||
Then the panel closes, the image leaves the list and the info toast "That image no longer exists" shows, as in features/media_browse.feature
|
||
|
||
Scenario: Copy URL copies the absolute variant url
|
||
When the user activates "Copy URL" on the row "thumb"
|
||
Then "{origin}/static/uploadedImages/<storedName>?v=thumb" is written to the clipboard, also for a row that is not generated
|
||
And a toast shows id "media-variant-url-copied-1-thumb", title "URL copied" and text "Paste it wherever you need the image address."
|
||
|
||
Scenario: Purge a generated row
|
||
When the user activates "Purge" on the row "thumb"
|
||
Then DELETE /v1/media/1/variants/thumb is sent
|
||
When the API answers 200 {"purged_files": 1, "purged_bytes": 18432}
|
||
Then the row shows "Not generated" with no size, the summary reads "3 of 5 generated" and a toast shows id "media-variants-purged-1-thumb" and title "Purged 1 file (18 KB)"
|
||
|
||
Scenario Outline: Purge fails
|
||
When DELETE /v1/media/1/variants/thumb answers <status>
|
||
Then the row stays "Generated" and the toast of the back-office error table for <status> shows
|
||
|
||
Examples:
|
||
| status |
|
||
| 403 |
|
||
| 404 |
|
||
| 500 |
|
||
|
||
Scenario: Preview
|
||
When the user activates "Preview" on the row "thumb"
|
||
Then a dialog opens, 880 px wide on desktop and a bottom sheet on mobile, titled "Preview: thumb", with a "Close" button
|
||
And two panes sit side by side on desktop (stacked on mobile), each 300 px high (180 px on mobile), bordered, on --ui-bg-muted, overflow scrolling to pan
|
||
And the left pane shows the variant file at actual size under "thumb · 400×300 · WebP" and "18 KB · saves 99%"
|
||
And the right pane shows the original at actual size under "Original · 4032×3024 · JPEG" and "3.9 MB"
|
||
And the hint reads "Both images are shown at actual size. Scroll to pan."
|
||
And focus is trapped in the dialog and returns to the Preview button on close
|
||
|
||
Scenario: No variants defined
|
||
Given GET /v1/media/1/variants resolves with no item and "unsupported" null
|
||
Then the Original block shows, then the empty block with the layers icon, the title "No variants defined", the text "Define a variant in Settings to get resized copies of this image." and a link "Settings → Media → Variants" with an arrow-up-right icon to /settings/media/variants
|
||
And no summary, no list and no "Generate all" show
|
||
|
||
Scenario: An SVG source
|
||
Given the panel is open for "logo-renew.svg" and GET /v1/media/4/variants resolves {"unsupported": "svg", "items": []}
|
||
Then the Original block reads "SVG · Vector · 8 KB"
|
||
And an info alert shows the title "Variants aren't generated for this format." and the text "SVG images are served as they are."
|
||
And no summary, no list and no "Generate all" show
|
||
|
||
Rule: Generate all and purge by image in the Activity screen
|
||
|
||
Scenario Outline: The two new sentences
|
||
Given an activity event with action "<action>" and outcome "success" and target name "north-wall.jpg"
|
||
Then the Action cell reads "<sentence>" with "north-wall.jpg" in semibold, last
|
||
|
||
Examples:
|
||
| action | sentence |
|
||
| media.variant_generate_all| Generated all variants of north-wall.jpg |
|
||
| media.variant_purge_image | Purged the variants of north-wall.jpg |
|
||
|
||
Scenario Outline: The failed sentences
|
||
Given an activity event with action "<action>", outcome "failed" and target name "north-wall.jpg"
|
||
Then the Action cell reads "<sentence>" and the Failed badge shows
|
||
|
||
Examples:
|
||
| action | sentence |
|
||
| media.variant_generate_all| Failed to generate the variants of north-wall.jpg |
|
||
| media.variant_purge_image | Failed to purge the variants of north-wall.jpg |
|
||
|
||
Scenario Outline: The detail line under the sentence
|
||
Given an activity event with action "<action>" and params <params>
|
||
Then a muted 12 px line under the sentence reads "<detail>" in the row and in the details panel
|
||
|
||
Examples:
|
||
| action | params | detail |
|
||
| media.variant_generate_all| {"generated": 5, "failed": 0, "causes": []} | 5 variants generated |
|
||
| media.variant_generate_all| {"generated": 1, "failed": 0, "causes": []} | 1 variant generated |
|
||
| media.variant_generate_all| {"generated": 4, "failed": 1, "causes": ["too_large"]} | 4 generated · 1 failed (Image too large) |
|
||
| media.variant_generate_all| {"generated": 3, "failed": 2, "causes": ["too_large", "unsupported_format"]} | 3 generated · 2 failed (Image too large, Unsupported source format) |
|
||
| media.variant_purge_image | {"purged_files": 4, "purged_bytes": 266240} | Removed 4 files (260 KB) |
|
||
| media.variant_purge_image | {"purged_files": 1, "purged_bytes": 18432} | Removed 1 file (18 KB) |
|
||
|
||
Scenario: Events without a detail line are unchanged
|
||
Given an event whose action has no detail line, such as "media.rename"
|
||
Then the row shows no second line
|
||
|
||
Scenario: The French interface
|
||
Given the locale is French
|
||
Then the two sentences, their failed forms and the detail lines come from messages.ts and no English string appears
|
||
|
||
Rule: Accessibility, vocabulary and hygiene
|
||
|
||
Scenario: Status is never colour alone
|
||
Then each row status shows an icon and a word (Generated circle-check, Not generated circle-dashed, Generating loader, Failed circle-x) and each icon-only button has an accessible name and a tooltip
|
||
|
||
Scenario: Touch targets on mobile
|
||
Given the viewport is below 640 px
|
||
Then the segmented control, the tabs, the row buttons and the dialog buttons are at least 44 px high
|
||
|
||
Scenario: Progress is announced
|
||
When a generation starts and ends
|
||
Then an aria-live="polite" region announces "Generating variants" and the result
|
||
|
||
Scenario: Vocabulary
|
||
Then the screens say "variant", "generated" and "purge", never "thumbnail preset", "version" or "rendition"
|
||
|
||
Scenario: French strings
|
||
Given the locale is French
|
||
Then every new string of this file has a French entry and no English string appears
|