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_markdown_to_html
84 r = RichText.new("markdown", "foo http://example.com/ bar")
87 assert_select "a[href='http://example.com/']", 1
88 assert_select "a[rel='nofollow noopener noreferrer']", 1
91 r = RichText.new("markdown", "foo [bar](http://example.com/) baz")
94 assert_select "a[href='http://example.com/']", 1
95 assert_select "a[rel='nofollow noopener noreferrer']", 1
98 r = RichText.new("markdown", "foo <a rel='junk me trash' href='http://example.com/'>bar</a>) baz")
101 assert_select "a[href='http://example.com/']", 1
102 assert_select "a[rel='me nofollow noopener noreferrer']", 1
105 r = RichText.new("markdown", "foo example@example.com bar")
108 assert_select "a[href='mailto:example@example.com']", 1
109 assert_select "a[rel='nofollow noopener noreferrer']", 1
112 r = RichText.new("markdown", "foo [bar](mailto:example@example.com) bar")
115 assert_select "a[href='mailto:example@example.com']", 1
116 assert_select "a[rel='nofollow noopener noreferrer']", 1
119 r = RichText.new("markdown", "foo  bar")
121 assert_select "img", 1
122 assert_select "img[alt='bar']", 1
123 assert_select "img[src='http://example.com/example.png']", 1
126 r = RichText.new("markdown", "# foo bar baz")
128 assert_select "h1", "foo bar baz"
131 r = RichText.new("markdown", "## foo bar baz")
133 assert_select "h2", "foo bar baz"
136 r = RichText.new("markdown", "### foo bar baz")
138 assert_select "h3", "foo bar baz"
141 r = RichText.new("markdown", "* foo bar baz")
143 assert_select "ul" do
144 assert_select "li", "foo bar baz"
148 r = RichText.new("markdown", "1. foo bar baz")
150 assert_select "ol" do
151 assert_select "li", "foo bar baz"
155 r = RichText.new("markdown", "foo *bar* _baz_ qux")
157 assert_select "em", "bar"
158 assert_select "em", "baz"
161 r = RichText.new("markdown", "foo **bar** __baz__ qux")
163 assert_select "strong", "bar"
164 assert_select "strong", "baz"
167 r = RichText.new("markdown", "foo `bar` baz")
169 assert_select "code", "bar"
172 r = RichText.new("markdown", " foo bar baz")
174 assert_select "pre", /^\s*foo bar baz\s*$/
177 r = RichText.new("markdown", "|column|column")
179 assert_select "table[class='table table-sm w-auto']"
182 r = RichText.new("markdown", "Click Me\n{:.btn.btn-warning}")
184 assert_select "p[class='btn btn-warning']", false
185 assert_select "p", /^Click Me$/
188 r = RichText.new("markdown", "<p style='color:red'>Danger</p>")
190 assert_select "p[style='color:red']", false
191 assert_select "p", /^Danger$/
195 def test_markdown_table_alignment
196 # Ensure that kramdown table alignment styles are converted to bootstrap classes
197 markdown_table = <<~MARKDOWN
202 r = RichText.new("markdown", markdown_table)
204 assert_select "td[style='text-align:center']", false
205 assert_select "td[class='text-center']", true
206 assert_select "td[style='text-align:right']", false
207 assert_select "td[class='text-end']", true
211 def test_markdown_to_text
212 r = RichText.new("markdown", "foo [bar](http://example.com/) baz")
213 assert_equal "foo [bar](http://example.com/) baz", r.to_text
216 def test_text_to_html_linkify
217 with_settings(:linkify_hosts => ["replace-me.example.com"], :linkify_hosts_replacement => "repl.example.com") do
218 r = RichText.new("text", "foo http://example.com/ bar")
220 assert_dom "a", :count => 1, :text => "http://example.com/" do
221 assert_dom "> @href", "http://example.com/"
222 assert_dom "> @rel", "nofollow noopener noreferrer"
228 def test_text_to_html_linkify_replace
229 with_settings(:linkify_hosts => ["replace-me.example.com"], :linkify_hosts_replacement => "repl.example.com") do
230 r = RichText.new("text", "foo https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12 bar")
232 assert_dom "a", :count => 1, :text => "repl.example.com/some/path?query=te<st&limit=20>10#result12" do
233 assert_dom "> @href", "https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12"
234 assert_dom "> @rel", "nofollow noopener noreferrer"
240 def test_text_to_html_linkify_replace_other_scheme
241 with_settings(:linkify_hosts => ["replace-me.example.com"], :linkify_hosts_replacement => "repl.example.com") do
242 r = RichText.new("text", "foo ftp://replace-me.example.com/some/path?query=te<st&limit=20>10#result12 bar")
244 assert_dom "a", :count => 1, :text => "ftp://replace-me.example.com/some/path?query=te<st&limit=20>10#result12" do
245 assert_dom "> @href", "ftp://replace-me.example.com/some/path?query=te<st&limit=20>10#result12"
246 assert_dom "> @rel", "nofollow noopener noreferrer"
252 def test_text_to_html_linkify_replace_undefined
253 with_settings(:linkify_hosts => ["replace-me.example.com"], :linkify_hosts_replacement => nil) do
254 r = RichText.new("text", "foo https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12 bar")
256 assert_dom "a", :count => 1, :text => "https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12" do
257 assert_dom "> @href", "https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12"
258 assert_dom "> @rel", "nofollow noopener noreferrer"
264 def test_text_to_html_linkify_wiki_replace_prefix
265 with_settings(:linkify_wiki_hosts => ["replace-me-wiki.example.com"], :linkify_wiki_hosts_replacement => "wiki.example.com",
266 :linkify_wiki_optional_path_prefix => "^/wiki(?=/[A-Z])") do
267 r = RichText.new("text", "foo https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal bar")
269 assert_dom "a", :count => 1, :text => "wiki.example.com/Tag:surface%3Dmetal" do
270 assert_dom "> @href", "https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal"
271 assert_dom "> @rel", "nofollow noopener noreferrer"
277 def test_text_to_html_linkify_wiki_replace_prefix_undefined
278 with_settings(:linkify_wiki_hosts => ["replace-me-wiki.example.com"], :linkify_wiki_hosts_replacement => "wiki.example.com",
279 :linkify_wiki_optional_path_prefix => nil) do
280 r = RichText.new("text", "foo https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal bar")
282 assert_dom "a", :count => 1, :text => "wiki.example.com/wiki/Tag:surface%3Dmetal" do
283 assert_dom "> @href", "https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal"
284 assert_dom "> @rel", "nofollow noopener noreferrer"
290 def test_text_to_html_linkify_wiki_replace_undefined_prefix
291 with_settings(:linkify_wiki_hosts => ["replace-me-wiki.example.com"], :linkify_wiki_hosts_replacement => nil,
292 :linkify_wiki_optional_path_prefix => "^/wiki(?=/[A-Z])") do
293 r = RichText.new("text", "foo https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal bar")
295 assert_dom "a", :count => 1, :text => "https://replace-me-wiki.example.com/Tag:surface%3Dmetal" do
296 assert_dom "> @href", "https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal"
297 assert_dom "> @rel", "nofollow noopener noreferrer"
303 def test_text_to_html_linkify_wiki_replace_prefix_no_match
304 with_settings(:linkify_wiki_hosts => ["replace-me-wiki.example.com"], :linkify_wiki_hosts_replacement => "wiki.example.com",
305 :linkify_wiki_optional_path_prefix => "^/wiki(?=/[A-Z])") do
306 r = RichText.new("text", "foo https://replace-me-wiki.example.com/wiki/w bar")
308 assert_dom "a", :count => 1, :text => "wiki.example.com/wiki/w" do
309 assert_dom "> @href", "https://replace-me-wiki.example.com/wiki/w"
310 assert_dom "> @rel", "nofollow noopener noreferrer"
316 def test_text_to_html_email
317 r = RichText.new("text", "foo example@example.com bar")
323 def test_text_to_html_escape
324 r = RichText.new("text", "foo < bar & baz > qux")
326 assert_select "p", "foo < bar & baz > qux"
330 def test_text_to_text
331 r = RichText.new("text", "foo http://example.com/ bar")
332 assert_equal "foo http://example.com/ bar", r.to_text
335 def test_text_no_opengraph_properties
336 r = RichText.new("text", "foo https://example.com/ bar")
338 assert_nil r.image_alt
339 assert_nil r.description
342 def test_html_no_opengraph_properties
343 r = RichText.new("html", "foo <a href='https://example.com/'>bar</a> baz")
345 assert_nil r.image_alt
346 assert_nil r.description
349 def test_markdown_no_image
350 r = RichText.new("markdown", "foo [bar](https://example.com/) baz")
352 assert_nil r.image_alt
355 def test_markdown_image
356 r = RichText.new("markdown", "foo  baz")
357 assert_equal "https://example.com/image.jpg", r.image
358 assert_equal "bar", r.image_alt
361 def test_markdown_first_image
362 r = RichText.new("markdown", "foo  baz\nfoo  baz")
363 assert_equal "https://example.com/image1.jpg", r.image
364 assert_equal "bar1", r.image_alt
367 def test_markdown_image_with_empty_src
368 r = RichText.new("markdown", "![invalid]()")
370 assert_nil r.image_alt
373 def test_markdown_skip_image_with_empty_src
374 r = RichText.new("markdown", "![invalid]() ")
375 assert_equal "https://example.com/valid.gif", r.image
376 assert_equal "valid", r.image_alt
379 def test_markdown_html_image
380 r = RichText.new("markdown", "<img src='https://example.com/img_element.png' alt='alt text here'>")
381 assert_equal "https://example.com/img_element.png", r.image
382 assert_equal "alt text here", r.image_alt
385 def test_markdown_html_image_without_alt
386 r = RichText.new("markdown", "<img src='https://example.com/img_element.png'>")
387 assert_equal "https://example.com/img_element.png", r.image
388 assert_nil r.image_alt
391 def test_markdown_html_image_with_empty_src
392 r = RichText.new("markdown", "<img src='' alt='forgot src'>")
394 assert_nil r.image_alt
397 def test_markdown_skip_html_image_with_empty_src
398 r = RichText.new("markdown", "<img src='' alt='forgot src'> <img src='https://example.com/next_img_element.png' alt='have src'>")
399 assert_equal "https://example.com/next_img_element.png", r.image
400 assert_equal "have src", r.image_alt
403 def test_markdown_html_image_without_src
404 r = RichText.new("markdown", "<img alt='totally forgot src'>")
406 assert_nil r.image_alt
409 def test_markdown_skip_html_image_without_src
410 r = RichText.new("markdown", "<img alt='totally forgot src'> <img src='https://example.com/next_img_element.png' alt='have src'>")
411 assert_equal "https://example.com/next_img_element.png", r.image
412 assert_equal "have src", r.image_alt
415 def test_markdown_no_description
416 r = RichText.new("markdown", "#Nope")
417 assert_nil r.description
420 def test_markdown_description
421 r = RichText.new("markdown", "This is an article about something.")
422 assert_equal "This is an article about something.", r.description
425 def test_markdown_description_after_heading
426 r = RichText.new("markdown", "#Heading\n\nHere starts the text.")
427 assert_equal "Here starts the text.", r.description
430 def test_markdown_description_after_image
431 r = RichText.new("markdown", "\n\nThis is below the image.")
432 assert_equal "This is below the image.", r.description
435 def test_markdown_description_only_first_paragraph
436 r = RichText.new("markdown", "This thing.\n\nMaybe also that thing.")
437 assert_equal "This thing.", r.description
440 def test_markdown_description_elements
441 r = RichText.new("markdown", "*Something* **important** [here](https://example.com/).")
442 assert_equal "Something important here.", r.description
445 def test_markdown_html_description
446 r = RichText.new("markdown", "<p>Can use HTML tags.</p>")
447 assert_equal "Can use HTML tags.", r.description
450 def test_markdown_description_max_length
451 m = RichText::DESCRIPTION_MAX_LENGTH
454 r = RichText.new("markdown", "x" * m)
455 assert_equal "x" * m, r.description
457 r = RichText.new("markdown", "y" * (m + 1))
458 assert_equal "#{'y' * (m - o)}...", r.description
460 r = RichText.new("markdown", "*zzzzzzzzz*z" * ((m + 1) / 10.0).ceil)
461 assert_equal "#{'z' * (m - o)}...", r.description
464 def test_markdown_description_word_break_threshold_length
465 m = RichText::DESCRIPTION_MAX_LENGTH
466 t = RichText::DESCRIPTION_WORD_BREAK_THRESHOLD_LENGTH
469 r = RichText.new("markdown", "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1) - 1)}")
470 assert_equal "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1) - 1)}", r.description
472 r = RichText.new("markdown", "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1))}")
473 assert_equal "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1) - 4)}...", r.description
475 r = RichText.new("markdown", "#{'x' * (t - o)} #{'y' * (m - (t - o) - 1)}")
476 assert_equal "#{'x' * (t - o)} #{'y' * (m - (t - o) - 1)}", r.description
478 r = RichText.new("markdown", "#{'x' * (t - o)} #{'y' * (m - (t - o))}")
479 assert_equal "#{'x' * (t - o)}...", r.description
482 def test_markdown_description_word_break_multiple_spaces
483 m = RichText::DESCRIPTION_MAX_LENGTH
484 t = RichText::DESCRIPTION_WORD_BREAK_THRESHOLD_LENGTH
487 r = RichText.new("markdown", "#{'x' * (t - o)} #{'y' * (m - (t - o - 1))}")
488 assert_equal "#{'x' * (t - o)}...", r.description
493 def assert_html(richtext, &block)
494 html = richtext.to_html
495 assert_predicate html, :html_safe?
496 root = Nokogiri::HTML::DocumentFragment.parse(html)
497 assert_select root, "*" do