Files
RenewCMS/features/editor_image_upload.feature
T

137 lines
8.3 KiB
Gherkin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Feature: Wiring the editor's image upload flow
As a verified RenewCMS owner writing an article
I want the editor's image button (and dropping a file onto the editor) to open an upload
modal that validates the file, shows progress, and inserts the result at my cursor
So that I can add images to an article's body without leaving the editor, with clear
feedback when a file is rejected or the upload fails
Background:
Given EditorView.vue listens for ArticleEditor's "request-image" event and, on it, opens
ImageUploader (a UModal titled "Add an image") with the current article's id
And dropping a file directly onto the editor surface also opens ImageUploader, with that
file already provided to it instead of an empty idle state
And ImageUploader validates a chosen or dropped file in the browser, checking both its MIME
type and its file extension against png/jpeg/webp/svg, and that its size is at most
10 × 1024 × 1024 bytes, before sending anything
And a file that passes validation is uploaded via a new useImageUpload composable, using
XMLHttpRequest (not useApi) so upload progress is observable, as multipart/form-data with
the file under the field name "file", to POST /v1/article/{articleId}/image, per
features/upload_article_image.feature's already-implemented and already-mounted contract
And a 201 response's {"location": "..."} is inserted into ArticleEditor's content at the
current cursor position via an exposed insertImage(src, alt) method, with alt ""; this
moves useArticleDraft's state to "dirty" the same way any other body edit does
And closing the modal while an upload is in flight aborts the XMLHttpRequest
And a 401 response is handled entirely by useApi's global handling
(features/api_client_global_error_handling.feature) and is out of scope for new scenarios
here; the same is true of a 404 (the article was deleted elsewhere), which the editor's
existing not-found handling already covers (features/editor_core.feature)
Scenario: Opening the uploader from the toolbar's image button shows the idle state
Given the user is on /article/12/edit with the editor focused
When the user activates the toolbar's "Image" button
Then ImageUploader opens titled "Add an image"
And it shows "Drop an image here", a "Choose file" action, and the help text
"PNG, JPEG, WEBP, SVG · max 10 MB"
And its cancel action reads "Cancel"
And no request has been sent
Scenario: Dragging a file over the editor surface shows the dragging-over state
Given ImageUploader is open in the idle state
When the user drags a file over the drop zone
Then the drop zone reads "Drop to upload"
Scenario: Dropping a valid file onto the editor opens the uploader already uploading it
Given the user is on /article/12/edit with the editor focused and ImageUploader is not open
When the user drops a file named "before.png", content type "image/png", size 3.9 MB, onto
the editor surface
Then ImageUploader opens titled "Add an image"
And POST /v1/article/12/image is sent with that file under the field name "file"
And the uploading state is shown, not the idle state
Scenario Outline: Choosing a file the browser can reject never sends a request
Given ImageUploader is open in the idle state for article 12
When the user chooses a file named "<filename>", content type "<content_type>", size <size>
Then no request is sent to POST /v1/article/12/image
And the rejected state shows the title "\"<filename>\" can't be used"
And its text reads "<description>"
And its confirm action reads "Choose another file" and its cancel action reads "Cancel"
Examples:
| filename | content_type | size | description |
| plan.pdf | application/pdf | 1.2 MB | Use a PNG, JPEG, WEBP or SVG image, up to 10 MB. |
| disguised.png | application/pdf | 1.2 MB | Use a PNG, JPEG, WEBP or SVG image, up to 10 MB. |
| north-wall.tif | image/png | 14.2 MB | north-wall.tif is 14.2 MB. Images can be up to 10 MB. |
Scenario: "Choose another file" from the rejected state returns to idle
Given ImageUploader is in the rejected state for a file named "plan.pdf"
When the user activates "Choose another file"
Then the idle state is shown again, with the file picker available
Scenario: A valid upload shows progress and its cancel action reads "Cancel upload"
Given ImageUploader is open in the idle state for article 12
When the user chooses a file named "north-wall.jpg", content type "image/jpeg", size 3.9 MB
Then POST /v1/article/12/image is sent with that file under the field name "file"
And the uploading state shows "north-wall.jpg 0%" and "Uploading 0.0 of 3.9 MB"
And its cancel action reads "Cancel upload"
When the upload reports 2.4 MB sent of 3.9 MB
Then the uploading state shows "north-wall.jpg 62%" and "Uploading 2.4 of 3.9 MB"
Scenario: Closing the dialog during an upload cancels it
Given ImageUploader is uploading a file named "north-wall.jpg" to article 12
When the user activates "Cancel upload" (or otherwise closes the modal)
Then the in-flight XMLHttpRequest is aborted
And the modal closes
And the editor's body is unchanged and remains in its previous dirty/clean state
Scenario: A successful upload inserts the image at the cursor and marks the draft dirty
Given the user is on /article/12/edit in the Saved state and ImageUploader is uploading a
file named "north-wall.jpg" to article 12
When POST /v1/article/12/image resolves 201 with {"location": "/static/uploadedImages/north-wall.jpg"}
Then the uploading state briefly shows "Image added"
And the image at "/static/uploadedImages/north-wall.jpg" is inserted into the editor's
content at the cursor via insertImage
And SaveStatus reads "Unsaved changes" and the Save button becomes 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 modal closes 600 ms after the response resolves
Scenario Outline: A server-side upload failure keeps the article unchanged and offers a retry
Given ImageUploader is uploading a file named "north-wall.jpg" to article 12
When POST /v1/article/12/image <failure>
Then the failed state shows the title "Upload failed"
And its text reads "The server didn't accept the file. Your article hasn't changed."
And its confirm action reads "Try again"
And the editor's body is unchanged, and no image is inserted
Examples:
| failure |
| resolves with a 500 response |
| never reaches the server |
Scenario: "Try again" re-attempts the upload with the same file
Given ImageUploader is in the failed state for a file named "north-wall.jpg", article 12,
after a 500 response
When the user activates "Try again"
Then POST /v1/article/12/image is sent again with that same file under the field name "file"
And the uploading state is shown
Scenario: A server 400 for a type the client should have caught shows the same rejected copy
Given ImageUploader is uploading a file named "notes.pdf" to article 12 (a type that
defensively passed the browser's own check, e.g. a MIME type the browser misreported)
When POST /v1/article/12/image resolves 400
Then the rejected state shows the title "\"notes.pdf\" can't be used"
And its text reads "Use a PNG, JPEG, WEBP or SVG image, up to 10 MB."
And its confirm action reads "Choose another file"
And the editor's body is unchanged, and no image is inserted
Scenario: A server 400 for a size the client should have caught shows the same size-named copy
Given ImageUploader is uploading a file named "north-wall.tif" to article 12 (a file that
defensively passed the browser's own size check somehow, e.g. its reported size changed
between selection and send)
When POST /v1/article/12/image resolves 400
Then the rejected state shows the title "\"north-wall.tif\" can't be used"
And its text reads "north-wall.tif is 14.2 MB. Images can be up to 10 MB."
And its confirm action reads "Choose another file"
And the editor's body is unchanged, and no image is inserted