FIX: smarter newline handling for <img> tags on line alone

Run all of pretty text spec on new engine
This commit is contained in:
Sam
2017-07-10 12:20:50 -04:00
parent 890a99232d
commit ba9898c5a9
3 changed files with 515 additions and 366 deletions

View File

@ -4,6 +4,10 @@ require 'html_normalize'
describe PrettyText do
before do
SiteSetting.enable_experimental_markdown_it = true
end
def n(html)
HtmlNormalize.normalize(html)
end
@ -12,23 +16,15 @@ describe PrettyText do
n(PrettyText.cook(*args))
end
# see: https://github.com/sparklemotion/nokogiri/issues/1173
skip 'allows html entities correctly' do
expect(PrettyText.cook("&aleph;&pound;&#162;")).to eq("<p>&aleph;&pound;&#162;</p>")
end
let(:wrapped_image) { "<div class=\"lightbox-wrapper\"><a href=\"//localhost:3000/uploads/default/4399/33691397e78b4d75.png\" class=\"lightbox\" title=\"Screen Shot 2014-04-14 at 9.47.10 PM.png\"><img src=\"//localhost:3000/uploads/default/_optimized/bd9/b20/bbbcd6a0c0_655x500.png\" width=\"655\" height=\"500\"><div class=\"meta\">\n<span class=\"filename\">Screen Shot 2014-04-14 at 9.47.10 PM.png</span><span class=\"informations\">966x737 1.47 MB</span><span class=\"expand\"></span>\n</div></a></div>" }
let(:wrapped_image_excerpt) { }
describe "Cooking" do
describe "off topic quoting" do
it "can correctly populate topic title" do
topic = Fabricate(:topic, title: "this is a test topic :slight_smile:")
expected = <<HTML
<aside class="quote" data-post="2" data-topic="#{topic.id}"><div class="title">
<div class="quote-controls"></div><a href="http://test.localhost/t/this-is-a-test-topic-slight-smile/#{topic.id}/2">This is a test topic <img src="/images/emoji/twitter/slight_smile.png?v=5" title="slight_smile" alt="slight_smile" class="emoji"></a>
</div>
<blockquote><p>ddd</p></blockquote></aside>
HTML
expect(PrettyText.cook("[quote=\"EvilTrout, post:2, topic:#{topic.id}\"]ddd\n[/quote]", topic_id: 1)).to match_html expected
end
end
describe "Quoting" do
describe "with avatar" do
let(:default_avatar) { "//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/{size}.png" }
@ -38,19 +34,91 @@ HTML
User.stubs(:default_template).returns(default_avatar)
end
it "produces a quote even with new lines in it" do
expect(PrettyText.cook("[quote=\"#{user.username}, post:123, topic:456, full:true\"]ddd\n[/quote]")).to match_html "<aside class=\"quote\" data-post=\"123\" data-topic=\"456\" data-full=\"true\"><div class=\"title\">\n<div class=\"quote-controls\"></div>\n<img alt='' width=\"20\" height=\"20\" src=\"//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png\" class=\"avatar\">#{user.username}:</div>\n<blockquote><p>ddd</p></blockquote></aside>"
it "do off topic quoting with emoji unescape" do
topic = Fabricate(:topic, title: "this is a test topic :slight_smile:")
expected = <<~HTML
<aside class="quote" data-topic="#{topic.id}" data-post="2">
<div class="title">
<div class="quote-controls"></div>
<a href="http://test.localhost/t/this-is-a-test-topic-slight-smile/#{topic.id}/2">This is a test topic <img src="/images/emoji/twitter/slight_smile.png?v=5" title="slight_smile" alt="slight_smile" class="emoji"></a>
</div>
<blockquote>
<p>ddd</p>
</blockquote>
</aside>
HTML
expect(cook("[quote=\"EvilTrout, post:2, topic:#{topic.id}\"]\nddd\n[/quote]", topic_id: 1)).to eq(n(expected))
end
it "should produce a quote" do
expect(PrettyText.cook("[quote=\"#{user.username}, post:123, topic:456, full:true\"]ddd[/quote]")).to match_html "<aside class=\"quote\" data-post=\"123\" data-topic=\"456\" data-full=\"true\"><div class=\"title\">\n<div class=\"quote-controls\"></div>\n<img alt='' width=\"20\" height=\"20\" src=\"//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png\" class=\"avatar\">#{user.username}:</div>\n<blockquote><p>ddd</p></blockquote></aside>"
it "produces a quote even with new lines in it" do
md = <<~MD
[quote="#{user.username}, post:123, topic:456, full:true"]
ddd
[/quote]
MD
html = <<~HTML
<aside class="quote" data-post="123" data-topic="456">
<div class="title">
<div class="quote-controls"></div>
<img alt width="20" height="20" src="//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png" class="avatar"> #{user.username}:</div>
<blockquote>
<p>ddd</p>
</blockquote>
</aside>
HTML
expect(PrettyText.cook(md)).to eq(html.strip)
end
it "trims spaces on quote params" do
expect(PrettyText.cook("[quote=\"#{user.username}, post:555, topic: 666\"]ddd[/quote]")).to match_html "<aside class=\"quote\" data-post=\"555\" data-topic=\"666\"><div class=\"title\">\n<div class=\"quote-controls\"></div>\n<img alt='' width=\"20\" height=\"20\" src=\"//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png\" class=\"avatar\">#{user.username}:</div>\n<blockquote><p>ddd</p></blockquote></aside>"
md = <<~MD
[quote="#{user.username}, post:555, topic: 666"]
ddd
[/quote]
MD
html = <<~HTML
<aside class="quote" data-post="555" data-topic="666">
<div class="title">
<div class="quote-controls"></div>
<img alt width="20" height="20" src="//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png" class="avatar"> #{user.username}:</div>
<blockquote>
<p>ddd</p>
</blockquote>
</aside>
HTML
expect(PrettyText.cook(md)).to eq(html.strip)
end
end
it "can handle quote edge cases" do
expect(PrettyText.cook("a\n[quote]\ntest\n[/quote]\n\n\na")).to include('aside')
expect(PrettyText.cook("- a\n[quote]\ntest\n[/quote]\n\n\na")).to include('aside')
expect(PrettyText.cook("[quote]\ntest")).not_to include('aside')
expect(PrettyText.cook("[quote]abc\ntest\n[/quote]")).not_to include('aside')
expect(PrettyText.cook("[quote]\ntest\n[/quote]z")).not_to include('aside')
nested = <<~QUOTE
[quote]
a
[quote]
b
[/quote]
c
[/quote]
QUOTE
cooked = PrettyText.cook(nested)
expect(cooked.scan('aside').length).to eq(4)
expect(cooked.scan('quote]').length).to eq(0)
end
describe "with letter avatar" do
let(:user) { Fabricate(:user) }
@ -61,22 +129,44 @@ HTML
end
it "should have correct avatar url" do
expect(PrettyText.cook("[quote=\"#{user.username}, post:123, topic:456, full:true\"]ddd[/quote]")).to include("/forum/letter_avatar_proxy")
md = <<~MD
[quote="#{user.username}, post:123, topic:456, full:true"]
ddd
[/quote]
MD
expect(PrettyText.cook(md)).to include("/forum/letter_avatar_proxy")
end
end
end
end
describe "Mentions" do
it "should handle 3 mentions in a row" do
expect(PrettyText.cook('@hello @hello @hello')).to match_html "<p><span class=\"mention\">@hello</span> <span class=\"mention\">@hello</span> <span class=\"mention\">@hello</span></p>"
end
it "should handle group mentions with a hyphen and without" do
expect(PrettyText.cook('@hello @hello-hello')).to match_html "<p><span class=\"mention\">@hello</span> <span class=\"mention\">@hello-hello</span></p>"
it "can handle mentions" do
Fabricate(:user, username: "sam")
expect(PrettyText.cook("hi @sam! hi")).to match_html '<p>hi <a class="mention" href="/u/sam">@sam</a>! hi</p>'
expect(PrettyText.cook("hi\n@sam")).to eq("<p>hi<br>\n<a class=\"mention\" href=\"/u/sam\">@sam</a></p>")
end
it "can handle mentions inside a hyperlink" do
expect(PrettyText.cook("<a> @inner</a> ")).to match_html '<p><a> @inner</a></p>'
end
it "should sanitize the html" do
expect(PrettyText.cook("<script>alert(42)</script>")).to match_html "<p></p>"
it "can handle mentions inside a hyperlink" do
expect(PrettyText.cook("[link @inner](http://site.com)")).to match_html '<p><a href="http://site.com" rel="nofollow noopener">link @inner</a></p>'
end
it "can handle a list of mentions" do
expect(PrettyText.cook("@a,@b")).to match_html('<p><span class="mention">@a</span>,<span class="mention">@b</span></p>')
end
it "should handle group mentions with a hyphen and without" do
expect(PrettyText.cook('@hello @hello-hello')).to match_html "<p><span class=\"mention\">@hello</span> <span class=\"mention\">@hello-hello</span></p>"
end
it 'should allow for @mentions to have punctuation' do
@ -85,13 +175,52 @@ HTML
)
end
# see: https://github.com/sparklemotion/nokogiri/issues/1173
skip 'allows html entities correctly' do
expect(PrettyText.cook("&aleph;&pound;&#162;")).to eq("<p>&aleph;&pound;&#162;</p>")
end
describe "code fences" do
it 'indents code correctly' do
code = <<~MD
X
```
#
x
```
MD
cooked = PrettyText.cook(code)
html = <<~HTML
<p>X</p>
<pre><code class="lang-auto"> #
x
</code></pre>
HTML
expect(cooked).to eq(html.strip)
end
it "doesn't replace emoji in code blocks with our emoji sets if emoji is enabled" do
expect(PrettyText.cook("```\n💣`\n```\n")).not_to match(/\:bomb\:/)
end
it 'can include code class correctly' do
expect(PrettyText.cook("```cpp\ncpp\n```")).to match_html("<pre><code class='lang-cpp'>cpp\n</code></pre>")
expect(PrettyText.cook("```\ncpp\n```")).to match_html("<pre><code class='lang-auto'>cpp\n</code></pre>")
expect(PrettyText.cook("```text\ncpp\n```")).to match_html("<pre><code class='lang-nohighlight'>cpp\n</code></pre>")
end
it 'indents code correctly' do
code = "X\n```\n\n #\n x\n```"
cooked = PrettyText.cook(code)
expect(cooked).to match_html("<p>X</p>\n<pre><code class=\"lang-auto\">\n #\n x\n</code></pre>")
end
it 'does censor code fences' do
SiteSetting.censored_words = 'apple|banana'
expect(PrettyText.cook("# banana")).not_to include('banana')
end
end
describe "rel nofollow" do
before do
SiteSetting.add_rel_nofollow_to_user_content = true
@ -330,7 +459,6 @@ HTML
)).to eq("boom")
end
end
end
describe "strip links" do
@ -402,20 +530,55 @@ HTML
end
end
it 'can escape *' do
expect(PrettyText.cook("***a***a")).to match_html("<p><strong><em>a</em></strong>a</p>")
expect(PrettyText.cook("***\\****a")).to match_html("<p><strong><em>*</em></strong>a</p>")
it 'Is smart about linebreaks and IMG tags' do
raw = <<~MD
a <img>
<img>
<img>
<img>
<img>
a
<img>
- li
<img>
```
test
```
```
test
```
MD
html = <<~HTML
<p>a <img><br>
<img></p>
<p>
<img><br>
<img></p>
<p>
<img></p>
<p>a</p>
<p>
<img></p>
<ul>
<li>li</li>
</ul>
<p>
<img></p>
<pre><code class="lang-auto">test
</code></pre>
<pre><code class="lang-auto">test
</code></pre>
HTML
expect(PrettyText.cook(raw)).to eq(html.strip)
end
it 'can include code class correctly' do
expect(PrettyText.cook("```cpp\ncpp\n```")).to match_html("<p></p><pre><code class='lang-cpp'>cpp</code></pre>")
end
it 'indents code correctly' do
code = "X\n```\n\n #\n x\n```"
cooked = PrettyText.cook(code)
expect(cooked).to match_html("<p>X<br></p>\n\n<p></p><pre><code class=\"lang-auto\"> #\n x</code></pre>")
end
it 'can substitute s3 cdn correctly' do
SiteSetting.enable_s3_uploads = true
@ -425,19 +588,22 @@ HTML
SiteSetting.s3_cdn_url = "https://awesome.cdn"
# add extra img tag to ensure it does not blow up
raw = <<HTML
<img>
<img src='https:#{Discourse.store.absolute_base_url}/original/9/9/99c9384b8b6d87f8509f8395571bc7512ca3cad1.jpg'>
<img src='http:#{Discourse.store.absolute_base_url}/original/9/9/99c9384b8b6d87f8509f8395571bc7512ca3cad1.jpg'>
<img src='#{Discourse.store.absolute_base_url}/original/9/9/99c9384b8b6d87f8509f8395571bc7512ca3cad1.jpg'>
raw = <<~HTML
<img>
<img src='https:#{Discourse.store.absolute_base_url}/original/9/9/99c9384b8b6d87f8509f8395571bc7512ca3cad1.jpg'>
<img src='http:#{Discourse.store.absolute_base_url}/original/9/9/99c9384b8b6d87f8509f8395571bc7512ca3cad1.jpg'>
<img src='#{Discourse.store.absolute_base_url}/original/9/9/99c9384b8b6d87f8509f8395571bc7512ca3cad1.jpg'>
HTML
HTML
html = <<~HTML
<p>
<img><br>
<img src="https://awesome.cdn/original/9/9/99c9384b8b6d87f8509f8395571bc7512ca3cad1.jpg"><br>
<img src="https://awesome.cdn/original/9/9/99c9384b8b6d87f8509f8395571bc7512ca3cad1.jpg"><br>
<img src="https://awesome.cdn/original/9/9/99c9384b8b6d87f8509f8395571bc7512ca3cad1.jpg"></p>
HTML
cooked = <<HTML
<p> <img><br> <img src="https://awesome.cdn/original/9/9/99c9384b8b6d87f8509f8395571bc7512ca3cad1.jpg"><br> <img src="https://awesome.cdn/original/9/9/99c9384b8b6d87f8509f8395571bc7512ca3cad1.jpg"><br> <img src="https://awesome.cdn/original/9/9/99c9384b8b6d87f8509f8395571bc7512ca3cad1.jpg"></p>
HTML
expect(PrettyText.cook(raw)).to match_html(cooked)
expect(PrettyText.cook(raw)).to eq(html.strip)
end
describe "emoji" do
@ -449,10 +615,6 @@ HTML
expect(PrettyText.cook("`💣`")).not_to match(/\:bomb\:/)
end
it "doesn't replace emoji in code blocks with our emoji sets if emoji is enabled" do
expect(PrettyText.cook("```\n💣`\n```\n")).not_to match(/\:bomb\:/)
end
it "replaces some glyphs that are not in the emoji range" do
expect(PrettyText.cook("")).to match(/\:slight_smile\:/)
end
@ -470,22 +632,6 @@ HTML
end
end
describe "tag and category links" do
it "produces tag links" do
Fabricate(:topic, {tags: [Fabricate(:tag, name: 'known')]})
cooked = PrettyText.cook(" #unknown::tag #known::tag")
html = <<~HTML
<p> <span class=\"hashtag\">#unknown::tag</span> <a class=\"hashtag\" href=\"http://test.localhost/tags/known\">#<span>known</span></a></p>
HTML
expect(cooked).to match_html(html)
end
# TODO does it make sense to generate hashtags for tags that are missing in action?
end
describe "custom emoji" do
it "replaces the custom emoji" do
CustomEmoji.create!(name: 'trout', upload: Fabricate(:upload))
@ -504,317 +650,245 @@ HTML
end
end
context "markdown it" do
before do
SiteSetting.enable_experimental_markdown_it = true
end
it "replaces skin toned emoji" do
expect(PrettyText.cook("hello 👱🏿")).to eq("<p>hello <img src=\"/images/emoji/twitter/blonde_woman/6.png?v=5\" title=\":blonde_woman:t6:\" class=\"emoji\" alt=\":blonde_woman:t6:\"></p>")
expect(PrettyText.cook("hello 👩‍🎤")).to eq("<p>hello <img src=\"/images/emoji/twitter/woman_singer.png?v=5\" title=\":woman_singer:\" class=\"emoji\" alt=\":woman_singer:\"></p>")
expect(PrettyText.cook("hello 👩🏾‍🎓")).to eq("<p>hello <img src=\"/images/emoji/twitter/woman_student/5.png?v=5\" title=\":woman_student:t5:\" class=\"emoji\" alt=\":woman_student:t5:\"></p>")
expect(PrettyText.cook("hello 🤷‍♀️")).to eq("<p>hello <img src=\"/images/emoji/twitter/woman_shrugging.png?v=5\" title=\":woman_shrugging:\" class=\"emoji\" alt=\":woman_shrugging:\"></p>")
end
it "replaces skin toned emoji" do
expect(PrettyText.cook("hello 👱🏿‍♀️")).to eq("<p>hello <img src=\"/images/emoji/twitter/blonde_woman/6.png?v=5\" title=\":blonde_woman:t6:\" class=\"emoji\" alt=\":blonde_woman:t6:\"></p>")
expect(PrettyText.cook("hello 👩‍🎤")).to eq("<p>hello <img src=\"/images/emoji/twitter/woman_singer.png?v=5\" title=\":woman_singer:\" class=\"emoji\" alt=\":woman_singer:\"></p>")
expect(PrettyText.cook("hello 👩🏾‍🎓")).to eq("<p>hello <img src=\"/images/emoji/twitter/woman_student/5.png?v=5\" title=\":woman_student:t5:\" class=\"emoji\" alt=\":woman_student:t5:\"></p>")
expect(PrettyText.cook("hello 🤷‍♀️")).to eq("<p>hello <img src=\"/images/emoji/twitter/woman_shrugging.png?v=5\" title=\":woman_shrugging:\" class=\"emoji\" alt=\":woman_shrugging:\"></p>")
end
it "supports href schemes" do
SiteSetting.allowed_href_schemes = "macappstore|steam"
cooked = cook("[Steam URL Scheme](steam://store/452530)")
expected = '<p><a href="steam://store/452530" rel="nofollow noopener">Steam URL Scheme</a></p>'
expect(cooked).to eq(n expected)
end
it "supports href schemes" do
SiteSetting.allowed_href_schemes = "macappstore|steam"
cooked = cook("[Steam URL Scheme](steam://store/452530)")
expected = '<p><a href="steam://store/452530" rel="nofollow noopener">Steam URL Scheme</a></p>'
expect(cooked).to eq(n expected)
end
it "supports forbidden schemes" do
SiteSetting.allowed_href_schemes = "macappstore|itunes"
cooked = cook("[Steam URL Scheme](steam://store/452530)")
expected = '<p><a>Steam URL Scheme</a></p>'
expect(cooked).to eq(n expected)
end
it "supports forbidden schemes" do
SiteSetting.allowed_href_schemes = "macappstore|itunes"
cooked = cook("[Steam URL Scheme](steam://store/452530)")
expected = '<p><a>Steam URL Scheme</a></p>'
expect(cooked).to eq(n expected)
end
it "produces tag links" do
Fabricate(:topic, {tags: [Fabricate(:tag, name: 'known')]})
it "produces tag links" do
Fabricate(:topic, {tags: [Fabricate(:tag, name: 'known')]})
cooked = PrettyText.cook(" #unknown::tag #known::tag")
cooked = PrettyText.cook(" #unknown::tag #known::tag")
html = <<~HTML
<p><span class=\"hashtag\">#unknown::tag</span> <a class=\"hashtag\" href=\"http://test.localhost/tags/known\">#<span>known</span></a></p>
HTML
html = <<~HTML
<p><span class=\"hashtag\">#unknown::tag</span> <a class=\"hashtag\" href=\"http://test.localhost/tags/known\">#<span>known</span></a></p>
HTML
expect(cooked).to eq(html.strip)
expect(cooked).to eq(html.strip)
cooked = PrettyText.cook("[`a` #known::tag here](http://somesite.com)")
cooked = PrettyText.cook("[`a` #known::tag here](http://somesite.com)")
html = <<~HTML
<p><a href="http://somesite.com" rel="nofollow noopener"><code>a</code> #known::tag here</a></p>
HTML
html = <<~HTML
<p><a href="http://somesite.com" rel="nofollow noopener"><code>a</code> #known::tag here</a></p>
HTML
expect(cooked).to eq(html.strip)
expect(cooked).to eq(html.strip)
cooked = PrettyText.cook("<a href='http://somesite.com'>`a` #known::tag here</a>")
cooked = PrettyText.cook("<a href='http://somesite.com'>`a` #known::tag here</a>")
expect(cooked).to eq(html.strip)
expect(cooked).to eq(html.strip)
cooked = PrettyText.cook("<A href='/a'>test</A> #known::tag")
html = <<~HTML
<p><a href="/a">test</a> <a class="hashtag" href="http://test.localhost/tags/known">#<span>known</span></a></p>
HTML
cooked = PrettyText.cook("<A href='/a'>test</A> #known::tag")
html = <<~HTML
<p><a href="/a">test</a> <a class="hashtag" href="http://test.localhost/tags/known">#<span>known</span></a></p>
HTML
expect(cooked).to eq(html.strip)
expect(cooked).to eq(html.strip)
# ensure it does not fight with the autolinker
expect(PrettyText.cook(' http://somewhere.com/#known')).not_to include('hashtag')
expect(PrettyText.cook(' http://somewhere.com/?#known')).not_to include('hashtag')
expect(PrettyText.cook(' http://somewhere.com/?abc#known')).not_to include('hashtag')
end
it "can handle mixed lists" do
# known bug in old md engine
cooked = PrettyText.cook("* a\n\n1. b")
expect(cooked).to match_html("<ul>\n<li>a</li>\n</ul><ol>\n<li>b</li>\n</ol>")
end
it "can handle traditional vs non traditional newlines" do
SiteSetting.traditional_markdown_linebreaks = true
expect(PrettyText.cook("1\n2")).to match_html "<p>1 2</p>"
SiteSetting.traditional_markdown_linebreaks = false
expect(PrettyText.cook("1\n2")).to match_html "<p>1<br>\n2</p>"
end
it "can handle mentions" do
Fabricate(:user, username: "sam")
expect(PrettyText.cook("hi @sam! hi")).to match_html '<p>hi <a class="mention" href="/u/sam">@sam</a>! hi</p>'
expect(PrettyText.cook("hi\n@sam")).to eq("<p>hi<br>\n<a class=\"mention\" href=\"/u/sam\">@sam</a></p>")
end
it "can handle mentions inside a hyperlink" do
expect(PrettyText.cook("<a> @inner</a> ")).to match_html '<p><a> @inner</a></p>'
end
it "can handle mentions inside a hyperlink" do
expect(PrettyText.cook("[link @inner](http://site.com)")).to match_html '<p><a href="http://site.com" rel="nofollow noopener">link @inner</a></p>'
end
it "can handle a list of mentions" do
expect(PrettyText.cook("@a,@b")).to match_html('<p><span class="mention">@a</span>,<span class="mention">@b</span></p>')
end
it "can handle emoji by name" do
expected = <<HTML
<p><img src="/images/emoji/twitter/smile.png?v=5\" title=":smile:" class="emoji" alt=":smile:"><img src="/images/emoji/twitter/sunny.png?v=5" title=":sunny:" class="emoji" alt=":sunny:"></p>
HTML
expect(PrettyText.cook(":smile::sunny:")).to eq(expected.strip)
end
it "handles emoji boundaries correctly" do
cooked = PrettyText.cook("a,:man:t2:,b")
expected = '<p>a,<img src="/images/emoji/twitter/man/2.png?v=5" title=":man:t2:" class="emoji" alt=":man:t2:">,b</p>'
expect(cooked).to match(expected.strip)
end
it "can handle emoji by translation" do
expected = '<p><img src="/images/emoji/twitter/wink.png?v=5" title=":wink:" class="emoji" alt=":wink:"></p>'
expect(PrettyText.cook(";)")).to eq(expected)
end
it "can handle multiple emojis by translation" do
cooked = PrettyText.cook(":) ;) :)")
expect(cooked.split("img").length-1).to eq(3)
end
it "handles emoji boundries correctly" do
expect(PrettyText.cook(",:)")).to include("emoji")
expect(PrettyText.cook(":-)\n")).to include("emoji")
expect(PrettyText.cook("a :)")).to include("emoji")
expect(PrettyText.cook(":),")).not_to include("emoji")
expect(PrettyText.cook("abcde ^:;-P")).to include("emoji")
end
it 'can include code class correctly' do
expect(PrettyText.cook("```cpp\ncpp\n```")).to match_html("<pre><code class='lang-cpp'>cpp\n</code></pre>")
expect(PrettyText.cook("```\ncpp\n```")).to match_html("<pre><code class='lang-auto'>cpp\n</code></pre>")
expect(PrettyText.cook("```text\ncpp\n```")).to match_html("<pre><code class='lang-nohighlight'>cpp\n</code></pre>")
end
it 'indents code correctly' do
code = "X\n```\n\n #\n x\n```"
cooked = PrettyText.cook(code)
expect(cooked).to match_html("<p>X</p>\n<pre><code class=\"lang-auto\">\n #\n x\n</code></pre>")
end
it 'can censor words correctly' do
SiteSetting.censored_words = 'apple|banana'
expect(PrettyText.cook('yay banana yay')).not_to include('banana')
expect(PrettyText.cook('yay `banana` yay')).not_to include('banana')
expect(PrettyText.cook("yay \n\n```\nbanana\n````\n yay")).not_to include('banana')
expect(PrettyText.cook("# banana")).not_to include('banana')
expect(PrettyText.cook("# banana")).to include("\u25a0\u25a0")
end
it 'supports typographer' do
SiteSetting.enable_markdown_typographer = true
expect(PrettyText.cook('(tm)')).to eq('<p>™</p>')
SiteSetting.enable_markdown_typographer = false
expect(PrettyText.cook('(tm)')).to eq('<p>(tm)</p>')
end
it 'handles onebox correctly' do
expect(PrettyText.cook("http://a.com\nhttp://b.com").split("onebox").length).to eq(3)
expect(PrettyText.cook("http://a.com\n\nhttp://b.com").split("onebox").length).to eq(3)
expect(PrettyText.cook("a\nhttp://a.com")).to include('onebox')
expect(PrettyText.cook("> http://a.com")).not_to include('onebox')
expect(PrettyText.cook("a\nhttp://a.com a")).not_to include('onebox')
expect(PrettyText.cook("a\nhttp://a.com\na")).to include('onebox')
expect(PrettyText.cook("http://a.com")).to include('onebox')
expect(PrettyText.cook("http://a.com ")).to include('onebox')
expect(PrettyText.cook("http://a.com a")).not_to include('onebox')
expect(PrettyText.cook("- http://a.com")).not_to include('onebox')
expect(PrettyText.cook("<http://a.com>")).not_to include('onebox')
expect(PrettyText.cook(" http://a.com")).not_to include('onebox')
expect(PrettyText.cook("a\n http://a.com")).not_to include('onebox')
end
it "can handle bbcode" do
expect(PrettyText.cook("a[b]b[/b]c")).to eq('<p>a<span class="bbcode-b">b</span>c</p>')
expect(PrettyText.cook("a[i]b[/i]c")).to eq('<p>a<span class="bbcode-i">b</span>c</p>')
end
it "can handle quote edge cases" do
expect(PrettyText.cook("a\n[quote]\ntest\n[/quote]\n\n\na")).to include('aside')
expect(PrettyText.cook("- a\n[quote]\ntest\n[/quote]\n\n\na")).to include('aside')
expect(PrettyText.cook("[quote]\ntest")).not_to include('aside')
expect(PrettyText.cook("[quote]abc\ntest\n[/quote]")).not_to include('aside')
expect(PrettyText.cook("[quote]\ntest\n[/quote]z")).not_to include('aside')
nested = <<~QUOTE
[quote]
a
[quote]
b
[/quote]
c
[/quote]
QUOTE
cooked = PrettyText.cook(nested)
expect(cooked.scan('aside').length).to eq(4)
expect(cooked.scan('quote]').length).to eq(0)
end
it "can onebox local topics" do
op = Fabricate(:post)
reply = Fabricate(:post, topic_id: op.topic_id)
url = Discourse.base_url + reply.url
quote = create_post(topic_id: op.topic.id, raw: "This is a sample reply with a quote\n\n#{url}")
quote.reload
expect(quote.cooked).not_to include('[quote')
end
it "supports tables" do
markdown = <<~MD
| Tables | Are | Cool |
| ------------- |:-------------:| -----:|
| col 3 is | right-aligned | $1600 |
MD
expected = <<~HTML
<table>
<thead>
<tr>
<th>Tables</th>
<th style="text-align:center">Are</th>
<th style="text-align:right">Cool</th>
</tr>
</thead>
<tbody>
<tr>
<td>col 3 is</td>
<td style="text-align:center">right-aligned</td>
<td style="text-align:right">$1600</td>
</tr>
</tbody>
</table>
HTML
expect(PrettyText.cook(markdown)).to eq(expected.strip)
end
it "do off topic quoting with emoji unescape" do
topic = Fabricate(:topic, title: "this is a test topic :slight_smile:")
expected = <<~HTML
<aside class="quote" data-topic="#{topic.id}" data-post="2">
<div class="title">
<div class="quote-controls"></div>
<a href="http://test.localhost/t/this-is-a-test-topic-slight-smile/#{topic.id}/2">This is a test topic <img src="/images/emoji/twitter/slight_smile.png?v=5" title="slight_smile" alt="slight_smile" class="emoji"></a>
</div>
<blockquote>
<p>ddd</p>
</blockquote>
</aside>
HTML
expect(cook("[quote=\"EvilTrout, post:2, topic:#{topic.id}\"]\nddd\n[/quote]", topic_id: 1)).to eq(n(expected))
end
it "supports img bbcode" do
cooked = PrettyText.cook "[img]http://www.image/test.png[/img]"
html = "<p><img src=\"http://www.image/test.png\" alt></p>"
expect(cooked).to eq(html)
end
it "provides safety for img bbcode" do
cooked = PrettyText.cook "[img]http://aaa.com<script>alert(1);</script>[/img]"
html = '<p><img src="http://aaa.com&lt;script&gt;alert(1);&lt;/script&gt;" alt></p>'
expect(cooked).to eq(html)
end
it "supports email bbcode" do
cooked = PrettyText.cook "[email]sam@sam.com[/email]"
html = '<p><a href="mailto:sam@sam.com" data-bbcode="true">sam@sam.com</a></p>'
expect(cooked).to eq(html)
end
it "supports url bbcode" do
cooked = PrettyText.cook "[url]http://sam.com[/url]"
html = '<p><a href="http://sam.com" data-bbcode="true" rel="nofollow noopener">http://sam.com</a></p>'
expect(cooked).to eq(html)
end
it "supports inline code bbcode" do
cooked = PrettyText.cook "Testing [code]codified **stuff** and `more` stuff[/code]"
html = "<p>Testing <code>codified **stuff** and `more` stuff</code></p>"
expect(cooked).to eq(html)
end
it "supports block code bbcode" do
cooked = PrettyText.cook "[code]\ncodified\n\n\n **stuff** and `more` stuff\n[/code]"
html = "<pre><code class=\"lang-auto\">codified\n\n\n **stuff** and `more` stuff</code></pre>"
expect(cooked).to eq(html)
end
it "support special handling for space in urls" do
cooked = PrettyText.cook "http://testing.com?a%20b"
html = '<p><a href="http://testing.com?a%20b" class="onebox" rel="nofollow noopener">http://testing.com?a%20b</a></p>'
expect(cooked).to eq(html)
end
it "supports onebox for decoded urls" do
cooked = PrettyText.cook "http://testing.com?a%50b"
html = '<p><a href="http://testing.com?a%50b" class="onebox" rel="nofollow noopener">http://testing.com?aPb</a></p>'
expect(cooked).to eq(html)
end
# ensure it does not fight with the autolinker
expect(PrettyText.cook(' http://somewhere.com/#known')).not_to include('hashtag')
expect(PrettyText.cook(' http://somewhere.com/?#known')).not_to include('hashtag')
expect(PrettyText.cook(' http://somewhere.com/?abc#known')).not_to include('hashtag')
end
it "can handle mixed lists" do
# known bug in old md engine
cooked = PrettyText.cook("* a\n\n1. b")
expect(cooked).to match_html("<ul>\n<li>a</li>\n</ul><ol>\n<li>b</li>\n</ol>")
end
it "can handle traditional vs non traditional newlines" do
SiteSetting.traditional_markdown_linebreaks = true
expect(PrettyText.cook("1\n2")).to match_html "<p>1 2</p>"
SiteSetting.traditional_markdown_linebreaks = false
expect(PrettyText.cook("1\n2")).to match_html "<p>1<br>\n2</p>"
end
it "can handle emoji by name" do
expected = <<HTML
<p><img src="/images/emoji/twitter/smile.png?v=5\" title=":smile:" class="emoji" alt=":smile:"><img src="/images/emoji/twitter/sunny.png?v=5" title=":sunny:" class="emoji" alt=":sunny:"></p>
HTML
expect(PrettyText.cook(":smile::sunny:")).to eq(expected.strip)
end
it "handles emoji boundaries correctly" do
cooked = PrettyText.cook("a,:man:t2:,b")
expected = '<p>a,<img src="/images/emoji/twitter/man/2.png?v=5" title=":man:t2:" class="emoji" alt=":man:t2:">,b</p>'
expect(cooked).to match(expected.strip)
end
it "can handle emoji by translation" do
expected = '<p><img src="/images/emoji/twitter/wink.png?v=5" title=":wink:" class="emoji" alt=":wink:"></p>'
expect(PrettyText.cook(";)")).to eq(expected)
end
it "can handle multiple emojis by translation" do
cooked = PrettyText.cook(":) ;) :)")
expect(cooked.split("img").length-1).to eq(3)
end
it "handles emoji boundries correctly" do
expect(PrettyText.cook(",:)")).to include("emoji")
expect(PrettyText.cook(":-)\n")).to include("emoji")
expect(PrettyText.cook("a :)")).to include("emoji")
expect(PrettyText.cook(":),")).not_to include("emoji")
expect(PrettyText.cook("abcde ^:;-P")).to include("emoji")
end
it 'can censor words correctly' do
SiteSetting.censored_words = 'apple|banana'
expect(PrettyText.cook('yay banana yay')).not_to include('banana')
expect(PrettyText.cook('yay `banana` yay')).not_to include('banana')
expect(PrettyText.cook("# banana")).not_to include('banana')
expect(PrettyText.cook("# banana")).to include("\u25a0\u25a0")
end
it 'supports typographer' do
SiteSetting.enable_markdown_typographer = true
expect(PrettyText.cook('(tm)')).to eq('<p>™</p>')
SiteSetting.enable_markdown_typographer = false
expect(PrettyText.cook('(tm)')).to eq('<p>(tm)</p>')
end
it 'handles onebox correctly' do
expect(PrettyText.cook("http://a.com\nhttp://b.com").split("onebox").length).to eq(3)
expect(PrettyText.cook("http://a.com\n\nhttp://b.com").split("onebox").length).to eq(3)
expect(PrettyText.cook("a\nhttp://a.com")).to include('onebox')
expect(PrettyText.cook("> http://a.com")).not_to include('onebox')
expect(PrettyText.cook("a\nhttp://a.com a")).not_to include('onebox')
expect(PrettyText.cook("a\nhttp://a.com\na")).to include('onebox')
expect(PrettyText.cook("http://a.com")).to include('onebox')
expect(PrettyText.cook("http://a.com ")).to include('onebox')
expect(PrettyText.cook("http://a.com a")).not_to include('onebox')
expect(PrettyText.cook("- http://a.com")).not_to include('onebox')
expect(PrettyText.cook("<http://a.com>")).not_to include('onebox')
expect(PrettyText.cook(" http://a.com")).not_to include('onebox')
expect(PrettyText.cook("a\n http://a.com")).not_to include('onebox')
end
it "can handle bbcode" do
expect(PrettyText.cook("a[b]b[/b]c")).to eq('<p>a<span class="bbcode-b">b</span>c</p>')
expect(PrettyText.cook("a[i]b[/i]c")).to eq('<p>a<span class="bbcode-i">b</span>c</p>')
end
it "can onebox local topics" do
op = Fabricate(:post)
reply = Fabricate(:post, topic_id: op.topic_id)
url = Discourse.base_url + reply.url
quote = create_post(topic_id: op.topic.id, raw: "This is a sample reply with a quote\n\n#{url}")
quote.reload
expect(quote.cooked).not_to include('[quote')
end
it "supports tables" do
markdown = <<~MD
| Tables | Are | Cool |
| ------------- |:-------------:| -----:|
| col 3 is | right-aligned | $1600 |
MD
expected = <<~HTML
<table>
<thead>
<tr>
<th>Tables</th>
<th style="text-align:center">Are</th>
<th style="text-align:right">Cool</th>
</tr>
</thead>
<tbody>
<tr>
<td>col 3 is</td>
<td style="text-align:center">right-aligned</td>
<td style="text-align:right">$1600</td>
</tr>
</tbody>
</table>
HTML
expect(PrettyText.cook(markdown)).to eq(expected.strip)
end
it "supports img bbcode" do
cooked = PrettyText.cook "[img]http://www.image/test.png[/img]"
html = "<p><img src=\"http://www.image/test.png\" alt></p>"
expect(cooked).to eq(html)
end
it "provides safety for img bbcode" do
cooked = PrettyText.cook "[img]http://aaa.com<script>alert(1);</script>[/img]"
html = '<p><img src="http://aaa.com&lt;script&gt;alert(1);&lt;/script&gt;" alt></p>'
expect(cooked).to eq(html)
end
it "supports email bbcode" do
cooked = PrettyText.cook "[email]sam@sam.com[/email]"
html = '<p><a href="mailto:sam@sam.com" data-bbcode="true">sam@sam.com</a></p>'
expect(cooked).to eq(html)
end
it "supports url bbcode" do
cooked = PrettyText.cook "[url]http://sam.com[/url]"
html = '<p><a href="http://sam.com" data-bbcode="true" rel="nofollow noopener">http://sam.com</a></p>'
expect(cooked).to eq(html)
end
it "supports inline code bbcode" do
cooked = PrettyText.cook "Testing [code]codified **stuff** and `more` stuff[/code]"
html = "<p>Testing <code>codified **stuff** and `more` stuff</code></p>"
expect(cooked).to eq(html)
end
it "supports block code bbcode" do
cooked = PrettyText.cook "[code]\ncodified\n\n\n **stuff** and `more` stuff\n[/code]"
html = "<pre><code class=\"lang-auto\">codified\n\n\n **stuff** and `more` stuff</code></pre>"
expect(cooked).to eq(html)
end
it "support special handling for space in urls" do
cooked = PrettyText.cook "http://testing.com?a%20b"
html = '<p><a href="http://testing.com?a%20b" class="onebox" rel="nofollow noopener">http://testing.com?a%20b</a></p>'
expect(cooked).to eq(html)
end
it "supports onebox for decoded urls" do
cooked = PrettyText.cook "http://testing.com?a%50b"
html = '<p><a href="http://testing.com?a%50b" class="onebox" rel="nofollow noopener">http://testing.com?aPb</a></p>'
expect(cooked).to eq(html)
end
it "should sanitize the html" do
expect(PrettyText.cook("<script>alert(42)</script>")).to eq ""
end
end