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_text_to_html_dont_linkify_too_easily
491 # We used to allow this, but removed it because it
492 # caused issues in some languages.
493 # Eg: example below is Polish for "Reverted in 12345"
494 r = RichText.new("text", "Wycofane w 12345")
501 def test_deactivated_linkify_expansion_in_markdown
502 t0 = "foo `surface=metal` bar"
504 r1 = RichText.new("markdown", t0)
505 t1 = Nokogiri::HTML.fragment(r1.to_html).text
507 assert_equal t0.delete("`"), t1.strip
510 def test_text_to_html_linkify_trims_punctuation
511 r = RichText.new("text", "foo `surface=metal) bar inscription=🙂 baz")
513 assert_dom "a", :count => 1
514 assert_dom "a[href$='Tag:surface=metal']", :text => "surface=metal"
518 def test_text_to_html_linkify_recognizes_non_standard_wiki_pages
519 r_paren = RichText.new("text", "foo source=Isle_of_Man_Government_1:25000_map_(2007) bar")
520 assert_html r_paren do
521 assert_dom "a[href*='Tag:source']", :text => "source=Isle_of_Man_Government_1:25000_map_(2007)"
523 r_latin_ext = RichText.new("text", "foo cuisine=açaà bar")
524 assert_html r_latin_ext do
525 assert_dom "a[href*='Tag:cuisine']", :text => "cuisine=açaÃ"
527 r_cyrillic = RichText.new("text", "foo name=Продукты bar")
528 assert_html r_cyrillic do
529 assert_dom "a[href*='Tag:name']", :text => "name=Продукты"
531 r_cjk = RichText.new("text", "foo shop=園芸店 bar")
533 assert_dom "a[href*='Tag:shop']", :text => "shop=園芸店"
537 def test_text_to_html_email
538 r = RichText.new("text", "foo example@example.com bar")
544 def test_text_to_html_escape
545 r = RichText.new("text", "foo < bar & baz > qux")
547 assert_select "p", "foo < bar & baz > qux"
551 def test_text_to_text
552 r = RichText.new("text", "foo http://example.com/ bar")
553 assert_equal "foo http://example.com/ bar", r.to_text
556 def test_text_no_opengraph_properties
557 r = RichText.new("text", "foo https://example.com/ bar")
559 assert_nil r.image_alt
560 assert_nil r.description
563 def test_html_no_opengraph_properties
564 r = RichText.new("html", "foo <a href='https://example.com/'>bar</a> baz")
566 assert_nil r.image_alt
567 assert_nil r.description
570 def test_markdown_no_image
571 r = RichText.new("markdown", "foo [bar](https://example.com/) baz")
573 assert_nil r.image_alt
576 def test_markdown_image
577 r = RichText.new("markdown", "foo  baz")
578 assert_equal "https://example.com/image.jpg", r.image
579 assert_equal "bar", r.image_alt
582 def test_markdown_first_image
583 r = RichText.new("markdown", "foo  baz\nfoo  baz")
584 assert_equal "https://example.com/image1.jpg", r.image
585 assert_equal "bar1", r.image_alt
588 def test_markdown_image_with_empty_src
589 r = RichText.new("markdown", "![invalid]()")
591 assert_nil r.image_alt
594 def test_markdown_skip_image_with_empty_src
595 r = RichText.new("markdown", "![invalid]() ")
596 assert_equal "https://example.com/valid.gif", r.image
597 assert_equal "valid", r.image_alt
600 def test_markdown_html_image
601 r = RichText.new("markdown", "<img src='https://example.com/img_element.png' alt='alt text here'>")
602 assert_equal "https://example.com/img_element.png", r.image
603 assert_equal "alt text here", r.image_alt
606 def test_markdown_html_image_without_alt
607 r = RichText.new("markdown", "<img src='https://example.com/img_element.png'>")
608 assert_equal "https://example.com/img_element.png", r.image
609 assert_nil r.image_alt
612 def test_markdown_html_image_with_empty_src
613 r = RichText.new("markdown", "<img src='' alt='forgot src'>")
615 assert_nil r.image_alt
618 def test_markdown_skip_html_image_with_empty_src
619 r = RichText.new("markdown", "<img src='' alt='forgot src'> <img src='https://example.com/next_img_element.png' alt='have src'>")
620 assert_equal "https://example.com/next_img_element.png", r.image
621 assert_equal "have src", r.image_alt
624 def test_markdown_html_image_without_src
625 r = RichText.new("markdown", "<img alt='totally forgot src'>")
627 assert_nil r.image_alt
630 def test_markdown_skip_html_image_without_src
631 r = RichText.new("markdown", "<img alt='totally forgot src'> <img src='https://example.com/next_img_element.png' alt='have src'>")
632 assert_equal "https://example.com/next_img_element.png", r.image
633 assert_equal "have src", r.image_alt
636 def test_markdown_no_description
637 r = RichText.new("markdown", "#Nope")
638 assert_nil r.description
641 def test_markdown_description
642 r = RichText.new("markdown", "This is an article about something.")
643 assert_equal "This is an article about something.", r.description
646 def test_markdown_description_after_heading
647 r = RichText.new("markdown", "#Heading\n\nHere starts the text.")
648 assert_equal "Here starts the text.", r.description
651 def test_markdown_description_after_image
652 r = RichText.new("markdown", "\n\nThis is below the image.")
653 assert_equal "This is below the image.", r.description
656 def test_markdown_description_only_first_paragraph
657 r = RichText.new("markdown", "This thing.\n\nMaybe also that thing.")
658 assert_equal "This thing.", r.description
661 def test_markdown_description_elements
662 r = RichText.new("markdown", "*Something* **important** [here](https://example.com/).")
663 assert_equal "Something important here.", r.description
666 def test_markdown_html_description
667 r = RichText.new("markdown", "<p>Can use HTML tags.</p>")
668 assert_equal "Can use HTML tags.", r.description
671 def test_markdown_description_max_length
672 m = RichText::DESCRIPTION_MAX_LENGTH
675 r = RichText.new("markdown", "x" * m)
676 assert_equal "x" * m, r.description
678 r = RichText.new("markdown", "y" * (m + 1))
679 assert_equal "#{'y' * (m - o)}...", r.description
681 r = RichText.new("markdown", "*zzzzzzzzz*z" * ((m + 1) / 10.0).ceil)
682 assert_equal "#{'z' * (m - o)}...", r.description
685 def test_markdown_description_word_break_threshold_length
686 m = RichText::DESCRIPTION_MAX_LENGTH
687 t = RichText::DESCRIPTION_WORD_BREAK_THRESHOLD_LENGTH
690 r = RichText.new("markdown", "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1) - 1)}")
691 assert_equal "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1) - 1)}", r.description
693 r = RichText.new("markdown", "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1))}")
694 assert_equal "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1) - 4)}...", r.description
696 r = RichText.new("markdown", "#{'x' * (t - o)} #{'y' * (m - (t - o) - 1)}")
697 assert_equal "#{'x' * (t - o)} #{'y' * (m - (t - o) - 1)}", r.description
699 r = RichText.new("markdown", "#{'x' * (t - o)} #{'y' * (m - (t - o))}")
700 assert_equal "#{'x' * (t - o)}...", r.description
703 def test_markdown_description_word_break_multiple_spaces
704 m = RichText::DESCRIPTION_MAX_LENGTH
705 t = RichText::DESCRIPTION_WORD_BREAK_THRESHOLD_LENGTH
708 r = RichText.new("markdown", "#{'x' * (t - o)} #{'y' * (m - (t - o - 1))}")
709 assert_equal "#{'x' * (t - o)}...", r.description
714 def assert_html(richtext, &block)
715 html = richtext.to_html
716 assert_predicate html, :html_safe?
717 root = Nokogiri::HTML::DocumentFragment.parse(html)
718 assert_select root, "*" do