1 # frozen_string_literal: true
5 class RichTextTest < ActiveSupport::TestCase
6 include Rails::Dom::Testing::Assertions::SelectorAssertions
9 r = RichText.new("html", "foo http://example.com/ bar")
12 assert_select "a[href='http://example.com/']", 1
13 assert_select "a[rel='nofollow noopener noreferrer']", 1
16 r = RichText.new("html", "foo <a href='http://example.com/'>bar</a> baz")
19 assert_select "a[href='http://example.com/']", 1
20 assert_select "a[rel='nofollow noopener noreferrer']", 1
23 r = RichText.new("html", "foo <a rel='junk me trash' href='http://example.com/'>bar</a> baz")
26 assert_select "a[href='http://example.com/']", 1
27 assert_select "a[rel='me nofollow noopener noreferrer']", 1
30 r = RichText.new("html", "foo example@example.com bar")
35 r = RichText.new("html", "foo <a href='mailto:example@example.com'>bar</a> baz")
38 assert_select "a[href='mailto:example@example.com']", 1
39 assert_select "a[rel='nofollow noopener noreferrer']", 1
42 r = RichText.new("html", "foo <div>bar</div> baz")
44 assert_select "div", false
45 assert_select "p", /^foo *bar *baz$/
48 r = RichText.new("html", "foo <script>bar = 1;</script> baz")
50 assert_select "script", false
51 assert_select "p", /^foo *baz$/
54 r = RichText.new("html", "foo <style>div { display: none; }</style> baz")
56 assert_select "style", false
57 assert_select "p", /^foo *baz$/
60 r = RichText.new("html", "<table><tr><td>column</td></tr></table>")
62 assert_select "table[class='table table-sm w-auto']"
65 r = RichText.new("html", "<p class='btn btn-warning'>Click Me</p>")
67 assert_select "p[class='btn btn-warning']", false
68 assert_select "p", /^Click Me$/
71 r = RichText.new("html", "<p style='color:red'>Danger</p>")
73 assert_select "p[style='color:red']", false
74 assert_select "p", /^Danger$/
79 r = RichText.new("html", "foo <a href='http://example.com/'>bar</a> baz")
80 assert_equal "foo <a href='http://example.com/'>bar</a> baz", r.to_text
83 def test_html_spam_score
84 r = RichText.new("html", "foo <a href='http://example.com/'>bar</a> baz")
85 assert_equal 55, r.spam_score.round
88 def test_markdown_to_html
89 r = RichText.new("markdown", "foo http://example.com/ bar")
92 assert_select "a[href='http://example.com/']", 1
93 assert_select "a[rel='nofollow noopener noreferrer']", 1
96 r = RichText.new("markdown", "foo [bar](http://example.com/) baz")
99 assert_select "a[href='http://example.com/']", 1
100 assert_select "a[rel='nofollow noopener noreferrer']", 1
103 r = RichText.new("markdown", "foo <a rel='junk me trash' href='http://example.com/'>bar</a>) baz")
106 assert_select "a[href='http://example.com/']", 1
107 assert_select "a[rel='me nofollow noopener noreferrer']", 1
110 r = RichText.new("markdown", "foo example@example.com bar")
113 assert_select "a[href='mailto:example@example.com']", 1
114 assert_select "a[rel='nofollow noopener noreferrer']", 1
117 r = RichText.new("markdown", "foo [bar](mailto:example@example.com) bar")
120 assert_select "a[href='mailto:example@example.com']", 1
121 assert_select "a[rel='nofollow noopener noreferrer']", 1
124 r = RichText.new("markdown", "foo  bar")
126 assert_select "img", 1
127 assert_select "img[alt='bar']", 1
128 assert_select "img[src='http://example.com/example.png']", 1
131 r = RichText.new("markdown", "# foo bar baz")
133 assert_select "h1", "foo bar baz"
136 r = RichText.new("markdown", "## foo bar baz")
138 assert_select "h2", "foo bar baz"
141 r = RichText.new("markdown", "### foo bar baz")
143 assert_select "h3", "foo bar baz"
146 r = RichText.new("markdown", "* foo bar baz")
148 assert_select "ul" do
149 assert_select "li", "foo bar baz"
153 r = RichText.new("markdown", "1. foo bar baz")
155 assert_select "ol" do
156 assert_select "li", "foo bar baz"
160 r = RichText.new("markdown", "foo *bar* _baz_ qux")
162 assert_select "em", "bar"
163 assert_select "em", "baz"
166 r = RichText.new("markdown", "foo **bar** __baz__ qux")
168 assert_select "strong", "bar"
169 assert_select "strong", "baz"
172 r = RichText.new("markdown", "foo `bar` baz")
174 assert_select "code", "bar"
177 r = RichText.new("markdown", " foo bar baz")
179 assert_select "pre", /^\s*foo bar baz\s*$/
182 r = RichText.new("markdown", "|column|column")
184 assert_select "table[class='table table-sm w-auto']"
187 r = RichText.new("markdown", "Click Me\n{:.btn.btn-warning}")
189 assert_select "p[class='btn btn-warning']", false
190 assert_select "p", /^Click Me$/
193 r = RichText.new("markdown", "<p style='color:red'>Danger</p>")
195 assert_select "p[style='color:red']", false
196 assert_select "p", /^Danger$/
200 def test_markdown_table_alignment
201 # Ensure that kramdown table alignment styles are converted to bootstrap classes
202 markdown_table = <<~MARKDOWN
207 r = RichText.new("markdown", markdown_table)
209 assert_select "td[style='text-align:center']", false
210 assert_select "td[class='text-center']", true
211 assert_select "td[style='text-align:right']", false
212 assert_select "td[class='text-end']", true
216 def test_markdown_to_text
217 r = RichText.new("markdown", "foo [bar](http://example.com/) baz")
218 assert_equal "foo [bar](http://example.com/) baz", r.to_text
221 def test_markdown_spam_score
222 r = RichText.new("markdown", "foo [bar](http://example.com/) baz")
223 assert_equal 50, r.spam_score.round
226 def test_text_to_html_linkify
227 with_settings(:linkify_hosts => ["replace-me.example.com"], :linkify_hosts_replacement => "repl.example.com") do
228 r = RichText.new("text", "foo http://example.com/ bar")
230 assert_dom "a", :count => 1, :text => "http://example.com/" do
231 assert_dom "> @href", "http://example.com/"
232 assert_dom "> @rel", "nofollow noopener noreferrer"
238 def test_text_to_html_linkify_replace
239 with_settings(:linkify_hosts => ["replace-me.example.com"], :linkify_hosts_replacement => "repl.example.com") do
240 r = RichText.new("text", "foo https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12 bar")
242 assert_dom "a", :count => 1, :text => "repl.example.com/some/path?query=te<st&limit=20>10#result12" do
243 assert_dom "> @href", "https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12"
244 assert_dom "> @rel", "nofollow noopener noreferrer"
250 def test_text_to_html_linkify_replace_other_scheme
251 with_settings(:linkify_hosts => ["replace-me.example.com"], :linkify_hosts_replacement => "repl.example.com") do
252 r = RichText.new("text", "foo ftp://replace-me.example.com/some/path?query=te<st&limit=20>10#result12 bar")
254 assert_dom "a", :count => 1, :text => "ftp://replace-me.example.com/some/path?query=te<st&limit=20>10#result12" do
255 assert_dom "> @href", "ftp://replace-me.example.com/some/path?query=te<st&limit=20>10#result12"
256 assert_dom "> @rel", "nofollow noopener noreferrer"
262 def test_text_to_html_linkify_replace_undefined
263 with_settings(:linkify_hosts => ["replace-me.example.com"], :linkify_hosts_replacement => nil) do
264 r = RichText.new("text", "foo https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12 bar")
266 assert_dom "a", :count => 1, :text => "https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12" do
267 assert_dom "> @href", "https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12"
268 assert_dom "> @rel", "nofollow noopener noreferrer"
274 def test_text_to_html_linkify_wiki_replace_prefix
275 with_settings(:linkify_wiki_hosts => ["replace-me-wiki.example.com"], :linkify_wiki_hosts_replacement => "wiki.example.com",
276 :linkify_wiki_optional_path_prefix => "^/wiki(?=/[A-Z])") do
277 r = RichText.new("text", "foo https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal bar")
279 assert_dom "a", :count => 1, :text => "wiki.example.com/Tag:surface%3Dmetal" do
280 assert_dom "> @href", "https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal"
281 assert_dom "> @rel", "nofollow noopener noreferrer"
287 def test_text_to_html_linkify_wiki_replace_prefix_undefined
288 with_settings(:linkify_wiki_hosts => ["replace-me-wiki.example.com"], :linkify_wiki_hosts_replacement => "wiki.example.com",
289 :linkify_wiki_optional_path_prefix => nil) do
290 r = RichText.new("text", "foo https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal bar")
292 assert_dom "a", :count => 1, :text => "wiki.example.com/wiki/Tag:surface%3Dmetal" do
293 assert_dom "> @href", "https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal"
294 assert_dom "> @rel", "nofollow noopener noreferrer"
300 def test_text_to_html_linkify_wiki_replace_undefined_prefix
301 with_settings(:linkify_wiki_hosts => ["replace-me-wiki.example.com"], :linkify_wiki_hosts_replacement => nil,
302 :linkify_wiki_optional_path_prefix => "^/wiki(?=/[A-Z])") do
303 r = RichText.new("text", "foo https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal bar")
305 assert_dom "a", :count => 1, :text => "https://replace-me-wiki.example.com/Tag:surface%3Dmetal" do
306 assert_dom "> @href", "https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal"
307 assert_dom "> @rel", "nofollow noopener noreferrer"
313 def test_text_to_html_linkify_wiki_replace_prefix_no_match
314 with_settings(:linkify_wiki_hosts => ["replace-me-wiki.example.com"], :linkify_wiki_hosts_replacement => "wiki.example.com",
315 :linkify_wiki_optional_path_prefix => "^/wiki(?=/[A-Z])") do
316 r = RichText.new("text", "foo https://replace-me-wiki.example.com/wiki/w bar")
318 assert_dom "a", :count => 1, :text => "wiki.example.com/wiki/w" do
319 assert_dom "> @href", "https://replace-me-wiki.example.com/wiki/w"
320 assert_dom "> @rel", "nofollow noopener noreferrer"
326 def test_text_to_html_email
327 r = RichText.new("text", "foo example@example.com bar")
333 def test_text_to_html_escape
334 r = RichText.new("text", "foo < bar & baz > qux")
336 assert_select "p", "foo < bar & baz > qux"
340 def test_text_to_text
341 r = RichText.new("text", "foo http://example.com/ bar")
342 assert_equal "foo http://example.com/ bar", r.to_text
345 def test_text_spam_score
346 r = RichText.new("text", "foo http://example.com/ bar")
347 assert_equal 141, r.spam_score.round
350 def test_text_no_opengraph_properties
351 r = RichText.new("text", "foo https://example.com/ bar")
353 assert_nil r.image_alt
354 assert_nil r.description
357 def test_html_no_opengraph_properties
358 r = RichText.new("html", "foo <a href='https://example.com/'>bar</a> baz")
360 assert_nil r.image_alt
361 assert_nil r.description
364 def test_markdown_no_image
365 r = RichText.new("markdown", "foo [bar](https://example.com/) baz")
367 assert_nil r.image_alt
370 def test_markdown_image
371 r = RichText.new("markdown", "foo  baz")
372 assert_equal "https://example.com/image.jpg", r.image
373 assert_equal "bar", r.image_alt
376 def test_markdown_first_image
377 r = RichText.new("markdown", "foo  baz\nfoo  baz")
378 assert_equal "https://example.com/image1.jpg", r.image
379 assert_equal "bar1", r.image_alt
382 def test_markdown_image_with_empty_src
383 r = RichText.new("markdown", "![invalid]()")
385 assert_nil r.image_alt
388 def test_markdown_skip_image_with_empty_src
389 r = RichText.new("markdown", "![invalid]() ")
390 assert_equal "https://example.com/valid.gif", r.image
391 assert_equal "valid", r.image_alt
394 def test_markdown_html_image
395 r = RichText.new("markdown", "<img src='https://example.com/img_element.png' alt='alt text here'>")
396 assert_equal "https://example.com/img_element.png", r.image
397 assert_equal "alt text here", r.image_alt
400 def test_markdown_html_image_without_alt
401 r = RichText.new("markdown", "<img src='https://example.com/img_element.png'>")
402 assert_equal "https://example.com/img_element.png", r.image
403 assert_nil r.image_alt
406 def test_markdown_html_image_with_empty_src
407 r = RichText.new("markdown", "<img src='' alt='forgot src'>")
409 assert_nil r.image_alt
412 def test_markdown_skip_html_image_with_empty_src
413 r = RichText.new("markdown", "<img src='' alt='forgot src'> <img src='https://example.com/next_img_element.png' alt='have src'>")
414 assert_equal "https://example.com/next_img_element.png", r.image
415 assert_equal "have src", r.image_alt
418 def test_markdown_html_image_without_src
419 r = RichText.new("markdown", "<img alt='totally forgot src'>")
421 assert_nil r.image_alt
424 def test_markdown_skip_html_image_without_src
425 r = RichText.new("markdown", "<img alt='totally forgot src'> <img src='https://example.com/next_img_element.png' alt='have src'>")
426 assert_equal "https://example.com/next_img_element.png", r.image
427 assert_equal "have src", r.image_alt
430 def test_markdown_no_description
431 r = RichText.new("markdown", "#Nope")
432 assert_nil r.description
435 def test_markdown_description
436 r = RichText.new("markdown", "This is an article about something.")
437 assert_equal "This is an article about something.", r.description
440 def test_markdown_description_after_heading
441 r = RichText.new("markdown", "#Heading\n\nHere starts the text.")
442 assert_equal "Here starts the text.", r.description
445 def test_markdown_description_after_image
446 r = RichText.new("markdown", "\n\nThis is below the image.")
447 assert_equal "This is below the image.", r.description
450 def test_markdown_description_only_first_paragraph
451 r = RichText.new("markdown", "This thing.\n\nMaybe also that thing.")
452 assert_equal "This thing.", r.description
455 def test_markdown_description_elements
456 r = RichText.new("markdown", "*Something* **important** [here](https://example.com/).")
457 assert_equal "Something important here.", r.description
460 def test_markdown_html_description
461 r = RichText.new("markdown", "<p>Can use HTML tags.</p>")
462 assert_equal "Can use HTML tags.", r.description
465 def test_markdown_description_max_length
466 m = RichText::DESCRIPTION_MAX_LENGTH
469 r = RichText.new("markdown", "x" * m)
470 assert_equal "x" * m, r.description
472 r = RichText.new("markdown", "y" * (m + 1))
473 assert_equal "#{'y' * (m - o)}...", r.description
475 r = RichText.new("markdown", "*zzzzzzzzz*z" * ((m + 1) / 10.0).ceil)
476 assert_equal "#{'z' * (m - o)}...", r.description
479 def test_markdown_description_word_break_threshold_length
480 m = RichText::DESCRIPTION_MAX_LENGTH
481 t = RichText::DESCRIPTION_WORD_BREAK_THRESHOLD_LENGTH
484 r = RichText.new("markdown", "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1) - 1)}")
485 assert_equal "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1) - 1)}", r.description
487 r = RichText.new("markdown", "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1))}")
488 assert_equal "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1) - 4)}...", r.description
490 r = RichText.new("markdown", "#{'x' * (t - o)} #{'y' * (m - (t - o) - 1)}")
491 assert_equal "#{'x' * (t - o)} #{'y' * (m - (t - o) - 1)}", r.description
493 r = RichText.new("markdown", "#{'x' * (t - o)} #{'y' * (m - (t - o))}")
494 assert_equal "#{'x' * (t - o)}...", r.description
497 def test_markdown_description_word_break_multiple_spaces
498 m = RichText::DESCRIPTION_MAX_LENGTH
499 t = RichText::DESCRIPTION_WORD_BREAK_THRESHOLD_LENGTH
502 r = RichText.new("markdown", "#{'x' * (t - o)} #{'y' * (m - (t - o - 1))}")
503 assert_equal "#{'x' * (t - o)}...", r.description
508 def assert_html(richtext, &block)
509 html = richtext.to_html
510 assert_predicate html, :html_safe?
511 root = Nokogiri::HTML::DocumentFragment.parse(html)
512 assert_select root, "*" do