]> git.wincent.com - wikitext.git/blob - spec/external_link_spec.rb
Ensure ampersands in URIs are adequately entified
[wikitext.git] / spec / external_link_spec.rb
1 # encoding: utf-8
2 # Copyright 2007-2012 Wincent Colaiuta. All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are met:
6 #
7 # 1. Redistributions of source code must retain the above copyright notice,
8 #    this list of conditions and the following disclaimer.
9 # 2. Redistributions in binary form must reproduce the above copyright notice,
10 #    this list of conditions and the following disclaimer in the documentation
11 #    and/or other materials provided with the distribution.
12
13 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
17 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
23 # POSSIBILITY OF SUCH DAMAGE.
24
25 require 'spec_helper'
26
27 describe Wikitext::Parser, 'external links' do
28   before do
29     @parser = Wikitext::Parser.new
30   end
31
32   it 'should format valid external HTTP links' do
33     expected = %Q{<p><a href="http://google.com/" class="external">Google</a></p>\n}
34     @parser.parse('[http://google.com/ Google]').should == expected
35   end
36
37   it 'should format valid external HTTPS links' do
38     expected = %Q{<p><a href="https://google.com/" class="external">Google</a></p>\n}
39     @parser.parse('[https://google.com/ Google]').should == expected
40   end
41
42   it 'should format valid external FTP links' do
43     expected = %Q{<p><a href="ftp://google.com/" class="external">Google</a></p>\n}
44     @parser.parse('[ftp://google.com/ Google]').should == expected
45   end
46
47   it 'should format valid external SVN links' do
48     expected = %Q{<p><a href="svn://google.com/" class="external">Google</a></p>\n}
49     @parser.parse('[svn://google.com/ Google]').should == expected
50   end
51
52   it 'formats valid external mailto links' do
53     expected = %{<p><a href="mailto:user@example.com" class="mailto">john</a></p>\n}
54     @parser.parse('[mailto:user@example.com john]').should == expected
55   end
56
57   it 'does not treat raw email addresses as valid link targets' do
58     expected = %{<p>[<a href="mailto:user@example.com" class="mailto">user@example.com</a> john]</p>\n}
59     @parser.parse('[user@example.com john]').should == expected
60   end
61
62   it 'formats external mailto links where the linktext is itself an email' do
63     # reported here: https://wincent.com/issues/1955
64     expected = %{<p><a href="mailto:user@example.com" class="mailto">user@example.com</a></p>\n}
65     @parser.parse('[mailto:user@example.com user@example.com]').should == expected
66
67     # just in case that example seems a little contrived and trivial (you could
68     # work around it by instead just writing "user@example.com" in your markup),
69     # here's a more obviously useful one
70     expected = %{<p><a href="mailto:user@example.com" class="mailto">email me at user@example.com for more info</a></p>\n}
71     @parser.parse('[mailto:user@example.com email me at user@example.com for more info]').should == expected
72   end
73
74   it 'allows email addreses in link text' do
75     # more general case of bug reported here: https://wincent.com/issues/1955
76     expected = %{<p><a href="http://google.com/?q=user@example.com" class="external">Google for user@example.com</a></p>\n}
77     @parser.parse('[http://google.com/?q=user@example.com Google for user@example.com]').should == expected
78   end
79
80   it 'formats ampersands in link targets using entities' do
81     expected =%{<p><a href="http://google.com/?q=1&amp;lang=en" class="external">Google</a></p>\n}
82     @parser.parse('[http://google.com/?q=1&lang=en Google]').should == expected
83   end
84
85   it 'formats ampersands in URIs in link text' do
86     expected =%{<p><a href="http://google.com/?q=1&amp;lang=en" class="external">http://google.com/?q=1&amp;lang=en</a></p>\n}
87     @parser.parse('[http://google.com/?q=1&lang=en http://google.com/?q=1&lang=en]').should == expected
88   end
89
90   it 'should format absolute path links' do
91     expected = %Q{<p><a href="/foo/bar">fb</a></p>\n} # note no "external" class
92     @parser.parse('[/foo/bar fb]').should == expected
93   end
94
95   it 'should format deeply nested absolute path links' do
96     expected = %Q{<p><a href="/foo/bar/baz/bing">fb</a></p>\n} # note no "external" class
97     @parser.parse('[/foo/bar/baz/bing fb]').should == expected
98   end
99
100   it 'should format minimal absolute path links' do
101     expected = %Q{<p><a href="/">fb</a></p>\n} # note no "external" class
102     @parser.parse('[/ fb]').should == expected
103   end
104
105   it 'should format absolute path links with trailing slashes' do
106     expected = %Q{<p><a href="/foo/bar/">fb</a></p>\n} # note no "external" class
107     @parser.parse('[/foo/bar/ fb]').should == expected
108   end
109
110   it 'should not format relative path links' do
111     # relative paths don't make sense in wikitext because
112     # they could be displayed anywhere (eg. /wiki/article, /dashboard/ etc)
113     expected = %Q{<p>[foo/bar fb]</p>\n}
114     @parser.parse('[foo/bar fb]').should == expected
115   end
116
117   it 'should treat runs of spaces after the link target as a single space' do
118     expected = %Q{<p><a href="http://google.com/" class="external">Google</a></p>\n}
119     @parser.parse('[http://google.com/                  Google]').should == expected
120   end
121
122   it 'should not treat runs of spaces within the link text as a single space' do
123     expected = %Q{<p><a href="http://google.com/" class="external">Google    search</a></p>\n}
124     @parser.parse('[http://google.com/ Google    search]').should == expected
125   end
126
127   it 'should format a link with emphasis in the link text' do
128     expected = %Q{<p><a href="http://google.com/" class="external">Google <em>rocks</em></a></p>\n}
129     @parser.parse("[http://google.com/ Google ''rocks'']").should == expected
130   end
131
132   it "should automatically close unmatched '' tags in the link text" do
133     expected = %Q{<p><a href="http://google.com/" class="external">Google <em>SOC</em></a></p>\n}
134     @parser.parse("[http://google.com/ Google ''SOC]").should == expected
135   end
136
137   it 'should format a link with strong formatting in the link text' do
138     expected = %Q{<p><a href="http://google.com/" class="external"><strong>Google</strong> rocks</a></p>\n}
139     @parser.parse("[http://google.com/ '''Google''' rocks]").should == expected
140   end
141
142   it "should automatically close unmatched ''' tags in the link text" do
143     expected = %Q{<p><a href="http://google.com/" class="external">Google <strong>SOC</strong></a></p>\n}
144     @parser.parse("[http://google.com/ Google '''SOC]").should == expected
145   end
146
147   it 'should format a link with <tt></tt> tags in the link text' do
148     expected = %Q{<p><a href="http://google.com/" class="external">Google <code>SOC</code></a></p>\n}
149     @parser.parse("[http://google.com/ Google <tt>SOC</tt>]").should == expected
150   end
151
152   it 'should automatically close unmatched <tt> tags in the link text' do
153     expected = %Q{<p><a href="http://google.com/" class="external">Google <code>SOC</code></a></p>\n}
154     @parser.parse("[http://google.com/ Google <tt>SOC]").should == expected
155   end
156
157   it 'should format a link with strong and emphasis in the link text' do
158     expected = %Q{<p><a href="http://google.com/" class="external">Google <strong><em>rocks</em></strong></a></p>\n}
159     @parser.parse("[http://google.com/ Google '''''rocks''''']").should == expected
160   end
161
162   it "should automatically close unmatched ''''' tags in the link text" do
163     expected = %Q{<p><a href="http://google.com/" class="external">Google <strong><em>SOC</em></strong></a></p>\n}
164     @parser.parse("[http://google.com/ Google '''''SOC]").should == expected
165   end
166
167   it 'should respect "<nowiki></nowiki>" tags inside the link text' do
168     expected = %Q{<p><a href="http://google.com/" class="external">Google ] rocks</a></p>\n}
169     @parser.parse("[http://google.com/ Google <nowiki>]</nowiki> rocks]").should == expected  # was a bug
170
171     expected = %Q{<p><a href="http://google.com/" class="external">Google [ rocks</a></p>\n}
172     @parser.parse("[http://google.com/ Google <nowiki>[</nowiki> rocks]").should == expected  # was a bug
173   end
174
175   it 'should pass "[" in link text through literally' do
176     expected = %Q{<p><a href="http://google.com/" class="external">Google [ rocks</a></p>\n}
177     @parser.parse("[http://google.com/ Google [ rocks]").should == expected  # was a bug
178   end
179
180   it 'should pass "[[" in link text through literally' do
181     expected = %Q{<p><a href="http://google.com/" class="external">Google [[ rocks</a></p>\n}
182     @parser.parse("[http://google.com/ Google [[ rocks]").should == expected  # was a bug
183   end
184
185   it 'should pass "]]" in link text through literally' do
186     # note how "]]" is treated as a single token, not as a "]" which closes the link followed by another ("] rocks]")
187     expected = %Q{<p><a href="http://google.com/" class="external">Google ]] rocks</a></p>\n}
188     @parser.parse("[http://google.com/ Google ]] rocks]").should == expected  # was a bug
189   end
190
191   it 'should pass through ASCII entities in the link text' do
192     expected = %Q{<p><a href="http://google.com/" class="external">Google &quot;SOC&quot;</a></p>\n}  # QUOT
193     @parser.parse(%Q{[http://google.com/ Google "SOC"]}).should == expected
194     expected = %Q{<p><a href="http://google.com/" class="external">Google &lt;SOC&gt;</a></p>\n}      # LESS, GREATER
195     @parser.parse(%Q{[http://google.com/ Google <SOC>]}).should == expected
196     expected = %Q{<p><a href="http://google.com/" class="external">Google &amp; SOC</a></p>\n}        # AMP
197     @parser.parse(%Q{[http://google.com/ Google & SOC]}).should == expected
198   end
199
200   it 'should pass through named entities in the link text' do
201     expected = %Q{<p><a href="http://google.com/" class="external">Google &euro;</a></p>\n}
202     @parser.parse(%Q{[http://google.com/ Google &euro;]}).should == expected
203   end
204
205   it 'should pass through decimal entities in the link text' do
206     expected = %Q{<p><a href="http://google.com/" class="external">Google &#8364;</a></p>\n}
207     @parser.parse(%Q{[http://google.com/ Google &#8364;]}).should == expected
208   end
209
210   it 'should pass through hexadecimal entities in the link text' do
211     expected = %Q{<p><a href="http://google.com/" class="external">Google &#x20ac;</a></p>\n}
212     @parser.parse(%Q{[http://google.com/ Google &#x20ac;]}).should == expected
213   end
214
215   it 'should convert non-ASCII characters in the link text into entities' do
216     expected = %Q{<p><a href="http://google.com/" class="external">Google &#x20ac;</a></p>\n}
217     @parser.parse(%Q{[http://google.com/ Google €]}).should == expected
218   end
219
220   it 'should pass through unexpected external link end tokens literally' do
221     @parser.parse('foo ] bar').should == "<p>foo ] bar</p>\n"                                     # in plain scope
222     @parser.parse("foo '']'' bar").should == "<p>foo <em>]</em> bar</p>\n"                        # in EM scope
223     @parser.parse("foo ''']''' bar").should == "<p>foo <strong>]</strong> bar</p>\n"              # in STRONG scope
224     @parser.parse("foo ''''']''''' bar").should == "<p>foo <strong><em>]</em></strong> bar</p>\n" # in STRONG_EM scope
225     @parser.parse('foo <tt>]</tt> bar').should == "<p>foo <code>]</code> bar</p>\n"               # in TT scope
226     @parser.parse('= foo ] bar =').should == "<h1>foo ] bar</h1>\n"                               # in H1 scope
227     @parser.parse('== foo ] bar ==').should == "<h2>foo ] bar</h2>\n"                             # in H2 scope
228     @parser.parse('=== foo ] bar ===').should == "<h3>foo ] bar</h3>\n"                           # in H3 scope
229     @parser.parse('==== foo ] bar ====').should == "<h4>foo ] bar</h4>\n"                         # in H4 scope
230     @parser.parse('===== foo ] bar =====').should == "<h5>foo ] bar</h5>\n"                       # in H5 scope
231     @parser.parse('====== foo ] bar ======').should == "<h6>foo ] bar</h6>\n"                     # in H6 scope
232     @parser.parse('> ]').should == "<blockquote>\n  <p>]</p>\n</blockquote>\n"                    # in BLOCKQUOTE scope
233   end
234
235   describe '#external_link_rel attribute' do
236     it 'defaults to nil (external links do not have a rel attribute)' do
237       @parser.parse('http://google.com/').should == \
238         %Q{<p><a href="http://google.com/" class="external">http://google.com/</a></p>\n}
239     end
240
241     context 'set at parse time' do
242       it 'uses the rel attribute in external links' do
243         @parser.parse('http://google.com/', :external_link_rel => 'nofollow').should == \
244           %Q{<p><a href="http://google.com/" class="external" rel="nofollow">http://google.com/</a></p>\n}
245       end
246     end
247
248     context 'set at initialization time' do
249       let (:parser) { Wikitext::Parser.new :external_link_rel => 'nofollow' }
250
251       it 'uses the rel attribute in external links' do
252         parser.parse('http://google.com/').should == \
253           %Q{<p><a href="http://google.com/" class="external" rel="nofollow">http://google.com/</a></p>\n}
254       end
255
256       it 'is overrideable' do
257         parser.parse('http://google.com/', :external_link_rel => nil).should == \
258           %Q{<p><a href="http://google.com/" class="external">http://google.com/</a></p>\n}
259       end
260     end
261
262     context 'set via an accessor' do
263       let (:parser) do
264         parser = Wikitext::Parser.new
265         parser.external_link_rel = 'nofollow'
266         parser
267       end
268
269       it 'uses the rel attribute in external links' do
270         parser.parse('http://google.com/').should == \
271           %Q{<p><a href="http://google.com/" class="external" rel="nofollow">http://google.com/</a></p>\n}
272       end
273     end
274   end
275
276   describe 'questionable links' do
277     it 'should handle links which contain an embedded [ character' do
278       # note that [ is allowed in the link text, although the result may be unexpected to the user
279       expected = %Q{<p><a href="http://google.com/" class="external">[hello</a></p>\n}
280       @parser.parse("[http://google.com/ [hello]").should == expected
281     end
282
283     it 'should handle links which contain an embedded ] character' do
284       # note how the first ] terminates the link
285       expected = %Q{<p><a href="http://google.com/" class="external">[hello</a> world]</p>\n}
286       @parser.parse("[http://google.com/ [hello] world]").should == expected
287     end
288
289     it 'should handle links which contain an embedded [[ character' do
290       # note that [[ is allowed in the link text
291       expected = %Q{<p><a href="http://google.com/" class="external">[[hello</a></p>\n}
292       @parser.parse("[http://google.com/ [[hello]").should == expected
293     end
294
295     it 'should handle links which contain an embedded ]] character' do
296       # note how this time ]] does not terminate the link because it is tokenized as LINK_END rather than EXT_LINK_END
297       expected = %Q{<p><a href="http://google.com/" class="external">[[hello]] world</a></p>\n}
298       @parser.parse("[http://google.com/ [[hello]] world]").should == expected
299     end
300
301     it 'should allow URIs in the link text' do
302       # not sure why you'd want to do this, but...
303       expected = %Q{<p><a href="http://example.net/" class="external">hello http://example.com/ world</a></p>\n}
304       @parser.parse('[http://example.net/ hello http://example.com/ world]').should == expected
305     end
306   end
307
308   describe 'invalid links' do
309     it "should pass through links which don't have a valid target" do
310       expected = "<p>[well]</p>\n"
311       @parser.parse("[well]").should == expected
312     end
313
314     it "should pass through links which don't have any target" do
315       expected = "<p>[]</p>\n"
316       @parser.parse('[]').should == expected
317     end
318
319     it 'should pass through unterminated links (EOF)' do
320       expected = "<p>[</p>\n"
321       @parser.parse('[').should == expected
322
323       expected = "<p>[well</p>\n"
324       @parser.parse("[well").should == expected
325
326       expected = %Q{<p>[<a href="http://example.com/" class="external">http://example.com/</a></p>\n}
327       @parser.parse("[http://example.com/").should == expected
328
329       expected = %Q{<p>[<a href="http://example.com/" class="external">http://example.com/</a> </p>\n}
330       @parser.parse("[http://example.com/ ").should == expected
331
332       expected = %Q{<p>[<a href="http://example.com/" class="external">http://example.com/</a> visit</p>\n}
333       @parser.parse("[http://example.com/ visit").should == expected # was a bug
334
335       expected = %Q{<h6>[<a href="http://example.com/" class="external">http://example.com/</a> visit</h6>\n}
336       @parser.parse("====== [http://example.com/ visit").should == expected # was a bug
337
338       expected = %Q{<h5>[<a href="http://example.com/" class="external">http://example.com/</a> visit</h5>\n}
339       @parser.parse("===== [http://example.com/ visit").should == expected # was a bug
340
341       expected = %Q{<h4>[<a href="http://example.com/" class="external">http://example.com/</a> visit</h4>\n}
342       @parser.parse("==== [http://example.com/ visit").should == expected # was a bug
343
344       expected = %Q{<h3>[<a href="http://example.com/" class="external">http://example.com/</a> visit</h3>\n}
345       @parser.parse("=== [http://example.com/ visit").should == expected # was a bug
346
347       expected = %Q{<h2>[<a href="http://example.com/" class="external">http://example.com/</a> visit</h2>\n}
348       @parser.parse("== [http://example.com/ visit").should == expected # was a bug
349
350       expected = %Q{<h1>[<a href="http://example.com/" class="external">http://example.com/</a> visit</h1>\n}
351       @parser.parse("= [http://example.com/ visit").should == expected # was a bug
352
353       expected = %Q{<p>[<a href="http://example.com/" class="external">http://example.com/</a> ...</p>\n}
354       @parser.parse("[http://example.com/ <nowiki>...\n").should == expected
355     end
356
357     it 'should pass through unterminated links (end-of-line)' do
358       expected = "<p>[</p>\n"
359       @parser.parse("[\n").should == expected
360
361       expected = "<p>[well</p>\n"
362       @parser.parse("[well\n").should == expected
363
364       expected = %Q{<p>[<a href="http://example.com/" class="external">http://example.com/</a></p>\n}
365       @parser.parse("[http://example.com/\n").should == expected
366
367       expected = %Q{<p>[<a href="http://example.com/" class="external">http://example.com/</a> </p>\n}
368       @parser.parse("[http://example.com/ \n").should == expected
369
370       expected = %Q{<p>[<a href="http://example.com/" class="external">http://example.com/</a> visit</p>\n}
371       @parser.parse("[http://example.com/ visit\n").should == expected # was a bug
372
373       expected = %Q{<h6>[<a href="http://example.com/" class="external">http://example.com/</a> visit</h6>\n}
374       @parser.parse("====== [http://example.com/ visit\n").should == expected # was a bug
375
376       expected = %Q{<h5>[<a href="http://example.com/" class="external">http://example.com/</a> visit</h5>\n}
377       @parser.parse("===== [http://example.com/ visit\n").should == expected # was a bug
378
379       expected = %Q{<h4>[<a href="http://example.com/" class="external">http://example.com/</a> visit</h4>\n}
380       @parser.parse("==== [http://example.com/ visit\n").should == expected # was a bug
381
382       expected = %Q{<h3>[<a href="http://example.com/" class="external">http://example.com/</a> visit</h3>\n}
383       @parser.parse("=== [http://example.com/ visit\n").should == expected # was a bug
384
385       expected = %Q{<h2>[<a href="http://example.com/" class="external">http://example.com/</a> visit</h2>\n}
386       @parser.parse("== [http://example.com/ visit\n").should == expected # was a bug
387
388       expected = %Q{<h1>[<a href="http://example.com/" class="external">http://example.com/</a> visit</h1>\n}
389       @parser.parse("= [http://example.com/ visit\n").should == expected # was a bug
390
391       # here's a slightly more complicated example using a blockquote
392       expected = %Q{<blockquote>\n  <p>[<a href="http://google.com/" class="external">http://google.com/</a></p>\n</blockquote>\n}
393       @parser.parse("> [http://google.com/\n").should == expected # was a bug
394     end
395   end
396
397   describe 'regressions' do
398     # assorted examples
399     it 'should not turn failed absolute links into external hyperlinks' do
400       # was emitting: <p>[<a href="/hello" class="external">/hello</a> this</p>\n
401       expected =   %Q{<p>[<a href="/hello">/hello</a> this</p>\n}
402       @parser.parse('[/hello this').should == expected
403
404       # was emitting: <p>[<a href="/hello" class="external">/hello</a> </p>\n
405       expected =   %Q{<p>[<a href="/hello">/hello</a> </p>\n}
406       @parser.parse('[/hello ').should == expected
407
408       # was emitting: <h1>hello [<a href="/hello" class="external">/hello</a> </h1>\n
409       expected =   %Q{<h1>hello [<a href="/hello">/hello</a> </h1>\n}
410       @parser.parse('= hello [/hello =').should == expected
411     end
412   end
413 end