137 lines
8.3 KiB
Gherkin
137 lines
8.3 KiB
Gherkin
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
|