1 # frozen_string_literal: true
5 class RichTextTest < ActiveSupport::TestCase
6 include Rails::Dom::Testing::Assertions::SelectorAssertions
13 r = RichText.new("html", "foo http://example.com/ bar")
16 assert_select "a[href='http://example.com/']", 1
17 assert_select "a[rel='nofollow noopener noreferrer']", 1
20 r = RichText.new("html", "foo <a href='http://example.com/'>bar</a> baz")
23 assert_select "a[href='http://example.com/']", 1
24 assert_select "a[rel='nofollow noopener noreferrer']", 1
27 r = RichText.new("html", "foo <a rel='junk me trash' href='http://example.com/'>bar</a> baz")
30 assert_select "a[href='http://example.com/']", 1
31 assert_select "a[rel='me nofollow noopener noreferrer']", 1
34 r = RichText.new("html", "foo example@example.com bar")
39 r = RichText.new("html", "foo <a href='mailto:example@example.com'>bar</a> baz")
42 assert_select "a[href='mailto:example@example.com']", 1
43 assert_select "a[rel='nofollow noopener noreferrer']", 1
46 r = RichText.new("html", "foo <div>bar</div> baz")
48 assert_select "div", false
49 assert_select "p", /^foo *bar *baz$/
52 r = RichText.new("html", "foo <script>bar = 1;</script> baz")
54 assert_select "script", false
55 assert_select "p", /^foo *baz$/
58 r = RichText.new("html", "foo <style>div { display: none; }</style> baz")
60 assert_select "style", false
61 assert_select "p", /^foo *baz$/
64 r = RichText.new("html", "<table><tr><td>column</td></tr></table>")
66 assert_select "table[class='table table-sm w-auto']"
69 r = RichText.new("html", "<p class='btn btn-warning'>Click Me</p>")
71 assert_select "p[class='btn btn-warning']", false
72 assert_select "p", /^Click Me$/
75 r = RichText.new("html", "<p style='color:red'>Danger</p>")
77 assert_select "p[style='color:red']", false
78 assert_select "p", /^Danger$/
83 r = RichText.new("html", "foo <a href='http://example.com/'>bar</a> baz")
84 assert_equal "foo <a href='http://example.com/'>bar</a> baz", r.to_text
87 def test_markdown_to_html
88 r = RichText.new("markdown", "foo http://example.com/ bar")
91 assert_select "a[href='http://example.com/']", 1
92 assert_select "a[rel='nofollow noopener noreferrer']", 1
95 r = RichText.new("markdown", "foo [bar](http://example.com/) baz")
98 assert_select "a[href='http://example.com/']", 1
99 assert_select "a[rel='nofollow noopener noreferrer']", 1
102 r = RichText.new("markdown", "foo <a rel='junk me trash' href='http://example.com/'>bar</a>) baz")
105 assert_select "a[href='http://example.com/']", 1
106 assert_select "a[rel='me nofollow noopener noreferrer']", 1
109 r = RichText.new("markdown", "foo example@example.com bar")
112 assert_select "a[href='mailto:example@example.com']", 1
113 assert_select "a[rel='nofollow noopener noreferrer']", 1
116 r = RichText.new("markdown", "foo [bar](mailto:example@example.com) bar")
119 assert_select "a[href='mailto:example@example.com']", 1
120 assert_select "a[rel='nofollow noopener noreferrer']", 1
123 r = RichText.new("markdown", "foo  bar")
125 assert_select "img", 1
126 assert_select "img[alt='bar']", 1
127 assert_select "img[src='http://example.com/example.png']", 1
130 r = RichText.new("markdown", "# foo bar baz")
132 assert_select "h1", "foo bar baz"
135 r = RichText.new("markdown", "## foo bar baz")
137 assert_select "h2", "foo bar baz"
140 r = RichText.new("markdown", "### foo bar baz")
142 assert_select "h3", "foo bar baz"
145 r = RichText.new("markdown", "* foo bar baz")
147 assert_select "ul" do
148 assert_select "li", "foo bar baz"
152 r = RichText.new("markdown", "1. foo bar baz")
154 assert_select "ol" do
155 assert_select "li", "foo bar baz"
159 r = RichText.new("markdown", "foo *bar* _baz_ qux")
161 assert_select "em", "bar"
162 assert_select "em", "baz"
165 r = RichText.new("markdown", "foo **bar** __baz__ qux")
167 assert_select "strong", "bar"
168 assert_select "strong", "baz"
171 r = RichText.new("markdown", "foo `bar` baz")
173 assert_select "code", "bar"
176 r = RichText.new("markdown", " foo bar baz")
178 assert_select "pre", /^\s*foo bar baz\s*$/
181 r = RichText.new("markdown", "|column|column")
183 assert_select "table[class='table table-sm w-auto']"
186 r = RichText.new("markdown", "Click Me\n{:.btn.btn-warning}")
188 assert_select "p[class='btn btn-warning']", false
189 assert_select "p", /^Click Me$/
192 r = RichText.new("markdown", "<p style='color:red'>Danger</p>")
194 assert_select "p[style='color:red']", false
195 assert_select "p", /^Danger$/
199 def test_markdown_table_alignment
200 # Ensure that kramdown table alignment styles are converted to bootstrap classes
201 markdown_table = <<~MARKDOWN
206 r = RichText.new("markdown", markdown_table)
208 assert_select "td[style='text-align:center']", false
209 assert_select "td[class='text-center']", true
210 assert_select "td[style='text-align:right']", false
211 assert_select "td[class='text-end']", true
215 def test_markdown_to_text
216 r = RichText.new("markdown", "foo [bar](http://example.com/) baz")
217 assert_equal "foo [bar](http://example.com/) baz", r.to_text
220 def test_text_to_html_linkify
221 with_settings(:linkify => { :normalisation_rules => [{ :hosts => ["replace-me.example.com"], :host_replacement => "repl.example.com" }] }) do
222 r = RichText.new("text", "foo http://example.com/ bar")
224 assert_dom "a", :count => 1, :text => "http://example.com/" do
225 assert_dom "> @href", "http://example.com/"
226 assert_dom "> @rel", "nofollow noopener noreferrer"
232 def test_text_to_html_linkify_replace
233 with_settings(:linkify => { :normalisation_rules => [{ :hosts => ["replace-me.example.com"], :host_replacement => "repl.example.com" }] }) do
234 r = RichText.new("text", "foo https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12 bar")
236 assert_dom "a", :count => 1, :text => "repl.example.com/some/path?query=te<st&limit=20>10#result12" do
237 assert_dom "> @href", "https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12"
238 assert_dom "> @rel", "nofollow noopener noreferrer"
244 def test_text_to_html_linkify_recognize
245 with_settings(:linkify => { :normalisation_rules => [{ :hosts => ["replace-me.example.com"], :host_replacement => "repl.example.com" }] }) do
246 r = RichText.new("text", "foo repl.example.com/some/path?query=te<st&limit=20>10#result12 bar")
248 assert_dom "a", :count => 1, :text => "repl.example.com/some/path?query=te<st&limit=20>10#result12" do
249 assert_dom "> @href", "http://replace-me.example.com/some/path?query=te<st&limit=20>10#result12"
250 assert_dom "> @rel", "nofollow noopener noreferrer"
256 def test_text_to_html_linkify_replace_other_scheme
257 with_settings(:linkify => { :normalisation_rules => [{ :hosts => ["replace-me.example.com"], :host_replacement => "repl.example.com" }] }) do
258 r = RichText.new("text", "foo ftp://replace-me.example.com/some/path?query=te<st&limit=20>10#result12 bar")
260 assert_dom "a", :count => 1, :text => "ftp://replace-me.example.com/some/path?query=te<st&limit=20>10#result12" do
261 assert_dom "> @href", "ftp://replace-me.example.com/some/path?query=te<st&limit=20>10#result12"
262 assert_dom "> @rel", "nofollow noopener noreferrer"
268 def test_text_to_html_linkify_replace_undefined
269 with_settings(:linkify => { :normalisation_rules => [{ :hosts => ["replace-me.example.com"] }] }) do
270 r = RichText.new("text", "foo https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12 bar")
272 assert_dom "a", :count => 1, :text => "https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12" do
273 assert_dom "> @href", "https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12"
274 assert_dom "> @rel", "nofollow noopener noreferrer"
280 def test_text_to_html_linkify_wiki_replace_prefix
281 with_settings(:linkify => { :normalisation_rules => [{ :hosts => ["replace-me-wiki.example.com"], :host_replacement => "wiki.example.com", :optional_path_prefix => "^/wiki(?=/[A-Z])" }] }) do
282 r = RichText.new("text", "foo https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal bar")
284 assert_dom "a", :count => 1, :text => "wiki.example.com/Tag:surface%3Dmetal" do
285 assert_dom "> @href", "https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal"
286 assert_dom "> @rel", "nofollow noopener noreferrer"
292 def test_text_to_html_linkify_wiki_replace_prefix_undefined
293 with_settings(:linkify => { :normalisation_rules => [{ :hosts => ["replace-me-wiki.example.com"], :host_replacement => "wiki.example.com" }] }) do
294 r = RichText.new("text", "foo https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal bar")
296 assert_dom "a", :count => 1, :text => "wiki.example.com/wiki/Tag:surface%3Dmetal" do
297 assert_dom "> @href", "https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal"
298 assert_dom "> @rel", "nofollow noopener noreferrer"
304 def test_text_to_html_linkify_wiki_replace_undefined_prefix
305 with_settings(:linkify => { :normalisation_rules => [{ :hosts => ["replace-me-wiki.example.com"], :optional_path_prefix => "^/wiki(?=/[A-Z])" }] }) do
306 r = RichText.new("text", "foo https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal bar")
308 assert_dom "a", :count => 1, :text => "https://replace-me-wiki.example.com/Tag:surface%3Dmetal" do
309 assert_dom "> @href", "https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal"
310 assert_dom "> @rel", "nofollow noopener noreferrer"
316 def test_text_to_html_linkify_wiki_replace_prefix_no_match
317 with_settings(:linkify => { :normalisation_rules => [{ :hosts => ["replace-me-wiki.example.com"], :host_replacement => "wiki.example.com", :optional_path_prefix => "^/wiki(?=/[A-Z])" }] }) do
318 r = RichText.new("text", "foo https://replace-me-wiki.example.com/wiki/w bar")
320 assert_dom "a", :count => 1, :text => "wiki.example.com/wiki/w" do
321 assert_dom "> @href", "https://replace-me-wiki.example.com/wiki/w"
322 assert_dom "> @rel", "nofollow noopener noreferrer"
328 def test_text_to_html_linkify_recognize_wiki
329 with_settings(:linkify => { :normalisation_rules => [{ :hosts => ["replace-me-wiki.example.com"], :host_replacement => "wiki.example.com", :optional_path_prefix => "^/wiki(?=/[A-Z])" }] }) do
330 r = RichText.new("text", "foo wiki.example.com/Tag:surface%3Dmetal bar")
332 assert_dom "a", :count => 1, :text => "wiki.example.com/Tag:surface%3Dmetal" do
333 assert_dom "> @href", "http://replace-me-wiki.example.com/Tag:surface%3Dmetal"
334 assert_dom "> @rel", "nofollow noopener noreferrer"
340 def test_text_to_html_linkify_idempotent
341 with_settings(:linkify => { :normalisation_rules => [{ :hosts => ["test.host"], :host_replacement => "test.host" }] }) do
342 t0 = "foo https://test.host/way/123456789 bar"
344 r1 = RichText.new("text", t0)
345 t1 = Nokogiri::HTML.fragment(r1.to_html).text
347 r2 = RichText.new("text", t1)
348 t2 = Nokogiri::HTML.fragment(r2.to_html).text
354 def test_text_to_html_linkify_recognize_path
355 with_settings(:linkify => { :detection_rules => [{ :patterns => ["@(?<username>\\w+)"], :path_template => "user/\\k<username>" }] }) do
356 r = RichText.new("text", "foo @example bar")
358 assert_dom "a", :count => 1, :text => "http://test.host/user/example" do
359 assert_dom "> @href", "http://test.host/user/example"
360 assert_dom "> @rel", "nofollow noopener noreferrer"
366 def test_text_to_html_linkify_recognize_path_no_partial_match
367 with_settings(:linkify => { :detection_rules => [{ :patterns => ["@(?<username>\\w+)"], :path_template => "user/\\k<username>" }] }) do
368 r = RichText.new("text", "foo example@example.com bar")
375 def test_text_to_html_linkify_recognize_wiki_key_value_path
376 with_settings(:linkify => { :detection_rules => [{ :patterns => ["(?<key>[^\"?#<>/\\s]+)=(?<value>[^\"?#<>\\s]+)"], :path_template => "Tag:\\k<key>=\\k<value>", :host => "http://example.wiki" }] }) do
377 r = RichText.new("text", "foo surface=metal bar")
379 assert_dom "a", :count => 1, :text => "http://example.wiki/Tag:surface=metal" do
380 assert_dom "> @href", "http://example.wiki/Tag:surface=metal"
381 assert_dom "> @rel", "nofollow noopener noreferrer"
387 def test_text_to_html_linkify_recognize_wiki_key_path
388 with_settings(:linkify => { :detection_rules => [{ :patterns => ["(?<key>[^\"?#<>/\\s]+)=\\*?"], :path_template => "Key:\\k<key>", :host => "http://example.wiki" }] }) do
389 r = RichText.new("text", "foo surface=* bar")
391 assert_dom "a", :count => 1, :text => "http://example.wiki/Key:surface" do
392 assert_dom "> @href", "http://example.wiki/Key:surface"
393 assert_dom "> @rel", "nofollow noopener noreferrer"
399 def test_text_to_html_linkify_openstreetmap_links
400 with_settings(:server_url => "www.openstreetmap.org", :server_protocol => "https") do
402 "https://www.openstreetmap.org/note/4655490" =>
403 ["note/4655490", "https://www.openstreetmap.org/note/4655490"],
405 "https://www.openstreetmap.org/changeset/163353772" =>
406 ["changeset/163353772", "https://www.openstreetmap.org/changeset/163353772"],
408 "https://www.openstreetmap.org/way/1249366504" =>
409 ["way/1249366504", "https://www.openstreetmap.org/way/1249366504"],
411 "https://www.openstreetmap.org/way/1249366504/history" =>
412 ["way/1249366504/history", "https://www.openstreetmap.org/way/1249366504/history"],
414 "https://www.openstreetmap.org/way/1249366504/history/2" =>
415 ["way/1249366504/history/2", "https://www.openstreetmap.org/way/1249366504/history/2"],
417 "https://www.openstreetmap.org/node/12639964186" =>
418 ["node/12639964186", "https://www.openstreetmap.org/node/12639964186"],
420 "https://www.openstreetmap.org/relation/7876483" =>
421 ["relation/7876483", "https://www.openstreetmap.org/relation/7876483"],
423 "https://www.openstreetmap.org/user/aharvey" =>
424 ["@aharvey", "https://www.openstreetmap.org/user/aharvey"],
426 "https://wiki.openstreetmap.org/wiki/Key:boundary" =>
427 ["boundary=*", "https://wiki.openstreetmap.org/wiki/Key:boundary"],
429 "https://wiki.openstreetmap.org/wiki/Tag:boundary=place" =>
430 ["boundary=place", "https://wiki.openstreetmap.org/wiki/Tag:boundary=place"],
433 ["boundary=*", "https://wiki.openstreetmap.org/wiki/Key:boundary"],
436 ["boundary=place", "https://wiki.openstreetmap.org/wiki/Tag:boundary=place"],
439 ["@aharvey", "https://www.openstreetmap.org/user/aharvey"],
441 "node/12639964186" =>
442 ["node/12639964186", "https://www.openstreetmap.org/node/12639964186"],
444 "node 12639964186" =>
445 ["node/12639964186", "https://www.openstreetmap.org/node/12639964186"],
448 ["node/12639964186", "https://www.openstreetmap.org/node/12639964186"],
451 ["way/1249366504", "https://www.openstreetmap.org/way/1249366504"],
454 ["way/1249366504", "https://www.openstreetmap.org/way/1249366504"],
457 ["way/1249366504", "https://www.openstreetmap.org/way/1249366504"],
459 "relation/7876483" =>
460 ["relation/7876483", "https://www.openstreetmap.org/relation/7876483"],
462 "relation 7876483" =>
463 ["relation/7876483", "https://www.openstreetmap.org/relation/7876483"],
466 ["relation/7876483", "https://www.openstreetmap.org/relation/7876483"],
468 "changeset/163353772" =>
469 ["changeset/163353772", "https://www.openstreetmap.org/changeset/163353772"],
471 "changeset 163353772" =>
472 ["changeset/163353772", "https://www.openstreetmap.org/changeset/163353772"],
475 ["note/4655490", "https://www.openstreetmap.org/note/4655490"],
478 ["note/4655490", "https://www.openstreetmap.org/note/4655490"]
481 cases.each do |input, (expected_text, expected_href)|
482 r = RichText.new("text", input)
484 assert_dom "a[href='#{expected_href}']", :count => 1, :text => expected_text
490 def test_text_to_html_linkify_no_year_misinterpretation
491 r = RichText.new("text", "We thought there was no way 2020 could be worse than 2019. We were wrong. Please note 2025 is the first square year since OSM started. In that year, some osmlab repos switched from node 22 to bun 1.3.")
497 def test_text_to_html_dont_linkify_too_easily
498 # We used to allow this, but removed it because it
499 # caused issues in some languages.
500 # Eg: example below is Polish for "Reverted in 12345"
501 r = RichText.new("text", "Wycofane w 12345")
508 def test_deactivated_linkify_expansion_in_markdown
509 t0 = "foo `surface=metal` bar"
511 r1 = RichText.new("markdown", t0)
512 t1 = Nokogiri::HTML.fragment(r1.to_html).text
514 assert_equal t0.delete("`"), t1.strip
517 def test_text_to_html_linkify_trims_punctuation
518 r = RichText.new("text", "foo `surface=metal) bar inscription=🙂 baz")
520 assert_dom "a", :count => 1
521 assert_dom "a[href$='Tag:surface=metal']", :text => "surface=metal"
525 def test_text_to_html_linkify_recognizes_non_standard_wiki_pages
526 r_paren = RichText.new("text", "foo source=Isle_of_Man_Government_1:25000_map_(2007) bar")
527 assert_html r_paren do
528 assert_dom "a[href*='Tag:source']", :text => "source=Isle_of_Man_Government_1:25000_map_(2007)"
530 r_latin_ext = RichText.new("text", "foo cuisine=açaà bar")
531 assert_html r_latin_ext do
532 assert_dom "a[href*='Tag:cuisine']", :text => "cuisine=açaÃ"
534 r_cyrillic = RichText.new("text", "foo name=Продукты bar")
535 assert_html r_cyrillic do
536 assert_dom "a[href*='Tag:name']", :text => "name=Продукты"
538 r_cjk = RichText.new("text", "foo shop=園芸店 bar")
540 assert_dom "a[href*='Tag:shop']", :text => "shop=園芸店"
544 def test_text_to_html_email
545 r = RichText.new("text", "foo example@example.com bar")
551 def test_text_to_html_escape
552 r = RichText.new("text", "foo < bar & baz > qux")
554 assert_select "p", "foo < bar & baz > qux"
558 def test_text_to_text
559 r = RichText.new("text", "foo http://example.com/ bar")
560 assert_equal "foo http://example.com/ bar", r.to_text
563 def test_text_no_opengraph_properties
564 r = RichText.new("text", "foo https://example.com/ bar")
566 assert_nil r.image_alt
567 assert_nil r.description
570 def test_html_no_opengraph_properties
571 r = RichText.new("html", "foo <a href='https://example.com/'>bar</a> baz")
573 assert_nil r.image_alt
574 assert_nil r.description
577 def test_markdown_no_image
578 r = RichText.new("markdown", "foo [bar](https://example.com/) baz")
580 assert_nil r.image_alt
583 def test_markdown_image
584 r = RichText.new("markdown", "foo  baz")
585 assert_equal "https://example.com/image.jpg", r.image
586 assert_equal "bar", r.image_alt
589 def test_markdown_first_image
590 r = RichText.new("markdown", "foo  baz\nfoo  baz")
591 assert_equal "https://example.com/image1.jpg", r.image
592 assert_equal "bar1", r.image_alt
595 def test_markdown_image_with_empty_src
596 r = RichText.new("markdown", "![invalid]()")
598 assert_nil r.image_alt
601 def test_markdown_skip_image_with_empty_src
602 r = RichText.new("markdown", "![invalid]() ")
603 assert_equal "https://example.com/valid.gif", r.image
604 assert_equal "valid", r.image_alt
607 def test_markdown_html_image
608 r = RichText.new("markdown", "<img src='https://example.com/img_element.png' alt='alt text here'>")
609 assert_equal "https://example.com/img_element.png", r.image
610 assert_equal "alt text here", r.image_alt
613 def test_markdown_html_image_without_alt
614 r = RichText.new("markdown", "<img src='https://example.com/img_element.png'>")
615 assert_equal "https://example.com/img_element.png", r.image
616 assert_nil r.image_alt
619 def test_markdown_html_image_with_empty_src
620 r = RichText.new("markdown", "<img src='' alt='forgot src'>")
622 assert_nil r.image_alt
625 def test_markdown_skip_html_image_with_empty_src
626 r = RichText.new("markdown", "<img src='' alt='forgot src'> <img src='https://example.com/next_img_element.png' alt='have src'>")
627 assert_equal "https://example.com/next_img_element.png", r.image
628 assert_equal "have src", r.image_alt
631 def test_markdown_html_image_without_src
632 r = RichText.new("markdown", "<img alt='totally forgot src'>")
634 assert_nil r.image_alt
637 def test_markdown_skip_html_image_without_src
638 r = RichText.new("markdown", "<img alt='totally forgot src'> <img src='https://example.com/next_img_element.png' alt='have src'>")
639 assert_equal "https://example.com/next_img_element.png", r.image
640 assert_equal "have src", r.image_alt
643 def test_markdown_no_description
644 r = RichText.new("markdown", "#Nope")
645 assert_nil r.description
648 def test_markdown_description
649 r = RichText.new("markdown", "This is an article about something.")
650 assert_equal "This is an article about something.", r.description
653 def test_markdown_description_after_heading
654 r = RichText.new("markdown", "#Heading\n\nHere starts the text.")
655 assert_equal "Here starts the text.", r.description
658 def test_markdown_description_after_image
659 r = RichText.new("markdown", "\n\nThis is below the image.")
660 assert_equal "This is below the image.", r.description
663 def test_markdown_description_only_first_paragraph
664 r = RichText.new("markdown", "This thing.\n\nMaybe also that thing.")
665 assert_equal "This thing.", r.description
668 def test_markdown_description_elements
669 r = RichText.new("markdown", "*Something* **important** [here](https://example.com/).")
670 assert_equal "Something important here.", r.description
673 def test_markdown_html_description
674 r = RichText.new("markdown", "<p>Can use HTML tags.</p>")
675 assert_equal "Can use HTML tags.", r.description
678 def test_markdown_description_max_length
679 m = RichText::DESCRIPTION_MAX_LENGTH
682 r = RichText.new("markdown", "x" * m)
683 assert_equal "x" * m, r.description
685 r = RichText.new("markdown", "y" * (m + 1))
686 assert_equal "#{'y' * (m - o)}...", r.description
688 r = RichText.new("markdown", "*zzzzzzzzz*z" * ((m + 1) / 10.0).ceil)
689 assert_equal "#{'z' * (m - o)}...", r.description
692 def test_markdown_description_word_break_threshold_length
693 m = RichText::DESCRIPTION_MAX_LENGTH
694 t = RichText::DESCRIPTION_WORD_BREAK_THRESHOLD_LENGTH
697 r = RichText.new("markdown", "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1) - 1)}")
698 assert_equal "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1) - 1)}", r.description
700 r = RichText.new("markdown", "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1))}")
701 assert_equal "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1) - 4)}...", r.description
703 r = RichText.new("markdown", "#{'x' * (t - o)} #{'y' * (m - (t - o) - 1)}")
704 assert_equal "#{'x' * (t - o)} #{'y' * (m - (t - o) - 1)}", r.description
706 r = RichText.new("markdown", "#{'x' * (t - o)} #{'y' * (m - (t - o))}")
707 assert_equal "#{'x' * (t - o)}...", r.description
710 def test_markdown_description_word_break_multiple_spaces
711 m = RichText::DESCRIPTION_MAX_LENGTH
712 t = RichText::DESCRIPTION_WORD_BREAK_THRESHOLD_LENGTH
715 r = RichText.new("markdown", "#{'x' * (t - o)} #{'y' * (m - (t - o - 1))}")
716 assert_equal "#{'x' * (t - o)}...", r.description
721 def assert_html(richtext, &block)
722 html = richtext.to_html
723 assert_predicate html, :html_safe?
724 root = Nokogiri::HTML::DocumentFragment.parse(html)
725 assert_select root, "*" do