Files
RenewCMS/features/media_field_library.feature
T

297 lines
20 KiB
Gherkin

Feature: Media field usage in the Media library
As an owner, admin or verified user managing the media library
I want to see which entries use an image and be warned before deleting it
So that I know what an image is used for and what a delete will break
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 not touched
And the design is docs/design/media-field-changes.md section 3.8 and section 4, with the board group "11 Media library: usage" (4 states) of "RenewCMS Screens Schema.dc.html", which draws MediaFrame.dc.html with entries counted as uses
And GET /v1/media/ items carry "used_in", which counts articles, authors, sites and entries together, and no split
And GET /v1/media/{id}/usage answers an array ordered articles, authors, sites, then entries by content type name then entry id, with no total and no "first few" wrapper
And an article item is {"type":"article","id","title","published","fields"}
And an entry item is {"type":"entry","id","fields","content_type_key","content_type_name","first_field":{"key","type","value"}} with no title and no published, and first_field absent when the type has no first field
And the SPA keeps the first 3 entry items, counts the rest, and derives the entry title from first_field with the title rules of the entry list (see "Entry title in the usage lists")
And deleting a used image stays allowed; entries keep the id and show "This image is no longer in the library"
And the usage badge, panel and dialog never branch on the role; the Media page is the same for every user who reaches it
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 decisions D1 to D12 of this slice are listed in the QA procedure header and in the conversation
And out of scope: filtering the library by media-field use, the read-only view of a custom type, any change to the Go API
Scenario Outline: The label of the usage badge
Given an image with used_in <used_in>
Then the badge reads "<badge>"
Examples:
| used_in | badge |
| 0 | Unused |
| 1 | Used in 1 |
| 3 | Used in 3 |
| 7 | Used in 7 |
Scenario: The badge counts articles and entries together
Given bathroom-tiles.png has used_in 3 (0 articles, 3 entries) and lime-plaster.jpg has used_in 7 (2 articles, 5 entries)
Then the grid card, the list row and the mobile card of bathroom-tiles.png read "Used in 3" and those of lime-plaster.jpg read "Used in 7"
And the badge is the outline kind of MediaBadge, without an icon, as today
Scenario: Tooltip for entries only (design "Grid: usage badge tooltip")
Given bathroom-tiles.png has 0 articles and 3 entries, and a desktop viewport
When the pointer rests on the badge "Used in 3"
Then GET /v1/media/10/usage is sent once
And a tooltip (--ui-bg-inverted, --ui-text-inverted, 12/18, 6 px 8 px padding, below the badge, left aligned) reads on one line "Used in entries: 3"
Scenario: Tooltip for articles and entries
Given lime-plaster.jpg has 2 articles and 5 entries, and a desktop viewport
When the pointer rests on the badge "Used in 7"
Then the tooltip reads "Used in articles: 2" on the first line and "Used in entries: 5" on the second
Scenario: Tooltip for articles only
Given north-wall.jpg has 2 articles and 0 entries, and a desktop viewport
When the pointer rests on the badge "Used in 2"
Then the tooltip reads "Used in articles: 2" and no "Used in entries" line shows
Scenario: Tooltip opens on keyboard focus
Given a desktop viewport
When the keyboard focus reaches the card (grid) of an image that is used
Then the same tooltip opens, and it closes on blur or Escape without closing anything else
And the focus ring of the card is unchanged
Scenario: The usage is fetched once per image
Given the tooltip of image 10 was opened once
When the user hovers it again, or opens its detail panel
Then no second GET /v1/media/10/usage is sent while the list is not reloaded
And a reload of the list (search, filter, sort, page, retry) clears the cached usage
Scenario: The tooltip waits for the usage
Given GET /v1/media/10/usage has not answered
Then no tooltip shows and the badge keeps its label
When it answers and the pointer or focus is still on the badge
Then the tooltip opens
Scenario Outline: The tooltip never shows without a useful split
Given the pointer rests on the badge of an image and GET /v1/media/{id}/usage <outcome>
Then no tooltip shows and nothing else changes
Examples:
| outcome |
| answers 500 |
| fails on the network |
| answers 404 |
| answers only authors and sites |
Scenario: No tooltip on mobile
Given a 390 px viewport
Then no tooltip opens on tap and the badge is read inside the card as "Used in 3"
Scenario: No tooltip on an unused image
Given an image with used_in 0
Then the badge "Unused" has no tooltip and no usage request is sent for it
Scenario Outline: The accessible name of a card (proposed copy)
Given an image "north-wall.jpg", 3.9 MB, with used_in <used_in>
Then the card button is named "<name>"
Examples:
| used_in | name |
| 0 | north-wall.jpg, 3.9 MB, unused |
| 1 | north-wall.jpg, 3.9 MB, used in 1 place |
| 7 | north-wall.jpg, 3.9 MB, used in 7 places |
Scenario: The list table column (design "List view: usage badge", written spec 3.8)
Given a desktop viewport and the list view
Then the "Used in" column of a used image is the underlined button "Used in 3" that opens the detail panel, as the "N articles" button does today
And an image with used_in 0 shows the "Unused" badge
And the mobile card shows the "Used in 3" badge and the existing meta line
Scenario: Panel for an image used by articles and entries (design "Detail panel: used in entries")
Given lime-plaster.jpg has 2 articles and 5 entries, whose first three entries are Events "Lime plaster workshop", Events "Heritage stone walk" and Events "Winter open day"
When the user opens its panel
Then the preview is 16:10 on desktop and 16:3.6 on mobile only when usage holds at least one entry, and 16:10 and 4:3 otherwise
And the section "Used in 2 articles" lists the two articles with title, Online or Draft and the arrow-up-right icon, as today, with the note "Counted from the text of your articles each time you open this page."
And below it the section "Used in entries" (600 14/22, 8 px gap) holds a bordered list of 3 rows, then the row "and 2 more" in the muted colour, with a muted top border and no link
And each entry row is 36 px high on desktop and 48 px on mobile and holds, left to right, the content type name in 12 px muted (52 px wide, one line, ellipsis), the entry title as a link in weight 500 underlined (one line, ellipsis) and the arrow-up-right icon (14 px, muted)
And the link of a row goes to /content/events/<id> in the same tab
Scenario Outline: Counts in the "Used in entries" section
Given an image whose usage holds <entries> entry items
Then the section shows <rows> rows and <more>
Examples:
| entries | rows | more |
| 1 | 1 | no "and N more" row |
| 3 | 3 | no "and N more" row |
| 4 | 3 | the row "and 1 more" |
| 5 | 3 | the row "and 2 more" |
| 120 | 3 | the row "and 117 more" |
Scenario: Entries only, no article
Given bathroom-tiles.png has 3 entries and 0 articles
When the user opens its panel
Then the articles heading reads "Not used in any article"
And the line under it reads "No article shows it." and never "This image is safe to delete."
And the section "Used in entries" follows
Scenario: Articles only
Given north-wall.jpg has 2 articles and 0 entries
Then the panel is unchanged from before this slice and shows no "Used in entries" section
Scenario: Nothing uses the image
Given window-frame.jpg has used_in 0
Then the panel reads "Not used in any article" and "This image is safe to delete. No article shows it."
And no usage request is sent
Scenario: Authors and sites only (decision D7)
Given an image with used_in 2 and a usage that holds one author and one site
Then the articles heading reads "Not used in any article"
And the line reads "No article shows it." and no "safe to delete" claim is made
And no authors or sites are listed
Scenario: The articles heading counts articles only
Given lime-plaster.jpg has used_in 7 (2 articles, 5 entries)
Then the heading reads "Used in 2 articles" and never "Used in 7 articles"
Scenario: Entry link keyboard and focus
Given the panel of lime-plaster.jpg is open
Then Tab reaches the articles links, then the entry links, in reading order
And an entry link has the 2 px --ui-primary focus ring with a 2 px offset
And "and 2 more" is not a stop
Scenario: Usage loading in the panel (decision D6)
Given GET /v1/media/11/usage has not answered
Then after 150 ms a skeleton of three rows shows under the heading "Used in" (proposed copy)
And before 150 ms nothing shows in the section
And Copy URL, Rename and Delete stay enabled
When it answers
Then the real sections replace the skeleton
Scenario Outline: Usage failure in the panel (decision D5)
Given the panel of an image with used_in 7 is opened
When GET /v1/media/{id}/usage <outcome>
Then <result>
Examples:
| outcome | result |
| answers 500 | the section reads "Couldn't load where this image is used." (proposed copy) with the ghost button "Try again", and Delete stays enabled |
| fails on the network | the same |
| answers 429 | the same |
| answers 404 | the existing gone flow: the panel closes and "That image no longer exists" shows |
| answers 401 | the global redirect, no local copy |
Scenario: Try again in the panel
Given the usage failure is shown
When the user activates "Try again"
Then only GET /v1/media/{id}/usage is sent again and the sections replace the message
Scenario: Switching images while the usage is loading
Given the panel of image 10 is waiting for its usage
When the user opens image 11
Then the answer of image 10 is ignored and the panel shows only image 11 data
Scenario Outline: Entry title from first_field
Given an entry item with id 42 and first_field {"key":"title","type":"<type>","value":<value>}
Then the row title reads "<title>"
Examples:
| type | value | title |
| text | "Lime plaster workshop" | Lime plaster workshop |
| text | "" | Entry 42 |
| text | null | Entry 42 |
| richtext | "## Heritage walk\n\nMeet at 9" | the first line as richTextFirstLine gives it (not the Markdown marks of the whole body) |
| richtext | "" | Entry 42 |
| number | 1500 | 1,500 (as the entry list formats a number) |
| number | 0 | 0 |
| boolean | true | Yes |
| boolean | false | No |
| date | "2026-11-21" | the entry list date format of 2026-11-21 |
| select | "Workshop" | Workshop |
| media | 7 | Entry 42 |
| media | null | Entry 42 |
Scenario: first_field is absent or unknown
Given an entry item with id 42 and no first_field, or a first_field of an unknown type
Then the row title reads "Entry 42" for no first_field and the String of the value for an unknown type, as describeCell does for the default case
Scenario: Long titles and long type names (decision D4)
Given an entry title of 120 characters and the type name "Press mentions and partner coverage"
Then both stay on one line with an ellipsis, the row height does not change, the title link keeps the full title as its accessible name and a native title attribute, and "and N more" is unaffected
Scenario: The order of the three rows
Given the API returns entries ordered by content type name then entry id
Then the three kept rows keep that order and the SPA does not sort again
Scenario: Delete an image used by entries and articles (design "Delete: image used by entries")
Given lime-plaster.jpg has 2 articles and 5 entries and the user activates Delete in its panel
Then the confirm dialog (ConfirmDialog, tone danger) titled "Delete an image that's in use?" opens
And its description reads "\"lime-plaster.jpg\" is used in 5 entries and 2 articles. Those entry fields will show \"This image is no longer in the library\", and the articles will show a broken image. This can't be undone."
And the body holds, in this order, the bordered "Used in entries" list (heading "Used in entries", 3 rows, then "and 2 more") and the bordered list of the 2 articles with title and Online or Draft
And the buttons are "Cancel" and the destructive "Delete image"
And the entry rows show the type name and the title and are not links in the dialog (decision D10)
Scenario Outline: The wording by what uses the image
Given an image "lime-plaster.jpg" with <articles> articles and <entries> entries in its loaded usage
Then the title is "<title>"
And the description reads <description>
Examples:
| articles | entries | title | description |
| 2 | 5 | Delete an image that's in use? | "lime-plaster.jpg" is used in 5 entries and 2 articles. Those entry fields will show "This image is no longer in the library", and the articles will show a broken image. This can't be undone. |
| 1 | 1 | Delete an image that's in use? | "lime-plaster.jpg" is used in 1 entry and 1 article. Those entry fields will show "This image is no longer in the library", and the articles will show a broken image. This can't be undone. |
| 2 | 0 | Delete an image that's in use? | "lime-plaster.jpg" is used in 2 articles. If you delete it, they will show a broken image. This can't be undone. |
| 0 | 3 | Delete an image that's in use? | "lime-plaster.jpg" is used in 3 entries. Those entry fields will show "This image is no longer in the library". This can't be undone. (proposed copy) |
| 0 | 0 | Delete this image? | "lime-plaster.jpg" will be deleted from your library. No article uses it. This can't be undone. (used_in is 0) |
Scenario: Delete an unused image
Given window-frame.jpg has used_in 0
Then the dialog is titled "Delete this image?" with the unchanged copy, no lists and no usage request
Scenario: Delete an image used only by authors or sites
Given used_in 2 and a usage with no article and no entry
Then the existing in-use title shows with the generic fallback description of the next scenario
Scenario: The usage is not ready when the dialog opens (decision D6)
Given the user activates Delete before GET /v1/media/{id}/usage answered, or after it failed
Then the dialog is titled "Delete an image that's in use?" because used_in is above 0
And the description reads "\"lime-plaster.jpg\" is in use. Articles will show a broken image and entry fields will show \"This image is no longer in the library\". This can't be undone." (proposed copy)
And no lists show, "Delete image" is enabled, and when the usage answers while the dialog is open the dialog switches to the full copy and lists without moving the focus
Scenario: Confirm and results
Given the delete dialog is open
When the user activates "Delete image"
Then DELETE /v1/media/<id> is sent and the existing in-progress, failed, done and 404 states run unchanged
And on 204 the toast "Image deleted" / "\"lime-plaster.jpg\" is gone from your library." shows and the focus moves to the next item
Scenario: Cancel and Escape
When the user activates "Cancel" or presses Esc
Then nothing is deleted and the focus returns to the Delete button of the panel
Scenario: The variants note is kept
Given an image with cached variants and used by entries
Then the existing variant removal note still shows under the lists
Scenario: Mobile dialog
Given a 390 px viewport
Then the dialog is a bottom sheet, the entry rows are 48 px high and the buttons are 44 px high
Scenario: Bulk delete counts uses, not articles (proposed copy)
Given 3 images are selected, 2 of them used for 7 places in total (articles and entries)
Then each row keeps its badge "Used in N" or "Unused"
And the description counts "7 places" in the sentence where it said "7 articles" and reads "which will show a broken image or a missing-image notice." instead of "which will show a broken image."
And no usage request is sent for the bulk dialog
Scenario: Merge counts uses, not articles (proposed copy)
Given a merge group whose other copies have used_in 5 and 2
Then the dialog says "7 places will be updated" and "Kept · used in N places now"
And the toast after the merge keeps "N copies merged. M articles updated." from articles_updated
Scenario: Accessibility of the usage UI
Then the tooltip is exposed with aria-describedby on its trigger and uses role "tooltip"
And the entry title link is named "<title>, <content type name>" (proposed)
And "and 2 more" is plain text read in order
And the arrow-up-right icon is aria-hidden
And a skeleton or error line in the panel is inside the section so a screen reader reads it after the heading
And the delete dialog moves focus to "Cancel" as it does today, and its lists are read after the description
And mobile targets are 44 px, with rows of 48 px, and there is no hover on mobile