feat(SPA): media library indications for custom medial field type usage
This commit is contained in:
1 parent
3e7fc9a4a5
commit
0fc3511e2b
28 files changed
+2060
-131
No files matched your search
@@ -0,0 +1,296 @@
|
||||
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
|
||||
Reference in new issue
Block a user