225 lines
12 KiB
Gherkin
225 lines
12 KiB
Gherkin
Feature: The editor's Library tab, picking an image from the media library
|
||
|
||
As a verified RenewCMS owner writing an article
|
||
I want to browse my library from the "Add an image" dialog and insert an existing image
|
||
with alt text
|
||
So that I can reuse an image without uploading it again
|
||
|
||
Background:
|
||
Given this slice is SPA only for this feature: the API from the media library slice is used
|
||
unchanged, and the Go change to remove the legacy alias is specified in
|
||
features/remove_legacy_article_image_alias.feature
|
||
And the screen is docs/design/screens/media-picker.md, drawn by docs/design/source/MediaFrame.dc.html,
|
||
inside ImageUploader (UModal "Add an image") on /article/:id/edit
|
||
And the dialog has a title "Add an image", an x to close, and two tabs under the title,
|
||
"Upload" (icon upload) and "Library" (icon images), 36 px high on desktop and 44 px on
|
||
mobile with a 2 px primary underline on the selected one; it is 640 px wide on desktop and
|
||
a bottom sheet on mobile
|
||
And the footer reads "Cancel", and on the Library tab also a primary "Insert image" that is
|
||
disabled until an image is selected
|
||
And the Library tab calls GET /v1/media/?q=Q&page=N&size=25 through useApi, with q only while a
|
||
search is set and page 1, then 2, 3, ... when loading more (no filter and no sort parameter),
|
||
and the Upload tab is specified in features/editor_image_upload.feature
|
||
And the response is { items, total, page, size, counts }, newest first; "Load more" shows while
|
||
the loaded count is below the response total (the matching count, not counts.all); sizes read "8 KB" and
|
||
"3.9 MB", dimensions read "2400 × 1600" (an SVG reads "Vector"), dates are absolute
|
||
And the 12-image fixture, ids 1 to 12, is the one in features/media_browse.feature, and the
|
||
picker mocks use total 48 and counts { all: 48, unused: 2, duplicates: 2 }
|
||
And this slice is amended by features/media_pagination_api.feature: cursor and limit are gone,
|
||
LIBRARY_PAGE_LIMIT 24 became the page size 25, and a mock with items equal to its total is the last page
|
||
And an image is "in this article" when an image node with its id as data-image-id is in
|
||
the editor's current content, unsaved insertions included, when the dialog opens;
|
||
in the examples below images 1 (north-wall.jpg) and 4 (logo-renew.svg) are in the article
|
||
And the search field reads "Search by filename" (32 px high on desktop, 44 px with 16 px
|
||
text on mobile), with a search icon and, once it has text, an x that clears it
|
||
And the grid has 4 columns on desktop and 3 on mobile with a 12 px gap (8 px between
|
||
columns on mobile), square tiles with a 1 px border, and the filename under each tile in
|
||
12 px toned text with an ellipsis; it scrolls inside the dialog, 300 px high with nothing
|
||
selected, 190 px on desktop and 120 px on mobile with one selected
|
||
And every tile is one button whose accessible name reads like the Media page's, for example
|
||
"north-wall.jpg, 3.9 MB, used in 2 articles" or "window-frame.jpg, 1.8 MB, unused"
|
||
And skeleton tiles show only after 150 ms: 8 on desktop and 9 on mobile for a first load
|
||
or a search, one row (4 or 3) at the end for "Load more"
|
||
And a 401 or 403 on any of these calls is handled globally by useApi, with no local copy
|
||
|
||
Rule: Which tab opens
|
||
|
||
Scenario: The library has images, so Library opens
|
||
Given GET /v1/media/?page=1&size=25 resolves 200 with the 12-image fixture
|
||
When the user activates the toolbar's "Image" button
|
||
Then the Library tab is selected and the footer reads "Cancel" and a disabled "Insert image"
|
||
And nothing is drawn in the grid for the first 150 ms, then 8 skeleton tiles show until the response arrives
|
||
|
||
Scenario: The library is empty, so Upload opens
|
||
Given GET /v1/media/?page=1&size=25 resolves 200 with no item and no search set
|
||
When the dialog opens
|
||
Then the Upload tab is selected, in its idle state
|
||
When the user selects the Library tab
|
||
Then the empty panel shows
|
||
|
||
Scenario: A dropped file opens Upload and sends no list request
|
||
When the user drops "before.png" onto the editor
|
||
Then the Upload tab is selected and no GET /v1/media/?page=1&size=25 is sent
|
||
When the user selects the Library tab
|
||
Then GET /v1/media/?page=1&size=25 is sent
|
||
|
||
Rule: Browsing
|
||
|
||
Scenario: Library, nothing selected
|
||
Given the first page resolved with the 12-image fixture
|
||
Then the line "Newest first · 48 images" shows under the search field
|
||
And 12 tiles show in fixture order, 4 per row on desktop and 3 on mobile, each with its filename
|
||
And the tiles of "north-wall.jpg" and "logo-renew.svg" carry a sand "In this article" tag (bottom-left, 18 px high, 11 px text)
|
||
And no other tile carries it
|
||
And "Load more" shows below the grid, and "Insert image" is disabled
|
||
|
||
Scenario: A search is active, so the count is not shown
|
||
Given the user typed "wall" and the response resolved with 2 items
|
||
Then the line reads "Newest first"
|
||
|
||
Scenario: Load more is hidden on the last page
|
||
Given the loaded items equal the response total (for example total 12 with the 12-image fixture)
|
||
Then "Load more" is not shown
|
||
|
||
Scenario Outline: A tile's accessible name
|
||
Given the first page resolved with the 12-image fixture
|
||
Then the tile for "<filename>" is named "<name>"
|
||
|
||
Examples:
|
||
| filename | name |
|
||
| north-wall.jpg | north-wall.jpg, 3.9 MB, used in 2 articles |
|
||
| kitchen-before.webp | kitchen-before.webp, 1.1 MB, used in 1 article |
|
||
| window-frame.jpg | window-frame.jpg, 1.8 MB, unused |
|
||
|
||
Rule: Selection
|
||
|
||
Scenario Outline: A tile is selected by pointer or keyboard
|
||
Given the first page resolved with the 12-image fixture
|
||
When the user <action> on the tile of "kitchen-after.webp"
|
||
Then that tile has a 2 px primary outline (2 px offset) and a 20 px primary check at its top-right
|
||
And "Insert image" is enabled
|
||
|
||
Examples:
|
||
| action |
|
||
| clicks |
|
||
| presses Enter |
|
||
| presses Space |
|
||
|
||
Scenario: The selection panel
|
||
Given "kitchen-after.webp" is selected
|
||
Then the grid shrinks and a panel shows below it, under a 1 px top border
|
||
And the panel has a 56 px thumbnail, the filename "kitchen-after.webp", "2400 × 1600 · 1.2 MB", and a field "Alt text (optional)"
|
||
And the field's placeholder reads "e.g. North wall of the kitchen before plastering"
|
||
And its help reads "Describes the image for people who use a screen reader, and shows if it doesn't load. Leave it empty if the image is only decoration."
|
||
And "Load more" is hidden while an image is selected
|
||
|
||
Scenario: Selection is single
|
||
Given "kitchen-after.webp" is selected and the alt text reads "A kitchen"
|
||
When the user selects "window-frame.jpg"
|
||
Then only "window-frame.jpg" is selected and the alt text field is empty
|
||
|
||
Scenario: Activating the selected tile again keeps it selected
|
||
Given "kitchen-after.webp" is selected
|
||
When the user activates its tile
|
||
Then it stays selected
|
||
|
||
Scenario: An SVG's panel reads "Vector"
|
||
When the user selects "logo-renew.svg"
|
||
Then the panel reads "Vector · 8 KB"
|
||
|
||
Scenario: An image already in the article can be selected and inserted again
|
||
When the user selects "north-wall.jpg", which carries "In this article"
|
||
Then "Insert image" is enabled
|
||
|
||
Rule: Insert
|
||
|
||
Scenario: Insert with alt text
|
||
Given the user is on /article/12/edit in the Saved state with "kitchen-after.webp" (id 2, url "/static/uploadedImages/2.webp") selected
|
||
And the alt text reads "A kitchen with new oak worktops and white tiles"
|
||
When the user activates "Insert image"
|
||
Then insertImage is called with "/static/uploadedImages/2.webp", "A kitchen with new oak worktops and white tiles" and image id 2
|
||
And the image is in the editor's content at the cursor, with data-image-id 2
|
||
And SaveStatus reads "Unsaved changes" and Save is enabled
|
||
And a toast appears: color success, icon i-lucide-circle-check, title "Image added", description "Save to keep it in the article."
|
||
And the dialog closes at once
|
||
And no POST, PATCH or DELETE has been sent
|
||
|
||
Scenario: Empty alt text inserts a decorative image
|
||
Given "kitchen-after.webp" is selected and the alt text is empty
|
||
When the user activates "Insert image"
|
||
Then insertImage is called with alt ""
|
||
|
||
Scenario: Alt text lives only in the article
|
||
Given "kitchen-after.webp" is selected with alt text "A kitchen"
|
||
When the user activates "Insert image"
|
||
Then no request carries the alt text and the library item is unchanged
|
||
|
||
Scenario Outline: Leaving without inserting changes nothing
|
||
Given "kitchen-after.webp" is selected with alt text "A kitchen"
|
||
When the user <action>
|
||
Then the dialog closes, the editor's content and dirty state are unchanged, and no toast shows
|
||
|
||
Examples:
|
||
| action |
|
||
| activates "Cancel" |
|
||
| activates the x |
|
||
| presses Escape |
|
||
|
||
Rule: Search
|
||
|
||
Scenario: Typing searches after a debounce
|
||
Given the Library tab shows the 12-image fixture
|
||
When the user types "wall"
|
||
Then after a 120 ms debounce GET /v1/media/ is sent with q=wall, page=1 and size=25
|
||
And skeleton tiles show after 150 ms, and any selection is cleared
|
||
|
||
Scenario: The x in the field clears the search
|
||
Given the search field holds "wall" with results shown
|
||
When the user activates the x in the field
|
||
Then the field is empty and GET /v1/media/ is sent with page=1 and size=25 and no q
|
||
|
||
Scenario: No results
|
||
Given GET /v1/media/?q=wall&page=1&size=25 resolves 200 with no item
|
||
Then the search icon shows with the title `No images match "wall"`, the text "Check the spelling, or search for a shorter part of the filename." and a "Clear search" button
|
||
And the search field still shows "wall" above it, and "Insert image" is disabled
|
||
When the user activates "Clear search"
|
||
Then the field is emptied and the list is requested again with no q
|
||
|
||
Rule: Load more
|
||
|
||
Scenario: Loading more shows progress and appends
|
||
Given the first page resolved with 12 images and total 48
|
||
When the user activates "Load more"
|
||
Then GET /v1/media/ is sent with page=2 and size=25
|
||
And the button reads "Loading…" with a loader-circle and is disabled, and one row of skeleton tiles shows at the end
|
||
And focus stays on the button
|
||
When the request resolves 200 with 12 more images
|
||
Then the new tiles are appended after the first 12, and focus moves to the first new tile
|
||
|
||
Scenario: The next page and the search combine
|
||
Given the search "wall" is active and the response total is above the loaded count
|
||
When the user activates "Load more"
|
||
Then GET /v1/media/ is sent with q=wall, page=2 and size=25
|
||
|
||
Rule: Empty and failed
|
||
|
||
Scenario: Empty library
|
||
Given the Library tab is selected and GET /v1/media/?page=1&size=25 resolved with no item
|
||
Then the images icon shows with "Your library is empty", "Images you upload appear here, ready to use in any article." and a primary "Upload an image"
|
||
And "Insert image" is disabled
|
||
When the user activates "Upload an image"
|
||
Then the Upload tab is selected
|
||
|
||
Scenario Outline: The library fails to load
|
||
Given GET /v1/media/ <failure>
|
||
When the Library tab loads
|
||
Then an error alert shows in the dialog: icon circle-x, title "Couldn't load your library", text "Check your connection and try again.", and a "Try again" button
|
||
And "Insert image" is disabled
|
||
When the user activates "Try again"
|
||
Then the same request is sent again with a skeleton, and on 200 the tiles replace the alert
|
||
|
||
Examples:
|
||
| failure |
|
||
| resolves with a 500 response |
|
||
| never reaches the server |
|