]> git.wincent.com - wikitext.git/blob - spec/internal_link_spec.rb
Make it easier to set a link_proc
[wikitext.git] / spec / internal_link_spec.rb
1 # encoding: utf-8
2 # Copyright 2007-2013 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, 'internal links (space to underscore off)' do
28   before do
29     @parser = Wikitext::Parser.new :space_to_underscore => false
30   end
31
32   it 'should pass through unexpected link end tokens literally' do
33     @parser.parse('foo ]] bar').should == "<p>foo ]] bar</p>\n"                                     # in plain scope
34     @parser.parse("foo '']]'' bar").should == "<p>foo <em>]]</em> bar</p>\n"                        # in EM scope
35     @parser.parse("foo ''']]''' bar").should == "<p>foo <strong>]]</strong> bar</p>\n"              # in STRONG scope
36     @parser.parse("foo ''''']]''''' bar").should == "<p>foo <strong><em>]]</em></strong> bar</p>\n" # in STRONG_EM scope
37     @parser.parse('foo <tt>]]</tt> bar').should == "<p>foo <code>]]</code> bar</p>\n"               # in TT scope
38     @parser.parse('= foo ]] bar =').should == "<h1>foo ]] bar</h1>\n"                               # in H1 scope
39     @parser.parse('== foo ]] bar ==').should == "<h2>foo ]] bar</h2>\n"                             # in H2 scope
40     @parser.parse('=== foo ]] bar ===').should == "<h3>foo ]] bar</h3>\n"                           # in H3 scope
41     @parser.parse('==== foo ]] bar ====').should == "<h4>foo ]] bar</h4>\n"                         # in H4 scope
42     @parser.parse('===== foo ]] bar =====').should == "<h5>foo ]] bar</h5>\n"                       # in H5 scope
43     @parser.parse('====== foo ]] bar ======').should == "<h6>foo ]] bar</h6>\n"                     # in H6 scope
44     @parser.parse('> ]]').should == "<blockquote>\n  <p>]]</p>\n</blockquote>\n"                    # in BLOCKQUOTE scope
45   end
46
47   it 'should turn single words into links' do
48     @parser.parse('[[foo]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
49   end
50
51   it 'should turn multiple words into links' do
52     @parser.parse('[[foo bar]]').should == %Q{<p><a href="/wiki/foo%20bar">foo bar</a></p>\n}
53   end
54
55   it 'should trim leading whitespace' do
56     @parser.parse('[[ foo]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
57     @parser.parse('[[  foo]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
58     @parser.parse('[[   foo]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
59     @parser.parse('[[    foo]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
60   end
61
62   it 'should trim trailing whitespace' do
63     @parser.parse('[[foo ]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
64     @parser.parse('[[foo  ]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
65     @parser.parse('[[foo   ]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
66     @parser.parse('[[foo    ]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}   # was a bug (exception)
67     @parser.parse('[[foo     ]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}  # was a bug (crash)
68   end
69
70   it 'should trim leading and trailing whitespace (combined)' do
71     @parser.parse('[[ foo    ]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
72     @parser.parse('[[  foo   ]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
73     @parser.parse('[[   foo  ]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
74     @parser.parse('[[    foo ]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
75   end
76
77   it 'should leave embedded whitespace intact' do
78     @parser.parse('[[ foo bar ]]').should == %Q{<p><a href="/wiki/foo%20bar">foo bar</a></p>\n}
79     @parser.parse('[[foo bar ]]').should == %Q{<p><a href="/wiki/foo%20bar">foo bar</a></p>\n}
80     @parser.parse('[[ foo bar ]]').should == %Q{<p><a href="/wiki/foo%20bar">foo bar</a></p>\n}
81   end
82
83   it 'should encode and sanitize quotes' do
84     # note how percent encoding is used in the href, and named entities in the link text
85     @parser.parse('[[hello "world"]]').should == %Q{<p><a href="/wiki/hello%20%22world%22">hello &quot;world&quot;</a></p>\n}
86   end
87
88   it 'should encode and sanitize ampersands' do
89     @parser.parse('[[a & b]]').should == %Q{<p><a href="/wiki/a%20%26%20b">a &amp; b</a></p>\n}
90   end
91
92   it 'should allow ampersand entities (special exception)' do
93     @parser.parse('[[a &amp; b]]').should == %Q{<p><a href="/wiki/a%20%26%20b">a &amp; b</a></p>\n}
94   end
95
96   it 'should allow quote entities (special exception)' do
97     @parser.parse('[[a &quot; b]]').should == %Q{<p><a href="/wiki/a%20%22%20b">a &quot; b</a></p>\n}
98   end
99
100   it 'should handle mixed scenarios (quotes, ampersands, non-ASCII characers)' do
101     expected = %Q{<p><a href="/wiki/foo%2c%20%22bar%22%20%26%20baz%20%e2%82%ac">foo, &quot;bar&quot; &amp; baz &#x20ac;</a></p>\n}
102     @parser.parse('[[foo, "bar" & baz €]]').should == expected
103   end
104
105   it 'should handle embedded paths' do
106     expected = %Q{<p><a href="/wiki/foo%2fbar">foo/bar</a></p>\n}
107     @parser.parse('[[foo/bar]]').should == expected
108   end
109
110   it 'should handle links in paragraph flows' do
111     expected = %Q{<p>foo <a href="/wiki/bar">bar</a> baz</p>\n}
112     @parser.parse('foo [[bar]] baz').should == expected # was a bug
113   end
114
115   describe '"red link" support' do
116     it 'accepts a Proc object via the optional "link_proc" parameter' do
117       @parser.parse('foo', :link_proc => Proc.new { }).should == %Q{<p>foo</p>\n}
118     end
119
120     it 'accepts a lambda via the optional "link_proc" parameter' do
121       @parser.parse('foo', :link_proc => lambda { }).should == %Q{<p>foo</p>\n}
122     end
123
124     it 'applies custom link CSS when supplied (Proc object version)' do
125       link_proc = Proc.new { |target| target == 'bar' ? 'redlink' : nil }
126       expected = %Q{<p><a href="/wiki/foo">foo</a> <a href="/wiki/bar" class="redlink">bar</a></p>\n}
127       @parser.parse('[[foo]] [[bar]]', :link_proc => link_proc).should == expected
128     end
129
130     it 'applies custom link CSS when supplied (lambda version)' do
131       link_proc = lambda { |target| target == 'bar' ? 'redlink' : nil }
132       expected = %Q{<p><a href="/wiki/foo">foo</a> <a href="/wiki/bar" class="redlink">bar</a></p>\n}
133       @parser.parse('[[foo]] [[bar]]', :link_proc => link_proc).should == expected
134     end
135
136     it 'uses a lamba passed in when the Parser is initialized' do
137       link_proc = lambda { |target| target == 'bar' ? 'redlink' : nil }
138       parser = Wikitext::Parser.new :link_proc => link_proc
139       expected = %Q{<p><a href="/wiki/foo">foo</a> <a href="/wiki/bar" class="redlink">bar</a></p>\n}
140       parser.parse('[[foo]] [[bar]]').should == expected
141     end
142
143     it 'uses a lamba set as an attribute on the Parser' do
144       link_proc = lambda { |target| target == 'bar' ? 'redlink' : nil }
145       parser = Wikitext::Parser.new
146       parser.link_proc = link_proc
147       expected = %Q{<p><a href="/wiki/foo">foo</a> <a href="/wiki/bar" class="redlink">bar</a></p>\n}
148       parser.parse('[[foo]] [[bar]]').should == expected
149     end
150
151     it 'should apply no custom link CSS when supplied nil (Proc object version)' do
152       expected = %Q{<p><a href="/wiki/foo">foo</a></p>\n}
153       @parser.parse('[[foo]]', :link_proc => Proc.new { |target| nil }).should == expected
154     end
155
156     it 'should apply no custom link CSS when supplied nil (lambda version)' do
157       expected = %Q{<p><a href="/wiki/foo">foo</a></p>\n}
158       @parser.parse('[[foo]]', :link_proc => lambda { |target| nil }).should == expected
159     end
160
161     it 'should let exceptions bubble up from the link proc (Proc object version)' do
162       lambda { @parser.parse('[[foo]]', :link_proc => Proc.new { |target| raise 'bar' }) }.should raise_error(RuntimeError, /bar/)
163     end
164
165     it 'should let exceptions bubble up from the link proc (lambda version)' do
166       lambda { @parser.parse('[[foo]]', :link_proc => lambda { |target| raise 'bar' }) }.should raise_error(RuntimeError, /bar/)
167     end
168
169     it 'should complain if the link proc returns a non-stringy object (Proc object version)' do
170       lambda {
171         @parser.parse '[[foo]]', :link_proc => Proc.new { 1 }
172       }.should raise_error(TypeError, /can't convert/)
173     end
174
175     it 'should complain if the link proc returns a non-stringy object (lambda version)' do
176       lambda {
177         @parser.parse '[[foo]]', :link_proc => lambda { 1 }
178       }.should raise_error(TypeError, /can't convert/)
179     end
180
181     # a couple of Ruby's idiosynchrasies: different behaviour of lambdas and Procs
182     it 'should not complain if the Proc object accepts too many arguments' do
183       lambda {
184         @parser.parse '[[foo]]', :link_proc => Proc.new { |a,b| }
185       }.should_not raise_error(ArgumentError, /wrong number/)
186     end
187
188     it 'should complain if the lambda accepts too many arguments' do
189       lambda {
190         @parser.parse '[[foo]]', :link_proc => lambda { |a,b| }
191       }.should raise_error(ArgumentError, /wrong number/)
192     end
193
194     it 'should complain when "return" is used inside a "Proc.new" block' do
195       lambda {
196         @parser.parse '[[foo]]', :link_proc => Proc.new { return 'bar' }
197       }.should raise_error(LocalJumpError)
198     end
199
200     it 'should not complain when "return" is used inside a lambda' do
201       lambda {
202         @parser.parse '[[foo]]', :link_proc => lambda { return 'bar' }
203       }.should_not raise_error(LocalJumpError)
204     end
205
206     it 'should interact correctly with spaces in link targets (Proc object version)' do
207       link_proc = Proc.new { |target| target == 'bar b' ? 'redlink' : nil }
208       expected = %Q{<p><a href="/wiki/foo%20a">foo a</a> <a href="/wiki/bar%20b" class="redlink">bar b</a></p>\n}
209       @parser.parse('[[foo a]] [[bar b]]', :link_proc => link_proc).should == expected
210     end
211
212     it 'should interact correctly with spaces in link targets (lambda version)' do
213       link_proc = lambda { |target| target == 'bar b' ? 'redlink' : nil }
214       expected = %Q{<p><a href="/wiki/foo%20a">foo a</a> <a href="/wiki/bar%20b" class="redlink">bar b</a></p>\n}
215       @parser.parse('[[foo a]] [[bar b]]', :link_proc => link_proc).should == expected
216     end
217
218     it 'should interact correctly with explicit link text (Proc object version)' do
219       link_proc = Proc.new { |target| target == 'bar b' ? 'redlink' : nil }
220       expected = %Q{<p><a href="/wiki/foo%20a">hello</a> <a href="/wiki/bar%20b" class="redlink">world</a></p>\n}
221       @parser.parse('[[foo a|hello]] [[bar b|world]]', :link_proc => link_proc).should == expected
222     end
223
224     it 'should interact correctly with explicit link text (lambda version)' do
225       link_proc = lambda { |target| target == 'bar b' ? 'redlink' : nil }
226       expected = %Q{<p><a href="/wiki/foo%20a">hello</a> <a href="/wiki/bar%20b" class="redlink">world</a></p>\n}
227       @parser.parse('[[foo a|hello]] [[bar b|world]]', :link_proc => link_proc).should == expected
228     end
229
230     it 'should handle link targets with encoded parts (Proc object version)' do
231       link_proc = Proc.new { |target| target == 'información' ? 'redlink' : nil }
232       expected = %Q{<p><a href="/wiki/informaci%c3%b3n" class="redlink">informaci&#x00f3;n</a> <a href="/wiki/bar">bar</a></p>\n}
233       @parser.parse('[[información]] [[bar]]', :link_proc => link_proc).should == expected
234     end
235
236     it 'should handle link targets with encoded parts (lambda version)' do
237       link_proc = lambda { |target| target == 'información' ? 'redlink' : nil }
238       expected = %Q{<p><a href="/wiki/informaci%c3%b3n" class="redlink">informaci&#x00f3;n</a> <a href="/wiki/bar">bar</a></p>\n}
239       @parser.parse('[[información]] [[bar]]', :link_proc => link_proc).should == expected
240     end
241   end
242
243   describe 'custom link text' do
244     it 'should recognize link text placed after the separator' do
245       @parser.parse('[[foo|bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
246     end
247
248     it 'should trim whitespace to the left of the separator' do
249       @parser.parse('[[foo |bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
250       @parser.parse('[[foo  |bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
251       @parser.parse('[[foo   |bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
252       @parser.parse('[[foo    |bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
253       @parser.parse('[[foo     |bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
254       @parser.parse('[[foo      |bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
255     end
256
257     it 'should trim whitespace to the right of the separator' do
258       @parser.parse('[[foo| bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
259       @parser.parse('[[foo|  bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
260       @parser.parse('[[foo|   bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
261       @parser.parse('[[foo|    bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
262       @parser.parse('[[foo|     bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
263       @parser.parse('[[foo|      bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
264     end
265
266     it 'should trim whitespace on both sides of the separator (at the same time)' do
267       @parser.parse('[[foo      | bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
268       @parser.parse('[[foo     |  bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
269       @parser.parse('[[foo    |   bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
270       @parser.parse('[[foo   |    bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
271       @parser.parse('[[foo  |     bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
272       @parser.parse('[[foo |      bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
273     end
274
275     it 'should trim trailing whitespace from the link text' do
276       @parser.parse('[[foo|bar ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
277       @parser.parse('[[foo|bar  ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
278       @parser.parse('[[foo|bar   ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
279       @parser.parse('[[foo|bar    ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
280       @parser.parse('[[foo|bar     ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
281       @parser.parse('[[foo|bar      ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
282     end
283
284     it 'should trim leading and trailing whitespace from the link text' do
285       @parser.parse('[[foo|      bar ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
286       @parser.parse('[[foo|     bar  ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
287       @parser.parse('[[foo|    bar   ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
288       @parser.parse('[[foo|   bar    ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
289       @parser.parse('[[foo|  bar     ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
290       @parser.parse('[[foo| bar      ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
291     end
292
293     it 'should treat a separator inside the link text as part of the link text' do
294       @parser.parse('[[foo|bar|baz]]').should == %Q{<p><a href="/wiki/foo">bar|baz</a></p>\n}
295     end
296
297     it 'should treat separators outside of links as normal text' do
298       @parser.parse('foo|bar').should == %Q{<p>foo|bar</p>\n}
299     end
300
301     it 'should allow em markup in the custom link text' do
302       expected = %Q{<p><a href="/wiki/foo">bar <em>baz</em></a></p>\n}
303       @parser.parse("[[foo|bar ''baz'']]").should == expected
304     end
305
306     it 'should automatically close unclosed em markup in the custom link text' do
307       expected = %Q{<p><a href="/wiki/foo">bar <em>baz</em></a></p>\n}
308       @parser.parse("[[foo|bar ''baz]]").should == expected
309     end
310
311     it 'should allow strong markup in the custom link text' do
312       expected = %Q{<p><a href="/wiki/foo">bar <strong>baz</strong></a></p>\n}
313       @parser.parse("[[foo|bar '''baz''']]").should == expected
314     end
315
316     it 'should automatically close unclosed strong markup in the custom link text' do
317       expected = %Q{<p><a href="/wiki/foo">bar <strong>baz</strong></a></p>\n}
318       @parser.parse("[[foo|bar '''baz]]").should == expected
319     end
320
321     it 'should allow strong/em markup in the custom link text' do
322       expected = %Q{<p><a href="/wiki/foo">bar <strong><em>baz</em></strong></a></p>\n}
323       @parser.parse("[[foo|bar '''''baz''''']]").should == expected
324     end
325
326     it 'should automatically close unclosed strong/em markup in the custom link text' do
327       expected = %Q{<p><a href="/wiki/foo">bar <strong><em>baz</em></strong></a></p>\n}
328       @parser.parse("[[foo|bar '''''baz]]").should == expected
329     end
330
331     it 'should allow tt markup in the custom link text' do
332       expected = %Q{<p><a href="/wiki/foo">bar <code>baz</code></a></p>\n}
333       @parser.parse('[[foo|bar <tt>baz</tt>]]').should == expected
334     end
335
336     it 'should automatically close unclosed tt markup in the custom link text' do
337       expected = %Q{<p><a href="/wiki/foo">bar <code>baz</code></a></p>\n}
338       @parser.parse('[[foo|bar <tt>baz]]').should == expected
339     end
340
341     it 'should allow named entities in the custom link text' do
342       expected = %Q{<p><a href="/wiki/foo">bar &copy;</a></p>\n}
343       @parser.parse('[[foo|bar &copy;]]').should == expected
344
345       # explicitly test &quot; because it is tokenized separately from the other named entities
346       expected = %Q{<p><a href="/wiki/foo">bar &quot;</a></p>\n}
347       @parser.parse('[[foo|bar &quot;]]').should == expected
348
349       # explicitly test &amp; because it is tokenized separately from the other named entities
350       expected = %Q{<p><a href="/wiki/foo">bar &amp;</a></p>\n}
351       @parser.parse('[[foo|bar &amp;]]').should == expected
352     end
353
354     it 'should allow decimal entities in the custom link text' do
355       expected = %Q{<p><a href="/wiki/foo">bar &#8364;</a></p>\n}
356       @parser.parse('[[foo|bar &#8364;]]').should == expected
357     end
358
359     it 'should allow hexadecimal entities in the custom link text' do
360       expected = %Q{<p><a href="/wiki/foo">bar &#x20ac;</a></p>\n}
361       @parser.parse('[[foo|bar &#x20ac;]]').should == expected
362     end
363
364     it 'should sanitize non-ASCII characters in the custom link text' do
365       expected = %Q{<p><a href="/wiki/foo">bar &#x20ac;</a></p>\n}
366       @parser.parse('[[foo|bar €]]').should == expected
367     end
368
369     it 'should sanitize characters that have special meaning in HTML in the custom link text' do
370       expected = %Q{<p><a href="/wiki/foo">bar &lt;</a></p>\n}
371       @parser.parse('[[foo|bar <]]').should == expected
372
373       expected = %Q{<p><a href="/wiki/foo">bar &gt;</a></p>\n}
374       @parser.parse('[[foo|bar >]]').should == expected
375
376       expected = %Q{<p><a href="/wiki/foo">bar &amp;</a></p>\n}
377       @parser.parse('[[foo|bar &]]').should == expected
378
379       expected = %Q{<p><a href="/wiki/foo">bar &quot;baz&quot;</a></p>\n}
380       @parser.parse('[[foo|bar "baz"]]').should == expected
381     end
382
383     it 'should allow nowiki markup in the custom link text' do
384       expected = %Q{<p><a href="/wiki/foo">bar [[</a></p>\n}
385       @parser.parse("[[foo|bar <nowiki>[[</nowiki>]]").should == expected
386
387       expected = %Q{<p><a href="/wiki/foo">bar [</a></p>\n}
388       @parser.parse("[[foo|bar <nowiki>[</nowiki>]]").should == expected
389
390       expected = %Q{<p><a href="/wiki/foo">bar ]]</a></p>\n}
391       @parser.parse("[[foo|bar <nowiki>]]</nowiki>]]").should == expected
392
393       expected = %Q{<p><a href="/wiki/foo">bar ]</a></p>\n}
394       @parser.parse("[[foo|bar <nowiki>]</nowiki>]]").should == expected
395     end
396
397     it 'should handle paths in custom link text' do
398       expected = %Q{<p><a href="/wiki/hello%2fworld">foo/bar</a></p>\n}
399       @parser.parse('[[hello/world|foo/bar]]').should == expected
400     end
401   end
402
403   describe 'overriding the link prefix' do
404     it 'should be able to override the link prefix' do
405       @parser.internal_link_prefix = '/custom/'
406       @parser.parse('[[foo]]').should == %Q{<p><a href="/custom/foo">foo</a></p>\n}
407     end
408
409     it 'should interpet a nil link prefix as meaning no prefix' do
410       @parser.internal_link_prefix = nil
411       @parser.parse('[[foo]]').should == %Q{<p><a href="foo">foo</a></p>\n}
412     end
413   end
414
415   # "special links" existed in internal links up to and including wikitext version 1.3.2
416   # from version 1.4.0 onwards this feature was changed to instead work with external links
417   # as such, all of these specs have been updated to make sure that the old behaviour was removed
418   describe 'special links' do
419     it 'should no longer recognize links of the form "bug/10" as special links' do
420       @parser.parse('[[bug/10]]').should    == %Q{<p><a href="/wiki/bug%2f10">bug/10</a></p>\n}
421       @parser.parse('[[issue/25]]').should  == %Q{<p><a href="/wiki/issue%2f25">issue/25</a></p>\n}
422       @parser.parse('[[post/7]]').should    == %Q{<p><a href="/wiki/post%2f7">post/7</a></p>\n}
423     end
424
425     it 'should no longer accept custom link text in conjunction with special links' do
426       @parser.parse('[[bug/10|bug #10]]').should == %Q{<p><a href="/wiki/bug%2f10">bug #10</a></p>\n}
427     end
428
429     it 'should not emit special links regardless of custom internal link prefix overrides' do
430       @parser.internal_link_prefix = '/custom/'
431       @parser.parse('[[bug/10]]').should == %Q{<p><a href="/custom/bug%2f10">bug/10</a></p>\n}
432     end
433
434     it 'should (still) not classify links as special merely because of the presence of a slash' do
435       @parser.parse('[[foo/bar]]').should == %Q{<p><a href="/wiki/foo%2fbar">foo/bar</a></p>\n}
436     end
437
438     it 'should (still) not accept special links which have a leading forward slash' do
439       @parser.parse('[[/bug/10]]').should == %Q{<p><a href="/wiki/%2fbug%2f10">/bug/10</a></p>\n}
440     end
441   end
442
443   describe 'invalid links' do
444     it 'should not allow entities in the link text' do
445       @parser.parse('[[a &euro; b]]').should == "<p>[[a &euro; b]]</p>\n"
446     end
447
448     it 'should not allow URIs in the link text' do
449       expected = %Q{<p>[[hello <a href="http://example.com/" class="external">http://example.com/</a> world]]</p>\n}
450       @parser.parse('[[hello http://example.com/ world]]').should == expected
451     end
452
453     it 'should handle embedded [[ inside links' do
454       # note how first part "[[foo " in itself is invalid and so gets rejected and echoed literally
455       expected = %Q{<p>[[foo <a href="/wiki/bar">bar</a></p>\n}
456       @parser.parse('[[foo [[bar]]').should == expected
457     end
458
459     it 'should handled embedded ]] inside links' do
460       # note how the link gets terminated early and the trailing part is rejected and echoed literally
461       expected = %Q{<p><a href="/wiki/foo">foo</a>bar]]</p>\n}
462       @parser.parse('[[foo ]]bar]]').should == expected
463     end
464
465     it 'should handle embedded [ inside links' do
466       # [ is not allowed at all so the entire link is rendered invalid
467       expected = "<p>[[foo [bar]]</p>\n"
468       @parser.parse('[[foo [bar]]').should == expected
469     end
470
471     it 'should handle embedded ] inside links' do
472       # [ is not allowed at all so the entire link is rendered invalid
473       expected = "<p>[[foo ]bar]]</p>\n"
474       @parser.parse('[[foo ]bar]]').should == expected
475     end
476
477     describe 'unterminated link targets (end-of-file)' do
478       it 'should rollback and show the unterminated link' do
479         @parser.parse('[[foo').should == %Q{<p>[[foo</p>\n}
480       end
481
482       it 'should not trim leading whitespace when rolling back' do
483         @parser.parse('[[ foo').should    == %Q{<p>[[ foo</p>\n}
484         @parser.parse('[[  foo').should   == %Q{<p>[[  foo</p>\n}
485         @parser.parse('[[   foo').should  == %Q{<p>[[   foo</p>\n}
486         @parser.parse('[[    foo').should == %Q{<p>[[    foo</p>\n}
487       end
488
489       it 'should not trim trailing whitespace when rolling back' do
490         @parser.parse('[[foo ').should    == %Q{<p>[[foo </p>\n}
491         @parser.parse('[[foo  ').should   == %Q{<p>[[foo  </p>\n}
492         @parser.parse('[[foo   ').should  == %Q{<p>[[foo   </p>\n}
493         @parser.parse('[[foo    ').should == %Q{<p>[[foo    </p>\n}
494       end
495
496       it 'should not trim leadig and trailing whitespace (combined) when rolling back' do
497         @parser.parse('[[    foo ').should == %Q{<p>[[    foo </p>\n}
498         @parser.parse('[[   foo  ').should == %Q{<p>[[   foo  </p>\n}
499         @parser.parse('[[  foo   ').should == %Q{<p>[[  foo   </p>\n}
500         @parser.parse('[[ foo    ').should == %Q{<p>[[ foo    </p>\n}
501       end
502     end
503
504     describe 'unterminated link targets (end-of-line)' do
505       it 'should rollback and show the unterminated link' do
506         @parser.parse("[[foo\n").should == %Q{<p>[[foo</p>\n}
507       end
508
509       it 'should not trim leading whitespace when rolling back' do
510         @parser.parse("[[ foo\n").should    == %Q{<p>[[ foo</p>\n}
511         @parser.parse("[[  foo\n").should   == %Q{<p>[[  foo</p>\n}
512         @parser.parse("[[   foo\n").should  == %Q{<p>[[   foo</p>\n}
513         @parser.parse("[[    foo\n").should == %Q{<p>[[    foo</p>\n}
514       end
515
516       it 'should not trim trailing whitespace when rolling back' do
517         @parser.parse("[[foo \n").should    == %Q{<p>[[foo </p>\n}
518         @parser.parse("[[foo  \n").should   == %Q{<p>[[foo  </p>\n}
519         @parser.parse("[[foo   \n").should  == %Q{<p>[[foo   </p>\n}
520         @parser.parse("[[foo    \n").should == %Q{<p>[[foo    </p>\n}
521       end
522
523       it 'should not trim leading and trailing whitespace (combined) when rolling back' do
524         @parser.parse("[[ foo    \n").should == %Q{<p>[[ foo    </p>\n}
525         @parser.parse("[[  foo   \n").should == %Q{<p>[[  foo   </p>\n}
526         @parser.parse("[[   foo  \n").should == %Q{<p>[[   foo  </p>\n}
527         @parser.parse("[[    foo \n").should == %Q{<p>[[    foo </p>\n}
528       end
529     end
530
531     describe 'missing link text' do
532       it 'should use link target' do
533         @parser.parse('[[foo|]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
534       end
535     end
536
537     describe 'link cut off at separator (end-of-file)' do
538       it 'should rollback and show the unterminated link' do
539         @parser.parse('[[foo|').should == %Q{<p>[[foo|</p>\n}
540         @parser.parse('[[foo| ').should == %Q{<p>[[foo| </p>\n}
541         @parser.parse('[[foo|  ').should == %Q{<p>[[foo|  </p>\n}
542         @parser.parse('[[foo|   ').should == %Q{<p>[[foo|   </p>\n}
543         @parser.parse('[[foo|    ').should == %Q{<p>[[foo|    </p>\n}
544         @parser.parse('[[foo|     ').should == %Q{<p>[[foo|     </p>\n}
545         @parser.parse('[[foo|      ').should == %Q{<p>[[foo|      </p>\n}
546       end
547     end
548
549     describe 'link cut off at separator (end-of-line)' do
550       it 'should rollback and show the unterminated link' do
551         @parser.parse("[[foo|\n").should == %Q{<p>[[foo|</p>\n}
552         @parser.parse("[[foo| \n").should == %Q{<p>[[foo| </p>\n}
553         @parser.parse("[[foo|  \n").should == %Q{<p>[[foo|  </p>\n}
554         @parser.parse("[[foo|   \n").should == %Q{<p>[[foo|   </p>\n}
555         @parser.parse("[[foo|    \n").should == %Q{<p>[[foo|    </p>\n}
556         @parser.parse("[[foo|     \n").should == %Q{<p>[[foo|     </p>\n}
557         @parser.parse("[[foo|      \n").should == %Q{<p>[[foo|      </p>\n}
558       end
559     end
560
561     describe 'unterminated link text (end-of-file)' do
562       it 'should rollback and show the unterminated link' do
563         @parser.parse('[[foo|hello').should == %Q{<p>[[foo|hello</p>\n}
564         @parser.parse('[[foo|hello ').should == %Q{<p>[[foo|hello </p>\n}
565         @parser.parse('[[foo|hello  ').should == %Q{<p>[[foo|hello  </p>\n}
566         @parser.parse('[[foo|hello   ').should == %Q{<p>[[foo|hello   </p>\n}
567         @parser.parse('[[foo|hello    ').should == %Q{<p>[[foo|hello    </p>\n}
568         @parser.parse('[[foo|hello     ').should == %Q{<p>[[foo|hello     </p>\n}
569         @parser.parse('[[foo|hello      ').should == %Q{<p>[[foo|hello      </p>\n}
570       end
571     end
572
573     describe 'unterminated link text (end-of-line)' do
574       it 'should rollback and show the unterminated link' do
575         @parser.parse("[[foo|hello\n").should == %Q{<p>[[foo|hello</p>\n}
576         @parser.parse("[[foo|hello \n").should == %Q{<p>[[foo|hello </p>\n}
577         @parser.parse("[[foo|hello  \n").should == %Q{<p>[[foo|hello  </p>\n}
578         @parser.parse("[[foo|hello   \n").should == %Q{<p>[[foo|hello   </p>\n}
579         @parser.parse("[[foo|hello    \n").should == %Q{<p>[[foo|hello    </p>\n}
580         @parser.parse("[[foo|hello     \n").should == %Q{<p>[[foo|hello     </p>\n}
581         @parser.parse("[[foo|hello      \n").should == %Q{<p>[[foo|hello      </p>\n}
582       end
583     end
584   end
585 end
586
587 describe Wikitext::Parser, 'internal links (space to underscore on)' do
588   before do
589     @parser = Wikitext::Parser.new
590   end
591
592   it 'should pass through unexpected link end tokens literally' do
593     @parser.parse('foo ]] bar').should == "<p>foo ]] bar</p>\n"                                     # in plain scope
594     @parser.parse("foo '']]'' bar").should == "<p>foo <em>]]</em> bar</p>\n"                        # in EM scope
595     @parser.parse("foo ''']]''' bar").should == "<p>foo <strong>]]</strong> bar</p>\n"              # in STRONG scope
596     @parser.parse("foo ''''']]''''' bar").should == "<p>foo <strong><em>]]</em></strong> bar</p>\n" # in STRONG_EM scope
597     @parser.parse('foo <tt>]]</tt> bar').should == "<p>foo <code>]]</code> bar</p>\n"               # in TT scope
598     @parser.parse('= foo ]] bar =').should == "<h1>foo ]] bar</h1>\n"                               # in H1 scope
599     @parser.parse('== foo ]] bar ==').should == "<h2>foo ]] bar</h2>\n"                             # in H2 scope
600     @parser.parse('=== foo ]] bar ===').should == "<h3>foo ]] bar</h3>\n"                           # in H3 scope
601     @parser.parse('==== foo ]] bar ====').should == "<h4>foo ]] bar</h4>\n"                         # in H4 scope
602     @parser.parse('===== foo ]] bar =====').should == "<h5>foo ]] bar</h5>\n"                       # in H5 scope
603     @parser.parse('====== foo ]] bar ======').should == "<h6>foo ]] bar</h6>\n"                     # in H6 scope
604     @parser.parse('> ]]').should == "<blockquote>\n  <p>]]</p>\n</blockquote>\n"                    # in BLOCKQUOTE scope
605   end
606
607   it 'should turn single words into links' do
608     @parser.parse('[[foo]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
609   end
610
611   it 'should turn multiple words into links, converting spaces into underscores' do
612     @parser.parse('[[foo bar]]').should == %Q{<p><a href="/wiki/foo_bar">foo bar</a></p>\n}
613   end
614
615   it 'should trim leading whitespace' do
616     @parser.parse('[[ foo]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
617     @parser.parse('[[  foo]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
618     @parser.parse('[[   foo]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
619     @parser.parse('[[    foo]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
620   end
621
622   it 'should trim trailing whitespace' do
623     @parser.parse('[[foo ]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
624     @parser.parse('[[foo  ]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
625     @parser.parse('[[foo   ]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
626     @parser.parse('[[foo    ]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}   # was a bug (exception)
627     @parser.parse('[[foo     ]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}  # was a bug (crash)
628   end
629
630   it 'should trim leading and trailing whitespace (combined)' do
631     @parser.parse('[[ foo    ]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
632     @parser.parse('[[  foo   ]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
633     @parser.parse('[[   foo  ]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
634     @parser.parse('[[    foo ]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
635   end
636
637   it 'should convert embedded whitespace into underscores' do
638     @parser.parse('[[ foo bar ]]').should == %Q{<p><a href="/wiki/foo_bar">foo bar</a></p>\n}
639     @parser.parse('[[foo bar ]]').should == %Q{<p><a href="/wiki/foo_bar">foo bar</a></p>\n}
640     @parser.parse('[[ foo bar ]]').should == %Q{<p><a href="/wiki/foo_bar">foo bar</a></p>\n}
641   end
642
643   it 'should encode and sanitize quotes' do
644     # note how percent encoding is used in the href, and named entities in the link text
645     @parser.parse('[[hello "world"]]').should == %Q{<p><a href="/wiki/hello_%22world%22">hello &quot;world&quot;</a></p>\n}
646   end
647
648   it 'should encode and sanitize ampersands' do
649     @parser.parse('[[a & b]]').should == %Q{<p><a href="/wiki/a_%26_b">a &amp; b</a></p>\n}
650   end
651
652   it 'should allow ampersand entities (special exception)' do
653     @parser.parse('[[a &amp; b]]').should == %Q{<p><a href="/wiki/a_%26_b">a &amp; b</a></p>\n}
654   end
655
656   it 'should allow quote entities (special exception)' do
657     @parser.parse('[[a &quot; b]]').should == %Q{<p><a href="/wiki/a_%22_b">a &quot; b</a></p>\n}
658   end
659
660   it 'should handle mixed scenarios (quotes, ampersands, non-ASCII characers)' do
661     expected = %Q{<p><a href="/wiki/foo%2c_%22bar%22_%26_baz_%e2%82%ac">foo, &quot;bar&quot; &amp; baz &#x20ac;</a></p>\n}
662     @parser.parse('[[foo, "bar" & baz €]]').should == expected
663   end
664
665   it 'should handle links in paragraph flows' do
666     expected = %Q{<p>foo <a href="/wiki/bar">bar</a> baz</p>\n}
667     @parser.parse('foo [[bar]] baz').should == expected # was a bug
668   end
669
670   describe '"red link" support' do
671     it 'should accept a Proc object via the optional "link_proc" parameter' do
672       @parser.parse('foo', :link_proc => Proc.new { }).should == %Q{<p>foo</p>\n}
673     end
674
675     it 'should accept a lambda via the optional "link_proc" parameter' do
676       @parser.parse('foo', :link_proc => lambda { }).should == %Q{<p>foo</p>\n}
677     end
678
679     it 'should apply custom link CSS when supplied (Proc object version)' do
680       link_proc = Proc.new { |target| target == 'bar' ? 'redlink' : nil }
681       expected = %Q{<p><a href="/wiki/foo">foo</a> <a href="/wiki/bar" class="redlink">bar</a></p>\n}
682       @parser.parse('[[foo]] [[bar]]', :link_proc => link_proc).should == expected
683     end
684
685     it 'should apply custom link CSS when supplied (lambda version)' do
686       link_proc = lambda { |target| target == 'bar' ? 'redlink' : nil }
687       expected = %Q{<p><a href="/wiki/foo">foo</a> <a href="/wiki/bar" class="redlink">bar</a></p>\n}
688       @parser.parse('[[foo]] [[bar]]', :link_proc => link_proc).should == expected
689     end
690
691     it 'should apply no custom link CSS when supplied nil (Proc object version)' do
692       expected = %Q{<p><a href="/wiki/foo">foo</a></p>\n}
693       @parser.parse('[[foo]]', :link_proc => Proc.new { |target| nil }).should == expected
694     end
695
696     it 'should apply no custom link CSS when supplied nil (lambda version)' do
697       expected = %Q{<p><a href="/wiki/foo">foo</a></p>\n}
698       @parser.parse('[[foo]]', :link_proc => lambda { |target| nil }).should == expected
699     end
700
701     it 'should let exceptions bubble up from the link proc (Proc object version)' do
702       lambda { @parser.parse('[[foo]]', :link_proc => Proc.new { |target| raise 'bar' }) }.should raise_error(RuntimeError, /bar/)
703     end
704
705     it 'should let exceptions bubble up from the link proc (lambda version)' do
706       lambda { @parser.parse('[[foo]]', :link_proc => lambda { |target| raise 'bar' }) }.should raise_error(RuntimeError, /bar/)
707     end
708
709     it 'should complain if the link proc returns a non-stringy object (Proc object version)' do
710       lambda {
711         @parser.parse '[[foo]]', :link_proc => Proc.new { 1 }
712       }.should raise_error(TypeError, /can't convert/)
713     end
714
715     it 'should complain if the link proc returns a non-stringy object (lambda version)' do
716       lambda {
717         @parser.parse '[[foo]]', :link_proc => lambda { 1 }
718       }.should raise_error(TypeError, /can't convert/)
719     end
720
721     # a couple of Ruby's idiosynchrasies: different behaviour of lambdas and Procs
722     it 'should not complain if the Proc object accepts too many arguments' do
723       lambda {
724         @parser.parse '[[foo]]', :link_proc => Proc.new { |a,b| }
725       }.should_not raise_error(ArgumentError, /wrong number/)
726     end
727
728     it 'should complain if the lambda accepts too many arguments' do
729       lambda {
730         @parser.parse '[[foo]]', :link_proc => lambda { |a,b| }
731       }.should raise_error(ArgumentError, /wrong number/)
732     end
733
734     it 'should complain when "return" is used inside a "Proc.new" block' do
735       lambda {
736         @parser.parse '[[foo]]', :link_proc => Proc.new { return 'bar' }
737       }.should raise_error(LocalJumpError)
738     end
739
740     it 'should not complain when "return" is used inside a lambda' do
741       lambda {
742         @parser.parse '[[foo]]', :link_proc => lambda { return 'bar' }
743       }.should_not raise_error(LocalJumpError)
744     end
745
746     it 'should interact correctly with spaces in link targets (Proc object version)' do
747       link_proc = Proc.new { |target| target == 'bar b' ? 'redlink' : nil }
748       expected = %Q{<p><a href="/wiki/foo_a">foo a</a> <a href="/wiki/bar_b" class="redlink">bar b</a></p>\n}
749       @parser.parse('[[foo a]] [[bar b]]', :link_proc => link_proc).should == expected
750     end
751
752     it 'should interact correctly with spaces in link targets (lambda version)' do
753       link_proc = lambda { |target| target == 'bar b' ? 'redlink' : nil }
754       expected = %Q{<p><a href="/wiki/foo_a">foo a</a> <a href="/wiki/bar_b" class="redlink">bar b</a></p>\n}
755       @parser.parse('[[foo a]] [[bar b]]', :link_proc => link_proc).should == expected
756     end
757
758     it 'should interact correctly with explicit link text (Proc object version)' do
759       link_proc = Proc.new { |target| target == 'bar b' ? 'redlink' : nil }
760       expected = %Q{<p><a href="/wiki/foo_a">hello</a> <a href="/wiki/bar_b" class="redlink">world</a></p>\n}
761       @parser.parse('[[foo a|hello]] [[bar b|world]]', :link_proc => link_proc).should == expected
762     end
763
764     it 'should interact correctly with explicit link text (lambda version)' do
765       link_proc = lambda { |target| target == 'bar b' ? 'redlink' : nil }
766       expected = %Q{<p><a href="/wiki/foo_a">hello</a> <a href="/wiki/bar_b" class="redlink">world</a></p>\n}
767       @parser.parse('[[foo a|hello]] [[bar b|world]]', :link_proc => link_proc).should == expected
768     end
769
770     it 'should handle link targets with encoded parts (Proc object version)' do
771       link_proc = Proc.new { |target| target == 'información' ? 'redlink' : nil }
772       expected = %Q{<p><a href="/wiki/informaci%c3%b3n" class="redlink">informaci&#x00f3;n</a> <a href="/wiki/bar">bar</a></p>\n}
773       @parser.parse('[[información]] [[bar]]', :link_proc => link_proc).should == expected
774     end
775
776     it 'should handle link targets with encoded parts (lambda version)' do
777       link_proc = lambda { |target| target == 'información' ? 'redlink' : nil }
778       expected = %Q{<p><a href="/wiki/informaci%c3%b3n" class="redlink">informaci&#x00f3;n</a> <a href="/wiki/bar">bar</a></p>\n}
779       @parser.parse('[[información]] [[bar]]', :link_proc => link_proc).should == expected
780     end
781   end
782
783   describe 'custom link text' do
784     it 'should recognize link text placed after the separator' do
785       @parser.parse('[[foo|bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
786     end
787
788     it 'should trim whitespace to the left of the separator' do
789       @parser.parse('[[foo |bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
790       @parser.parse('[[foo  |bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
791       @parser.parse('[[foo   |bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
792       @parser.parse('[[foo    |bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
793       @parser.parse('[[foo     |bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
794       @parser.parse('[[foo      |bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
795     end
796
797     it 'should trim whitespace to the right of the separator' do
798       @parser.parse('[[foo| bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
799       @parser.parse('[[foo|  bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
800       @parser.parse('[[foo|   bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
801       @parser.parse('[[foo|    bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
802       @parser.parse('[[foo|     bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
803       @parser.parse('[[foo|      bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
804     end
805
806     it 'should trim whitespace on both sides of the separator (at the same time)' do
807       @parser.parse('[[foo      | bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
808       @parser.parse('[[foo     |  bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
809       @parser.parse('[[foo    |   bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
810       @parser.parse('[[foo   |    bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
811       @parser.parse('[[foo  |     bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
812       @parser.parse('[[foo |      bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
813     end
814
815     it 'should trim trailing whitespace from the link text' do
816       @parser.parse('[[foo|bar ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
817       @parser.parse('[[foo|bar  ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
818       @parser.parse('[[foo|bar   ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
819       @parser.parse('[[foo|bar    ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
820       @parser.parse('[[foo|bar     ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
821       @parser.parse('[[foo|bar      ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
822     end
823
824     it 'should trim leading and trailing whitespace from the link text' do
825       @parser.parse('[[foo|      bar ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
826       @parser.parse('[[foo|     bar  ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
827       @parser.parse('[[foo|    bar   ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
828       @parser.parse('[[foo|   bar    ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
829       @parser.parse('[[foo|  bar     ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
830       @parser.parse('[[foo| bar      ]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
831     end
832
833     it 'should treat a separator inside the link text as part of the link text' do
834       @parser.parse('[[foo|bar|baz]]').should == %Q{<p><a href="/wiki/foo">bar|baz</a></p>\n}
835     end
836
837     it 'should treat separators outside of links as normal text' do
838       @parser.parse('foo|bar').should == %Q{<p>foo|bar</p>\n}
839     end
840
841     it 'should allow em markup in the custom link text' do
842       expected = %Q{<p><a href="/wiki/foo">bar <em>baz</em></a></p>\n}
843       @parser.parse("[[foo|bar ''baz'']]").should == expected
844     end
845
846     it 'should automatically close unclosed em markup in the custom link text' do
847       expected = %Q{<p><a href="/wiki/foo">bar <em>baz</em></a></p>\n}
848       @parser.parse("[[foo|bar ''baz]]").should == expected
849     end
850
851     it 'should allow strong markup in the custom link text' do
852       expected = %Q{<p><a href="/wiki/foo">bar <strong>baz</strong></a></p>\n}
853       @parser.parse("[[foo|bar '''baz''']]").should == expected
854     end
855
856     it 'should automatically close unclosed strong markup in the custom link text' do
857       expected = %Q{<p><a href="/wiki/foo">bar <strong>baz</strong></a></p>\n}
858       @parser.parse("[[foo|bar '''baz]]").should == expected
859     end
860
861     it 'should allow strong/em markup in the custom link text' do
862       expected = %Q{<p><a href="/wiki/foo">bar <strong><em>baz</em></strong></a></p>\n}
863       @parser.parse("[[foo|bar '''''baz''''']]").should == expected
864     end
865
866     it 'should automatically close unclosed strong/em markup in the custom link text' do
867       expected = %Q{<p><a href="/wiki/foo">bar <strong><em>baz</em></strong></a></p>\n}
868       @parser.parse("[[foo|bar '''''baz]]").should == expected
869     end
870
871     it 'should allow tt markup in the custom link text' do
872       expected = %Q{<p><a href="/wiki/foo">bar <code>baz</code></a></p>\n}
873       @parser.parse('[[foo|bar <tt>baz</tt>]]').should == expected
874     end
875
876     it 'should automatically close unclosed tt markup in the custom link text' do
877       expected = %Q{<p><a href="/wiki/foo">bar <code>baz</code></a></p>\n}
878       @parser.parse('[[foo|bar <tt>baz]]').should == expected
879     end
880
881     it 'should allow named entities in the custom link text' do
882       expected = %Q{<p><a href="/wiki/foo">bar &copy;</a></p>\n}
883       @parser.parse('[[foo|bar &copy;]]').should == expected
884
885       # explicitly test &quot; because it is tokenized separately from the other named entities
886       expected = %Q{<p><a href="/wiki/foo">bar &quot;</a></p>\n}
887       @parser.parse('[[foo|bar &quot;]]').should == expected
888
889       # explicitly test &amp; because it is tokenized separately from the other named entities
890       expected = %Q{<p><a href="/wiki/foo">bar &amp;</a></p>\n}
891       @parser.parse('[[foo|bar &amp;]]').should == expected
892     end
893
894     it 'should allow decimal entities in the custom link text' do
895       expected = %Q{<p><a href="/wiki/foo">bar &#8364;</a></p>\n}
896       @parser.parse('[[foo|bar &#8364;]]').should == expected
897     end
898
899     it 'should allow hexadecimal entities in the custom link text' do
900       expected = %Q{<p><a href="/wiki/foo">bar &#x20ac;</a></p>\n}
901       @parser.parse('[[foo|bar &#x20ac;]]').should == expected
902     end
903
904     it 'should sanitize non-ASCII characters in the custom link text' do
905       expected = %Q{<p><a href="/wiki/foo">bar &#x20ac;</a></p>\n}
906       @parser.parse('[[foo|bar €]]').should == expected
907     end
908
909     it 'should sanitize characters that have special meaning in HTML in the custom link text' do
910       expected = %Q{<p><a href="/wiki/foo">bar &lt;</a></p>\n}
911       @parser.parse('[[foo|bar <]]').should == expected
912
913       expected = %Q{<p><a href="/wiki/foo">bar &gt;</a></p>\n}
914       @parser.parse('[[foo|bar >]]').should == expected
915
916       expected = %Q{<p><a href="/wiki/foo">bar &amp;</a></p>\n}
917       @parser.parse('[[foo|bar &]]').should == expected
918
919       expected = %Q{<p><a href="/wiki/foo">bar &quot;baz&quot;</a></p>\n}
920       @parser.parse('[[foo|bar "baz"]]').should == expected
921     end
922
923     it 'should allow nowiki markup in the custom link text' do
924       expected = %Q{<p><a href="/wiki/foo">bar [[</a></p>\n}
925       @parser.parse("[[foo|bar <nowiki>[[</nowiki>]]").should == expected
926
927       expected = %Q{<p><a href="/wiki/foo">bar [</a></p>\n}
928       @parser.parse("[[foo|bar <nowiki>[</nowiki>]]").should == expected
929
930       expected = %Q{<p><a href="/wiki/foo">bar ]]</a></p>\n}
931       @parser.parse("[[foo|bar <nowiki>]]</nowiki>]]").should == expected
932
933       expected = %Q{<p><a href="/wiki/foo">bar ]</a></p>\n}
934       @parser.parse("[[foo|bar <nowiki>]</nowiki>]]").should == expected
935     end
936   end
937
938   describe 'overriding the link prefix' do
939     it 'should be able to override the link prefix' do
940       @parser.internal_link_prefix = '/custom/'
941       @parser.parse('[[foo]]').should == %Q{<p><a href="/custom/foo">foo</a></p>\n}
942     end
943
944     it 'should interpet a nil link prefix as meaning no prefix' do
945       @parser.internal_link_prefix = nil
946       @parser.parse('[[foo]]').should == %Q{<p><a href="foo">foo</a></p>\n}
947     end
948   end
949
950   # see note above about "special links" being removed from internal links from 1.4.0 onwards
951   describe 'special links' do
952     it 'should no longer recognize links of the form "bug/10" as special links' do
953       @parser.parse('[[bug/10]]').should    == %Q{<p><a href="/wiki/bug%2f10">bug/10</a></p>\n}
954       @parser.parse('[[issue/25]]').should  == %Q{<p><a href="/wiki/issue%2f25">issue/25</a></p>\n}
955       @parser.parse('[[post/7]]').should    == %Q{<p><a href="/wiki/post%2f7">post/7</a></p>\n}
956     end
957
958     it 'should no longer accept custom link text in conjunction with special links' do
959       @parser.parse('[[bug/10|bug #10]]').should == %Q{<p><a href="/wiki/bug%2f10">bug #10</a></p>\n}
960     end
961
962     it 'should not emit special links regardless of custom internal link prefix overrides' do
963       @parser.internal_link_prefix = '/custom/'
964       @parser.parse('[[bug/10]]').should == %Q{<p><a href="/custom/bug%2f10">bug/10</a></p>\n}
965     end
966
967     it 'should (still) not classify links as special merely because of the presence of a slash' do
968       @parser.parse('[[foo/bar]]').should == %Q{<p><a href="/wiki/foo%2fbar">foo/bar</a></p>\n}
969     end
970
971     it 'should (still) not accept special links which have a leading forward slash' do
972       @parser.parse('[[/bug/10]]').should == %Q{<p><a href="/wiki/%2fbug%2f10">/bug/10</a></p>\n}
973     end
974   end
975
976   describe 'invalid links' do
977     it 'should not allow entities in the link text' do
978       @parser.parse('[[a &euro; b]]').should == "<p>[[a &euro; b]]</p>\n"
979     end
980
981     it 'should not allow URIs in the link text' do
982       expected = %Q{<p>[[hello <a href="http://example.com/" class="external">http://example.com/</a> world]]</p>\n}
983       @parser.parse('[[hello http://example.com/ world]]').should == expected
984     end
985
986     it 'should handle embedded [[ inside links' do
987       # note how first part "[[foo " in itself is invalid and so gets rejected and echoed literally
988       expected = %Q{<p>[[foo <a href="/wiki/bar">bar</a></p>\n}
989       @parser.parse('[[foo [[bar]]').should == expected
990     end
991
992     it 'should handled embedded ]] inside links' do
993       # note how the link gets terminated early and the trailing part is rejected and echoed literally
994       expected = %Q{<p><a href="/wiki/foo">foo</a>bar]]</p>\n}
995       @parser.parse('[[foo ]]bar]]').should == expected
996     end
997
998     it 'should handle embedded [ inside links' do
999       # [ is not allowed at all so the entire link is rendered invalid
1000       expected = "<p>[[foo [bar]]</p>\n"
1001       @parser.parse('[[foo [bar]]').should == expected
1002     end
1003
1004     it 'should handle embedded ] inside links' do
1005       # [ is not allowed at all so the entire link is rendered invalid
1006       expected = "<p>[[foo ]bar]]</p>\n"
1007       @parser.parse('[[foo ]bar]]').should == expected
1008     end
1009
1010     describe 'unterminated link targets (end-of-file)' do
1011       it 'should rollback and show the unterminated link' do
1012         @parser.parse('[[foo').should == %Q{<p>[[foo</p>\n}
1013       end
1014
1015       it 'should not trim leading whitespace when rolling back' do
1016         @parser.parse('[[ foo').should    == %Q{<p>[[ foo</p>\n}
1017         @parser.parse('[[  foo').should   == %Q{<p>[[  foo</p>\n}
1018         @parser.parse('[[   foo').should  == %Q{<p>[[   foo</p>\n}
1019         @parser.parse('[[    foo').should == %Q{<p>[[    foo</p>\n}
1020       end
1021
1022       it 'should not trim trailing whitespace when rolling back' do
1023         @parser.parse('[[foo ').should    == %Q{<p>[[foo </p>\n}
1024         @parser.parse('[[foo  ').should   == %Q{<p>[[foo  </p>\n}
1025         @parser.parse('[[foo   ').should  == %Q{<p>[[foo   </p>\n}
1026         @parser.parse('[[foo    ').should == %Q{<p>[[foo    </p>\n}
1027       end
1028
1029       it 'should not trim leadig and trailing whitespace (combined) when rolling back' do
1030         @parser.parse('[[    foo ').should == %Q{<p>[[    foo </p>\n}
1031         @parser.parse('[[   foo  ').should == %Q{<p>[[   foo  </p>\n}
1032         @parser.parse('[[  foo   ').should == %Q{<p>[[  foo   </p>\n}
1033         @parser.parse('[[ foo    ').should == %Q{<p>[[ foo    </p>\n}
1034       end
1035     end
1036
1037     describe 'unterminated link targets (end-of-line)' do
1038       it 'should rollback and show the unterminated link' do
1039         @parser.parse("[[foo\n").should == %Q{<p>[[foo</p>\n}
1040       end
1041
1042       it 'should not trim leading whitespace when rolling back' do
1043         @parser.parse("[[ foo\n").should    == %Q{<p>[[ foo</p>\n}
1044         @parser.parse("[[  foo\n").should   == %Q{<p>[[  foo</p>\n}
1045         @parser.parse("[[   foo\n").should  == %Q{<p>[[   foo</p>\n}
1046         @parser.parse("[[    foo\n").should == %Q{<p>[[    foo</p>\n}
1047       end
1048
1049       it 'should not trim trailing whitespace when rolling back' do
1050         @parser.parse("[[foo \n").should    == %Q{<p>[[foo </p>\n}
1051         @parser.parse("[[foo  \n").should   == %Q{<p>[[foo  </p>\n}
1052         @parser.parse("[[foo   \n").should  == %Q{<p>[[foo   </p>\n}
1053         @parser.parse("[[foo    \n").should == %Q{<p>[[foo    </p>\n}
1054       end
1055
1056       it 'should not trim leading and trailing whitespace (combined) when rolling back' do
1057         @parser.parse("[[ foo    \n").should == %Q{<p>[[ foo    </p>\n}
1058         @parser.parse("[[  foo   \n").should == %Q{<p>[[  foo   </p>\n}
1059         @parser.parse("[[   foo  \n").should == %Q{<p>[[   foo  </p>\n}
1060         @parser.parse("[[    foo \n").should == %Q{<p>[[    foo </p>\n}
1061       end
1062     end
1063
1064     describe 'missing link text' do
1065       it 'should use link target (zero-width link text)' do
1066         @parser.parse('[[foo|]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
1067       end
1068
1069       # was a bug in version <= 1.6
1070       # emitted: <p><a href="/wiki/foo"></a></p>\n
1071       it 'should use link target (blank link text)' do
1072         @parser.parse('[[foo| ]]').should == %Q{<p><a href="/wiki/foo">foo</a></p>\n}
1073       end
1074     end
1075
1076     describe 'link cut off at separator (end-of-file)' do
1077       it 'should rollback and show the unterminated link' do
1078         @parser.parse('[[foo|').should == %Q{<p>[[foo|</p>\n}
1079         @parser.parse('[[foo| ').should == %Q{<p>[[foo| </p>\n}
1080         @parser.parse('[[foo|  ').should == %Q{<p>[[foo|  </p>\n}
1081         @parser.parse('[[foo|   ').should == %Q{<p>[[foo|   </p>\n}
1082         @parser.parse('[[foo|    ').should == %Q{<p>[[foo|    </p>\n}
1083         @parser.parse('[[foo|     ').should == %Q{<p>[[foo|     </p>\n}
1084         @parser.parse('[[foo|      ').should == %Q{<p>[[foo|      </p>\n}
1085       end
1086     end
1087
1088     describe 'link cut off at separator (end-of-line)' do
1089       it 'should rollback and show the unterminated link' do
1090         @parser.parse("[[foo|\n").should == %Q{<p>[[foo|</p>\n}
1091         @parser.parse("[[foo| \n").should == %Q{<p>[[foo| </p>\n}
1092         @parser.parse("[[foo|  \n").should == %Q{<p>[[foo|  </p>\n}
1093         @parser.parse("[[foo|   \n").should == %Q{<p>[[foo|   </p>\n}
1094         @parser.parse("[[foo|    \n").should == %Q{<p>[[foo|    </p>\n}
1095         @parser.parse("[[foo|     \n").should == %Q{<p>[[foo|     </p>\n}
1096         @parser.parse("[[foo|      \n").should == %Q{<p>[[foo|      </p>\n}
1097       end
1098     end
1099
1100     describe 'unterminated link text (end-of-file)' do
1101       it 'should rollback and show the unterminated link' do
1102         @parser.parse('[[foo|hello').should == %Q{<p>[[foo|hello</p>\n}
1103         @parser.parse('[[foo|hello ').should == %Q{<p>[[foo|hello </p>\n}
1104         @parser.parse('[[foo|hello  ').should == %Q{<p>[[foo|hello  </p>\n}
1105         @parser.parse('[[foo|hello   ').should == %Q{<p>[[foo|hello   </p>\n}
1106         @parser.parse('[[foo|hello    ').should == %Q{<p>[[foo|hello    </p>\n}
1107         @parser.parse('[[foo|hello     ').should == %Q{<p>[[foo|hello     </p>\n}
1108         @parser.parse('[[foo|hello      ').should == %Q{<p>[[foo|hello      </p>\n}
1109       end
1110     end
1111
1112     describe 'unterminated link text (end-of-line)' do
1113       it 'should rollback and show the unterminated link' do
1114         @parser.parse("[[foo|hello\n").should == %Q{<p>[[foo|hello</p>\n}
1115         @parser.parse("[[foo|hello \n").should == %Q{<p>[[foo|hello </p>\n}
1116         @parser.parse("[[foo|hello  \n").should == %Q{<p>[[foo|hello  </p>\n}
1117         @parser.parse("[[foo|hello   \n").should == %Q{<p>[[foo|hello   </p>\n}
1118         @parser.parse("[[foo|hello    \n").should == %Q{<p>[[foo|hello    </p>\n}
1119         @parser.parse("[[foo|hello     \n").should == %Q{<p>[[foo|hello     </p>\n}
1120         @parser.parse("[[foo|hello      \n").should == %Q{<p>[[foo|hello      </p>\n}
1121       end
1122     end
1123   end
1124 end