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 => { :normalisation_rules => [{ :hosts => ["replace-me.example.com"], :host_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 => { :normalisation_rules => [{ :hosts => ["replace-me.example.com"], :host_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_recognize
241 with_settings(:linkify => { :normalisation_rules => [{ :hosts => ["replace-me.example.com"], :host_replacement => "repl.example.com" }] }) do
242 r = RichText.new("text", "foo repl.example.com/some/path?query=te<st&limit=20>10#result12 bar")
244 assert_dom "a", :count => 1, :text => "repl.example.com/some/path?query=te<st&limit=20>10#result12" do
245 assert_dom "> @href", "http://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_other_scheme
253 with_settings(:linkify => { :normalisation_rules => [{ :hosts => ["replace-me.example.com"], :host_replacement => "repl.example.com" }] }) do
254 r = RichText.new("text", "foo ftp://replace-me.example.com/some/path?query=te<st&limit=20>10#result12 bar")
256 assert_dom "a", :count => 1, :text => "ftp://replace-me.example.com/some/path?query=te<st&limit=20>10#result12" do
257 assert_dom "> @href", "ftp://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_replace_undefined
265 with_settings(:linkify => { :normalisation_rules => [{ :hosts => ["replace-me.example.com"] }] }) do
266 r = RichText.new("text", "foo https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12 bar")
268 assert_dom "a", :count => 1, :text => "https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12" do
269 assert_dom "> @href", "https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12"
270 assert_dom "> @rel", "nofollow noopener noreferrer"
276 def test_text_to_html_linkify_wiki_replace_prefix
277 with_settings(:linkify => { :normalisation_rules => [{ :hosts => ["replace-me-wiki.example.com"], :host_replacement => "wiki.example.com", :optional_path_prefix => "^/wiki(?=/[A-Z])" }] }) do
278 r = RichText.new("text", "foo https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal bar")
280 assert_dom "a", :count => 1, :text => "wiki.example.com/Tag:surface%3Dmetal" do
281 assert_dom "> @href", "https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal"
282 assert_dom "> @rel", "nofollow noopener noreferrer"
288 def test_text_to_html_linkify_wiki_replace_prefix_undefined
289 with_settings(:linkify => { :normalisation_rules => [{ :hosts => ["replace-me-wiki.example.com"], :host_replacement => "wiki.example.com" }] }) 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 => { :normalisation_rules => [{ :hosts => ["replace-me-wiki.example.com"], :optional_path_prefix => "^/wiki(?=/[A-Z])" }] }) do
302 r = RichText.new("text", "foo https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal bar")
304 assert_dom "a", :count => 1, :text => "https://replace-me-wiki.example.com/Tag:surface%3Dmetal" do
305 assert_dom "> @href", "https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal"
306 assert_dom "> @rel", "nofollow noopener noreferrer"
312 def test_text_to_html_linkify_wiki_replace_prefix_no_match
313 with_settings(:linkify => { :normalisation_rules => [{ :hosts => ["replace-me-wiki.example.com"], :host_replacement => "wiki.example.com", :optional_path_prefix => "^/wiki(?=/[A-Z])" }] }) do
314 r = RichText.new("text", "foo https://replace-me-wiki.example.com/wiki/w bar")
316 assert_dom "a", :count => 1, :text => "wiki.example.com/wiki/w" do
317 assert_dom "> @href", "https://replace-me-wiki.example.com/wiki/w"
318 assert_dom "> @rel", "nofollow noopener noreferrer"
324 def test_text_to_html_linkify_recognize_wiki
325 with_settings(:linkify => { :normalisation_rules => [{ :hosts => ["replace-me-wiki.example.com"], :host_replacement => "wiki.example.com", :optional_path_prefix => "^/wiki(?=/[A-Z])" }] }) do
326 r = RichText.new("text", "foo wiki.example.com/Tag:surface%3Dmetal bar")
328 assert_dom "a", :count => 1, :text => "wiki.example.com/Tag:surface%3Dmetal" do
329 assert_dom "> @href", "http://replace-me-wiki.example.com/Tag:surface%3Dmetal"
330 assert_dom "> @rel", "nofollow noopener noreferrer"
336 def test_text_to_html_linkify_idempotent
337 with_settings(:linkify => { :normalisation_rules => [{ :hosts => ["test.host"], :host_replacement => "test.host" }] }) do
338 t0 = "foo https://test.host/way/123456789 bar"
340 r1 = RichText.new("text", t0)
341 t1 = Nokogiri::HTML.fragment(r1.to_html).text
343 r2 = RichText.new("text", t1)
344 t2 = Nokogiri::HTML.fragment(r2.to_html).text
350 def test_text_to_html_linkify_recognize_path
351 with_settings(:linkify => { :detection_rules => [{ :patterns => ["@(?<username>\\w+)"], :path_template => "user/\\k<username>" }] }) do
352 r = RichText.new("text", "foo @example bar")
354 assert_dom "a", :count => 1, :text => "http://test.host/user/example" do
355 assert_dom "> @href", "http://test.host/user/example"
356 assert_dom "> @rel", "nofollow noopener noreferrer"
362 def test_text_to_html_linkify_recognize_path_no_partial_match
363 with_settings(:linkify => { :detection_rules => [{ :patterns => ["@(?<username>\\w+)"], :path_template => "user/\\k<username>" }] }) do
364 r = RichText.new("text", "foo example@example.com bar")
371 def test_text_to_html_linkify_recognize_wiki_path
372 with_settings(:linkify => { :detection_rules => [{ :patterns => ["(?<key>[^\"?#<>/\\s]+)=(?<value>[^\"?#<>\\s]+)"], :path_template => "Tag:\\k<key>=\\k<value>", :host => "http://example.wiki" }] }) do
373 r = RichText.new("text", "foo surface=metal bar")
375 assert_dom "a", :count => 1, :text => "http://example.wiki/Tag:surface=metal" do
376 assert_dom "> @href", "http://example.wiki/Tag:surface=metal"
377 assert_dom "> @rel", "nofollow noopener noreferrer"
381 with_settings(:linkify => { :detection_rules => [{ :patterns => ["(?<key>[^\"?#<>/\\s]+)=\\*?"], :path_template => "Key:\\k<key>", :host => "http://example.wiki" }] }) do
382 r = RichText.new("text", "foo surface=* bar")
384 assert_dom "a", :count => 1, :text => "http://example.wiki/Key:surface" do
385 assert_dom "> @href", "http://example.wiki/Key:surface"
386 assert_dom "> @rel", "nofollow noopener noreferrer"
392 def test_text_to_html_linkify_openstreetmap_links
393 with_settings(:server_url => "www.openstreetmap.org", :server_protocol => "https") do
395 "https://www.openstreetmap.org/note/4655490" =>
396 ["note/4655490", "https://www.openstreetmap.org/note/4655490"],
398 "https://www.openstreetmap.org/changeset/163353772" =>
399 ["changeset/163353772", "https://www.openstreetmap.org/changeset/163353772"],
401 "https://www.openstreetmap.org/way/1249366504" =>
402 ["way/1249366504", "https://www.openstreetmap.org/way/1249366504"],
404 "https://www.openstreetmap.org/way/1249366504/history" =>
405 ["way/1249366504/history", "https://www.openstreetmap.org/way/1249366504/history"],
407 "https://www.openstreetmap.org/way/1249366504/history/2" =>
408 ["way/1249366504/history/2", "https://www.openstreetmap.org/way/1249366504/history/2"],
410 "https://www.openstreetmap.org/node/12639964186" =>
411 ["node/12639964186", "https://www.openstreetmap.org/node/12639964186"],
413 "https://www.openstreetmap.org/relation/7876483" =>
414 ["relation/7876483", "https://www.openstreetmap.org/relation/7876483"],
416 "https://www.openstreetmap.org/user/aharvey" =>
417 ["@aharvey", "https://www.openstreetmap.org/user/aharvey"],
419 "https://wiki.openstreetmap.org/wiki/Key:boundary" =>
420 ["boundary=*", "https://wiki.openstreetmap.org/wiki/Key:boundary"],
422 "https://wiki.openstreetmap.org/wiki/Tag:boundary=place" =>
423 ["boundary=place", "https://wiki.openstreetmap.org/wiki/Tag:boundary=place"],
426 ["boundary=*", "https://wiki.openstreetmap.org/wiki/Key:boundary"],
429 ["boundary=place", "https://wiki.openstreetmap.org/wiki/Tag:boundary=place"],
432 ["@aharvey", "https://www.openstreetmap.org/user/aharvey"],
434 "node/12639964186" =>
435 ["node/12639964186", "https://www.openstreetmap.org/node/12639964186"],
437 "node 12639964186" =>
438 ["node/12639964186", "https://www.openstreetmap.org/node/12639964186"],
441 ["node/12639964186", "https://www.openstreetmap.org/node/12639964186"],
444 ["way/1249366504", "https://www.openstreetmap.org/way/1249366504"],
447 ["way/1249366504", "https://www.openstreetmap.org/way/1249366504"],
450 ["way/1249366504", "https://www.openstreetmap.org/way/1249366504"],
452 "relation/7876483" =>
453 ["relation/7876483", "https://www.openstreetmap.org/relation/7876483"],
455 "relation 7876483" =>
456 ["relation/7876483", "https://www.openstreetmap.org/relation/7876483"],
459 ["relation/7876483", "https://www.openstreetmap.org/relation/7876483"],
461 "changeset/163353772" =>
462 ["changeset/163353772", "https://www.openstreetmap.org/changeset/163353772"],
464 "changeset 163353772" =>
465 ["changeset/163353772", "https://www.openstreetmap.org/changeset/163353772"],
468 ["note/4655490", "https://www.openstreetmap.org/note/4655490"],
471 ["note/4655490", "https://www.openstreetmap.org/note/4655490"]
474 cases.each do |input, (expected_text, expected_href)|
475 r = RichText.new("text", input)
477 assert_dom "a[href='#{expected_href}']", :count => 1, :text => expected_text
483 def test_text_to_html_linkify_no_year_misinterpretation
484 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.")
490 def test_deactivated_linkify_expansion_in_markdown
491 t0 = "foo `surface=metal` bar"
493 r1 = RichText.new("markdown", t0)
494 t1 = Nokogiri::HTML.fragment(r1.to_html).text
496 assert_equal t0.delete("`"), t1.strip
499 def test_text_to_html_linkify_trims_punctuation
500 r = RichText.new("text", "foo `surface=metal) bar inscription=🙂 baz")
502 assert_dom "a", :count => 1
503 assert_dom "a[href$='Tag:surface=metal']", :text => "surface=metal"
507 def test_text_to_html_linkify_recognizes_non_standard_wiki_pages
508 r_paren = RichText.new("text", "foo source=Isle_of_Man_Government_1:25000_map_(2007) bar")
509 assert_html r_paren do
510 assert_dom "a[href*='Tag:source']", :text => "source=Isle_of_Man_Government_1:25000_map_(2007)"
512 r_latin_ext = RichText.new("text", "foo cuisine=açaà bar")
513 assert_html r_latin_ext do
514 assert_dom "a[href*='Tag:cuisine']", :text => "cuisine=açaÃ"
516 r_cyrillic = RichText.new("text", "foo name=Продукты bar")
517 assert_html r_cyrillic do
518 assert_dom "a[href*='Tag:name']", :text => "name=Продукты"
520 r_cjk = RichText.new("text", "foo shop=園芸店 bar")
522 assert_dom "a[href*='Tag:shop']", :text => "shop=園芸店"
526 def test_text_to_html_email
527 r = RichText.new("text", "foo example@example.com bar")
533 def test_text_to_html_escape
534 r = RichText.new("text", "foo < bar & baz > qux")
536 assert_select "p", "foo < bar & baz > qux"
540 def test_text_to_text
541 r = RichText.new("text", "foo http://example.com/ bar")
542 assert_equal "foo http://example.com/ bar", r.to_text
545 def test_text_no_opengraph_properties
546 r = RichText.new("text", "foo https://example.com/ bar")
548 assert_nil r.image_alt
549 assert_nil r.description
552 def test_html_no_opengraph_properties
553 r = RichText.new("html", "foo <a href='https://example.com/'>bar</a> baz")
555 assert_nil r.image_alt
556 assert_nil r.description
559 def test_markdown_no_image
560 r = RichText.new("markdown", "foo [bar](https://example.com/) baz")
562 assert_nil r.image_alt
565 def test_markdown_image
566 r = RichText.new("markdown", "foo  baz")
567 assert_equal "https://example.com/image.jpg", r.image
568 assert_equal "bar", r.image_alt
571 def test_markdown_first_image
572 r = RichText.new("markdown", "foo  baz\nfoo  baz")
573 assert_equal "https://example.com/image1.jpg", r.image
574 assert_equal "bar1", r.image_alt
577 def test_markdown_image_with_empty_src
578 r = RichText.new("markdown", "![invalid]()")
580 assert_nil r.image_alt
583 def test_markdown_skip_image_with_empty_src
584 r = RichText.new("markdown", "![invalid]() ")
585 assert_equal "https://example.com/valid.gif", r.image
586 assert_equal "valid", r.image_alt
589 def test_markdown_html_image
590 r = RichText.new("markdown", "<img src='https://example.com/img_element.png' alt='alt text here'>")
591 assert_equal "https://example.com/img_element.png", r.image
592 assert_equal "alt text here", r.image_alt
595 def test_markdown_html_image_without_alt
596 r = RichText.new("markdown", "<img src='https://example.com/img_element.png'>")
597 assert_equal "https://example.com/img_element.png", r.image
598 assert_nil r.image_alt
601 def test_markdown_html_image_with_empty_src
602 r = RichText.new("markdown", "<img src='' alt='forgot src'>")
604 assert_nil r.image_alt
607 def test_markdown_skip_html_image_with_empty_src
608 r = RichText.new("markdown", "<img src='' alt='forgot src'> <img src='https://example.com/next_img_element.png' alt='have src'>")
609 assert_equal "https://example.com/next_img_element.png", r.image
610 assert_equal "have src", r.image_alt
613 def test_markdown_html_image_without_src
614 r = RichText.new("markdown", "<img alt='totally forgot src'>")
616 assert_nil r.image_alt
619 def test_markdown_skip_html_image_without_src
620 r = RichText.new("markdown", "<img alt='totally forgot src'> <img src='https://example.com/next_img_element.png' alt='have src'>")
621 assert_equal "https://example.com/next_img_element.png", r.image
622 assert_equal "have src", r.image_alt
625 def test_markdown_no_description
626 r = RichText.new("markdown", "#Nope")
627 assert_nil r.description
630 def test_markdown_description
631 r = RichText.new("markdown", "This is an article about something.")
632 assert_equal "This is an article about something.", r.description
635 def test_markdown_description_after_heading
636 r = RichText.new("markdown", "#Heading\n\nHere starts the text.")
637 assert_equal "Here starts the text.", r.description
640 def test_markdown_description_after_image
641 r = RichText.new("markdown", "\n\nThis is below the image.")
642 assert_equal "This is below the image.", r.description
645 def test_markdown_description_only_first_paragraph
646 r = RichText.new("markdown", "This thing.\n\nMaybe also that thing.")
647 assert_equal "This thing.", r.description
650 def test_markdown_description_elements
651 r = RichText.new("markdown", "*Something* **important** [here](https://example.com/).")
652 assert_equal "Something important here.", r.description
655 def test_markdown_html_description
656 r = RichText.new("markdown", "<p>Can use HTML tags.</p>")
657 assert_equal "Can use HTML tags.", r.description
660 def test_markdown_description_max_length
661 m = RichText::DESCRIPTION_MAX_LENGTH
664 r = RichText.new("markdown", "x" * m)
665 assert_equal "x" * m, r.description
667 r = RichText.new("markdown", "y" * (m + 1))
668 assert_equal "#{'y' * (m - o)}...", r.description
670 r = RichText.new("markdown", "*zzzzzzzzz*z" * ((m + 1) / 10.0).ceil)
671 assert_equal "#{'z' * (m - o)}...", r.description
674 def test_markdown_description_word_break_threshold_length
675 m = RichText::DESCRIPTION_MAX_LENGTH
676 t = RichText::DESCRIPTION_WORD_BREAK_THRESHOLD_LENGTH
679 r = RichText.new("markdown", "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1) - 1)}")
680 assert_equal "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1) - 1)}", r.description
682 r = RichText.new("markdown", "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1))}")
683 assert_equal "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1) - 4)}...", r.description
685 r = RichText.new("markdown", "#{'x' * (t - o)} #{'y' * (m - (t - o) - 1)}")
686 assert_equal "#{'x' * (t - o)} #{'y' * (m - (t - o) - 1)}", r.description
688 r = RichText.new("markdown", "#{'x' * (t - o)} #{'y' * (m - (t - o))}")
689 assert_equal "#{'x' * (t - o)}...", r.description
692 def test_markdown_description_word_break_multiple_spaces
693 m = RichText::DESCRIPTION_MAX_LENGTH
694 t = RichText::DESCRIPTION_WORD_BREAK_THRESHOLD_LENGTH
697 r = RichText.new("markdown", "#{'x' * (t - o)} #{'y' * (m - (t - o - 1))}")
698 assert_equal "#{'x' * (t - o)}...", r.description
703 def assert_html(richtext, &block)
704 html = richtext.to_html
705 assert_predicate html, :html_safe?
706 root = Nokogiri::HTML::DocumentFragment.parse(html)
707 assert_select root, "*" do