comparison .svn/pristine/0f/0f05e49d9c9481ce6698da320e7a0f815ec9a184.svn-base @ 1298:4f746d8966dd redmine_2.3_integration

Merge from redmine-2.3 branch to create new branch redmine-2.3-integration
author Chris Cannam
date Fri, 14 Jun 2013 09:28:30 +0100
parents 622f24f53b42
children
comparison
equal deleted inserted replaced
1297:0a574315af3e 1298:4f746d8966dd
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_acronyms
163 assert_html_output(
164 'this is an acronym: GPL(General Public License)' => 'this is an acronym: <acronym title="General Public License">GPL</acronym>',
165 '2 letters JP(Jean-Philippe) acronym' => '2 letters <acronym title="Jean-Philippe">JP</acronym> acronym',
166 'GPL(This is a double-quoted "title")' => '<acronym title="This is a double-quoted &quot;title&quot;">GPL</acronym>'
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_textile_should_not_mangle_brackets
272 assert_equal '<p>[msg1][msg2]</p>', to_html('[msg1][msg2]')
273 end
274
275 def test_textile_should_escape_image_urls
276 # this is onclick="alert('XSS');" in encoded form
277 raw = '!/images/comment.png"onclick=&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x27;&#x58;&#x53;&#x53;&#x27;&#x29;;&#x22;!'
278 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>'
279 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
280 end
281
282
283 STR_WITHOUT_PRE = [
284 # 0
285 "h1. Title
286
287 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.",
288 # 1
289 "h2. Heading 2
290
291 Maecenas sed elit sit amet mi accumsan vestibulum non nec velit. Proin porta tincidunt lorem, consequat rhoncus dolor fermentum in.
292
293 Cras ipsum felis, ultrices at porttitor vel, faucibus eu nunc.",
294 # 2
295 "h2. Heading 2
296
297 Morbi facilisis accumsan orci non pharetra.
298
299 h3. Heading 3
300
301 Nulla nunc nisi, egestas in ornare vel, posuere ac libero.",
302 # 3
303 "h3. Heading 3
304
305 Praesent eget turpis nibh, a lacinia nulla.",
306 # 4
307 "h2. Heading 2
308
309 Ut rhoncus elementum adipiscing."]
310
311 TEXT_WITHOUT_PRE = STR_WITHOUT_PRE.join("\n\n").freeze
312
313 def test_get_section_should_return_the_requested_section_and_its_hash
314 assert_section_with_hash STR_WITHOUT_PRE[1], TEXT_WITHOUT_PRE, 2
315 assert_section_with_hash STR_WITHOUT_PRE[2..3].join("\n\n"), TEXT_WITHOUT_PRE, 3
316 assert_section_with_hash STR_WITHOUT_PRE[3], TEXT_WITHOUT_PRE, 5
317 assert_section_with_hash STR_WITHOUT_PRE[4], TEXT_WITHOUT_PRE, 6
318
319 assert_section_with_hash '', TEXT_WITHOUT_PRE, 0
320 assert_section_with_hash '', TEXT_WITHOUT_PRE, 10
321 end
322
323 def test_update_section_should_update_the_requested_section
324 replacement = "New text"
325
326 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)
327 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)
328 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)
329 assert_equal [STR_WITHOUT_PRE[0..3], replacement].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(6, replacement)
330
331 assert_equal TEXT_WITHOUT_PRE, @formatter.new(TEXT_WITHOUT_PRE).update_section(0, replacement)
332 assert_equal TEXT_WITHOUT_PRE, @formatter.new(TEXT_WITHOUT_PRE).update_section(10, replacement)
333 end
334
335 def test_update_section_with_hash_should_update_the_requested_section
336 replacement = "New text"
337
338 assert_equal [STR_WITHOUT_PRE[0], replacement, STR_WITHOUT_PRE[2..4]].flatten.join("\n\n"),
339 @formatter.new(TEXT_WITHOUT_PRE).update_section(2, replacement, Digest::MD5.hexdigest(STR_WITHOUT_PRE[1]))
340 end
341
342 def test_update_section_with_wrong_hash_should_raise_an_error
343 assert_raise Redmine::WikiFormatting::StaleSectionError do
344 @formatter.new(TEXT_WITHOUT_PRE).update_section(2, "New text", Digest::MD5.hexdigest("Old text"))
345 end
346 end
347
348 STR_WITH_PRE = [
349 # 0
350 "h1. Title
351
352 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.",
353 # 1
354 "h2. Heading 2
355
356 <pre><code class=\"ruby\">
357 def foo
358 end
359 </code></pre>
360
361 <pre><code><pre><code class=\"ruby\">
362 Place your code here.
363 </code></pre>
364 </code></pre>
365
366 Morbi facilisis accumsan orci non pharetra.
367
368 <pre>
369 Pre Content:
370
371 h2. Inside pre
372
373 <tag> inside pre block
374
375 Morbi facilisis accumsan orci non pharetra.
376 </pre>",
377 # 2
378 "h3. Heading 3
379
380 Nulla nunc nisi, egestas in ornare vel, posuere ac libero."]
381
382 def test_get_section_should_ignore_pre_content
383 text = STR_WITH_PRE.join("\n\n")
384
385 assert_section_with_hash STR_WITH_PRE[1..2].join("\n\n"), text, 2
386 assert_section_with_hash STR_WITH_PRE[2], text, 3
387 end
388
389 def test_update_section_should_not_escape_pre_content_outside_section
390 text = STR_WITH_PRE.join("\n\n")
391 replacement = "New text"
392
393 assert_equal [STR_WITH_PRE[0..1], "New text"].flatten.join("\n\n"),
394 @formatter.new(text).update_section(3, replacement)
395 end
396
397 def test_get_section_should_support_lines_with_spaces_before_heading
398 # the lines after Content 2 and Heading 4 contain a space
399 text = <<-STR
400 h1. Heading 1
401
402 Content 1
403
404 h1. Heading 2
405
406 Content 2
407
408 h1. Heading 3
409
410 Content 3
411
412 h1. Heading 4
413
414 Content 4
415 STR
416
417 [1, 2, 3, 4].each do |index|
418 assert_match /\Ah1. Heading #{index}.+Content #{index}/m, @formatter.new(text).get_section(index).first
419 end
420 end
421
422 def test_get_section_should_support_headings_starting_with_a_tab
423 text = <<-STR
424 h1.\tHeading 1
425
426 Content 1
427
428 h1. Heading 2
429
430 Content 2
431 STR
432
433 assert_match /\Ah1.\tHeading 1\s+Content 1\z/, @formatter.new(text).get_section(1).first
434 end
435
436 private
437
438 def assert_html_output(to_test, expect_paragraph = true)
439 to_test.each do |text, expected|
440 assert_equal(( expect_paragraph ? "<p>#{expected}</p>" : expected ), @formatter.new(text).to_html, "Formatting the following text failed:\n===\n#{text}\n===\n")
441 end
442 end
443
444 def to_html(text)
445 @formatter.new(text).to_html
446 end
447
448 def assert_section_with_hash(expected, text, index)
449 result = @formatter.new(text).get_section(index)
450
451 assert_kind_of Array, result
452 assert_equal 2, result.size
453 assert_equal expected, result.first, "section content did not match"
454 assert_equal Digest::MD5.hexdigest(expected), result.last, "section hash did not match"
455 end
456 end