6.4 KiB
QA: Crop and resize step in the editor's image uploader
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. Reuse editor_image_upload.qa.md's fake-XHR setup for the upload itself. Stub the chosen
file's decoded natural size (e.g. mock Image/createImageBitmap or whatever the component
uses to read naturalWidth/naturalHeight) so tests control it deterministically, and stub the
crop/resize step's own output producer (whatever function turns the frame rect + width preset
into a Blob) as a spy, asserting it is called with the frame's rect in natural-image pixels and
the resolved output width/height, and stubbing its returned Blob's size — without asserting how
it renders internally (canvas/OffscreenCanvas is a coder implementation detail).
- A raster file opens "Crop and resize" instead of uploading — choose
north-wall.jpg(image/jpeg, 3.9 MB, natural 4032×3024); assert no XHR is created; assert the modal title is "Crop and resize", description "north-wall.jpg · 3.9 MB", ratio "Original" and width "1600 px" selected, mono line "Uploads as 1600 × 1200 px · about 420 KB", footer "Choose another file" / "Upload image". - An SVG bypasses the step — choose
diagram.svg(image/svg+xml, 40 KB); assert an XHR to/v1/media/is created immediately and the crop step never renders. - Ratio presets lock the frame, Examples-driven (16:9 / 4:3 / 1:1) — from the opened step, select each ratio pill; assert the frame's reported rect and the mono line's output size match the feature's Examples table.
- Width preset recomputes the output — select width "800 px"; assert the mono line reads "Uploads as 800 × 600 px" (assert dimensions exactly; assert only that the size figure changed to a smaller value, not a specific number).
- A crop narrower than the width shows the info row and keeps its own size — resize the frame (via its resize API/spy) to 640×640 with ratio "1:1", width "1600 px"; assert the mono line reads "Uploads as 640 × 640 px · about {size}" and the info row reads exactly "The crop is 640 px wide, so it's uploaded at that size. Enlarging it would make it blurry."
- Free ratio removes the constraint — select ratio "Free"; assert a subsequent resize call with independent width/height is accepted without being coerced back to a ratio.
- Arrow and Shift+Arrow keys move/resize — focus the frame; fire
ArrowRight; assert the frame's x increases by 1 px and the mono line's size is unchanged; fireShift+ArrowRight; assert the frame's width increases by 1 px and the mono line updates. - Rotate cycles 90° and Reset restores defaults — activate "Rotate"; assert the reported rotation is 90°; activate "Reset"; assert ratio "Original", width "1600 px", the frame back to the whole unrotated image, and the mono line "Uploads as 1600 × 1200 px · about 420 KB".
- "Choose another file" discards the step — activate it; assert no XHR was created and the idle state renders.
- A newly chosen file reopens at defaults — from idle after "Choose another file" out of a
16:9/800px step, choose
sunset.jpg(image/jpeg, 2.1 MB, natural 3000×2000); assert the new crop step opens with ratio "Original", width "1600 px", frame as the whole new image (not the previous file's ratio/width). - "Upload image" sends the cropped/resized file — from the default 1600×1200/420 KB step,
activate "Upload image"; assert the XHR is created with a file whose declared size is 420 KB
(not 3.9 MB); assert the uploading state shows "Uploading 0.0 of 420 KB"; fire an
upload.progresswithloaded: 180 * 1024, total: 420 * 1024; assert "north-wall.jpg 43%" and "Uploading 180 of 420 KB".
Visual (manual verification via task visual:check -- editor — desktop and mobile, light and
dark)
Reference: docs/design/screens/editor.md states "Upload: crop and resize", "Upload: crop to a
ratio", "Upload: crop narrower than the width", and the updated "Upload: in progress"; board
docs/design/source/RenewCMS Screens Back-office.dc.html, group "Editor". Add each state's mock
setup to web/visual/states.ts, reusing editor_core.qa.md's fixture article (id 12, "How we
price a bathroom renovation", draft) and the sample file "north-wall.jpg · 3.9 MB".
- Upload: crop and resize — modal "Crop and resize" / "north-wall.jpg · 3.9 MB", ratio "Original", width "1600 px", cancel "Choose another file", confirm "Upload image".
- Upload: crop to a ratio — same modal, ratio "16:9" selected, frame locked to that ratio.
- Upload: crop narrower than the width — ratio "1:1", width "1600 px", frame resized to 640×640, info row "The crop is 640 px wide, so it's uploaded at that size. Enlarging it would make it blurry."
- Upload: in progress (updated) — modal "Add an image", uploading "north-wall.jpg 43%" / "Uploading 180 of 420 KB", cancel "Cancel upload".
- Mobile (all of the above at 390×844): same modal layout constraints as other editor modals.
- Keyboard/focus: the crop frame is reachable by Tab; its
aria-labelis "Crop area. Arrow keys move it, Shift and arrow keys resize it."; Arrow/Shift+Arrow move and resize it without moving focus elsewhere; the aspect-ratio and width radiogroups follow standard roving-tabindex radiogroup keyboard behavior (arrow keys between pills, one tab stop per group); "Rotate" and "Reset" are reachable and named by their visible text.- Motion: no auto-close or timed transition in this step; frame drag/resize follows pointer with no easing delay (not part of the 10 % capture budget, spot-checked only).
Manual verification
- Real image decoding and resizing — Vitest stubs
naturalWidth/naturalHeightand the output producer; confirm real JPEG/PNG/WEBP files of varying orientations and EXIF rotation crop, rotate and resize correctly againsttask devwith a live server, since jsdom has no real canvas/image pipeline. - Real POST of the resized file — confirm the actual uploaded bytes decode to the expected
pixel dimensions server-side, complementing
features/upload_article_image.feature's httptest coverage of the handler in isolation.