feat(SPA): media field type
This commit is contained in:
1 parent
a2d596f4ca
commit
3e7fc9a4a5
57 files changed
+6109
-75
No files matched your search
@@ -0,0 +1,492 @@
|
||||
Feature: Media field type in the SPA
|
||||
As an owner or admin building a content type, and as a verified user filling an entry
|
||||
I want a Media field that holds one image from the media library
|
||||
So that entries link to a library image and always show its current version
|
||||
|
||||
Background:
|
||||
Given the SPA calls the API with relative URLs under /v1 and the "jwt" HttpOnly cookie
|
||||
And API errors are plain text
|
||||
And this slice changes only web/ (and web/visual); the backend of features/media_field_type.feature is done
|
||||
And the design is docs/design/media-field-changes.md sections 1, 2, 3.2 to 3.7, 4, 6 and 7, with the board groups 2 Schema builder, 3 Review and delete, 4 Field types, 5 Content and entry list, 6 Entry editor and 10 Media picker for fields of "RenewCMS Screens Schema.dc.html"
|
||||
And the registry is lib/fieldTypes with lib/entryEditors, the single source for the builder, the list cells and the entry form; no screen branches on the type name "media" where a registry property can say it
|
||||
And a media value is the id of a library image (a uint32 of at least 1) or null
|
||||
And every entry answer (POST 201, PUT 200, GET one, each list item) carries a top-level "media" object keyed by media field key: {"id","filename","width","height","size","thumbnail_url"} for an existing image, or {"id","missing":true} when the image is gone; a null value has no key
|
||||
And the API refuses with 422 "invalid_entry: <key>: required" or "invalid_entry: <key>: type", 409 "schema_incompatible" for a new required media field on a type with entries, and 422 "invalid_field" for forbidden options
|
||||
And the picker is the existing ImageUploader with MediaPicker, loading GET /v1/media/?page=&size=&q= with the total in the "counts" and "total" fields as the article picker does; the Sites, Authors, SEO and article pickers are unchanged
|
||||
And the picker in field mode never requests /v1/image-variants and never shows the variant select or the alt text
|
||||
And the form dimming stays the entry form's 70 % (design conflict 12, existing rule kept); inside the control the thumbnail drops to 60 % and the buttons take the disabled look
|
||||
And sentence case, "image" and never "asset", buttons are verbs
|
||||
And every string goes through t() and has a French entry in i18n/messages.ts
|
||||
And a copy tagged "(proposed copy)" is not in the design and is the coder's single point of change
|
||||
And user decisions already taken: Apply is disabled for the certain refusal; thumbnail after Description in schema order; "Clear"; "1600 × 900 · 240 KB"; "Use this image" and "Remove image"; "Pick an image" on optional fields; row heights 48 and 68
|
||||
And out of scope (slice C): "Used in" badge, usage panel and delete warning of the Media library, the read-only view of a custom type, filtering by media
|
||||
|
||||
Scenario Outline: Media in the registry
|
||||
Given the registry entry for "media"
|
||||
Then <property> is <value>
|
||||
|
||||
Examples:
|
||||
| property | value |
|
||||
| type name in the API | media (the seventh entry, after select) |
|
||||
| label | Media |
|
||||
| description | One image from your media library |
|
||||
| icon | lucide:image |
|
||||
| control | MediaFieldControl |
|
||||
| pairs with a neighbour | no, full width (as text, richtext and select) |
|
||||
| empty value | null |
|
||||
| initial on create | null; a media field has no default, so no "Default" tag ever shows |
|
||||
| initial on edit | the stored id, or null |
|
||||
| supports unique | no |
|
||||
| supports limits | no |
|
||||
| supports default | no |
|
||||
| supports choices | no |
|
||||
| counter | none |
|
||||
| help | One image from your library. |
|
||||
| list cell | thumbnail, column 80 px, not right-aligned, not sortable |
|
||||
| sample in the API preview | null |
|
||||
|
||||
Scenario Outline: Client validation of a media value
|
||||
Given a media field "Thumbnail" that is <required>
|
||||
When the value is <value>
|
||||
Then the rule is <rule>
|
||||
|
||||
Examples:
|
||||
| required | value | rule |
|
||||
| required | null | required |
|
||||
| required | undefined | required |
|
||||
| optional | null | none |
|
||||
| optional | 7 | none |
|
||||
| required | 7 | none |
|
||||
| optional | 0 | type |
|
||||
| optional | -1 | type |
|
||||
| optional | 7.5 | type |
|
||||
| optional | "7" | type |
|
||||
| optional | true | type |
|
||||
| optional | 4294967296 | type |
|
||||
|
||||
Scenario Outline: Messages of a media field
|
||||
Given a media field labelled "Thumbnail"
|
||||
Then the message of the rule <rule> reads <message>
|
||||
|
||||
Examples:
|
||||
| rule | message |
|
||||
| required | Pick an image for Thumbnail. |
|
||||
| type | Thumbnail must be an image from the library. |
|
||||
| min | not produced for media |
|
||||
| max | not produced for media |
|
||||
| options | not produced for media |
|
||||
| unique | not produced for media |
|
||||
|
||||
Scenario: A loaded media field is not shown as Text
|
||||
Given GET /v1/content-type/events returns a field {"key":"thumbnail","label":"Thumbnail","type":"media","required":false,"unique":false,"default":null,"min":null,"max":null,"options":[]}
|
||||
Then the builder, the entry form and the list resolve it to the media entry of the registry, never to the fallback type "text"
|
||||
|
||||
# ---------- Schema builder (groups 2 and 4)
|
||||
|
||||
Scenario: Seven type tiles
|
||||
Given the inspector of a field that can still change type
|
||||
Then seven radio tiles are shown in this order, each with its icon, name and description
|
||||
| tile | icon | description |
|
||||
| Text | type | Short text, like a title or a name |
|
||||
| Rich text | pilcrow | Long text with formatting, written in Markdown |
|
||||
| Number | hash | Whole or decimal numbers |
|
||||
| Yes or no | toggle-left | An on or off switch |
|
||||
| Date | calendar | A calendar day, without a time |
|
||||
| Choice | list | One option from a list you define |
|
||||
| Media | image | One image from your media library |
|
||||
And the grid has 3 columns in the desktop inspector and 2 in the mobile sheet
|
||||
|
||||
Scenario: Adding a media field (design "New media field, seven types")
|
||||
Given Events with 7 saved fields and 37 entries
|
||||
When the user activates "Add field", types the label "Thumbnail" and chooses the tile "Media"
|
||||
Then the new row shows the badge "New", the key "thumbnail" (Auto, editable) with the help "The name used in the API. Starts with a letter. Lowercase letters, digits and underscores."
|
||||
And the seven tiles are all enabled with Media selected and the legend "Type" has no lock
|
||||
And the chip reads "Unsaved changes"
|
||||
And the row is: grip, a 28 px square holding the image icon, the label, the key in mono and the badge "New"
|
||||
|
||||
Scenario: The inspector of a media field shows only what applies (design "Media field selected")
|
||||
Given Events with a saved optional media field "Thumbnail" as its eighth field
|
||||
When the user selects the row "Thumbnail"
|
||||
Then the inspector reads "Field: Thumbnail" and holds, in this order, the Label input, the locked Key "thumbnail" with "Used in the API, so it can't change.", the Type tiles, the switch "Required" with "An entry can't be saved with this field empty." and the hint block
|
||||
And the legend of the tiles reads "Type" with a lock and "Can't change after saving", Media is selected and the six other tiles are dimmed to 40 % and not selectable
|
||||
And the hint block is info-soft with an info icon and reads "Holds one image from the media library. Entries link to the image instead of keeping a copy, so they always show its current version."
|
||||
And "Unique values", "Default value", the limit inputs, the "Limits" heading and the choice list are not rendered at all
|
||||
|
||||
Scenario: Changing a new row to and from Media
|
||||
Given a new row of type Text, required, with a Longest of 80
|
||||
When the user chooses the tile "Media"
|
||||
Then the label, the key and Required are kept and unique, default, limits and choices are reset
|
||||
When the user then chooses the tile "Number"
|
||||
Then the media hint is gone and the number controls show with no stale value
|
||||
|
||||
Scenario: A required media field (design "Media field, required")
|
||||
Given a saved media field "Thumbnail" that is required
|
||||
Then its row carries the existing Required marker "R" (accessible name "Required") next to the key
|
||||
And its inspector shows Required switched on
|
||||
|
||||
Scenario: The PUT and POST body of a media field
|
||||
Given a media field with the label "Thumbnail", the key "thumbnail" and Required off
|
||||
When the type is saved
|
||||
Then the field is sent as {"key":"thumbnail","label":"Thumbnail","type":"media","required":false,"unique":false,"default":null,"min":null,"max":null,"options":[]}
|
||||
And a round trip of the loaded field to the payload produces no change
|
||||
|
||||
Scenario: Media in the API preview
|
||||
Given the builder holds a media field "Thumbnail"
|
||||
Then the sample entry shows "thumbnail": null and no "Example text"
|
||||
|
||||
Scenario: The mobile sheet of a media field
|
||||
Given a 390 px viewport
|
||||
When the user selects the row "Thumbnail"
|
||||
Then the 92 % sheet titled "Field: Thumbnail" shows the same inspector in one column with the type tiles in 2 columns and the footer button "Done"
|
||||
|
||||
Scenario: The field-type gallery (group 4) has no SPA screen
|
||||
Then the registry facts above are covered by the registry scenarios and no route renders the gallery frame
|
||||
|
||||
# ---------- Review (group 3)
|
||||
|
||||
Scenario: A required media field added to a type with entries is refused first (design "Review: required media field refused")
|
||||
Given Events with 12 entries and a new media field "Thumbnail" that is required
|
||||
When the user activates Save
|
||||
Then no PUT is sent
|
||||
And the dialog "Review changes to Events" with "Apply them to save the content type." opens (a bottom sheet on mobile)
|
||||
And the summary reads "Add 1 field" and "Events has 12 entries. Each one is checked against the new fields when you apply."
|
||||
And the row "Add Thumbnail" has the warning badge "Can't be applied" and the text "Media, required. A media field can't have a default, so the 12 existing entries would have no image. Make Thumbnail optional to add it."
|
||||
And the note "Back to editing keeps your other changes." shows instead of the "Flagged changes can be refused..." note
|
||||
And "Back to editing" is enabled and "Apply changes" is disabled (aria-disabled), so pressing it sends nothing
|
||||
|
||||
Scenario: The refusal is only for a new required media field with entries
|
||||
Given Events with <entries> entries and the edit <edit>
|
||||
When the user activates Save
|
||||
Then <result>
|
||||
|
||||
Examples:
|
||||
| entries | edit | result |
|
||||
| 12 | adds Thumbnail, Media, optional | the review opens, "Apply changes" is enabled |
|
||||
| 12 | adds Thumbnail, Media, required | the review opens, "Apply changes" is disabled |
|
||||
| 0 | adds Thumbnail, Media, required | no review, the PUT is sent at once |
|
||||
| 12 | makes the saved optional media field Thumbnail required | the review opens, row "Change Thumbnail" "Now required." flagged "Entries may not fit", Apply enabled; the server decides |
|
||||
| 12 | adds Thumbnail required and changes Capacity | both rows listed, Apply disabled |
|
||||
|
||||
Scenario Outline: The clean row (design "Review: optional media field")
|
||||
Given Events with <entries> entries and a new optional media field "Thumbnail"
|
||||
Then the row "Add Thumbnail" reads "<text>" with no flag
|
||||
And "Apply changes" is enabled
|
||||
|
||||
Examples:
|
||||
| entries | text |
|
||||
| 12 | Media, optional. 12 entries are unaffected. |
|
||||
| 1 | Media, optional. 1 entry is unaffected. (proposed copy) |
|
||||
|
||||
Scenario: Back to editing after the refusal
|
||||
Given the refused review is open
|
||||
When the user chooses "Back to editing" (or presses Esc or the close icon)
|
||||
Then the dialog closes, every edit is kept and no request was sent
|
||||
When the user turns Required off on the new row and activates Save
|
||||
Then the review opens with the clean row and "Apply changes" enabled
|
||||
|
||||
Scenario: The refusal follows offline and applying
|
||||
Given the refused review is open
|
||||
Then "Apply changes" stays disabled whatever the connection
|
||||
And with a clean review, "Apply changes" is still disabled while offline, as before
|
||||
|
||||
Scenario: The server refuses anyway
|
||||
Given the entry count was stale and the client opened no refusal
|
||||
When the PUT answers 409 "schema_incompatible"
|
||||
Then the existing "Nothing was changed" refusal flow of features/schema_builder_edit.feature runs unchanged
|
||||
|
||||
Scenario: 422 invalid_field on a media field
|
||||
Given the PUT answers 422 "invalid_field"
|
||||
Then the existing invalid-field failure of the builder is shown and the edits stay
|
||||
|
||||
# ---------- MediaFieldControl in the entry editor (group 6)
|
||||
|
||||
Scenario: Where the control sits
|
||||
Given Events with the fields Title, Starts on, Ends on, Online, Capacity, Category, Description and Thumbnail
|
||||
Then the control for Thumbnail is the eighth, after Description, full width in the 760 px column, and the footer lines come after it
|
||||
|
||||
Scenario: Empty, optional (design "Media control: empty, optional")
|
||||
Given /content/events/new, or an entry whose thumbnail is null
|
||||
Then the label reads "Thumbnail"
|
||||
And a dashed box (1 px, border-accented, padding 12) holds a 40 px square with the image icon, the text "No image selected." and the outline button "Pick an image" with the image-plus icon
|
||||
And the help line reads "One image from your library."
|
||||
And on mobile the box stacks and the button is 44 px high
|
||||
|
||||
Scenario: Empty, required (design "Media control: empty, required")
|
||||
Given the field is required
|
||||
Then the label reads "Thumbnail *" with the asterisk hidden from assistive technology, and the rest is as the empty state
|
||||
|
||||
Scenario: Filled (design "Media control: filled")
|
||||
Given an entry whose thumbnail is 7 and media.thumbnail is {"id":7,"filename":"lime-plaster-workshop.jpg","width":1600,"height":900,"size":245760,"thumbnail_url":"/media/lime-plaster-workshop.jpg"}
|
||||
Then a solid 1 px box (border-accented, padding 12) holds a 160 × 120 thumbnail button showing thumbnail_url, the filename in weight 500, "1600 × 900 · 240 KB" in 12/18 muted, the outline button "Change image" (image icon) and the ghost button "Remove" (x icon)
|
||||
And the help line reads "One image from your library."
|
||||
And on mobile the thumbnail is full width at 16:9 and the two buttons are full width and 44 px high
|
||||
And a width or height of null (a vector image) reads "Vector · 8 KB"
|
||||
|
||||
Scenario: Hover (design "Media control: hover")
|
||||
Given the filled state on desktop
|
||||
When the pointer is over the thumbnail
|
||||
Then the box border takes the --ui-text-muted colour and a 28 px bar at the bottom of the thumbnail (--ui-bg-inverted, --ui-text-inverted) reads "Change image" with the image icon at 14 px
|
||||
And mobile has no hover state and shows the filled state
|
||||
|
||||
Scenario: Focus (design "Media control: focus")
|
||||
Given the filled state
|
||||
When the keyboard focus reaches the thumbnail button
|
||||
Then it has a 2 px --ui-primary outline with a 2 px offset
|
||||
|
||||
Scenario: Pointer and keyboard open the picker
|
||||
Given the filled state
|
||||
When the user activates the thumbnail, "Change image", or on an empty control "Pick an image"
|
||||
Then the picker opens in field mode and nothing else changes
|
||||
|
||||
Scenario: Remove
|
||||
Given the filled state
|
||||
When the user activates "Remove"
|
||||
Then the value becomes null, the control shows the empty state and the chip reads "Unsaved changes"
|
||||
And the live region says "Image removed from Thumbnail." and the focus is on "Pick an image"
|
||||
|
||||
Scenario: Missing image, optional (design "Media control: image no longer in the library")
|
||||
Given media.thumbnail is {"id":7,"missing":true} and the field is optional
|
||||
Then the box is solid with a dashed 160 × 120 placeholder (16:9 on mobile) holding image-off at 24 px, with role "img" and the name "Image no longer in the library"
|
||||
And beside it "This image is no longer in the library" in weight 500, the help "You can still save. The entry keeps the link until you change it." in 12/18 muted, the outline button "Choose another image" and the ghost button "Clear"
|
||||
And there is no help line under the box
|
||||
And "Save" is allowed and the data sent keeps thumbnail 7
|
||||
|
||||
Scenario: Clear
|
||||
Given the missing state
|
||||
When the user activates "Clear"
|
||||
Then the value becomes null, the live region says "Thumbnail cleared." and the focus is on "Pick an image"
|
||||
|
||||
Scenario: Missing image, required (design "Media control: missing image, required")
|
||||
Given media.thumbnail is {"id":7,"missing":true} and the field is required
|
||||
Then the in-box help reads "Thumbnail is required. Choose another image to save."
|
||||
And when the user saves, no request is sent, the box takes the error border, the line "Pick an image for Thumbnail." shows with a circle-alert icon, the chip reads "Not saved" and the focus is on "Choose another image"
|
||||
|
||||
Scenario: Validation error on an empty required field (design "Media control: validation error")
|
||||
Given an empty required media field
|
||||
When the user activates "Save"
|
||||
Then no request is sent
|
||||
And the box border is --ui-error with an inset 1 px --ui-error, the error line "Pick an image for Thumbnail." with circle-alert at 14 px replaces the help line, and the chip reads "Not saved"
|
||||
And the focus moves to "Pick an image" and the page scrolls to it
|
||||
And only the first field in schema order with an error takes the focus
|
||||
|
||||
Scenario: Picking clears the error
|
||||
Given the validation error is shown
|
||||
When the user picks an image
|
||||
Then the error and the error border go, the chip reads "Unsaved changes" and nothing else is validated
|
||||
|
||||
Scenario: Saving (design "Media control: saving")
|
||||
Given the PUT has not answered
|
||||
Then the form is dimmed to 70 %, the thumbnail is at 60 %, the buttons take the disabled look, the thumbnail button and the buttons are not operable and the picker cannot open
|
||||
And Save reads "Saving…"
|
||||
|
||||
Scenario: Read-only after a schema change (design "Media control: read-only, type changed")
|
||||
Given the PUT answers 422 "invalid_entry: thumbnail: unknown_field" and thumbnail is a media field of the loaded type
|
||||
Then a warning alert "The fields of Events changed" reads "The Thumbnail field was changed after you opened this form. Reload the form to continue. What you typed here can't be saved against the old fields." with the action "Reload form"
|
||||
And the control looks as in Saving and cannot be operated
|
||||
And "Reload form" fetches the type and the entry again and rebuilds the form with the control from the answered media map
|
||||
And any other key keeps the copy of features/content_entry_editor.feature state 12
|
||||
|
||||
Scenario Outline: What the control shows and sends
|
||||
Given the loaded value <loaded> with media <media>
|
||||
When the user <action> and activates "Save"
|
||||
Then the control shows <shown>
|
||||
And the data holds "thumbnail": <sent>
|
||||
|
||||
Examples:
|
||||
| loaded | media | action | shown | sent |
|
||||
| null | none | saves with nothing changed | the empty state | null |
|
||||
| 7 | existing image 7 | saves with nothing changed | the filled state | 7 |
|
||||
| 7 | missing image 7 (optional) | saves with nothing changed | the missing state | 7 |
|
||||
| 7 | existing image 7 | picks image 8 | image 8 (its own library data) | 8 |
|
||||
| 7 | existing image 7 | activates Remove | the empty state | null |
|
||||
| 7 | missing image 7 | activates Clear | the empty state | null |
|
||||
| null | none | picks image 8 | image 8 | 8 |
|
||||
|
||||
Scenario: Right after a pick the control uses the picked library item
|
||||
Given the user picks "bathroom-tiles.png" (id 8, 800 × 600, 51200 bytes) from the library
|
||||
Then before any save the control shows its filename, "800 × 600 · 50 KB" and its url as the thumbnail
|
||||
And no request is made for it
|
||||
When the PUT answers 200 with media.thumbnail for image 8
|
||||
Then the control is rebuilt from the answered media map
|
||||
|
||||
Scenario: Dirty tracking of a media value
|
||||
Given an entry whose thumbnail is 7
|
||||
When the user picks image 8 then picks image 7 again
|
||||
Then the chip reads "Saved", "Save" is disabled and the leave guard does not prompt
|
||||
When the user picks image 8
|
||||
Then the chip reads "Unsaved changes" and the leave guard prompts
|
||||
|
||||
Scenario Outline: Server answers for a media field
|
||||
Given the client found no error and the PUT or POST is sent
|
||||
When the server answers <status> "<body>"
|
||||
Then <result>
|
||||
|
||||
Examples:
|
||||
| status | body | result |
|
||||
| 422 | invalid_entry: thumbnail: required| "Pick an image for Thumbnail." under the control, error border, chip "Not saved", focus on the first button |
|
||||
| 422 | invalid_entry: thumbnail: type | "Thumbnail must be an image from the library." under the control, chip "Not saved", focus on "Change image" or "Pick an image" (the image was deleted between the pick and the save) |
|
||||
| 409 | schema_incompatible | not an entry answer; never produced by the entry routes |
|
||||
|
||||
Scenario: Create payload with a media field
|
||||
Given /content/events/new with Title "Open day", Starts on 2026-11-21 and the media field left empty
|
||||
When the user activates "Save"
|
||||
Then POST /v1/content-type/events/entry is sent once with "thumbnail": null among the other keys, never absent
|
||||
And with image 8 picked it holds "thumbnail": 8, a JSON number
|
||||
|
||||
Scenario: The title of an entry whose first field is media
|
||||
Given a type whose first field is a media field
|
||||
Then the page title, the breadcrumb and the saved toast read "Entry 42" (an id is not a title) and the list card title reads the same (proposed)
|
||||
|
||||
Scenario: Layout of the generated form with a media field
|
||||
Given a boolean, a number or a date field before the media field
|
||||
Then the media field never pairs and is full width at every viewport
|
||||
|
||||
# ---------- Picker in field mode (group 10)
|
||||
|
||||
Scenario: Opening the picker from an optional field (design "Library: nothing chosen")
|
||||
Given the control holds image 1 "north-wall.jpg" and the field is optional
|
||||
When the user activates "Change image"
|
||||
Then the dialog (640 px wide on desktop, a bottom sheet on mobile) is titled "Choose an image" and has the tabs "Upload" and "Library", opening on Library
|
||||
And the search reads "Search by filename"
|
||||
And the first row of the Library tab is "No image" with "Leave this field empty."
|
||||
And the summary reads "Newest first · 48 images", the tiles are square, 4 columns on desktop and 3 on mobile, and "Load more" shows
|
||||
And the tile of image 1 carries the badge "Current" (sand fill, --rc-on-sand text, 18 px high, bottom left)
|
||||
And "Cancel" is enabled and "Use this image" is disabled (aria-disabled)
|
||||
And there is no alt text field, no variant select, and no request to /v1/image-variants
|
||||
|
||||
Scenario: Choosing an image (design "Library: image chosen")
|
||||
When the user selects a tile
|
||||
Then it gets the primary outline with a check at top right, aria-pressed is true, and a strip appears below the grid with a 56 px thumbnail, the filename and "dimensions · size"
|
||||
And the grid shrinks and "Load more" is hidden while a tile is selected, as in the article picker
|
||||
And "Use this image" is enabled
|
||||
When the user activates it
|
||||
Then the dialog closes, the value becomes that image's id, the control shows its data, the live region says "Image selected: <filename>" and the focus is on "Change image"
|
||||
|
||||
Scenario: Choosing "No image" (design "Library: No image chosen")
|
||||
When the user selects the row "No image"
|
||||
Then it takes the sand fill, the primary border and a check, with aria-pressed true, and any selected tile is released
|
||||
And the primary button reads "Remove image"
|
||||
When the user activates it
|
||||
Then the value becomes null, the live region says "Image removed from Thumbnail." and the focus is on "Pick an image"
|
||||
|
||||
Scenario: A required field has no "No image" row (design "Library: required field")
|
||||
Given the field is required
|
||||
Then the Library tab shows no "No image" row and the title is still "Choose an image"
|
||||
|
||||
Scenario: Choosing the current image again
|
||||
Given the control holds image 1
|
||||
When the user selects the tile "Current" and activates "Use this image"
|
||||
Then the dialog closes, the value does not change, the chip stays as it was, and no announcement is made (proposed)
|
||||
|
||||
Scenario: Empty library (design "Library: empty")
|
||||
Given GET /v1/media/ returns no image
|
||||
Then the picker opens on the Upload tab unless the user already chose a tab, and the Library tab reads "Your library is empty" / "Images you upload appear here, ready to use in any article or entry." with "Upload an image"
|
||||
And "Upload an image" switches to the Upload tab
|
||||
And the article picker keeps "Images you upload appear here, ready to use in any article."
|
||||
|
||||
Scenario Outline: Library failures (design "Library: failed to load")
|
||||
Given GET /v1/media/ answers <outcome>
|
||||
Then <result>
|
||||
|
||||
Examples:
|
||||
| outcome | result |
|
||||
| 500 | the error alert "Couldn't load your library" / "Check your connection and try again." with "Try again", no "No image" row |
|
||||
| a network error | the same |
|
||||
| 429 | the countdown alert of the Media page |
|
||||
| 401 | the global redirect, no local copy |
|
||||
|
||||
Scenario: Try again
|
||||
Given the failed state
|
||||
When the user activates "Try again"
|
||||
Then only GET /v1/media/ is sent again and the grid replaces the alert
|
||||
|
||||
Scenario: Loading (design "Library: loading")
|
||||
Given GET /v1/media/ has not answered
|
||||
Then after 150 ms skeleton tiles show (8 on desktop, 9 on mobile), no "No image" row shows and "Use this image" is disabled
|
||||
|
||||
Scenario: Search and no results
|
||||
When the user types "wall"
|
||||
Then the search is trimmed and debounced, the selection is released and the grid reloads
|
||||
And an empty match reads "No images match \"wall\"" with "Clear search", as in the article picker
|
||||
|
||||
Scenario: Load more
|
||||
When the user activates "Load more"
|
||||
Then the next page loads, the focus stays on the button until the new tiles arrive and then moves to the first new tile
|
||||
|
||||
Scenario: Upload tab (design "Upload tab")
|
||||
When the user opens the Upload tab
|
||||
Then the existing flow runs unchanged: "Drop an image here, or choose a file", "PNG, JPEG, WEBP, SVG · max 10 MB", crop and resize, the duplicate check "Already in your library" with "Use existing" and "Upload anyway"
|
||||
And a finished upload lands in the library and becomes the value of the field, as in the Sites and Authors pickers, then the dialog closes and the live region says "Image selected: <filename>" (to confirm, decision D5)
|
||||
|
||||
Scenario Outline: Upload failures keep the existing copy
|
||||
Given the POST /v1/media/ answers <outcome>
|
||||
Then <copy> shows and nothing is chosen
|
||||
|
||||
Examples:
|
||||
| outcome | copy |
|
||||
| 413 | That file is too large. |
|
||||
| 400 | That request wasn't valid. Check the fields and try again. |
|
||||
| 429 | Too many requests. Try again in a few minutes. |
|
||||
| 500 | Something went wrong on our side. Nothing was changed. Try again in a moment. |
|
||||
|
||||
Scenario: Cancel and Escape
|
||||
When the user activates "Cancel" or presses Esc or the close icon
|
||||
Then nothing changes and the focus returns to the button that opened the picker
|
||||
|
||||
Scenario: Tile names in field mode
|
||||
Then each tile is a button named "stone-steps.jpg, 310 KB" and "stone-steps.jpg, 310 KB, Current" for the current image, with aria-pressed, and no "used in" part
|
||||
And "No image" is a button with aria-pressed and "Use this image" uses aria-disabled while nothing is chosen
|
||||
|
||||
Scenario: The other pickers are unchanged
|
||||
Then the article picker still reads "Add an image", "Insert image", "In this article", the alt text and the variant select
|
||||
And the Sites, Authors and SEO pickers still read "Use image" with no strip and no "No image" row
|
||||
|
||||
# ---------- Entry list (group 5), minimal safe cell
|
||||
|
||||
Scenario: The Thumbnail column (design "Thumbnail column")
|
||||
Given Events with a Thumbnail media field, the columns Title, Thumbnail, Starts on and Category chosen, and the items 42 and 41 with an existing image, 40 and 37 with null, 39 with a missing image
|
||||
Then the header reads "Thumbnail" with no sort icon and no aria-sort, in a column 80 px wide
|
||||
And an existing image shows a 40 px square thumbnail from media.thumbnail_url, with 4 px above and below, so the row stays 48 px
|
||||
And a null value shows "–" in the muted colour
|
||||
And a missing image shows a dashed 40 px square with the unlink icon, role "img" and the name "Image no longer in the library"
|
||||
And the thumbnail is read from the item's own "media" object, with no extra request
|
||||
|
||||
Scenario: Tooltip of the missing placeholder
|
||||
Given a missing image in the list on desktop
|
||||
When the pointer or the focus reaches the dashed square
|
||||
Then a tooltip reads "Image no longer in the library"
|
||||
And mobile has no tooltip
|
||||
|
||||
Scenario: The mobile card (design "Thumbnail column", mobile)
|
||||
Given a 390 px viewport and the Thumbnail column chosen
|
||||
Then a 32 px thumbnail leads each card, "–" stands for none, a dashed 32 px square for a missing image, and the card keeps its 68 px minimum height
|
||||
And when no media field is among the chosen columns the card is unchanged
|
||||
|
||||
Scenario: Columns menu (design "Columns menu with Thumbnail")
|
||||
When the user opens "Columns"
|
||||
Then "Thumbnail" is listed with the image icon, its key "thumbnail", and a checkbox like the other fields
|
||||
And the summary reads "4 of 6" and on mobile "Choose up to 6 fields to show. 4 selected."
|
||||
And the cap of 6, Reset and the stored choice work as for any field
|
||||
|
||||
Scenario: Thumbnail as the first column
|
||||
Given a type whose first field is a media field
|
||||
Then the first column still links to the editor and the thumbnail is the link content
|
||||
|
||||
Scenario: Plain text of a media cell
|
||||
Given the card or a title needs the text of a media cell
|
||||
Then it reads "–" for none and never the numeric id
|
||||
|
||||
# ---------- Accessibility (media-field-changes.md section 6)
|
||||
|
||||
Scenario: Accessibility of the control
|
||||
Then the control is role="group" labelled by the field label
|
||||
And the main button (Pick an image, or Change image) carries aria-required when the field is required
|
||||
And the accessible names are "Pick an image for Thumbnail", "Change image, lime-plaster-workshop.jpg, 1600 by 900, 240 KB" (the thumbnail), "Remove image from Thumbnail", "Clear Thumbnail" and, for the placeholder, role "img" "Image no longer in the library"
|
||||
And the error text has an id, and the main button gets aria-invalid="true" and aria-describedby with the error id then the help id
|
||||
And the tab order is label, thumbnail, Change image, Remove, then help or error (two stops open the same picker, as drawn; decision D8)
|
||||
And one polite live region announces "Image selected: lime-plaster-workshop.jpg", "Image removed from Thumbnail." and "Thumbnail cleared."; nothing is announced when the picker opens
|
||||
And focus goes to the first invalid control on a refused save and, after the picker, to the places of the picker scenarios
|
||||
And mobile targets are 44 px and there is no hover on mobile
|
||||
|
||||
Scenario: Accessibility of the builder and the review
|
||||
Then the Media tile is a radio in the existing radio group, the dimmed tiles are disabled, "Can't be applied" is a text badge and not only a colour, and the disabled "Apply changes" is announced as disabled
|
||||
Reference in new issue
Block a user