]> git.openstreetmap.org Git - rails.git/blob - test/lib/rich_text_test.rb
Add frozen_string_literal comments to ruby files
[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_html_spam_score
84     r = RichText.new("html", "foo <a href='http://example.com/'>bar</a> baz")
85     assert_equal 55, r.spam_score.round
86   end
87
88   def test_markdown_to_html
89     r = RichText.new("markdown", "foo http://example.com/ bar")
90     assert_html r do
91       assert_select "a", 1
92       assert_select "a[href='http://example.com/']", 1
93       assert_select "a[rel='nofollow noopener noreferrer']", 1
94     end
95
96     r = RichText.new("markdown", "foo [bar](http://example.com/) baz")
97     assert_html r do
98       assert_select "a", 1
99       assert_select "a[href='http://example.com/']", 1
100       assert_select "a[rel='nofollow noopener noreferrer']", 1
101     end
102
103     r = RichText.new("markdown", "foo <a rel='junk me trash' href='http://example.com/'>bar</a>) baz")
104     assert_html r do
105       assert_select "a", 1
106       assert_select "a[href='http://example.com/']", 1
107       assert_select "a[rel='me nofollow noopener noreferrer']", 1
108     end
109
110     r = RichText.new("markdown", "foo example@example.com bar")
111     assert_html r do
112       assert_select "a", 1
113       assert_select "a[href='mailto:example@example.com']", 1
114       assert_select "a[rel='nofollow noopener noreferrer']", 1
115     end
116
117     r = RichText.new("markdown", "foo [bar](mailto:example@example.com) bar")
118     assert_html r do
119       assert_select "a", 1
120       assert_select "a[href='mailto:example@example.com']", 1
121       assert_select "a[rel='nofollow noopener noreferrer']", 1
122     end
123
124     r = RichText.new("markdown", "foo ![bar](http://example.com/example.png) bar")
125     assert_html r do
126       assert_select "img", 1
127       assert_select "img[alt='bar']", 1
128       assert_select "img[src='http://example.com/example.png']", 1
129     end
130
131     r = RichText.new("markdown", "# foo bar baz")
132     assert_html r do
133       assert_select "h1", "foo bar baz"
134     end
135
136     r = RichText.new("markdown", "## foo bar baz")
137     assert_html r do
138       assert_select "h2", "foo bar baz"
139     end
140
141     r = RichText.new("markdown", "### foo bar baz")
142     assert_html r do
143       assert_select "h3", "foo bar baz"
144     end
145
146     r = RichText.new("markdown", "* foo bar baz")
147     assert_html r do
148       assert_select "ul" do
149         assert_select "li", "foo bar baz"
150       end
151     end
152
153     r = RichText.new("markdown", "1. foo bar baz")
154     assert_html r do
155       assert_select "ol" do
156         assert_select "li", "foo bar baz"
157       end
158     end
159
160     r = RichText.new("markdown", "foo *bar* _baz_ qux")
161     assert_html r do
162       assert_select "em", "bar"
163       assert_select "em", "baz"
164     end
165
166     r = RichText.new("markdown", "foo **bar** __baz__ qux")
167     assert_html r do
168       assert_select "strong", "bar"
169       assert_select "strong", "baz"
170     end
171
172     r = RichText.new("markdown", "foo `bar` baz")
173     assert_html r do
174       assert_select "code", "bar"
175     end
176
177     r = RichText.new("markdown", "    foo bar baz")
178     assert_html r do
179       assert_select "pre", /^\s*foo bar baz\s*$/
180     end
181
182     r = RichText.new("markdown", "|column|column")
183     assert_html r do
184       assert_select "table[class='table table-sm w-auto']"
185     end
186
187     r = RichText.new("markdown", "Click Me\n{:.btn.btn-warning}")
188     assert_html r do
189       assert_select "p[class='btn btn-warning']", false
190       assert_select "p", /^Click Me$/
191     end
192
193     r = RichText.new("markdown", "<p style='color:red'>Danger</p>")
194     assert_html r do
195       assert_select "p[style='color:red']", false
196       assert_select "p", /^Danger$/
197     end
198   end
199
200   def test_markdown_table_alignment
201     # Ensure that kramdown table alignment styles are converted to bootstrap classes
202     markdown_table = <<~MARKDOWN
203       | foo  | bar |
204       |:----:|----:|
205       |center|right|
206     MARKDOWN
207     r = RichText.new("markdown", markdown_table)
208     assert_html r do
209       assert_select "td[style='text-align:center']", false
210       assert_select "td[class='text-center']", true
211       assert_select "td[style='text-align:right']", false
212       assert_select "td[class='text-end']", true
213     end
214   end
215
216   def test_markdown_to_text
217     r = RichText.new("markdown", "foo [bar](http://example.com/) baz")
218     assert_equal "foo [bar](http://example.com/) baz", r.to_text
219   end
220
221   def test_markdown_spam_score
222     r = RichText.new("markdown", "foo [bar](http://example.com/) baz")
223     assert_equal 50, r.spam_score.round
224   end
225
226   def test_text_to_html_linkify
227     with_settings(:linkify_hosts => ["replace-me.example.com"], :linkify_hosts_replacement => "repl.example.com") do
228       r = RichText.new("text", "foo http://example.com/ bar")
229       assert_html r do
230         assert_dom "a", :count => 1, :text => "http://example.com/" do
231           assert_dom "> @href", "http://example.com/"
232           assert_dom "> @rel", "nofollow noopener noreferrer"
233         end
234       end
235     end
236   end
237
238   def test_text_to_html_linkify_replace
239     with_settings(:linkify_hosts => ["replace-me.example.com"], :linkify_hosts_replacement => "repl.example.com") do
240       r = RichText.new("text", "foo https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12 bar")
241       assert_html r do
242         assert_dom "a", :count => 1, :text => "repl.example.com/some/path?query=te<st&limit=20>10#result12" do
243           assert_dom "> @href", "https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12"
244           assert_dom "> @rel", "nofollow noopener noreferrer"
245         end
246       end
247     end
248   end
249
250   def test_text_to_html_linkify_replace_other_scheme
251     with_settings(:linkify_hosts => ["replace-me.example.com"], :linkify_hosts_replacement => "repl.example.com") do
252       r = RichText.new("text", "foo ftp://replace-me.example.com/some/path?query=te<st&limit=20>10#result12 bar")
253       assert_html r do
254         assert_dom "a", :count => 1, :text => "ftp://replace-me.example.com/some/path?query=te<st&limit=20>10#result12" do
255           assert_dom "> @href", "ftp://replace-me.example.com/some/path?query=te<st&limit=20>10#result12"
256           assert_dom "> @rel", "nofollow noopener noreferrer"
257         end
258       end
259     end
260   end
261
262   def test_text_to_html_linkify_replace_undefined
263     with_settings(:linkify_hosts => ["replace-me.example.com"], :linkify_hosts_replacement => nil) do
264       r = RichText.new("text", "foo https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12 bar")
265       assert_html r do
266         assert_dom "a", :count => 1, :text => "https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12" do
267           assert_dom "> @href", "https://replace-me.example.com/some/path?query=te<st&limit=20>10#result12"
268           assert_dom "> @rel", "nofollow noopener noreferrer"
269         end
270       end
271     end
272   end
273
274   def test_text_to_html_linkify_wiki_replace_prefix
275     with_settings(:linkify_wiki_hosts => ["replace-me-wiki.example.com"], :linkify_wiki_hosts_replacement => "wiki.example.com",
276                   :linkify_wiki_optional_path_prefix => "^/wiki(?=/[A-Z])") do
277       r = RichText.new("text", "foo https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal bar")
278       assert_html r do
279         assert_dom "a", :count => 1, :text => "wiki.example.com/Tag:surface%3Dmetal" do
280           assert_dom "> @href", "https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal"
281           assert_dom "> @rel", "nofollow noopener noreferrer"
282         end
283       end
284     end
285   end
286
287   def test_text_to_html_linkify_wiki_replace_prefix_undefined
288     with_settings(:linkify_wiki_hosts => ["replace-me-wiki.example.com"], :linkify_wiki_hosts_replacement => "wiki.example.com",
289                   :linkify_wiki_optional_path_prefix => nil) do
290       r = RichText.new("text", "foo https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal bar")
291       assert_html r do
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"
295         end
296       end
297     end
298   end
299
300   def test_text_to_html_linkify_wiki_replace_undefined_prefix
301     with_settings(:linkify_wiki_hosts => ["replace-me-wiki.example.com"], :linkify_wiki_hosts_replacement => nil,
302                   :linkify_wiki_optional_path_prefix => "^/wiki(?=/[A-Z])") do
303       r = RichText.new("text", "foo https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal bar")
304       assert_html r do
305         assert_dom "a", :count => 1, :text => "https://replace-me-wiki.example.com/Tag:surface%3Dmetal" do
306           assert_dom "> @href", "https://replace-me-wiki.example.com/wiki/Tag:surface%3Dmetal"
307           assert_dom "> @rel", "nofollow noopener noreferrer"
308         end
309       end
310     end
311   end
312
313   def test_text_to_html_linkify_wiki_replace_prefix_no_match
314     with_settings(:linkify_wiki_hosts => ["replace-me-wiki.example.com"], :linkify_wiki_hosts_replacement => "wiki.example.com",
315                   :linkify_wiki_optional_path_prefix => "^/wiki(?=/[A-Z])") do
316       r = RichText.new("text", "foo https://replace-me-wiki.example.com/wiki/w bar")
317       assert_html r do
318         assert_dom "a", :count => 1, :text => "wiki.example.com/wiki/w" do
319           assert_dom "> @href", "https://replace-me-wiki.example.com/wiki/w"
320           assert_dom "> @rel", "nofollow noopener noreferrer"
321         end
322       end
323     end
324   end
325
326   def test_text_to_html_email
327     r = RichText.new("text", "foo example@example.com bar")
328     assert_html r do
329       assert_select "a", 0
330     end
331   end
332
333   def test_text_to_html_escape
334     r = RichText.new("text", "foo < bar & baz > qux")
335     assert_html r do
336       assert_select "p", "foo < bar & baz > qux"
337     end
338   end
339
340   def test_text_to_text
341     r = RichText.new("text", "foo http://example.com/ bar")
342     assert_equal "foo http://example.com/ bar", r.to_text
343   end
344
345   def test_text_spam_score
346     r = RichText.new("text", "foo http://example.com/ bar")
347     assert_equal 141, r.spam_score.round
348   end
349
350   def test_text_no_opengraph_properties
351     r = RichText.new("text", "foo https://example.com/ bar")
352     assert_nil r.image
353     assert_nil r.image_alt
354     assert_nil r.description
355   end
356
357   def test_html_no_opengraph_properties
358     r = RichText.new("html", "foo <a href='https://example.com/'>bar</a> baz")
359     assert_nil r.image
360     assert_nil r.image_alt
361     assert_nil r.description
362   end
363
364   def test_markdown_no_image
365     r = RichText.new("markdown", "foo [bar](https://example.com/) baz")
366     assert_nil r.image
367     assert_nil r.image_alt
368   end
369
370   def test_markdown_image
371     r = RichText.new("markdown", "foo ![bar](https://example.com/image.jpg) baz")
372     assert_equal "https://example.com/image.jpg", r.image
373     assert_equal "bar", r.image_alt
374   end
375
376   def test_markdown_first_image
377     r = RichText.new("markdown", "foo ![bar1](https://example.com/image1.jpg) baz\nfoo ![bar2](https://example.com/image2.jpg) baz")
378     assert_equal "https://example.com/image1.jpg", r.image
379     assert_equal "bar1", r.image_alt
380   end
381
382   def test_markdown_image_with_empty_src
383     r = RichText.new("markdown", "![invalid]()")
384     assert_nil r.image
385     assert_nil r.image_alt
386   end
387
388   def test_markdown_skip_image_with_empty_src
389     r = RichText.new("markdown", "![invalid]() ![valid](https://example.com/valid.gif)")
390     assert_equal "https://example.com/valid.gif", r.image
391     assert_equal "valid", r.image_alt
392   end
393
394   def test_markdown_html_image
395     r = RichText.new("markdown", "<img src='https://example.com/img_element.png' alt='alt text here'>")
396     assert_equal "https://example.com/img_element.png", r.image
397     assert_equal "alt text here", r.image_alt
398   end
399
400   def test_markdown_html_image_without_alt
401     r = RichText.new("markdown", "<img src='https://example.com/img_element.png'>")
402     assert_equal "https://example.com/img_element.png", r.image
403     assert_nil r.image_alt
404   end
405
406   def test_markdown_html_image_with_empty_src
407     r = RichText.new("markdown", "<img src='' alt='forgot src'>")
408     assert_nil r.image
409     assert_nil r.image_alt
410   end
411
412   def test_markdown_skip_html_image_with_empty_src
413     r = RichText.new("markdown", "<img src='' alt='forgot src'> <img src='https://example.com/next_img_element.png' alt='have src'>")
414     assert_equal "https://example.com/next_img_element.png", r.image
415     assert_equal "have src", r.image_alt
416   end
417
418   def test_markdown_html_image_without_src
419     r = RichText.new("markdown", "<img alt='totally forgot src'>")
420     assert_nil r.image
421     assert_nil r.image_alt
422   end
423
424   def test_markdown_skip_html_image_without_src
425     r = RichText.new("markdown", "<img alt='totally forgot src'> <img src='https://example.com/next_img_element.png' alt='have src'>")
426     assert_equal "https://example.com/next_img_element.png", r.image
427     assert_equal "have src", r.image_alt
428   end
429
430   def test_markdown_no_description
431     r = RichText.new("markdown", "#Nope")
432     assert_nil r.description
433   end
434
435   def test_markdown_description
436     r = RichText.new("markdown", "This is an article about something.")
437     assert_equal "This is an article about something.", r.description
438   end
439
440   def test_markdown_description_after_heading
441     r = RichText.new("markdown", "#Heading\n\nHere starts the text.")
442     assert_equal "Here starts the text.", r.description
443   end
444
445   def test_markdown_description_after_image
446     r = RichText.new("markdown", "![bar](https://example.com/image.jpg)\n\nThis is below the image.")
447     assert_equal "This is below the image.", r.description
448   end
449
450   def test_markdown_description_only_first_paragraph
451     r = RichText.new("markdown", "This thing.\n\nMaybe also that thing.")
452     assert_equal "This thing.", r.description
453   end
454
455   def test_markdown_description_elements
456     r = RichText.new("markdown", "*Something* **important** [here](https://example.com/).")
457     assert_equal "Something important here.", r.description
458   end
459
460   def test_markdown_html_description
461     r = RichText.new("markdown", "<p>Can use HTML tags.</p>")
462     assert_equal "Can use HTML tags.", r.description
463   end
464
465   def test_markdown_description_max_length
466     m = RichText::DESCRIPTION_MAX_LENGTH
467     o = 3 # "...".length
468
469     r = RichText.new("markdown", "x" * m)
470     assert_equal "x" * m, r.description
471
472     r = RichText.new("markdown", "y" * (m + 1))
473     assert_equal "#{'y' * (m - o)}...", r.description
474
475     r = RichText.new("markdown", "*zzzzzzzzz*z" * ((m + 1) / 10.0).ceil)
476     assert_equal "#{'z' * (m - o)}...", r.description
477   end
478
479   def test_markdown_description_word_break_threshold_length
480     m = RichText::DESCRIPTION_MAX_LENGTH
481     t = RichText::DESCRIPTION_WORD_BREAK_THRESHOLD_LENGTH
482     o = 3 # "...".length
483
484     r = RichText.new("markdown", "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1) - 1)}")
485     assert_equal "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1) - 1)}", r.description
486
487     r = RichText.new("markdown", "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1))}")
488     assert_equal "#{'x' * (t - o - 1)} #{'y' * (m - (t - o - 1) - 4)}...", r.description
489
490     r = RichText.new("markdown", "#{'x' * (t - o)} #{'y' * (m - (t - o) - 1)}")
491     assert_equal "#{'x' * (t - o)} #{'y' * (m - (t - o) - 1)}", r.description
492
493     r = RichText.new("markdown", "#{'x' * (t - o)} #{'y' * (m - (t - o))}")
494     assert_equal "#{'x' * (t - o)}...", r.description
495   end
496
497   def test_markdown_description_word_break_multiple_spaces
498     m = RichText::DESCRIPTION_MAX_LENGTH
499     t = RichText::DESCRIPTION_WORD_BREAK_THRESHOLD_LENGTH
500     o = 3 # "...".length
501
502     r = RichText.new("markdown", "#{'x' * (t - o)}  #{'y' * (m - (t - o - 1))}")
503     assert_equal "#{'x' * (t - o)}...", r.description
504   end
505
506   private
507
508   def assert_html(richtext, &block)
509     html = richtext.to_html
510     assert_predicate html, :html_safe?
511     root = Nokogiri::HTML::DocumentFragment.parse(html)
512     assert_select root, "*" do
513       yield block
514     end
515   end
516 end