From ba9898c5a9b9fdaa61abeb1c051aa28705a2bb2e Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 10 Jul 2017 12:20:50 -0400 Subject: [PATCH] FIX: smarter newline handling for tags on line alone Run all of pretty text spec on new engine --- app/assets/javascripts/markdown-it-bundle.js | 1 + .../engines/markdown-it/html_img.js.es6 | 74 ++ spec/components/pretty_text_spec.rb | 806 ++++++++++-------- 3 files changed, 515 insertions(+), 366 deletions(-) create mode 100644 app/assets/javascripts/pretty-text/engines/markdown-it/html_img.js.es6 diff --git a/app/assets/javascripts/markdown-it-bundle.js b/app/assets/javascripts/markdown-it-bundle.js index 51fe107f811..3460c3a6027 100644 --- a/app/assets/javascripts/markdown-it-bundle.js +++ b/app/assets/javascripts/markdown-it-bundle.js @@ -12,3 +12,4 @@ //= require ./pretty-text/engines/markdown-it/table //= require ./pretty-text/engines/markdown-it/paragraph //= require ./pretty-text/engines/markdown-it/newline +//= require ./pretty-text/engines/markdown-it/html_img diff --git a/app/assets/javascripts/pretty-text/engines/markdown-it/html_img.js.es6 b/app/assets/javascripts/pretty-text/engines/markdown-it/html_img.js.es6 new file mode 100644 index 00000000000..8d5b02efa79 --- /dev/null +++ b/app/assets/javascripts/pretty-text/engines/markdown-it/html_img.js.es6 @@ -0,0 +1,74 @@ +// special handling for IMG tags on a line by themeselves +// we always have to handle it as so it is an inline +// see: https://talk.commonmark.org/t/newline-and-img-tags/2511 + +const REGEX = /^\s*$/i; + +function rule(state, startLine, endLine) { + + var nextLine, token, lineText, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + if (!state.md.options.html) { return false; } + + if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; } + let pos1 = state.src.charCodeAt(pos+1); + if (pos1 !== 73 /* I */ && pos1 !== 105 /* i */) { return false; } + + lineText = state.src.slice(pos, max); + + if (!REGEX.test(lineText)) { + return false; + } + + let lines = []; + lines.push(lineText); + + nextLine = startLine + 1; + for (; nextLine < endLine; nextLine++) { + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + lineText = state.src.slice(pos, max); + + if (lineText.trim() === "") { + break; + } + + if (!REGEX.test(lineText)) { + break; + } + + lines.push(lineText); + } + + state.line = nextLine; + let oldParentType = state.parentType; + state.parentType = 'paragraph'; + + token = state.push('paragraph_open', 'p', 0); + token.map = [startLine, state.line]; + + token = state.push('inline', '', 0); + token.content = lines.join('\n'); + token.map = [startLine, state.line]; + token.children = []; + + token = state.push('paragraph_close', 'p', -1); + state.parentType = oldParentType; + + return true; +} + + +export function setup(helper) { + + if (!helper.markdownIt) { return; } + + helper.registerPlugin(md=>{ + md.block.ruler.before('html_block', 'html_img', rule, {alt: ['fence']}); + }); +} diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb index 966ce95726a..7b7b7036fae 100644 --- a/spec/components/pretty_text_spec.rb +++ b/spec/components/pretty_text_spec.rb @@ -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("ℵ£¢")).to eq("

ℵ£¢

") + end + let(:wrapped_image) { "" } 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 = < -

ddd

-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 "" + it "do off topic quoting with emoji unescape" do + + topic = Fabricate(:topic, title: "this is a test topic :slight_smile:") + expected = <<~HTML + + 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 "" + 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 + + 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 "" + md = <<~MD + [quote="#{user.username}, post:555, topic: 666"] + ddd + [/quote] + MD + + html = <<~HTML + + 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 "

@hello @hello @hello

" end - it "should handle group mentions with a hyphen and without" do - expect(PrettyText.cook('@hello @hello-hello')).to match_html "

@hello @hello-hello

" + it "can handle mentions" do + Fabricate(:user, username: "sam") + expect(PrettyText.cook("hi @sam! hi")).to match_html '

hi @sam! hi

' + expect(PrettyText.cook("hi\n@sam")).to eq("

hi
\n@sam

") + end + + it "can handle mentions inside a hyperlink" do + expect(PrettyText.cook(" @inner ")).to match_html '

@inner

' end - it "should sanitize the html" do - expect(PrettyText.cook("")).to match_html "

