]> git.openstreetmap.org Git - rails.git/blob - test/lib/rich_text_test.rb
Move spam scoring to own class
[rails.git] / test / lib / rich_text_test.rb
1 # frozen_string_literal: true
2
3 require "test_helper"
4
5 class RichTextTest < ActiveSupport::TestCase
6   include Rails::Dom::Testing::Assertions::SelectorAssertions
7
8   def test_html_to_html
9     r = RichText.new("html", "foo http://example.com/ bar")
10     assert_html r do
11       assert_select "a", 1
12       assert_select "a[href='http://example.com/']", 1
13       assert_select "a[rel='nofollow noopener noreferrer']", 1
14     end
15
16     r = RichText.new("html", "foo <a href='http://example.com/'>bar</a> baz")
17     assert_html r do
18       assert_select "a", 1
19       assert_select "a[href='http://example.com/']", 1
20       assert_select "a[rel='nofollow noopener noreferrer']", 1
21     end
22
23     r = RichText.new("html", "foo <a rel='junk me trash' href='http://example.com/'>bar</a> baz")
24     assert_html r do
25       assert_select "a", 1
26       assert_select "a[href='http://example.com/']", 1
27       assert_select "a[rel='me nofollow noopener noreferrer']", 1
28     end
29
30     r = RichText.new("html", "foo example@example.com bar")
31     assert_html r do
32       assert_select "a", 0
33     end
34
35     r = RichText.new("html", "foo <a href='mailto:example@example.com'>bar</a> baz")
36     assert_html r do
37       assert_select "a", 1
38       assert_select "a[href='mailto:example@example.com']", 1
39       assert_select "a[rel='nofollow noopener noreferrer']", 1
40     end
41
42     r = RichText.new("html", "foo <div>bar</div> baz")
43     assert_html r do
44       assert_select "div", false
45       assert_select "p", /^foo *bar *baz$/
46     end
47
48     r = RichText.new("html", "foo <script>bar = 1;</script> baz")
49     assert_html r do
50       assert_select "script", false
51       assert_select "p", /^foo *baz$/
52     end
53
54     r = RichText.new("html", "foo <style>div { display: none; }</style> baz")
55     assert_html r do
56       assert_select "style", false
57       assert_select "p", /^foo *baz$/
58     end
59
60     r = RichText.new("html", "<table><tr><td>column</td></tr></table>")
61     assert_html r do
62       assert_select "table[class='table table-sm w-auto']"
63     end
64
65     r = RichText.new("html", "<p class='btn btn-warning'>Click Me</p>")
66     assert_html r do
67       assert_select "p[class='btn btn-warning']", false
68       assert_select "p", /^Click Me$/
69     end
70
71     r = RichText.new("html", "<p style='color:red'>Danger</p>")
72     assert_html r do
73       assert_select "p[style='color:red']", false
74       assert_select "p", /^Danger$/
75     end
76   end
77
78   def test_html_to_text
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
81   end
82
83   def test_markdown_to_html
84     r = RichText.new("markdown", "foo http://example.com/ bar")
85     assert_html r do
86       assert_select "a", 1
87       assert_select "a[href='http://example.com/']", 1
88       assert_select "a[rel='nofollow noopener noreferrer']", 1
89     end
90
91     r = RichText.new("markdown", "foo [bar](http://example.com/) baz")
92     assert_html r do
93       assert_select "a", 1
94       assert_select "a[href='http://example.com/']", 1
95       assert_select "a[rel='nofollow noopener noreferrer']", 1
96     end
97
98     r = RichText.new("markdown", "foo <a rel='junk me trash' href='http://example.com/'>bar</a>) baz")
99     assert_html r do
100       assert_select "a", 1
101       assert_select "a[href='http://example.com/']", 1
102       assert_select "a[rel='me nofollow noopener noreferrer']", 1
103     end
104
105     r = RichText.new("markdown", "foo example@example.com bar")
106     assert_html r do
107       assert_select "a", 1
108       assert_select "a[href='mailto:example@example.com']", 1
109       assert_select "a[rel='nofollow noopener noreferrer']", 1
110     end
111
112     r = RichText.new("markdown", "foo [bar](mailto:example@example.com) bar")
113     assert_html r do
114       assert_select "a", 1
115       assert_select "a[href='mailto:example@example.com']", 1
116       assert_select "a[rel='nofollow noopener noreferrer']", 1
117     end
118
119     r = RichText.new("markdown", "foo ![bar](http://example.com/example.png) bar")
120     assert_html r do
121       assert_select "img", 1
122       assert_select "img[alt='bar']", 1
123       assert_select "img[src='http://example.com/example.png']", 1
124     end
125
126     r = RichText.new("markdown", "# foo bar baz")
127     assert_html r do
128       assert_select "h1", "foo bar baz"
129     end
130
131     r = RichText.new("markdown", "## foo bar baz")
132     assert_html r do
133       assert_select "h2", "foo bar baz"
134     end
135
136     r = RichText.new("markdown", "### foo bar baz")
137     assert_html r do
138       assert_select "h3", "foo bar baz"
139     end
140
141     r = RichText.new("markdown", "* foo bar baz")
142     assert_html r do
143       assert_select "ul" do
144         assert_select "li", "foo bar baz"
145       end
146     end
147
148     r = RichText.new("markdown", "1. foo bar baz")
149     assert_html r do
150       assert_select "ol" do
151         assert_select "li", "foo bar baz"
152       end
153     end
154
155     r = RichText.new("markdown", "foo *bar* _baz_ qux")
156     assert_html r do
157       assert_select "em", "bar"
158       assert_select "em", "baz"
159     end
160
161     r = RichText.new("markdown", "foo **bar** __baz__ qux")
162     assert_html r do
163       assert_select "strong", "bar"
164       assert_select "strong", "baz"
165     end
166
167     r = RichText.new("markdown", "foo `bar` baz")
168     assert_html r do
169       assert_select "code", "bar"
170     end
171
172     r = RichText.new("markdown", "    foo bar baz")
173     assert_html r do
174       assert_select "pre", /^\s*foo bar baz\s*$/
175     end
176
177     r = RichText.new("markdown", "|column|column")
178     assert_html r do
179       assert_select "table[class='table table-sm w-auto']"
180     end
181
182     r = RichText.new("markdown", "Click Me\n{:.btn.btn-warning}")
183     assert_html r do
184       assert_select "p[class='btn btn-warning']", false
185       assert_select "p", /^Click Me$/
186     end
187
188     r = RichText.new("markdown", "<p style='color:red'>Danger</p>")
189     assert_html r do
190       assert_select "p[style='color:red']", false
191       assert_select "p", /^Danger$/
192     end
193   end
194
195   def test_markdown_table_alignment
196     # Ensure that kramdown table alignment styles are converted to bootstrap classes
197     markdown_table = <<~MARKDOWN
198       | foo  | bar |
199       |:----:|----:|
200       |center|right|
201     MARKDOWN
202     r = RichText.new("markdown", markdown_table)
203     assert_html r do
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
208     end
209   end
210
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
214   end
215
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")
219       assert_html r do
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"
223         end
224       end
225     end
226   end
227
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")
231       assert_html r do
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"
235         end
236       end
237     end
238   end
239
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")
243       assert_html r do
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"
247         end
248       end
249     end
250   end
251
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")
255       assert_html r do
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"
259         end
260       end
261     end
262   end
263
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")
268       assert_html r do
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"
272         end
273       end
274     end
275   end
276
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")
281       assert_html r do
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"
285         end
286       end
287     end
288   end
289
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")
294       assert_html r do
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"
298         end
299       end
300     end
301   end
302
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")
307       assert_html r do
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"
311         end
312       end
313     end
314   end
315
316   def test_text_to_html_email
317     r = RichText.new("text", "foo example@example.com bar")
318     assert_html r do
319       assert_select "a", 0
320     end
321   end
322
323   def test_text_to_html_escape
324     r = RichText.new("text", "foo < bar & baz > qux")
325     assert_html r do
326       assert_select "p", "foo < bar & baz > qux"
327     end
328   end
329
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
333   end
334
335   def test_text_no_opengraph_properties
336     r = RichText.new("text", "foo https://example.com/ bar")
337     assert_nil r.image
338     assert_nil r.image_alt
339     assert_nil r.description
340   end
341
342   def test_html_no_opengraph_properties
343     r = RichText.new("html", "foo <a href='https://example.com/'>bar</a> baz")
344     assert_nil r.image
345     assert_nil r.image_alt
346     assert_nil r.description
347   end
348
349   def test_markdown_no_image
350     r = RichText.new("markdown", "foo [bar](https://example.com/) baz")
351     assert_nil r.image
352     assert_nil r.image_alt
353   end
354
355   def test_markdown_image
356     r = RichText.new("markdown", "foo ![bar](https://example.com/image.jpg) baz")
357     assert_equal "https://example.com/image.jpg", r.image
358     assert_equal "bar", r.image_alt
359   end
360
361   def test_markdown_first_image
362     r = RichText.new("markdown", "foo ![bar1](https://example.com/image1.jpg) baz\nfoo ![bar2](https://example.com/image2.jpg) baz")
363     assert_equal "https://example.com/image1.jpg", r.image
364     assert_equal "bar1", r.image_alt
365   end
366
367   def test_markdown_image_with_empty_src
368     r = RichText.new("markdown", "![invalid]()")
369     assert_nil r.image
370     assert_nil r.image_alt
371   end
372
373   def test_markdown_skip_image_with_empty_src
374     r = RichText.new("markdown", "![invalid]() ![valid](https://example.com/valid.gif)")
375     assert_equal "https://example.com/valid.gif", r.image
376     assert_equal "valid", r.image_alt
377   end
378
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
383   end
384
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
389   end
390
391   def test_markdown_html_image_with_empty_src
392     r = RichText.new("markdown", "<img src='' alt='forgot src'>")
393     assert_nil r.image
394     assert_nil r.image_alt
395   end
396
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
401   end
402
403   def test_markdown_html_image_without_src
404     r = RichText.new("markdown", "<img alt='totally forgot src'>")
405     assert_nil r.image
406     assert_nil r.image_alt
407   end
408
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
413   end
414
415   def test_markdown_no_description
416     r = RichText.new("markdown", "#Nope")
417     assert_nil r.description
418   end
419
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
423   end
424
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
428   end
429
430   def test_markdown_description_after_image
431     r = RichText.new("markdown", "![bar](https://example.com/image.jpg)\n\nThis is below the image.")
432     assert_equal "This is below the image.", r.description
433   end
434
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
438   end
439
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
443   end
444
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
448   end
449
450   def test_markdown_description_max_length
451     m = RichText::DESCRIPTION_MAX_LENGTH
452     o = 3 # "...".length
453
454     r = RichText.new("markdown", "x" * m)
455     assert_equal "x" * m, r.description
456
457     r = RichText.new("markdown", "y" * (m + 1))
458     assert_equal "#{'y' * (m - o)}...", r.description
459
460     r = RichText.new("markdown", "*zzzzzzzzz*z" * ((m + 1) / 10.0).ceil)
461     assert_equal "#{'z' * (m - o)}...", r.description
462   end
463
464   def test_markdown_description_word_break_threshold_length
465     m = RichText::DESCRIPTION_MAX_LENGTH
466     t = RichText::DESCRIPTION_WORD_BREAK_THRESHOLD_LENGTH
467     o = 3 # "...".length
468
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
471
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
474
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
477
478     r = RichText.new("markdown", "#{'x' * (t - o)} #{'y' * (m - (t - o))}")
479     assert_equal "#{'x' * (t - o)}...", r.description
480   end
481
482   def test_markdown_description_word_break_multiple_spaces
483     m = RichText::DESCRIPTION_MAX_LENGTH
484     t = RichText::DESCRIPTION_WORD_BREAK_THRESHOLD_LENGTH
485     o = 3 # "...".length
486
487     r = RichText.new("markdown", "#{'x' * (t - o)}  #{'y' * (m - (t - o - 1))}")
488     assert_equal "#{'x' * (t - o)}...", r.description
489   end
490
491   private
492
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
498       yield block
499     end
500   end
501 end