# 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 ``. 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`). 1. **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. 2. **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). 3. **Backspace on the selected image opens the same confirm dialog instead of deleting the node** — select the image node (NodeSelection), fire a `keydown` for `Backspace`; 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). 4. **Delete on the selected image opens the same confirm dialog instead of deleting the node** — same as step 3 but firing `keydown` for `Delete`. 5. **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. 6. **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/10` was 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, icon `i-lucide-circle-check`, title "Image removed", description "Save to keep this change." 7. **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. 8. **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) against `task dev` with a live SQLite file and the real `DELETE /v1/article/{id}/image/{imageId}` handler, since `features/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 `keydown` simulation 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.