" + it "can handle mentions inside a hyperlink" do + expect(PrettyText.cook("[link @inner](http://site.com)")).to match_html '

link @inner

' + end + + it "can handle a list of mentions" do + expect(PrettyText.cook("@a,@b")).to match_html('

@a,@b

') + end + + it "should handle group mentions with a hyphen and without" do + expect(PrettyText.cook('@hello @hello-hello')).to match_html "

@hello @hello-hello

" 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("ℵ£¢")).to eq("

ℵ£¢

") + end + + describe "code fences" do + it 'indents code correctly' do + code = <<~MD + X + ``` + # + x + ``` + MD + cooked = PrettyText.cook(code) + + html = <<~HTML +

X

+
     #
+             x
+        
+ 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("
cpp\n
") + expect(PrettyText.cook("```\ncpp\n```")).to match_html("
cpp\n
") + expect(PrettyText.cook("```text\ncpp\n```")).to match_html("
cpp\n
") + end + + it 'indents code correctly' do + code = "X\n```\n\n #\n x\n```" + cooked = PrettyText.cook(code) + expect(cooked).to match_html("

X

\n
\n    #\n    x\n
") + 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("

aa

") - expect(PrettyText.cook("***\\****a")).to match_html("

*a

") + it 'Is smart about linebreaks and IMG tags' do + raw = <<~MD + a + + + + + + + a + + + - li + + + ``` + test + ``` + + ``` + test + ``` + MD + + html = <<~HTML +

a
+

+

+
+

+

+

+

a

+

+

+ +

+

+
test
+      
+
test
+      
+ 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("

cpp
") - end - - it 'indents code correctly' do - code = "X\n```\n\n #\n x\n```" - cooked = PrettyText.cook(code) - expect(cooked).to match_html("

X

\n\n

    #\n    x
") - 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 = < - - - + raw = <<~HTML + + + + + HTML -HTML + html = <<~HTML +

+
+
+
+

+ HTML - cooked = <


-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 -

#unknown::tag #known

- 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("

hello \":blonde_woman:t6:\"

") - expect(PrettyText.cook("hello šŸ‘©ā€šŸŽ¤")).to eq("

hello \":woman_singer:\"

") - expect(PrettyText.cook("hello šŸ‘©šŸ¾ā€šŸŽ“")).to eq("

hello \":woman_student:t5:\"

") - expect(PrettyText.cook("hello šŸ¤·ā€ā™€ļø")).to eq("

hello \":woman_shrugging:\"

") - end + it "replaces skin toned emoji" do + expect(PrettyText.cook("hello šŸ‘±šŸæā€ā™€ļø")).to eq("

hello \":blonde_woman:t6:\"

") + expect(PrettyText.cook("hello šŸ‘©ā€šŸŽ¤")).to eq("

hello \":woman_singer:\"

") + expect(PrettyText.cook("hello šŸ‘©šŸ¾ā€šŸŽ“")).to eq("

hello \":woman_student:t5:\"

") + expect(PrettyText.cook("hello šŸ¤·ā€ā™€ļø")).to eq("

hello \":woman_shrugging:\"

") + end - it "supports href schemes" do - SiteSetting.allowed_href_schemes = "macappstore|steam" - cooked = cook("[Steam URL Scheme](steam://store/452530)") - expected = '

Steam URL Scheme

' - 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 = '

Steam URL Scheme

' + 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 = '

Steam URL Scheme

' - 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 = '

Steam URL Scheme

' + 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 -

#unknown::tag #known

- HTML + html = <<~HTML +

#unknown::tag #known

+ 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 -

a #known::tag here

- HTML + html = <<~HTML +

a #known::tag here

+ HTML - expect(cooked).to eq(html.strip) + expect(cooked).to eq(html.strip) - cooked = PrettyText.cook("`a` #known::tag here") + cooked = PrettyText.cook("`a` #known::tag here") - expect(cooked).to eq(html.strip) + expect(cooked).to eq(html.strip) - cooked = PrettyText.cook("test #known::tag") - html = <<~HTML -

test #known

- HTML + cooked = PrettyText.cook("test #known::tag") + html = <<~HTML +

test #known

+ 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("
    \n
  1. b
  2. \n
") - end - - it "can handle traditional vs non traditional newlines" do - SiteSetting.traditional_markdown_linebreaks = true - expect(PrettyText.cook("1\n2")).to match_html "

1 2

" - - SiteSetting.traditional_markdown_linebreaks = false - expect(PrettyText.cook("1\n2")).to match_html "

1
\n2

" - end - - it "can handle mentions" do - Fabricate(:user, username: "sam") - expect(PrettyText.cook("hi @sam! hi")).to match_html '

hi @sam! hi

' - expect(PrettyText.cook("hi\n@sam")).to eq("

hi
\n@sam

") - end - - it "can handle mentions inside a hyperlink" do - expect(PrettyText.cook(" @inner ")).to match_html '

@inner

' - end - - - it "can handle mentions inside a hyperlink" do - expect(PrettyText.cook("[link @inner](http://site.com)")).to match_html '

link @inner

' - end - - it "can handle a list of mentions" do - expect(PrettyText.cook("@a,@b")).to match_html('

@a,@b

') - end - - it "can handle emoji by name" do - - expected = <:smile::sunny:

-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 = '

a,:man:t2:,b

' - expect(cooked).to match(expected.strip) - end - - it "can handle emoji by translation" do - expected = '

:wink:

' - 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("
cpp\n
") - expect(PrettyText.cook("```\ncpp\n```")).to match_html("
cpp\n
") - expect(PrettyText.cook("```text\ncpp\n```")).to match_html("
cpp\n
") - end - - it 'indents code correctly' do - code = "X\n```\n\n #\n x\n```" - cooked = PrettyText.cook(code) - expect(cooked).to match_html("

X

\n
\n    #\n    x\n
") - 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('

ā„¢

') - - SiteSetting.enable_markdown_typographer = false - expect(PrettyText.cook('(tm)')).to eq('

(tm)

') - 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("")).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('

abc

') - expect(PrettyText.cook("a[i]b[/i]c")).to eq('

abc

') - 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 - - - - - - - - - - - - - - - -
TablesAreCool
col 3 isright-aligned$1600
- 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 - - 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 = "

" - expect(cooked).to eq(html) - end - - it "provides safety for img bbcode" do - cooked = PrettyText.cook "[img]http://aaa.com[/img]" - html = '

' - expect(cooked).to eq(html) - end - - it "supports email bbcode" do - cooked = PrettyText.cook "[email]sam@sam.com[/email]" - html = '

sam@sam.com

' - expect(cooked).to eq(html) - end - - it "supports url bbcode" do - cooked = PrettyText.cook "[url]http://sam.com[/url]" - html = '

http://sam.com

' - expect(cooked).to eq(html) - end - - it "supports inline code bbcode" do - cooked = PrettyText.cook "Testing [code]codified **stuff** and `more` stuff[/code]" - html = "

Testing codified **stuff** and `more` stuff

" - 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 = "
codified\n\n\n  **stuff** and `more` stuff
" - expect(cooked).to eq(html) - end - - it "support special handling for space in urls" do - cooked = PrettyText.cook "http://testing.com?a%20b" - html = '

http://testing.com?a%20b

' - expect(cooked).to eq(html) - end - - it "supports onebox for decoded urls" do - cooked = PrettyText.cook "http://testing.com?a%50b" - html = '

http://testing.com?aPb

' - 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("
    \n
  • a
  • \n
    \n
  1. b
  2. \n
") + end + + it "can handle traditional vs non traditional newlines" do + SiteSetting.traditional_markdown_linebreaks = true + expect(PrettyText.cook("1\n2")).to match_html "

1 2

" + + SiteSetting.traditional_markdown_linebreaks = false + expect(PrettyText.cook("1\n2")).to match_html "

1
\n2

" + end + + + it "can handle emoji by name" do + + expected = <:smile::sunny:

+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 = '

a,:man:t2:,b

' + expect(cooked).to match(expected.strip) + end + + it "can handle emoji by translation" do + expected = '

:wink:

' + 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('

ā„¢

') + + SiteSetting.enable_markdown_typographer = false + expect(PrettyText.cook('(tm)')).to eq('

(tm)

') + 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("")).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('

abc

') + expect(PrettyText.cook("a[i]b[/i]c")).to eq('

abc

') + 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 + + + + + + + + + + + + + + + +
TablesAreCool
col 3 isright-aligned$1600
+ 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 = "

" + expect(cooked).to eq(html) + end + + it "provides safety for img bbcode" do + cooked = PrettyText.cook "[img]http://aaa.com[/img]" + html = '

' + expect(cooked).to eq(html) + end + + it "supports email bbcode" do + cooked = PrettyText.cook "[email]sam@sam.com[/email]" + html = '

sam@sam.com

' + expect(cooked).to eq(html) + end + + it "supports url bbcode" do + cooked = PrettyText.cook "[url]http://sam.com[/url]" + html = '

http://sam.com

' + expect(cooked).to eq(html) + end + + it "supports inline code bbcode" do + cooked = PrettyText.cook "Testing [code]codified **stuff** and `more` stuff[/code]" + html = "

Testing codified **stuff** and `more` stuff

" + 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 = "
codified\n\n\n  **stuff** and `more` stuff
" + expect(cooked).to eq(html) + end + + it "support special handling for space in urls" do + cooked = PrettyText.cook "http://testing.com?a%20b" + html = '

http://testing.com?a%20b

' + expect(cooked).to eq(html) + end + + it "supports onebox for decoded urls" do + cooked = PrettyText.cook "http://testing.com?a%50b" + html = '

http://testing.com?aPb

' + expect(cooked).to eq(html) + end + + + it "should sanitize the html" do + expect(PrettyText.cook("")).to eq "" + end + + end