Files
RenewCMS/features/editor_image_removal.qa.md
T

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).

  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.