5.2 KiB
QA: Removing an inline image from the editor
Automated (Vitest + Vue Test Utils, task test:web)
Mount EditorPage inside DashboardShell at /article/12/edit with the
editor_core.qa.md fixture article (id 12, "How we price a bathroom
renovation", is_online false), GET already resolved 200, and a body
containing <img src="/static/uploadedImages/before.png" data-image-id="10" alt="">. Mock useApi's request for DELETE /v1/article/12/image/10
(fetch-based, not XHR — reuse whatever mock
api_client_global_error_handling.qa.md already sets up for request).
- Selecting the image reveals the delete button — click the image node; assert a delete button (trash icon, error color) renders anchored to it; click elsewhere in the editor; assert the button is gone.
- Activating the delete button opens the confirm dialog — select the image, activate its delete button; assert a dialog titled "Remove this image?", description "This image will be removed from the article. This can't be undone.", cancel "Cancel", confirm "Remove image"; assert no request was sent yet; assert the image node is still present (Tiptap's native deletion did not run).
- Backspace on the selected image opens the same confirm dialog instead
of deleting the node — select the image node (NodeSelection), fire a
keydownforBackspace; assert the same dialog as step 2 opens; assert no request was sent yet; assert the image node is still present in the editor's document (i.e. the keydown was intercepted before Tiptap's default node-deletion ran, not just undone afterwards). - Delete on the selected image opens the same confirm dialog instead of
deleting the node — same as step 3 but firing
keydownforDelete. - Cancelling changes nothing — from the open dialog (opened via the button), click "Cancel"; assert the dialog closes, no request was sent, the image node is still in the editor's content, and SaveStatus/dirty state is unchanged.
- Confirming removes the image, marks dirty, and toasts (button
trigger) — from the open dialog (Saved state), click "Remove image";
assert a DELETE request to
/v1/article/12/image/10was made; resolve it 204; assert the dialog closes, the image node is gone from the editor's content, SaveStatus reads "Unsaved changes" and Save is enabled, and a toast appears with color success, iconi-lucide-circle-check, title "Image removed", description "Save to keep this change." - Confirming removes the image the same way when opened via Backspace/Delete — repeat step 6's assertions, but open the dialog via the Backspace keydown from step 3 instead of the button; assert the outcome (request, node removal, dirty state, toast) is identical.
- A failed removal keeps the image and shows the inline error, Examples-driven — for a 404 response, a 500 response, and a rejected/ network-failure request: from the open dialog (button trigger), click "Remove image"; resolve/reject accordingly; assert the dialog stays open showing the inline error "Couldn't remove the image, so it's still there. Try again in a moment."; assert the image node is still present and the draft's dirty state is unchanged.
Visual
No docs/design/ board or state exists for this trigger (see the feature's
Background). Per the user's explicit direction this slice ships the SPA
behaviour anyway, but task visual:check -- editor has nothing to compare
it against, so no visual capture is added for it — this is an intentional
gap, skip: no-design-coverage (distinct from skip: clock-sensitive), not
a QA oversight. If a board for this state is added to docs/design/ later,
it needs its own follow-up slice to add the visual check.
- Keyboard/focus: once a delete button is shown, it is reachable by Tab and activates on Enter/Space; pressing Backspace/Delete with the image node selected but focus elsewhere in the page does not trigger removal (only an actual Tiptap NodeSelection on the image should); ConfirmDialog's existing focus trap and cancel-button-default-focus behaviour (already covered by the article-delete flow) applies unchanged regardless of which trigger opened it.
Manual verification
- Real DELETE round trip against a running server — Vitest only mocks
useApi's request; confirm a real removal (204/404/500) againsttask devwith a live SQLite file and the realDELETE /v1/article/{id}/image/{imageId}handler, sincefeatures/remove_article_image.feature's httptest suite covers the handler in isolation but never through this SPA flow. - Real keyboard behaviour — confirm in a real browser that selecting an
image (click) and pressing Backspace/Delete opens the confirm dialog
rather than deleting the node, including with an IME or on a platform
where Backspace/Delete semantics differ, since jsdom's
keydownsimulation doesn't exercise Tiptap's real ProseMirror keymap precedence. - Design review of the delete-button/ConfirmDialog approach — since there is no board for this state, once a board is added, re-verify this implementation still matches it (or update the implementation to match); until then this is a best-effort, design-team-unreviewed UI.