0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-09 21:45:25 +08:00
discourse/spec/system/composer/prosemirror_toolbar_spec.rb
Jarek Radosz d1894ab17f
DEV: Fix prosemirror toolbar spec flakes (#40093)
Wait for the correct toolbar state before moving on. (reflects the
approach of the previous spec)
2026-05-16 18:46:10 +02:00

312 lines
10 KiB
Ruby
Vendored

# frozen_string_literal: true
describe "Composer - ProseMirror - Toolbar" do
include_context "with prosemirror editor"
describe "toolbar state updates" do
it "updates the toolbar state following the cursor position" do
open_composer
expect(page).to have_css(".toolbar__button.bold.--active", count: 0)
expect(page).to have_css(".toolbar__button.italic.--active", count: 0)
expect(page).to have_css(".toolbar__button.heading.--active", count: 0)
expect(page).to have_css(".toolbar__button.link.--active", count: 0)
expect(page).to have_css(".toolbar__button.list.--active", count: 0)
expect(page).to have_css(".toolbar__button.code.--active", count: 0)
expect(page).to have_css(".toolbar__button.blockquote.--active", count: 0)
composer.type_content("> - [***many `styles`***](https://example.com)")
composer.send_keys(:left, :left)
expect(page).to have_css(".toolbar__button.bold.--active", count: 1)
expect(page).to have_css(".toolbar__button.italic.--active", count: 1)
expect(page).to have_css(".toolbar__button.link.--active", count: 1)
expect(page).to have_css(".toolbar__button.list.--active", count: 1)
expect(page).to have_css(".toolbar__button.code.--active", count: 1)
expect(page).to have_css(".toolbar__button.blockquote.--active", count: 1)
end
end
describe "trailing paragraph" do
it "ensures there is always a trailing paragraph" do
open_composer
expect(rich).to have_css("p", count: 1)
composer.type_content("This is a test")
expect(rich).to have_css("p", count: 1)
expect(rich).to have_css("p", text: "This is a test", count: 1)
composer.send_keys([SystemHelpers::PLATFORM_KEY_MODIFIER, :shift, "_"]) # Insert a horizontal rule
expect(rich).to have_css("hr", count: 1)
expect(rich).to have_css("p", count: 2) # New paragraph inserted after the ruler
end
end
describe "auto-linking/unlinking while typing" do
it "auto-links non-protocol URLs and removes the link when no longer a URL" do
open_composer
composer.type_content("www.example.com and also mid-paragraph www.example2.com")
expect(rich).to have_css("a", text: "www.example.com")
expect(rich).to have_css("a", text: "www.example2.com")
expect(rich).to have_css("a", count: 2)
composer.send_keys(:backspace)
composer.send_keys(:backspace)
expect(rich).to have_css("a", count: 1)
composer.type_content("om")
expect(rich).to have_css("a", text: "www.example2.com")
end
it "auto-links protocol URLs" do
open_composer
composer.type_content("https://example.com")
expect(rich).to have_css("a", text: "https://example.com")
composer.send_keys(:backspace)
composer.send_keys(:backspace)
expect(rich).to have_css("a", text: "https://example.c")
end
it "doesn't auto-link immediately following a `" do
open_composer
composer.type_content("`https://example.com`")
expect(rich).to have_css("code", text: "https://example.com")
expect(rich).to have_no_css("a", text: "https://example.com")
end
it "doesn't auto-link within code marks" do
open_composer
composer.type_content("`code mark`")
composer.send_keys(:left)
composer.type_content(" https://example.com")
expect(rich).to have_css("code", text: "code mark https://example.com")
expect(rich).to have_no_css("a", text: "https://example.com")
end
it "doesn't continue a <https://url> markup='autolink'" do
open_composer
composer.type_content("<https://example.com>.de")
expect(rich).to have_css("a", text: "https://example.com")
expect(rich).to have_no_css("a", text: "https://example.com.de")
composer.toggle_rich_editor
expect(composer).to have_value("<https://example.com>.de")
end
end
describe "emojis" do
it "has the only-emoji class if 1-3 emojis are 'alone'" do
open_composer
composer.type_content("> :smile: ")
expect(rich).to have_css(".only-emoji", count: 1)
composer.type_content(":P ")
expect(rich).to have_css(".only-emoji", count: 2)
composer.type_content(":D ")
expect(rich).to have_css(".only-emoji", count: 3)
composer.type_content("Hey!")
expect(rich).to have_no_css(".only-emoji")
end
it "preserves formatting marks when replacing text with emojis using :code: pattern" do
open_composer
composer.type_content("**bold :smile:**")
expect(rich).to have_css("strong img.emoji")
expect(rich).to have_css("strong", text: "bold")
composer.toggle_rich_editor
expect(composer).to have_value("**bold :smile:**")
end
it "preserves formatting marks when replacing text with emojis using text shortcuts" do
open_composer
composer.type_content("*italics :) *")
expect(rich).to have_css("em img.emoji")
expect(rich).to have_css("em", text: "italics")
composer.toggle_rich_editor
expect(composer).to have_value("*italics :slight_smile:* ")
end
it "preserves link marks when replacing text with emojis" do
open_composer
composer.type_content("[link text :heart:](https://example.com)")
expect(rich).to have_css("a img.emoji")
expect(rich).to have_css("a", text: "link text")
composer.toggle_rich_editor
expect(composer).to have_value("[link text :heart:](https://example.com)")
end
end
describe "list toolbar" do
it "shows the list dropdown on mobile", mobile: true do
open_composer
list_menu = composer.list_menu
list_menu.expand
expect(page).to have_css("[data-name='list-bullet']")
expect(page).to have_css("[data-name='list-ordered']")
end
it "can apply a bullet list from the dropdown" do
open_composer
composer.type_content("A list item")
list_menu = composer.list_menu
list_menu.expand
list_menu.option("[data-name='list-bullet']").click
expect(rich).to have_css("ul li", text: "A list item")
end
it "can apply an ordered list from the dropdown" do
open_composer
composer.type_content("A list item")
list_menu = composer.list_menu
list_menu.expand
list_menu.option("[data-name='list-ordered']").click
expect(rich).to have_css("ol li", text: "A list item")
end
it "splits multi-line plain text into bullet list items" do
cdp.allow_clipboard
open_composer
cdp.copy_paste("apple\nbanana\ncoconut")
composer.select_all
list_menu = composer.list_menu
list_menu.expand
list_menu.option("[data-name='list-bullet']").click
expect(rich).to have_css("ul li", count: 3)
expect(rich).to have_css("ul li", text: "apple")
expect(rich).to have_css("ul li", text: "banana")
expect(rich).to have_css("ul li", text: "coconut")
end
it "splits multi-line plain text into ordered list items" do
cdp.allow_clipboard
open_composer
cdp.copy_paste("apple\nbanana\ncoconut")
composer.select_all
list_menu = composer.list_menu
list_menu.expand
list_menu.option("[data-name='list-ordered']").click
expect(rich).to have_css("ol li", count: 3)
expect(rich).to have_css("ol li", text: "apple")
expect(rich).to have_css("ol li", text: "banana")
expect(rich).to have_css("ol li", text: "coconut")
end
end
describe "heading toolbar" do
it "updates toolbar active state and icon based on current heading level" do
open_composer
composer.type_content("## This is a test\n#### And this is another test")
expect(page).to have_css(".toolbar__button.heading.--active", count: 1)
expect(find(".toolbar__button.heading")).to have_css(".d-icon-discourse-h4")
composer.send_keys(:up)
expect(page).to have_css(".toolbar__button.heading.--active", count: 1)
expect(find(".toolbar__button.heading")).to have_css(".d-icon-discourse-h2")
composer.select_all
expect(page).to have_no_css(".toolbar__button.heading.--active")
expect(find(".toolbar__button.heading")).to have_css(".d-icon-discourse-text")
end
it "puts a check next to current heading level in toolbar dropdown, or no check if multiple formats are selected" do
open_composer
composer.type_content("## This is a test\n#### And this is another test")
heading_menu = composer.heading_menu
heading_menu.expand
expect(heading_menu.option("[data-name='heading-4']")).to have_css(".d-icon-check")
heading_menu.collapse
composer.select_range_rich_editor(0, 0)
expect(find(".toolbar__button.heading")).to have_css(".d-icon-discourse-h2")
heading_menu.expand
expect(heading_menu.option("[data-name='heading-2']")).to have_css(".d-icon-check")
heading_menu.collapse
composer.select_all
expect(page).to have_no_css(".toolbar__button.heading.--active")
heading_menu.expand
expect(heading_menu.option("[data-name='heading-2']")).to have_no_css(".d-icon-check")
expect(heading_menu.option("[data-name='heading-4']")).to have_no_css(".d-icon-check")
end
it "can change heading level or reset to paragraph" do
open_composer
composer.type_content("This is a test")
heading_menu = composer.heading_menu
heading_menu.expand
heading_menu.option("[data-name='heading-2']").click
expect(rich).to have_css("h2", text: "This is a test")
heading_menu.expand
heading_menu.option("[data-name='heading-3']").click
expect(rich).to have_css("h3", text: "This is a test")
heading_menu.expand
heading_menu.option("[data-name='heading-paragraph']").click
expect(rich).to have_css("p", text: "This is a test")
end
it "can insert a heading on an empty line" do
open_composer
heading_menu = composer.heading_menu
heading_menu.expand
heading_menu.option("[data-name='heading-2']").click
composer.type_content("This is a test")
expect(rich).to have_css("h2", text: "This is a test")
end
end
end