discourse/spec/system/composer/prosemirror_uploads_spec.rb
Renato Atilio ce43d1b189
UX: better rich editor placeholder for non-image and image uploads (#39102)
Previously, all uploads in the rich editor used image placeholder nodes
— even for non-image files like PDFs. This looked broken since a blob
URL for a PDF rendered as a broken image.

This PR introduces a dedicated `upload_placeholder` inline node for
non-image uploads, rendered as a pill with the filename, upload
progress, and a cancel button. Image placeholders now also get a
progress/cancel overlay.

The entire upload lifecycle (insert → progress → resolve/cancel) is
excluded from undo history, so Ctrl+Z never leaves orphaned
placeholders. Deleting a placeholder automatically cancels the in-flight
upload, and duplicates from Option+drag or copy+paste are cleaned up via
`appendTransaction`.

It also fixes a flash of `transparent.png` on image upload completion by
resolving the cached upload URL in the same transaction as the
placeholder replacement.

<img width="275" height="109" alt="image"
src="https://github.com/user-attachments/assets/6b808094-46f9-44db-ad77-6a7714d51587"
/>

<img width="529" height="217" alt="image"
src="https://github.com/user-attachments/assets/3606f992-f6cd-4e88-863e-191c3cc72a9c"
/>

---------

Co-authored-by: chapoi <101828855+chapoi@users.noreply.github.com>
2026-05-20 16:48:51 -03:00

167 lines
5.8 KiB
Ruby
Vendored

# frozen_string_literal: true
describe "Composer - ProseMirror - Uploads" do
include_context "with prosemirror editor"
describe "image uploads" do
it "replaces the placeholder with the uploaded image without a transparent.png flash" do
open_composer
file_path = file_from_fixtures("logo.png", "images").path
attach_file("file-uploader", file_path, make_visible: true)
expect(composer).to have_no_in_progress_uploads
expect(rich).to have_css(".composer-image-node img")
expect(rich).to have_no_css("img[src*='transparent.png']")
end
it "produces correct markdown after upload completes" do
open_composer
file_path = file_from_fixtures("logo.png", "images").path
attach_file("file-uploader", file_path, make_visible: true)
expect(composer).to have_no_in_progress_uploads
expect(rich).to have_css(".composer-image-node img")
composer.toggle_rich_editor
expect(composer.composer_input.value).not_to include("blob:")
expect(composer.composer_input.value).not_to include("Uploading:")
expect(composer.composer_input.value).to match(%r{!\[.*\]\(upload://})
end
it "uploads multiple images at once" do
open_composer
file_path_1 = file_from_fixtures("logo.png", "images").path
file_path_2 = file_from_fixtures("logo.jpg", "images").path
attach_file("file-uploader", [file_path_1, file_path_2], make_visible: true)
expect(composer).to have_no_in_progress_uploads
expect(rich).to have_css(".composer-image-node img", count: 2)
end
end
describe "non-image uploads" do
before { SiteSetting.authorized_extensions += "|pdf" }
it "replaces the placeholder with a link after upload" do
open_composer
file_path = file_from_fixtures("small.pdf", "pdf").path
attach_file("file-uploader", file_path, make_visible: true)
expect(composer).to have_no_in_progress_uploads
expect(rich).to have_no_css(".upload-placeholder.--file")
expect(rich).to have_css("a[href]")
end
it "uploads mixed image and non-image files" do
open_composer
file_path_1 = file_from_fixtures("logo.png", "images").path
file_path_2 = file_from_fixtures("small.pdf", "pdf").path
attach_file("file-uploader", [file_path_1, file_path_2], make_visible: true)
expect(composer).to have_no_in_progress_uploads
expect(rich).to have_css(".composer-image-node img", count: 1)
expect(rich).to have_css("a[href]")
end
it "shows both placeholders simultaneously during mixed upload" do
open_composer
file_path_1 = file_from_fixtures("logo.png", "images").path
file_path_2 = file_from_fixtures("large.pdf", "pdf").path
cdp.with_slow_upload do
attach_file("file-uploader", [file_path_1, file_path_2], make_visible: true)
expect(composer).to have_in_progress_uploads
expect(rich).to have_css(".upload-placeholder.--image")
expect(rich).to have_css(".upload-placeholder.--file")
end
end
end
describe "upload cancellation" do
it "removes image placeholder when upload is cancelled" do
open_composer
file_path = file_from_fixtures("logo.png", "images").path
cdp.with_slow_upload do
attach_file("file-uploader", file_path, make_visible: true)
expect(composer).to have_in_progress_uploads
find("#cancel-file-upload").click
expect(composer).to have_no_in_progress_uploads
expect(rich).to have_no_css(".upload-placeholder.--image")
end
end
it "removes non-image placeholder when upload is cancelled" do
SiteSetting.authorized_extensions += "|pdf"
open_composer
file_path = file_from_fixtures("large.pdf", "pdf").path
cdp.with_slow_upload do
attach_file("file-uploader", file_path, make_visible: true)
expect(composer).to have_in_progress_uploads
find("#cancel-file-upload").click
expect(composer).to have_no_in_progress_uploads
expect(rich).to have_no_css(".upload-placeholder.--file")
end
end
it "cancels individual non-image upload via inline cancel button" do
SiteSetting.authorized_extensions += "|pdf"
open_composer
file_path = file_from_fixtures("large.pdf", "pdf").path
cdp.with_slow_upload do
attach_file("file-uploader", file_path, make_visible: true)
expect(rich).to have_css(".upload-placeholder.--file")
find(".upload-placeholder__cancel").click
expect(rich).to have_no_css(".upload-placeholder.--file")
end
end
it "cancels individual image upload via overlay cancel button" do
open_composer
file_path = file_from_fixtures("logo.png", "images").path
cdp.with_slow_upload do
attach_file("file-uploader", file_path, make_visible: true)
expect(rich).to have_css(".upload-placeholder__overlay")
find(".upload-placeholder__overlay .upload-placeholder__cancel").click
expect(rich).to have_no_css(".upload-placeholder.--image")
end
end
end
describe "progress indicator" do
it "shows progress overlay on image placeholder" do
open_composer
file_path = file_from_fixtures("logo.png", "images").path
cdp.with_slow_upload do
attach_file("file-uploader", file_path, make_visible: true)
expect(rich).to have_css(".upload-placeholder__overlay .upload-placeholder__progress")
end
end
it "shows progress on non-image placeholder" do
SiteSetting.authorized_extensions += "|pdf"
open_composer
file_path = file_from_fixtures("large.pdf", "pdf").path
cdp.with_slow_upload do
attach_file("file-uploader", file_path, make_visible: true)
expect(rich).to have_css(".upload-placeholder.--file .upload-placeholder__progress")
end
end
end
end