mirror of
https://github.com/discourse/discourse.git
synced 2025-06-06 03:06:53 +08:00

We have had multiple issues while using the chrome channel: - super slow start of the spec, I can confirm I don't have it on chromium - more risk of different failures between local dev machines and CI There was a test not working on chromium, as it doesn't have mp4 codecs out of the box. I changed the spec to use webm instead and it's now working correctly. This commit also fixes a bug with the video/trace paths which were incorrect, and also makes another test less flakey with uploads event though Im not very clear on what is going on here, we need to dig this. We now need to figure out a way to have this on the test image, but this is for now a better solution.
811 lines
25 KiB
Ruby
811 lines
25 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
describe "Composer - ProseMirror editor", type: :system do
|
|
fab!(:user) { Fabricate(:user, refresh_auto_groups: true) }
|
|
fab!(:tag)
|
|
|
|
let(:cdp) { PageObjects::CDP.new }
|
|
let(:composer) { PageObjects::Components::Composer.new }
|
|
let(:rich) { composer.rich_editor }
|
|
|
|
before do
|
|
sign_in(user)
|
|
SiteSetting.rich_editor = true
|
|
end
|
|
|
|
def open_composer_and_toggle_rich_editor
|
|
page.visit "/new-topic"
|
|
expect(composer).to be_opened
|
|
composer.toggle_rich_editor
|
|
composer.focus
|
|
end
|
|
|
|
it "hides the Composer container's preview button" do
|
|
page.visit "/new-topic"
|
|
|
|
expect(composer).to be_opened
|
|
expect(composer).to have_composer_preview_toggle
|
|
|
|
composer.toggle_rich_editor
|
|
|
|
expect(composer).to have_no_composer_preview_toggle
|
|
end
|
|
|
|
context "with autocomplete" do
|
|
it "triggers an autocomplete on mention" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("@#{user.username}")
|
|
|
|
expect(composer).to have_mention_autocomplete
|
|
end
|
|
|
|
it "triggers an autocomplete on hashtag" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("##{tag.name}")
|
|
|
|
expect(composer).to have_hashtag_autocomplete
|
|
end
|
|
|
|
it "triggers an autocomplete on emoji" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content(":smile")
|
|
|
|
expect(composer).to have_emoji_autocomplete
|
|
end
|
|
|
|
it "strips partially written emoji when using 'more' emoji modal" do
|
|
open_composer_and_toggle_rich_editor
|
|
|
|
composer.type_content("Why :repeat_single")
|
|
|
|
expect(composer).to have_emoji_autocomplete
|
|
|
|
# "more" emoji picker
|
|
composer.send_keys(:down, :enter)
|
|
find("img[data-emoji='repeat_single_button']").click
|
|
composer.toggle_rich_editor
|
|
|
|
expect(composer).to have_value("Why :repeat_single_button: ")
|
|
end
|
|
end
|
|
|
|
context "with inputRules" do
|
|
it "supports > to create a blockquote" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("> This is a blockquote")
|
|
|
|
expect(rich).to have_css("blockquote", text: "This is a blockquote")
|
|
end
|
|
|
|
it "supports n. to create an ordered list" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("1. Item 1\n5. Item 2")
|
|
|
|
expect(rich).to have_css("ol li", text: "Item 1")
|
|
expect(find("ol ol", text: "Item 2")["start"]).to eq(5)
|
|
end
|
|
|
|
it "supports *, - or + to create an unordered list" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("* Item 1\n")
|
|
composer.type_content("- Item 2\n")
|
|
composer.type_content("+ Item 3")
|
|
|
|
expect(rich).to have_css("ul ul li", count: 3)
|
|
end
|
|
|
|
it "uses 'tight' lists for both ordered and unordered lists by default" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("1. Item 1\n5. Item 2\n\n")
|
|
composer.type_content("* Item 1\n* Item 2")
|
|
expect(rich).to have_css("ol[data-tight='true']")
|
|
expect(rich).to have_css("ul[data-tight='true']")
|
|
end
|
|
|
|
it "supports ``` or 4 spaces to create a code block" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("```\nThis is a code block")
|
|
composer.send_keys(%i[shift enter])
|
|
composer.type_content(" This is a code block")
|
|
|
|
expect(rich).to have_css("pre code", text: "This is a code block", count: 2)
|
|
end
|
|
|
|
it "supports 1-6 #s to create a heading" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("# Heading 1\n")
|
|
composer.type_content("## Heading 2\n")
|
|
composer.type_content("### Heading 3\n")
|
|
composer.type_content("#### Heading 4\n")
|
|
composer.type_content("##### Heading 5\n")
|
|
composer.type_content("###### Heading 6\n")
|
|
|
|
expect(rich).to have_css("h1", text: "Heading 1")
|
|
expect(rich).to have_css("h2", text: "Heading 2")
|
|
expect(rich).to have_css("h3", text: "Heading 3")
|
|
expect(rich).to have_css("h4", text: "Heading 4")
|
|
expect(rich).to have_css("h5", text: "Heading 5")
|
|
expect(rich).to have_css("h6", text: "Heading 6")
|
|
end
|
|
|
|
it "supports _ or * to create an italic text" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("_This is italic_\n")
|
|
composer.type_content("Hey _This is italic_\n")
|
|
composer.type_content("*This is italic*\n")
|
|
composer.type_content("Hey*This is italic*\n")
|
|
|
|
expect(rich).to have_css("em", text: "This is italic", count: 4)
|
|
|
|
composer.toggle_rich_editor
|
|
|
|
expect(composer).to have_value(
|
|
"*This is italic*\n\nHey *This is italic*\n\n*This is italic*\n\nHey*This is italic*",
|
|
)
|
|
end
|
|
|
|
it "supports __ or ** to create a bold text" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("__This is bold__\n\n")
|
|
composer.type_content("**This is bold**\n\n")
|
|
composer.type_content("Hey __This is bold__\n\n")
|
|
composer.type_content("Hey**This is bold**")
|
|
|
|
expect(rich).to have_css("strong", text: "This is bold", count: 4)
|
|
|
|
composer.toggle_rich_editor
|
|
|
|
expect(composer).to have_value(
|
|
"**This is bold**\n\n**This is bold**\n\nHey **This is bold**\n\nHey**This is bold**",
|
|
)
|
|
end
|
|
|
|
it "supports ` to create a code text" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("`This is code`")
|
|
|
|
expect(rich).to have_css("code", text: "This is code")
|
|
end
|
|
|
|
it "supports typographer replacements" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content(
|
|
"foo +- bar... test???? wow!!!! x,, y-- --- a--> b<-- c-> d<- e<-> f<--> (tm) (pa)",
|
|
)
|
|
|
|
expect(rich).to have_css(
|
|
"p",
|
|
text: "foo ± bar… test??? wow!!! x, y– — a–> b←- c→ d← e←> f←→ ™ ¶",
|
|
)
|
|
end
|
|
|
|
it "supports ---, ***, ___, en-dash+hyphen, em-dash+hyphen to create a horizontal rule" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("Hey\n---There\n*** Friend\n___ How\n\u2013-are\n\u2014-you")
|
|
|
|
expect(rich).to have_css("hr", count: 5)
|
|
end
|
|
|
|
it "supports <http://example.com> to create an 'autolink'" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("<http://example.com>")
|
|
|
|
expect(rich).to have_css("a", text: "http://example.com")
|
|
|
|
composer.toggle_rich_editor
|
|
|
|
expect(composer).to have_value("<http://example.com>")
|
|
end
|
|
end
|
|
|
|
context "with oneboxing" do
|
|
let(:cdp) { PageObjects::CDP.new }
|
|
|
|
before do
|
|
def body(title)
|
|
<<~HTML
|
|
<html>
|
|
<head>
|
|
<title>#{title}</title>
|
|
<meta property="og:title" content="#{title}">
|
|
<meta property="og:description" content="This is an example site">
|
|
</head>
|
|
<body>
|
|
<h1>#{title}</h1>
|
|
<p>This domain is for use in examples.</p>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
stub_request(:head, %r{https://example\.com.*}).to_return(status: 200)
|
|
stub_request(:get, %r{https://example\.com.*}).to_return(
|
|
status: 200,
|
|
body: body("Example Site 1"),
|
|
)
|
|
|
|
stub_request(:head, %r{https://example2\.com.*}).to_return(status: 200)
|
|
stub_request(:get, %r{https://example2\.com.*}).to_return(
|
|
status: 200,
|
|
body: body("Example Site 2"),
|
|
)
|
|
|
|
stub_request(:head, %r{https://example3\.com.*}).to_return(status: 200)
|
|
stub_request(:get, %r{https://example3\.com.*}).to_return(
|
|
status: 200,
|
|
body: body("Example Site 3"),
|
|
)
|
|
end
|
|
|
|
it "creates an inline onebox for links within text" do
|
|
cdp.allow_clipboard
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("Check out this link ")
|
|
cdp.copy_paste("https://example.com/x")
|
|
composer.type_content(:space)
|
|
|
|
expect(rich).to have_css(
|
|
"a.inline-onebox[href='https://example.com/x']",
|
|
text: "Example Site 1",
|
|
)
|
|
|
|
composer.type_content("in the middle of text")
|
|
composer.toggle_rich_editor
|
|
|
|
expect(composer).to have_value(
|
|
"Check out this link https://example.com/x in the middle of text",
|
|
)
|
|
end
|
|
|
|
it "creates a full onebox for standalone links" do
|
|
cdp.allow_clipboard
|
|
open_composer_and_toggle_rich_editor
|
|
cdp.copy_paste("https://example.com")
|
|
page.send_keys(:enter)
|
|
|
|
expect(rich).to have_css("div.onebox-wrapper[data-onebox-src='https://example.com']")
|
|
expect(rich).to have_content("Example Site 1")
|
|
expect(rich).to have_content("This is an example site")
|
|
|
|
composer.toggle_rich_editor
|
|
|
|
expect(composer).to have_value("https://example.com\n\n")
|
|
end
|
|
|
|
it "creates an inline onebox for links that are part of a paragraph" do
|
|
cdp.allow_clipboard
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("Some text ")
|
|
cdp.copy_paste("https://example.com/x")
|
|
composer.type_content(:space)
|
|
|
|
expect(rich).to have_no_css("div.onebox-wrapper")
|
|
expect(rich).to have_css("a.inline-onebox", text: "Example Site 1")
|
|
|
|
composer.type_content("more text")
|
|
composer.toggle_rich_editor
|
|
|
|
expect(composer).to have_value("Some text https://example.com/x more text")
|
|
end
|
|
|
|
it "does not create oneboxes inside code blocks" do
|
|
cdp.allow_clipboard
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("```")
|
|
cdp.copy_paste("https://example.com")
|
|
|
|
expect(rich).to have_css("pre code")
|
|
expect(rich).to have_no_css("div.onebox-wrapper")
|
|
expect(rich).to have_no_css("a.inline-onebox")
|
|
expect(rich).to have_content("https://example.com")
|
|
|
|
composer.toggle_rich_editor
|
|
|
|
expect(composer).to have_value("```\nhttps://example.com\n```")
|
|
end
|
|
|
|
it "creates oneboxes for mixed content" do
|
|
cdp.allow_clipboard
|
|
open_composer_and_toggle_rich_editor
|
|
markdown = <<~MARKDOWN
|
|
https://example.com
|
|
|
|
Check this https://example.com/x and see if it fits you
|
|
|
|
https://example2.com
|
|
|
|
An inline to https://example2.com/x with text around it
|
|
|
|
https://example3.com
|
|
|
|
Another one for https://example3.com/x then
|
|
|
|
https://example.com
|
|
|
|
Phew, repeating https://example.com/x now
|
|
|
|
https://example2.com
|
|
|
|
And some text again https://example2.com/x
|
|
|
|
https://example3.com/x
|
|
|
|
Ok, that is it https://example3.com/x
|
|
After a hard break
|
|
MARKDOWN
|
|
cdp.copy_paste(markdown)
|
|
|
|
expect(rich).to have_css("a.inline-onebox", count: 6)
|
|
expect(rich).to have_css(
|
|
"a.inline-onebox[href='https://example.com/x']",
|
|
text: "Example Site 1",
|
|
)
|
|
expect(rich).to have_css(
|
|
"a.inline-onebox[href='https://example2.com/x']",
|
|
text: "Example Site 2",
|
|
)
|
|
expect(rich).to have_css(
|
|
"a.inline-onebox[href='https://example3.com/x']",
|
|
text: "Example Site 3",
|
|
)
|
|
|
|
expect(rich).to have_css("div.onebox-wrapper", count: 6)
|
|
expect(rich).to have_css("div.onebox-wrapper[data-onebox-src='https://example.com']")
|
|
expect(rich).to have_css("div.onebox-wrapper[data-onebox-src='https://example2.com']")
|
|
expect(rich).to have_css("div.onebox-wrapper[data-onebox-src='https://example3.com']")
|
|
|
|
composer.toggle_rich_editor
|
|
|
|
expect(composer).to have_value(markdown[0..-2])
|
|
end
|
|
|
|
it "creates inline oneboxes for repeated links in different paste events" do
|
|
cdp.allow_clipboard
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("Hey ")
|
|
cdp.copy_paste("https://example.com/x")
|
|
composer.type_content(:space).type_content("and").type_content(:space)
|
|
cdp.paste
|
|
composer.type_content(:enter)
|
|
|
|
expect(rich).to have_css(
|
|
"a.inline-onebox[href='https://example.com/x']",
|
|
text: "Example Site 1",
|
|
count: 2,
|
|
)
|
|
|
|
composer.toggle_rich_editor
|
|
|
|
expect(composer).to have_value("Hey https://example.com/x and https://example.com/x")
|
|
end
|
|
end
|
|
|
|
context "with keymap" do
|
|
PLATFORM_KEY_MODIFIER = SystemHelpers::PLATFORM_KEY_MODIFIER
|
|
it "supports Ctrl + B to create a bold text" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content([PLATFORM_KEY_MODIFIER, "b"])
|
|
composer.type_content("This is bold")
|
|
|
|
expect(rich).to have_css("strong", text: "This is bold")
|
|
end
|
|
|
|
it "supports Ctrl + I to create an italic text" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content([PLATFORM_KEY_MODIFIER, "i"])
|
|
composer.type_content("This is italic")
|
|
|
|
expect(rich).to have_css("em", text: "This is italic")
|
|
end
|
|
|
|
xit "supports Ctrl + K to create a link" do
|
|
open_composer_and_toggle_rich_editor
|
|
page.send_keys([PLATFORM_KEY_MODIFIER, "k"])
|
|
page.send_keys("https://www.example.com\t")
|
|
page.send_keys("This is a link")
|
|
page.send_keys(:enter)
|
|
|
|
expect(rich).to have_css("a", text: "This is a link")
|
|
end
|
|
|
|
it "supports Ctrl + Shift + 7 to create an ordered list" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("Item 1")
|
|
composer.send_keys([PLATFORM_KEY_MODIFIER, :shift, "7"])
|
|
|
|
expect(rich).to have_css("ol li", text: "Item 1")
|
|
end
|
|
|
|
it "supports Ctrl + Shift + 8 to create a bullet list" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("Item 1")
|
|
composer.send_keys([PLATFORM_KEY_MODIFIER, :shift, "8"])
|
|
|
|
expect(rich).to have_css("ul li", text: "Item 1")
|
|
end
|
|
|
|
it "supports Ctrl + Shift + 9 to create a blockquote" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("This is a blockquote")
|
|
composer.send_keys([PLATFORM_KEY_MODIFIER, :shift, "9"])
|
|
|
|
expect(rich).to have_css("blockquote", text: "This is a blockquote")
|
|
end
|
|
|
|
it "supports Ctrl + Shift + 1-6 for headings, 0 for reset" do
|
|
open_composer_and_toggle_rich_editor
|
|
(1..6).each do |i|
|
|
composer.type_content("\nHeading #{i}")
|
|
composer.send_keys([PLATFORM_KEY_MODIFIER, :shift, i.to_s])
|
|
|
|
expect(rich).to have_css("h#{i}", text: "Heading #{i}")
|
|
end
|
|
|
|
composer.send_keys([PLATFORM_KEY_MODIFIER, :shift, "0"])
|
|
expect(rich).not_to have_css("h6")
|
|
end
|
|
|
|
it "supports Ctrl + Z and Ctrl + Shift + Z to undo and redo" do
|
|
open_composer_and_toggle_rich_editor
|
|
cdp.copy_paste("This is a test")
|
|
composer.send_keys([PLATFORM_KEY_MODIFIER, "z"])
|
|
|
|
expect(rich).not_to have_css("p", text: "This is a test")
|
|
|
|
composer.send_keys([PLATFORM_KEY_MODIFIER, :shift, "z"])
|
|
|
|
expect(rich).to have_css("p", text: "This is a test")
|
|
end
|
|
|
|
it "supports Ctrl + Shift + _ to create a horizontal rule" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("This is a test")
|
|
composer.send_keys([PLATFORM_KEY_MODIFIER, :shift, "_"])
|
|
|
|
expect(rich).to have_css("hr")
|
|
end
|
|
|
|
it "supports Backspace to reset a heading" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("# With text")
|
|
|
|
expect(rich).to have_css("h1", text: "With text")
|
|
|
|
composer.send_keys(:home)
|
|
composer.send_keys(:backspace)
|
|
|
|
expect(rich).to have_css("p", text: "With text")
|
|
end
|
|
|
|
it "supports Backspace to reset a code_block" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("```code block")
|
|
composer.send_keys(:home)
|
|
composer.send_keys(:backspace)
|
|
|
|
expect(rich).to have_css("p", text: "code block")
|
|
end
|
|
|
|
it "doesn't add a new list item when backspacing from below a list" do
|
|
open_composer_and_toggle_rich_editor
|
|
composer.type_content("1. Item 1\nItem 2")
|
|
composer.send_keys(:down)
|
|
composer.type_content("Item 3")
|
|
composer.send_keys(:home)
|
|
composer.send_keys(:backspace)
|
|
|
|
expect(rich).to have_css("ol li", text: "Item 1")
|
|
expect(rich).to have_css("ol li", text: "Item 2Item 3")
|
|
end
|
|
|
|
it "supports Ctrl + M to toggle between rich and markdown editors" do
|
|
open_composer_and_toggle_rich_editor
|
|
|
|
composer.type_content("> This is a test")
|
|
|
|
expect(composer).to have_value(nil)
|
|
expect(rich).to have_css("blockquote", text: "This is a test")
|
|
|
|
composer.send_keys([:control, "m"])
|
|
|
|
expect(composer).to have_value("> This is a test")
|
|
expect(composer).to have_no_rich_editor
|
|
|
|
composer.send_keys([:control, "m"])
|
|
|
|
expect(composer).to have_value(nil)
|
|
expect(rich).to have_css("blockquote", text: "This is a test")
|
|
end
|
|
end
|
|
|
|
describe "pasting content" do
|
|
it "does not freeze the editor when pasting markdown code blocks without a language" do
|
|
with_logs do |logger|
|
|
open_composer_and_toggle_rich_editor
|
|
|
|
# The example is a bit convoluted, but it's the simplest way to reproduce the issue.
|
|
composer.type_content("This is a test\n\n")
|
|
cdp.copy_paste <<~MARKDOWN
|
|
```
|
|
puts SiteSetting.all_settings(filter_categories: ["uncategorized"]).map { |setting| setting[:setting] }.join("\n")
|
|
```
|
|
MARKDOWN
|
|
|
|
expect(logger.logs.map { |log| log[:message] }).not_to include(
|
|
"Maximum call stack size exceeded",
|
|
)
|
|
expect(rich).to have_css("pre code")
|
|
expect(rich).to have_css("select.code-language-select")
|
|
end
|
|
end
|
|
|
|
it "parses images copied from cooked with base62-sha1" do
|
|
cdp.allow_clipboard
|
|
open_composer_and_toggle_rich_editor
|
|
|
|
cdp.copy_paste(
|
|
'<img src="image.png" alt="alt text" data-base62-sha1="1234567890">',
|
|
html: true,
|
|
)
|
|
|
|
expect(rich).to have_css(
|
|
"img[src$='image.png'][alt='alt text'][data-orig-src='upload://1234567890']",
|
|
)
|
|
end
|
|
|
|
it "respects existing marks when pasting a url over a selection" do
|
|
cdp.allow_clipboard
|
|
open_composer_and_toggle_rich_editor
|
|
cdp.copy_paste("not selected `code`**bold**not*italic* not selected")
|
|
rich.find("strong").double_click
|
|
|
|
cdp.copy_paste("www.example.com")
|
|
|
|
expect(rich).to have_css("code", text: "code")
|
|
expect(rich).to have_css("strong", text: "bold")
|
|
expect(rich).to have_css("em", text: "italic")
|
|
|
|
composer.toggle_rich_editor
|
|
|
|
expect(composer).to have_value(
|
|
"not selected [`code`**bold**not*italic*](www.example.com) not selected",
|
|
)
|
|
end
|
|
|
|
it "auto-links pasted URLs from text/html over a selection" do
|
|
cdp.allow_clipboard
|
|
open_composer_and_toggle_rich_editor
|
|
|
|
cdp.copy_paste("not selected **bold** not selected")
|
|
rich.find("strong").double_click
|
|
|
|
cdp.copy_paste("<p>www.example.com</p>", html: true)
|
|
|
|
composer.toggle_rich_editor
|
|
|
|
expect(composer).to have_value("not selected **[bold](www.example.com)** not selected")
|
|
end
|
|
|
|
it "removes newlines from alt/title in pasted image" do
|
|
cdp.allow_clipboard
|
|
open_composer_and_toggle_rich_editor
|
|
|
|
cdp.copy_paste(<<~HTML, html: true)
|
|
<img src="https://example.com/image.png" alt="alt
|
|
with new
|
|
lines" title="title
|
|
with new
|
|
lines">
|
|
HTML
|
|
|
|
img = rich.find("img:nth-of-type(1)")
|
|
|
|
expect(img["src"]).to eq("https://example.com/image.png")
|
|
expect(img["alt"]).to eq("alt with new lines")
|
|
expect(img["title"]).to eq("title with new lines")
|
|
|
|
composer.toggle_rich_editor
|
|
|
|
expect(composer).to have_value(
|
|
'',
|
|
)
|
|
end
|
|
|
|
it "ignores text/html content if Files are present" do
|
|
cdp.allow_clipboard
|
|
open_composer_and_toggle_rich_editor
|
|
cdp.copy_test_image
|
|
cdp.paste
|
|
|
|
expect(rich).to have_css("img[data-orig-src]", count: 1)
|
|
|
|
composer.focus # making sure the toggle click won't be captured as a double click
|
|
composer.toggle_rich_editor
|
|
|
|
expect(composer).to have_value("")
|
|
end
|
|
|
|
it "should correctly merge text with link marks created from parsing" do
|
|
cdp.allow_clipboard
|
|
open_composer_and_toggle_rich_editor
|
|
|
|
cdp.copy_paste("This is a [link](https://example.com)")
|
|
expect(rich).to have_css("a", text: "link")
|
|
|
|
composer.type_content(:space)
|
|
composer.type_content(:left)
|
|
composer.type_content(:backspace)
|
|
|
|
expect(rich).to have_css("a", text: "lin")
|
|
end
|
|
end
|
|
|
|
describe "trailing paragraph" do
|
|
it "ensures there is always a trailing paragraph" do
|
|
open_composer_and_toggle_rich_editor
|
|
|
|
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([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_and_toggle_rich_editor
|
|
|
|
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_and_toggle_rich_editor
|
|
|
|
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_and_toggle_rich_editor
|
|
|
|
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_and_toggle_rich_editor
|
|
|
|
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_and_toggle_rich_editor
|
|
|
|
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 "uploads" do
|
|
it "handles uploads and disables the editor toggle while uploading" do
|
|
open_composer_and_toggle_rich_editor
|
|
|
|
file_path = file_from_fixtures("logo.png", "images").path
|
|
cdp.with_slow_upload do
|
|
attach_file(file_path) { composer.click_toolbar_button("upload") }
|
|
expect(composer).to have_in_progress_uploads
|
|
expect(composer.editor_toggle_switch).to be_disabled
|
|
end
|
|
|
|
expect(composer).to have_no_in_progress_uploads
|
|
expect(rich).to have_css("img:not(.ProseMirror-separator)", count: 1)
|
|
end
|
|
end
|
|
|
|
describe "code marks with fake cursor" do
|
|
it "allows typing after a code mark with/without the mark" do
|
|
open_composer_and_toggle_rich_editor
|
|
|
|
composer.type_content("This is ~~SPARTA!~~ `code!`.")
|
|
|
|
expect(rich).to have_css("code", text: "code!")
|
|
|
|
# within the code mark
|
|
composer.send_keys(:backspace)
|
|
composer.send_keys(:backspace)
|
|
composer.type_content("!")
|
|
|
|
expect(rich).to have_css("code", text: "code!")
|
|
|
|
# after the code mark
|
|
composer.send_keys(:right)
|
|
composer.type_content(".")
|
|
|
|
composer.toggle_rich_editor
|
|
|
|
expect(composer).to have_value("This is ~~SPARTA!~~ `code!`.")
|
|
end
|
|
|
|
xit "allows typing before a code mark with/without the mark" do
|
|
open_composer_and_toggle_rich_editor
|
|
|
|
composer.type_content("`code mark`")
|
|
|
|
expect(rich).to have_css("code", text: "code mark")
|
|
|
|
# before the code mark
|
|
composer.send_keys(:home)
|
|
composer.send_keys(:left)
|
|
composer.type_content("..")
|
|
|
|
# within the code mark
|
|
composer.send_keys(:right)
|
|
composer.type_content("!!")
|
|
|
|
composer.toggle_rich_editor
|
|
|
|
expect(composer).to have_value("..`!!code mark`")
|
|
end
|
|
end
|
|
|
|
describe "emojis" do
|
|
it "has the only-emoji class if 1-3 emojis are 'alone'" do
|
|
open_composer_and_toggle_rich_editor
|
|
|
|
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
|
|
end
|
|
end
|