comparison .svn/pristine/4a/4a1058fec0661c02d0fe7d4f1388a59811624904.svn-base @ 1464:261b3d9a4903 redmine-2.4

Update to Redmine 2.4 branch rev 12663
author Chris Cannam
date Tue, 14 Jan 2014 14:37:42 +0000
parents
children
comparison
equal deleted inserted replaced
1296:038ba2d95de8 1464:261b3d9a4903
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.expand_path('../../../../../test_helper', __FILE__)
19 require 'digest/md5'
20
21 class Redmine::WikiFormatting::TextileFormatterTest < ActionView::TestCase
22
23 def setup
24 @formatter = Redmine::WikiFormatting::Textile::Formatter
25 end
26
27 MODIFIERS = {
28 "*" => 'strong', # bold
29 "_" => 'em', # italic
30 "+" => 'ins', # underline
31 "-" => 'del', # deleted
32 "^" => 'sup', # superscript
33 "~" => 'sub' # subscript
34 }
35
36 def test_modifiers
37 assert_html_output(
38 '*bold*' => '<strong>bold</strong>',
39 'before *bold*' => 'before <strong>bold</strong>',
40 '*bold* after' => '<strong>bold</strong> after',
41 '*two words*' => '<strong>two words</strong>',
42 '*two*words*' => '<strong>two*words</strong>',
43 '*two * words*' => '<strong>two * words</strong>',
44 '*two* *words*' => '<strong>two</strong> <strong>words</strong>',
45 '*(two)* *(words)*' => '<strong>(two)</strong> <strong>(words)</strong>',
46 # with class
47 '*(foo)two words*' => '<strong class="foo">two words</strong>'
48 )
49 end
50
51 def test_modifiers_combination
52 MODIFIERS.each do |m1, tag1|
53 MODIFIERS.each do |m2, tag2|
54 next if m1 == m2
55 text = "#{m2}#{m1}Phrase modifiers#{m1}#{m2}"
56 html = "<#{tag2}><#{tag1}>Phrase modifiers</#{tag1}></#{tag2}>"
57 assert_html_output text => html
58 end
59 end
60 end
61
62 def test_styles
63 # single style
64 assert_html_output({
65 'p{color:red}. text' => '<p style="color:red;">text</p>',
66 'p{color:red;}. text' => '<p style="color:red;">text</p>',
67 'p{color: red}. text' => '<p style="color: red;">text</p>',
68 'p{color:#f00}. text' => '<p style="color:#f00;">text</p>',
69 'p{color:#ff0000}. text' => '<p style="color:#ff0000;">text</p>',
70 'p{border:10px}. text' => '<p style="border:10px;">text</p>',
71 'p{border:10}. text' => '<p style="border:10;">text</p>',
72 'p{border:10%}. text' => '<p style="border:10%;">text</p>',
73 'p{border:10em}. text' => '<p style="border:10em;">text</p>',
74 'p{border:1.5em}. text' => '<p style="border:1.5em;">text</p>',
75 'p{border-left:1px}. text' => '<p style="border-left:1px;">text</p>',
76 'p{border-right:1px}. text' => '<p style="border-right:1px;">text</p>',
77 'p{border-top:1px}. text' => '<p style="border-top:1px;">text</p>',
78 'p{border-bottom:1px}. text' => '<p style="border-bottom:1px;">text</p>',
79 }, false)
80
81 # multiple styles
82 assert_html_output({
83 'p{color:red; border-top:1px}. text' => '<p style="color:red;border-top:1px;">text</p>',
84 'p{color:red ; border-top:1px}. text' => '<p style="color:red;border-top:1px;">text</p>',
85 'p{color:red;border-top:1px}. text' => '<p style="color:red;border-top:1px;">text</p>',
86 }, false)
87
88 # styles with multiple values
89 assert_html_output({
90 'p{border:1px solid red;}. text' => '<p style="border:1px solid red;">text</p>',
91 'p{border-top-left-radius: 10px 5px;}. text' => '<p style="border-top-left-radius: 10px 5px;">text</p>',
92 }, false)
93 end
94
95 def test_invalid_styles_should_be_filtered
96 assert_html_output({
97 'p{invalid}. text' => '<p>text</p>',
98 'p{invalid:red}. text' => '<p>text</p>',
99 'p{color:(red)}. text' => '<p>text</p>',
100 'p{color:red;invalid:blue}. text' => '<p style="color:red;">text</p>',
101 'p{invalid:blue;color:red}. text' => '<p style="color:red;">text</p>',
102 'p{color:"}. text' => '<p>p{color:"}. text</p>',
103 }, false)
104 end
105
106 def test_inline_code
107 assert_html_output(
108 'this is @some code@' => 'this is <code>some code</code>',
109 '@<Location /redmine>@' => '<code>&lt;Location /redmine&gt;</code>'
110 )
111 end
112
113 def test_nested_lists
114 raw = <<-RAW
115 # Item 1
116 # Item 2
117 ** Item 2a
118 ** Item 2b
119 # Item 3
120 ** Item 3a
121 RAW
122
123 expected = <<-EXPECTED
124 <ol>
125 <li>Item 1</li>
126 <li>Item 2
127 <ul>
128 <li>Item 2a</li>
129 <li>Item 2b</li>
130 </ul>
131 </li>
132 <li>Item 3
133 <ul>
134 <li>Item 3a</li>
135 </ul>
136 </li>
137 </ol>
138 EXPECTED
139
140 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
141 end
142
143 def test_escaping
144 assert_html_output(
145 'this is a <script>' => 'this is a &lt;script&gt;'
146 )
147 end
148
149 def test_use_of_backslashes_followed_by_numbers_in_headers
150 assert_html_output({
151 'h1. 2009\02\09' => '<h1>2009\02\09</h1>'
152 }, false)
153 end
154
155 def test_double_dashes_should_not_strikethrough
156 assert_html_output(
157 'double -- dashes -- test' => 'double -- dashes -- test',
158 'double -- *dashes* -- test' => 'double -- <strong>dashes</strong> -- test'
159 )
160 end
161
162 def test_abbreviations
163 assert_html_output(
164 'this is an abbreviation: GPL(General Public License)' => 'this is an abbreviation: <abbr title="General Public License">GPL</abbr>',
165 '2 letters JP(Jean-Philippe) abbreviation' => '2 letters <abbr title="Jean-Philippe">JP</abbr> abbreviation',
166 'GPL(This is a double-quoted "title")' => '<abbr title="This is a double-quoted &quot;title&quot;">GPL</abbr>'
167 )
168 end
169
170 def test_blockquote
171 # orig raw text
172 raw = <<-RAW
173 John said:
174 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
175 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
176 > * Donec odio lorem,
177 > * sagittis ac,
178 > * malesuada in,
179 > * adipiscing eu, dolor.
180 >
181 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
182 > Proin a tellus. Nam vel neque.
183
184 He's right.
185 RAW
186
187 # expected html
188 expected = <<-EXPECTED
189 <p>John said:</p>
190 <blockquote>
191 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.<br />
192 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
193 <ul>
194 <li>Donec odio lorem,</li>
195 <li>sagittis ac,</li>
196 <li>malesuada in,</li>
197 <li>adipiscing eu, dolor.</li>
198 </ul>
199 <blockquote>
200 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
201 </blockquote>
202 <p>Proin a tellus. Nam vel neque.</p>
203 </blockquote>
204 <p>He's right.</p>
205 EXPECTED
206
207 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
208 end
209
210 def test_table
211 raw = <<-RAW
212 This is a table with empty cells:
213
214 |cell11|cell12||
215 |cell21||cell23|
216 |cell31|cell32|cell33|
217 RAW
218
219 expected = <<-EXPECTED
220 <p>This is a table with empty cells:</p>
221
222 <table>
223 <tr><td>cell11</td><td>cell12</td><td></td></tr>
224 <tr><td>cell21</td><td></td><td>cell23</td></tr>
225 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
226 </table>
227 EXPECTED
228
229 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
230 end
231
232 def test_table_with_line_breaks
233 raw = <<-RAW
234 This is a table with line breaks:
235
236 |cell11
237 continued|cell12||
238 |-cell21-||cell23
239 cell23 line2
240 cell23 *line3*|
241 |cell31|cell32
242 cell32 line2|cell33|
243
244 RAW
245
246 expected = <<-EXPECTED
247 <p>This is a table with line breaks:</p>
248
249 <table>
250 <tr>
251 <td>cell11<br />continued</td>
252 <td>cell12</td>
253 <td></td>
254 </tr>
255 <tr>
256 <td><del>cell21</del></td>
257 <td></td>
258 <td>cell23<br/>cell23 line2<br/>cell23 <strong>line3</strong></td>
259 </tr>
260 <tr>
261 <td>cell31</td>
262 <td>cell32<br/>cell32 line2</td>
263 <td>cell33</td>
264 </tr>
265 </table>
266 EXPECTED
267
268 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
269 end
270
271 def test_tables_with_lists
272 raw = <<-RAW
273 This is a table with lists:
274
275 |cell11|cell12|
276 |cell21|ordered list
277 # item
278 # item 2|
279 |cell31|unordered list
280 * item
281 * item 2|
282
283 RAW
284
285 expected = <<-EXPECTED
286 <p>This is a table with lists:</p>
287
288 <table>
289 <tr>
290 <td>cell11</td>
291 <td>cell12</td>
292 </tr>
293 <tr>
294 <td>cell21</td>
295 <td>ordered list<br /># item<br /># item 2</td>
296 </tr>
297 <tr>
298 <td>cell31</td>
299 <td>unordered list<br />* item<br />* item 2</td>
300 </tr>
301 </table>
302 EXPECTED
303
304 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
305 end
306
307 def test_textile_should_not_mangle_brackets
308 assert_equal '<p>[msg1][msg2]</p>', to_html('[msg1][msg2]')
309 end
310
311 def test_textile_should_escape_image_urls
312 # this is onclick="alert('XSS');" in encoded form
313 raw = '!/images/comment.png"onclick=&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x27;&#x58;&#x53;&#x53;&#x27;&#x29;;&#x22;!'
314 expected = '<p><img src="/images/comment.png&quot;onclick=&amp;#x61;&amp;#x6c;&amp;#x65;&amp;#x72;&amp;#x74;&amp;#x28;&amp;#x27;&amp;#x58;&amp;#x53;&amp;#x53;&amp;#x27;&amp;#x29;;&amp;#x22;" alt="" /></p>'
315 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
316 end
317
318
319 STR_WITHOUT_PRE = [
320 # 0
321 "h1. Title
322
323 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.",
324 # 1
325 "h2. Heading 2
326
327 Maecenas sed elit sit amet mi accumsan vestibulum non nec velit. Proin porta tincidunt lorem, consequat rhoncus dolor fermentum in.
328
329 Cras ipsum felis, ultrices at porttitor vel, faucibus eu nunc.",
330 # 2
331 "h2. Heading 2
332
333 Morbi facilisis accumsan orci non pharetra.
334
335 h3. Heading 3
336
337 Nulla nunc nisi, egestas in ornare vel, posuere ac libero.",
338 # 3
339 "h3. Heading 3
340
341 Praesent eget turpis nibh, a lacinia nulla.",
342 # 4
343 "h2. Heading 2
344
345 Ut rhoncus elementum adipiscing."]
346
347 TEXT_WITHOUT_PRE = STR_WITHOUT_PRE.join("\n\n").freeze
348
349 def test_get_section_should_return_the_requested_section_and_its_hash
350 assert_section_with_hash STR_WITHOUT_PRE[1], TEXT_WITHOUT_PRE, 2
351 assert_section_with_hash STR_WITHOUT_PRE[2..3].join("\n\n"), TEXT_WITHOUT_PRE, 3
352 assert_section_with_hash STR_WITHOUT_PRE[3], TEXT_WITHOUT_PRE, 5
353 assert_section_with_hash STR_WITHOUT_PRE[4], TEXT_WITHOUT_PRE, 6
354
355 assert_section_with_hash '', TEXT_WITHOUT_PRE, 0
356 assert_section_with_hash '', TEXT_WITHOUT_PRE, 10
357 end
358
359 def test_update_section_should_update_the_requested_section
360 replacement = "New text"
361
362 assert_equal [STR_WITHOUT_PRE[0], replacement, STR_WITHOUT_PRE[2..4]].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(2, replacement)
363 assert_equal [STR_WITHOUT_PRE[0..1], replacement, STR_WITHOUT_PRE[4]].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(3, replacement)
364 assert_equal [STR_WITHOUT_PRE[0..2], replacement, STR_WITHOUT_PRE[4]].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(5, replacement)
365 assert_equal [STR_WITHOUT_PRE[0..3], replacement].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(6, replacement)
366
367 assert_equal TEXT_WITHOUT_PRE, @formatter.new(TEXT_WITHOUT_PRE).update_section(0, replacement)
368 assert_equal TEXT_WITHOUT_PRE, @formatter.new(TEXT_WITHOUT_PRE).update_section(10, replacement)
369 end
370
371 def test_update_section_with_hash_should_update_the_requested_section
372 replacement = "New text"
373
374 assert_equal [STR_WITHOUT_PRE[0], replacement, STR_WITHOUT_PRE[2..4]].flatten.join("\n\n"),
375 @formatter.new(TEXT_WITHOUT_PRE).update_section(2, replacement, Digest::MD5.hexdigest(STR_WITHOUT_PRE[1]))
376 end
377
378 def test_update_section_with_wrong_hash_should_raise_an_error
379 assert_raise Redmine::WikiFormatting::StaleSectionError do
380 @formatter.new(TEXT_WITHOUT_PRE).update_section(2, "New text", Digest::MD5.hexdigest("Old text"))
381 end
382 end
383
384 STR_WITH_PRE = [
385 # 0
386 "h1. Title
387
388 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.",
389 # 1
390 "h2. Heading 2
391
392 <pre><code class=\"ruby\">
393 def foo
394 end
395 </code></pre>
396
397 <pre><code><pre><code class=\"ruby\">
398 Place your code here.
399 </code></pre>
400 </code></pre>
401
402 Morbi facilisis accumsan orci non pharetra.
403
404 <pre>
405 Pre Content:
406
407 h2. Inside pre
408
409 <tag> inside pre block
410
411 Morbi facilisis accumsan orci non pharetra.
412 </pre>",
413 # 2
414 "h3. Heading 3
415
416 Nulla nunc nisi, egestas in ornare vel, posuere ac libero."]
417
418 def test_get_section_should_ignore_pre_content
419 text = STR_WITH_PRE.join("\n\n")
420
421 assert_section_with_hash STR_WITH_PRE[1..2].join("\n\n"), text, 2
422 assert_section_with_hash STR_WITH_PRE[2], text, 3
423 end
424
425 def test_update_section_should_not_escape_pre_content_outside_section
426 text = STR_WITH_PRE.join("\n\n")
427 replacement = "New text"
428
429 assert_equal [STR_WITH_PRE[0..1], "New text"].flatten.join("\n\n"),
430 @formatter.new(text).update_section(3, replacement)
431 end
432
433 def test_get_section_should_support_lines_with_spaces_before_heading
434 # the lines after Content 2 and Heading 4 contain a space
435 text = <<-STR
436 h1. Heading 1
437
438 Content 1
439
440 h1. Heading 2
441
442 Content 2
443
444 h1. Heading 3
445
446 Content 3
447
448 h1. Heading 4
449
450 Content 4
451 STR
452
453 [1, 2, 3, 4].each do |index|
454 assert_match /\Ah1. Heading #{index}.+Content #{index}/m, @formatter.new(text).get_section(index).first
455 end
456 end
457
458 def test_get_section_should_support_headings_starting_with_a_tab
459 text = <<-STR
460 h1.\tHeading 1
461
462 Content 1
463
464 h1. Heading 2
465
466 Content 2
467 STR
468
469 assert_match /\Ah1.\tHeading 1\s+Content 1\z/, @formatter.new(text).get_section(1).first
470 end
471
472 private
473
474 def assert_html_output(to_test, expect_paragraph = true)
475 to_test.each do |text, expected|
476 assert_equal(( expect_paragraph ? "<p>#{expected}</p>" : expected ), @formatter.new(text).to_html, "Formatting the following text failed:\n===\n#{text}\n===\n")
477 end
478 end
479
480 def to_html(text)
481 @formatter.new(text).to_html
482 end
483
484 def assert_section_with_hash(expected, text, index)
485 result = @formatter.new(text).get_section(index)
486
487 assert_kind_of Array, result
488 assert_equal 2, result.size
489 assert_equal expected, result.first, "section content did not match"
490 assert_equal Digest::MD5.hexdigest(expected), result.last, "section hash did not match"
491 end
492 end