text_formatter_spec.rb 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. # frozen_string_literal: true
  2. require 'rails_helper'
  3. RSpec.describe TextFormatter do
  4. describe '#to_s' do
  5. subject { described_class.new(text, preloaded_accounts: preloaded_accounts).to_s }
  6. let(:preloaded_accounts) { nil }
  7. context 'when given text containing plain text' do
  8. let(:text) { 'text' }
  9. it 'paragraphizes the text' do
  10. expect(subject).to eq '<p>text</p>'
  11. end
  12. end
  13. context 'when given text containing line feeds' do
  14. let(:text) { "line\nfeed" }
  15. it 'removes line feeds' do
  16. expect(subject).to_not include "\n"
  17. end
  18. end
  19. context 'when given text containing linkable mentions' do
  20. let(:preloaded_accounts) { [Fabricate(:account, username: 'alice')] }
  21. let(:text) { '@alice' }
  22. it 'creates a mention link' do
  23. expect(subject).to include '<a href="https://cb6e6126.ngrok.io/@alice" class="u-url mention">@<span>alice</span></a></span>'
  24. end
  25. end
  26. context 'when given text containing unlinkable mentions' do
  27. let(:preloaded_accounts) { [] }
  28. let(:text) { '@alice' }
  29. it 'does not create a mention link' do
  30. expect(subject).to include '@alice'
  31. end
  32. end
  33. context 'when given a stand-alone medium URL' do
  34. let(:text) { 'https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4' }
  35. it 'matches the full URL' do
  36. expect(subject).to include 'href="https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4"'
  37. end
  38. end
  39. context 'when given a stand-alone google URL' do
  40. let(:text) { 'http://google.com' }
  41. it 'matches the full URL' do
  42. expect(subject).to include 'href="http://google.com"'
  43. end
  44. end
  45. context 'when given a stand-alone URL with a newer TLD' do
  46. let(:text) { 'http://example.gay' }
  47. it 'matches the full URL' do
  48. expect(subject).to include 'href="http://example.gay"'
  49. end
  50. end
  51. context 'when given a stand-alone IDN URL' do
  52. let(:text) { 'https://nic.みんな/' }
  53. it 'matches the full URL' do
  54. expect(subject).to include 'href="https://nic.みんな/"'
  55. end
  56. it 'has display URL' do
  57. expect(subject).to include '<span class="">nic.みんな/</span>'
  58. end
  59. end
  60. context 'when given a URL with a trailing period' do
  61. let(:text) { 'http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona. ' }
  62. it 'matches the full URL but not the period' do
  63. expect(subject).to include 'href="http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona"'
  64. end
  65. end
  66. context 'when given a URL enclosed with parentheses' do
  67. let(:text) { '(http://google.com/)' }
  68. it 'matches the full URL but not the parentheses' do
  69. expect(subject).to include 'href="http://google.com/"'
  70. end
  71. end
  72. context 'when given a URL with a trailing exclamation point' do
  73. let(:text) { 'http://www.google.com!' }
  74. it 'matches the full URL but not the exclamation point' do
  75. expect(subject).to include 'href="http://www.google.com"'
  76. end
  77. end
  78. context 'when given a URL with a trailing single quote' do
  79. let(:text) { "http://www.google.com'" }
  80. it 'matches the full URL but not the single quote' do
  81. expect(subject).to include 'href="http://www.google.com"'
  82. end
  83. end
  84. context 'when given a URL with a trailing angle bracket' do
  85. let(:text) { 'http://www.google.com>' }
  86. it 'matches the full URL but not the angle bracket' do
  87. expect(subject).to include 'href="http://www.google.com"'
  88. end
  89. end
  90. context 'when given a URL with a query string' do
  91. context 'with escaped unicode character' do
  92. let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink' }
  93. it 'matches the full URL' do
  94. expect(subject).to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&amp;q=autolink"'
  95. end
  96. end
  97. context 'with unicode character' do
  98. let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓&q=autolink' }
  99. it 'matches the full URL' do
  100. expect(subject).to include 'href="https://www.ruby-toolbox.com/search?utf8=✓&amp;q=autolink"'
  101. end
  102. end
  103. context 'with unicode character at the end' do
  104. let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓' }
  105. it 'matches the full URL' do
  106. expect(subject).to include 'href="https://www.ruby-toolbox.com/search?utf8=✓"'
  107. end
  108. end
  109. context 'with escaped and not escaped unicode characters' do
  110. let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&utf81=✓&q=autolink' }
  111. it 'preserves escaped unicode characters' do
  112. expect(subject).to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&amp;utf81=✓&amp;q=autolink"'
  113. end
  114. end
  115. end
  116. context 'when given a URL with parentheses in it' do
  117. let(:text) { 'https://en.wikipedia.org/wiki/Diaspora_(software)' }
  118. it 'matches the full URL' do
  119. expect(subject).to include 'href="https://en.wikipedia.org/wiki/Diaspora_(software)"'
  120. end
  121. end
  122. context 'when given a URL in quotation marks' do
  123. let(:text) { '"https://example.com/"' }
  124. it 'does not match the quotation marks' do
  125. expect(subject).to include 'href="https://example.com/"'
  126. end
  127. end
  128. context 'when given a URL in angle brackets' do
  129. let(:text) { '<https://example.com/>' }
  130. it 'does not match the angle brackets' do
  131. expect(subject).to include 'href="https://example.com/"'
  132. end
  133. end
  134. context 'when given a URL with Japanese path string' do
  135. let(:text) { 'https://ja.wikipedia.org/wiki/日本' }
  136. it 'matches the full URL' do
  137. expect(subject).to include 'href="https://ja.wikipedia.org/wiki/日本"'
  138. end
  139. end
  140. context 'when given a URL with Korean path string' do
  141. let(:text) { 'https://ko.wikipedia.org/wiki/대한민국' }
  142. it 'matches the full URL' do
  143. expect(subject).to include 'href="https://ko.wikipedia.org/wiki/대한민국"'
  144. end
  145. end
  146. context 'when given a URL with a full-width space' do
  147. let(:text) { 'https://example.com/ abc123' }
  148. it 'does not match the full-width space' do
  149. expect(subject).to include 'href="https://example.com/"'
  150. end
  151. end
  152. context 'when given a URL in Japanese quotation marks' do
  153. let(:text) { '「[https://example.org/」' }
  154. it 'does not match the quotation marks' do
  155. expect(subject).to include 'href="https://example.org/"'
  156. end
  157. end
  158. context 'when given a URL with Simplified Chinese path string' do
  159. let(:text) { 'https://baike.baidu.com/item/中华人民共和国' }
  160. it 'matches the full URL' do
  161. expect(subject).to include 'href="https://baike.baidu.com/item/中华人民共和国"'
  162. end
  163. end
  164. context 'when given a URL with Traditional Chinese path string' do
  165. let(:text) { 'https://zh.wikipedia.org/wiki/臺灣' }
  166. it 'matches the full URL' do
  167. expect(subject).to include 'href="https://zh.wikipedia.org/wiki/臺灣"'
  168. end
  169. end
  170. context 'when given a URL with trailing @ symbol' do
  171. let(:text) { 'https://gta.fandom.com/wiki/TW@ Content' }
  172. it 'matches the full URL' do
  173. expect(subject).to include 'href="https://gta.fandom.com/wiki/TW@"'
  174. end
  175. end
  176. context 'when given a URL containing unsafe code (XSS attack, visible part)' do
  177. let(:text) { 'http://example.com/b<del>b</del>' }
  178. it 'does not include the HTML in the URL' do
  179. expect(subject).to include '"http://example.com/b"'
  180. end
  181. it 'escapes the HTML' do
  182. expect(subject).to include '&lt;del&gt;b&lt;/del&gt;'
  183. end
  184. end
  185. context 'when given a URL containing unsafe code (XSS attack, invisible part)' do
  186. let(:text) { 'http://example.com/blahblahblahblah/a<script>alert("Hello")</script>' }
  187. it 'does not include the HTML in the URL' do
  188. expect(subject).to include '"http://example.com/blahblahblahblah/a"'
  189. end
  190. it 'escapes the HTML' do
  191. expect(subject).to include '&lt;script&gt;alert(&quot;Hello&quot;)&lt;/script&gt;'
  192. end
  193. end
  194. context 'when given text containing HTML code (script tag)' do
  195. let(:text) { '<script>alert("Hello")</script>' }
  196. it 'escapes the HTML' do
  197. expect(subject).to include '<p>&lt;script&gt;alert(&quot;Hello&quot;)&lt;/script&gt;</p>'
  198. end
  199. end
  200. context 'when given text containing HTML (XSS attack)' do
  201. let(:text) { %q{<img src="javascript:alert('XSS');">} }
  202. it 'escapes the HTML' do
  203. expect(subject).to include '<p>&lt;img src=&quot;javascript:alert(&#39;XSS&#39;);&quot;&gt;</p>'
  204. end
  205. end
  206. context 'when given an invalid URL' do
  207. let(:text) { 'http://www\.google\.com' }
  208. it 'outputs the raw URL' do
  209. expect(subject).to eq '<p>http://www\.google\.com</p>'
  210. end
  211. end
  212. context 'when given text containing a hashtag' do
  213. let(:text) { '#hashtag' }
  214. it 'creates a hashtag link' do
  215. expect(subject).to include '/tags/hashtag" class="mention hashtag" rel="tag">#<span>hashtag</span></a>'
  216. end
  217. end
  218. context 'when given text containing a hashtag with Unicode chars' do
  219. let(:text) { '#hashtagタグ' }
  220. it 'creates a hashtag link' do
  221. expect(subject).to include '/tags/hashtag%E3%82%BF%E3%82%B0" class="mention hashtag" rel="tag">#<span>hashtagタグ</span></a>'
  222. end
  223. end
  224. context 'when given text with a stand-alone xmpp: URI' do
  225. let(:text) { 'xmpp:user@instance.com' }
  226. it 'matches the full URI' do
  227. expect(subject).to include 'href="xmpp:user@instance.com"'
  228. end
  229. end
  230. context 'when given text with an xmpp: URI with a query-string' do
  231. let(:text) { 'please join xmpp:muc@instance.com?join right now' }
  232. it 'matches the full URI' do
  233. expect(subject).to include 'href="xmpp:muc@instance.com?join"'
  234. end
  235. end
  236. context 'when given text containing a magnet: URI' do
  237. let(:text) { 'wikipedia gives this example of a magnet uri: magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a' }
  238. it 'matches the full URI' do
  239. expect(subject).to include 'href="magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a"'
  240. end
  241. end
  242. end
  243. end