149 lines
8.1 KiB
Gherkin
149 lines
8.1 KiB
Gherkin
Feature: Removing an inline image from the editor
|
|
|
|
As a verified RenewCMS owner writing an article
|
|
I want to remove an image I've already inserted into the article body,
|
|
either through an explicit delete button or by pressing Backspace/Delete
|
|
on the selected image, directly from the editor
|
|
So that a mistaken or unwanted image doesn't have to stay in the article
|
|
forever, and removing it behaves like every other destructive action in
|
|
RenewCMS: it asks for confirmation first, however the user starts it
|
|
|
|
Background:
|
|
Given docs/design/ has no board or state for removing an inline image;
|
|
this feature was added at the user's explicit request ahead of design
|
|
coverage, reusing the editor's existing visual language rather than
|
|
inventing new components: the trash icon and error color already used
|
|
by the article "Delete" button, and the ConfirmDialog component
|
|
already used by "Delete this article?" (docs/design/screens/editor.md's
|
|
"Delete: confirm" state)
|
|
And this slice changes ArticleEditor's exposed insertImage(src, alt) to
|
|
insertImage(src, alt, imageId), stamping the inserted <img> node with
|
|
the image's numeric id as a data-image-id attribute, persisted in the
|
|
saved article body HTML; EditorView passes the id it now receives from
|
|
a successful upload (features/remove_article_image.feature's change to
|
|
POST .../image's 201 body) through to insertImage
|
|
And an image already present in an article's body from before this slice
|
|
has no data-image-id attribute and so shows no delete affordance and
|
|
is not intercepted by Backspace/Delete, until it is removed and
|
|
re-added
|
|
And selecting an image node that does carry a data-image-id attribute
|
|
shows a small delete button anchored to the image's top-right corner,
|
|
icon i-lucide-trash-2, color error, matching the article Delete
|
|
button's icon and color; deselecting the image (clicking elsewhere)
|
|
hides it
|
|
And there are two equivalent triggers for the same removal flow:
|
|
activating that delete button, and pressing Backspace or Delete while
|
|
the image node is selected (a Tiptap NodeSelection on the image).
|
|
Tiptap's native node deletion on Backspace/Delete is intercepted
|
|
before it reaches the document (e.g. via a keyboard-shortcut/keydown
|
|
handler registered on the Image node or at the editor level, checked
|
|
first while an image node is selected) so the key press never removes
|
|
the node directly; instead it triggers exactly the same flow as the
|
|
button
|
|
And either trigger emits ArticleEditor's new "request-remove-image"
|
|
event with the node's numeric image id; ArticleEditor does not call
|
|
the API or show any dialog itself, mirroring how "request-image"
|
|
already only opens ImageUploader from EditorView
|
|
And EditorView reacts to "request-remove-image" by opening the existing
|
|
ConfirmDialog component with title "Remove this image?", description
|
|
"This image will be removed from the article. This can't be undone.",
|
|
confirmLabel "Remove image", cancelLabel "Cancel", pendingLabel
|
|
"Removing…", tone "danger" — the same component and tone already used
|
|
for "Delete this article?"
|
|
And ConfirmDialog's action calls DELETE /v1/article/{articleId}/image/
|
|
{imageId} through useApi (not the raw XMLHttpRequest used for upload,
|
|
since there is no progress to observe)
|
|
And on success (204) the action removes the image node from
|
|
ArticleEditor's content via a new exposed removeImage(imageId) method,
|
|
which moves useArticleDraft's state to "dirty" the same way any other
|
|
body edit does; ConfirmDialog then closes and a toast appears: color
|
|
success, icon i-lucide-circle-check, title "Image removed",
|
|
description "Save to keep this change."
|
|
And on any failure (404 for either the article or the image, 500, or a
|
|
network failure) the action throws, so ConfirmDialog shows its own
|
|
inline error "Couldn't remove the image, so it's still there. Try
|
|
again in a moment." and stays open; the image node is not removed and
|
|
the draft's dirty state is unchanged
|
|
And a 401 is handled entirely by useApi's global handling
|
|
(features/api_client_global_error_handling.feature) and is out of
|
|
scope for new scenarios here
|
|
And once the confirm dialog is open, its cancel/confirm/failure
|
|
behaviour (covered below) is identical regardless of which trigger
|
|
opened it, so those scenarios are written once, against the button
|
|
trigger, and are not duplicated per trigger
|
|
|
|
Scenario: Selecting an inserted image reveals its delete button
|
|
Given the user is on /article/12/edit with an image in the body carrying
|
|
data-image-id "10"
|
|
When the user clicks that image
|
|
Then a delete button (trash icon, error color) appears at the image's
|
|
top-right corner
|
|
When the user clicks elsewhere in the editor
|
|
Then the delete button is no longer shown
|
|
|
|
Scenario Outline: Either trigger opens the confirm dialog and sends no request yet, without letting Tiptap delete the node itself
|
|
Given the image with data-image-id "10" is selected on /article/12/edit
|
|
When the user <triggers-removal>
|
|
Then a dialog titled "Remove this image?" opens, with description "This
|
|
image will be removed from the article. This can't be undone.",
|
|
cancel "Cancel" and confirm "Remove image"
|
|
And no request has been sent to DELETE /v1/article/12/image/10
|
|
And the image node is still present in the editor's content (Tiptap's
|
|
native node deletion did not run)
|
|
|
|
Examples:
|
|
| triggers-removal |
|
|
| activates the image's delete button |
|
|
| presses Backspace with the image node selected |
|
|
| presses Delete with the image node selected |
|
|
|
|
Scenario: Cancelling the confirm dialog changes nothing
|
|
Given the confirm dialog is open for image id 10 on /article/12/edit
|
|
When the user activates "Cancel"
|
|
Then the dialog closes
|
|
And no request has been sent to DELETE /v1/article/12/image/10
|
|
And the image is still present in the editor's content
|
|
And the draft's dirty state is unchanged
|
|
|
|
Scenario: Confirming removes the image, marks the draft dirty, and shows a success toast
|
|
Given the confirm dialog is open for image id 10 on /article/12/edit,
|
|
the draft in the Saved state
|
|
When the user activates "Remove image"
|
|
Then DELETE /v1/article/12/image/10 is sent
|
|
When that request resolves 204
|
|
Then the dialog closes
|
|
And the image is removed from the editor's content
|
|
And SaveStatus reads "Unsaved changes" and the Save button becomes
|
|
enabled
|
|
And a toast appears: color success, icon i-lucide-circle-check, title
|
|
"Image removed", description "Save to keep this change."
|
|
|
|
Scenario Outline: A failed removal keeps the image and shows the dialog's inline error
|
|
Given the confirm dialog is open for image id 10 on /article/12/edit
|
|
When the user activates "Remove image"
|
|
And DELETE /v1/article/12/image/10 <failure>
|
|
Then the dialog stays open, showing the inline error "Couldn't remove
|
|
the image, so it's still there. Try again in a moment."
|
|
And the image is still present in the editor's content
|
|
And the draft's dirty state is unchanged
|
|
|
|
Examples:
|
|
| failure |
|
|
| resolves with a 404 response |
|
|
| resolves with a 500 response |
|
|
| never reaches the server |
|
|
|
|
Scenario: Confirming a removal started by Backspace/Delete behaves exactly like confirming one started by the button
|
|
Given the confirm dialog is open for image id 10 on /article/12/edit,
|
|
opened by pressing Backspace with the image node selected, the draft
|
|
in the Saved state
|
|
When the user activates "Remove image"
|
|
Then DELETE /v1/article/12/image/10 is sent
|
|
When that request resolves 204
|
|
Then the dialog closes
|
|
And the image is removed from the editor's content
|
|
And SaveStatus reads "Unsaved changes" and the Save button becomes
|
|
enabled
|
|
And a toast appears: color success, icon i-lucide-circle-check, title
|
|
"Image removed", description "Save to keep this change."
|