##// END OF EJS Templates
Merged r9822 from trunk....
Jean-Philippe Lang -
r9657:081ee54bee62
parent child
Show More
@@ -1,134 +1,134
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'redcloth3'
18 require 'redcloth3'
19 require 'digest/md5'
19 require 'digest/md5'
20
20
21 module Redmine
21 module Redmine
22 module WikiFormatting
22 module WikiFormatting
23 module Textile
23 module Textile
24 class Formatter < RedCloth3
24 class Formatter < RedCloth3
25 include ActionView::Helpers::TagHelper
25 include ActionView::Helpers::TagHelper
26 include Redmine::WikiFormatting::LinksHelper
26 include Redmine::WikiFormatting::LinksHelper
27
27
28 alias :inline_auto_link :auto_link!
28 alias :inline_auto_link :auto_link!
29 alias :inline_auto_mailto :auto_mailto!
29 alias :inline_auto_mailto :auto_mailto!
30
30
31 # auto_link rule after textile rules so that it doesn't break !image_url! tags
31 # auto_link rule after textile rules so that it doesn't break !image_url! tags
32 RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto]
32 RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto]
33
33
34 def initialize(*args)
34 def initialize(*args)
35 super
35 super
36 self.hard_breaks=true
36 self.hard_breaks=true
37 self.no_span_caps=true
37 self.no_span_caps=true
38 self.filter_styles=false
38 self.filter_styles=false
39 end
39 end
40
40
41 def to_html(*rules)
41 def to_html(*rules)
42 @toc = []
42 @toc = []
43 super(*RULES).to_s
43 super(*RULES).to_s
44 end
44 end
45
45
46 def get_section(index)
46 def get_section(index)
47 section = extract_sections(index)[1]
47 section = extract_sections(index)[1]
48 hash = Digest::MD5.hexdigest(section)
48 hash = Digest::MD5.hexdigest(section)
49 return section, hash
49 return section, hash
50 end
50 end
51
51
52 def update_section(index, update, hash=nil)
52 def update_section(index, update, hash=nil)
53 t = extract_sections(index)
53 t = extract_sections(index)
54 if hash.present? && hash != Digest::MD5.hexdigest(t[1])
54 if hash.present? && hash != Digest::MD5.hexdigest(t[1])
55 raise Redmine::WikiFormatting::StaleSectionError
55 raise Redmine::WikiFormatting::StaleSectionError
56 end
56 end
57 t[1] = update unless t[1].blank?
57 t[1] = update unless t[1].blank?
58 t.reject(&:blank?).join "\n\n"
58 t.reject(&:blank?).join "\n\n"
59 end
59 end
60
60
61 def extract_sections(index)
61 def extract_sections(index)
62 @pre_list = []
62 @pre_list = []
63 text = self.dup
63 text = self.dup
64 rip_offtags text, false, false
64 rip_offtags text, false, false
65 before = ''
65 before = ''
66 s = ''
66 s = ''
67 after = ''
67 after = ''
68 i = 0
68 i = 0
69 l = 1
69 l = 1
70 started = false
70 started = false
71 ended = false
71 ended = false
72 text.scan(/(((?:.*?)(\A|\r?\n\r?\n))(h(\d+)(#{A}#{C})\.(?::(\S+))? (.*?)$)|.*)/m).each do |all, content, lf, heading, level|
72 text.scan(/(((?:.*?)(\A|\r?\n\s*\r?\n))(h(\d+)(#{A}#{C})\.(?::(\S+))? (.*?)$)|.*)/m).each do |all, content, lf, heading, level|
73 if heading.nil?
73 if heading.nil?
74 if ended
74 if ended
75 after << all
75 after << all
76 elsif started
76 elsif started
77 s << all
77 s << all
78 else
78 else
79 before << all
79 before << all
80 end
80 end
81 break
81 break
82 end
82 end
83 i += 1
83 i += 1
84 if ended
84 if ended
85 after << all
85 after << all
86 elsif i == index
86 elsif i == index
87 l = level.to_i
87 l = level.to_i
88 before << content
88 before << content
89 s << heading
89 s << heading
90 started = true
90 started = true
91 elsif i > index
91 elsif i > index
92 s << content
92 s << content
93 if level.to_i > l
93 if level.to_i > l
94 s << heading
94 s << heading
95 else
95 else
96 after << heading
96 after << heading
97 ended = true
97 ended = true
98 end
98 end
99 else
99 else
100 before << all
100 before << all
101 end
101 end
102 end
102 end
103 sections = [before.strip, s.strip, after.strip]
103 sections = [before.strip, s.strip, after.strip]
104 sections.each {|section| smooth_offtags_without_code_highlighting section}
104 sections.each {|section| smooth_offtags_without_code_highlighting section}
105 sections
105 sections
106 end
106 end
107
107
108 private
108 private
109
109
110 # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
110 # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
111 # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
111 # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
112 def hard_break( text )
112 def hard_break( text )
113 text.gsub!( /(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
113 text.gsub!( /(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
114 end
114 end
115
115
116 alias :smooth_offtags_without_code_highlighting :smooth_offtags
116 alias :smooth_offtags_without_code_highlighting :smooth_offtags
117 # Patch to add code highlighting support to RedCloth
117 # Patch to add code highlighting support to RedCloth
118 def smooth_offtags( text )
118 def smooth_offtags( text )
119 unless @pre_list.empty?
119 unless @pre_list.empty?
120 ## replace <pre> content
120 ## replace <pre> content
121 text.gsub!(/<redpre#(\d+)>/) do
121 text.gsub!(/<redpre#(\d+)>/) do
122 content = @pre_list[$1.to_i]
122 content = @pre_list[$1.to_i]
123 if content.match(/<code\s+class="(\w+)">\s?(.+)/m)
123 if content.match(/<code\s+class="(\w+)">\s?(.+)/m)
124 content = "<code class=\"#{$1} syntaxhl\">" +
124 content = "<code class=\"#{$1} syntaxhl\">" +
125 Redmine::SyntaxHighlighting.highlight_by_language($2, $1)
125 Redmine::SyntaxHighlighting.highlight_by_language($2, $1)
126 end
126 end
127 content
127 content
128 end
128 end
129 end
129 end
130 end
130 end
131 end
131 end
132 end
132 end
133 end
133 end
134 end
134 end
@@ -1,417 +1,442
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../../../test_helper', __FILE__)
18 require File.expand_path('../../../../../test_helper', __FILE__)
19 require 'digest/md5'
19 require 'digest/md5'
20
20
21 class Redmine::WikiFormatting::TextileFormatterTest < ActionView::TestCase
21 class Redmine::WikiFormatting::TextileFormatterTest < ActionView::TestCase
22
22
23 def setup
23 def setup
24 @formatter = Redmine::WikiFormatting::Textile::Formatter
24 @formatter = Redmine::WikiFormatting::Textile::Formatter
25 end
25 end
26
26
27 MODIFIERS = {
27 MODIFIERS = {
28 "*" => 'strong', # bold
28 "*" => 'strong', # bold
29 "_" => 'em', # italic
29 "_" => 'em', # italic
30 "+" => 'ins', # underline
30 "+" => 'ins', # underline
31 "-" => 'del', # deleted
31 "-" => 'del', # deleted
32 "^" => 'sup', # superscript
32 "^" => 'sup', # superscript
33 "~" => 'sub' # subscript
33 "~" => 'sub' # subscript
34 }
34 }
35
35
36 def test_modifiers
36 def test_modifiers
37 assert_html_output(
37 assert_html_output(
38 '*bold*' => '<strong>bold</strong>',
38 '*bold*' => '<strong>bold</strong>',
39 'before *bold*' => 'before <strong>bold</strong>',
39 'before *bold*' => 'before <strong>bold</strong>',
40 '*bold* after' => '<strong>bold</strong> after',
40 '*bold* after' => '<strong>bold</strong> after',
41 '*two words*' => '<strong>two words</strong>',
41 '*two words*' => '<strong>two words</strong>',
42 '*two*words*' => '<strong>two*words</strong>',
42 '*two*words*' => '<strong>two*words</strong>',
43 '*two * words*' => '<strong>two * words</strong>',
43 '*two * words*' => '<strong>two * words</strong>',
44 '*two* *words*' => '<strong>two</strong> <strong>words</strong>',
44 '*two* *words*' => '<strong>two</strong> <strong>words</strong>',
45 '*(two)* *(words)*' => '<strong>(two)</strong> <strong>(words)</strong>',
45 '*(two)* *(words)*' => '<strong>(two)</strong> <strong>(words)</strong>',
46 # with class
46 # with class
47 '*(foo)two words*' => '<strong class="foo">two words</strong>'
47 '*(foo)two words*' => '<strong class="foo">two words</strong>'
48 )
48 )
49 end
49 end
50
50
51 def test_modifiers_combination
51 def test_modifiers_combination
52 MODIFIERS.each do |m1, tag1|
52 MODIFIERS.each do |m1, tag1|
53 MODIFIERS.each do |m2, tag2|
53 MODIFIERS.each do |m2, tag2|
54 next if m1 == m2
54 next if m1 == m2
55 text = "#{m2}#{m1}Phrase modifiers#{m1}#{m2}"
55 text = "#{m2}#{m1}Phrase modifiers#{m1}#{m2}"
56 html = "<#{tag2}><#{tag1}>Phrase modifiers</#{tag1}></#{tag2}>"
56 html = "<#{tag2}><#{tag1}>Phrase modifiers</#{tag1}></#{tag2}>"
57 assert_html_output text => html
57 assert_html_output text => html
58 end
58 end
59 end
59 end
60 end
60 end
61
61
62 def test_styles
62 def test_styles
63 # single style
63 # single style
64 assert_html_output({
64 assert_html_output({
65 'p{color:red}. text' => '<p style="color:red;">text</p>',
65 'p{color:red}. text' => '<p style="color:red;">text</p>',
66 '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>',
67 'p{color: red}. text' => '<p style="color: red;">text</p>',
68 'p{color:#f00}. text' => '<p style="color:#f00;">text</p>',
68 'p{color:#f00}. text' => '<p style="color:#f00;">text</p>',
69 'p{color:#ff0000}. text' => '<p style="color:#ff0000;">text</p>',
69 'p{color:#ff0000}. text' => '<p style="color:#ff0000;">text</p>',
70 'p{border:10px}. text' => '<p style="border:10px;">text</p>',
70 'p{border:10px}. text' => '<p style="border:10px;">text</p>',
71 'p{border:10}. text' => '<p style="border:10;">text</p>',
71 'p{border:10}. text' => '<p style="border:10;">text</p>',
72 '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>',
73 'p{border:10em}. text' => '<p style="border:10em;">text</p>',
74 'p{border:1.5em}. text' => '<p style="border:1.5em;">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>',
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>',
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>',
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>',
78 'p{border-bottom:1px}. text' => '<p style="border-bottom:1px;">text</p>',
79 }, false)
79 }, false)
80
80
81 # multiple styles
81 # multiple styles
82 assert_html_output({
82 assert_html_output({
83 'p{color:red; border-top:1px}. text' => '<p style="color:red;border-top:1px;">text</p>',
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>',
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>',
85 'p{color:red;border-top:1px}. text' => '<p style="color:red;border-top:1px;">text</p>',
86 }, false)
86 }, false)
87
87
88 # styles with multiple values
88 # styles with multiple values
89 assert_html_output({
89 assert_html_output({
90 'p{border:1px solid red;}. text' => '<p style="border:1px solid red;">text</p>',
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>',
91 'p{border-top-left-radius: 10px 5px;}. text' => '<p style="border-top-left-radius: 10px 5px;">text</p>',
92 }, false)
92 }, false)
93 end
93 end
94
94
95 def test_invalid_styles_should_be_filtered
95 def test_invalid_styles_should_be_filtered
96 assert_html_output({
96 assert_html_output({
97 'p{invalid}. text' => '<p>text</p>',
97 'p{invalid}. text' => '<p>text</p>',
98 'p{invalid:red}. text' => '<p>text</p>',
98 'p{invalid:red}. text' => '<p>text</p>',
99 'p{color:(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>',
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>',
101 'p{invalid:blue;color:red}. text' => '<p style="color:red;">text</p>',
102 'p{color:"}. text' => '<p>p{color:"}. text</p>',
102 'p{color:"}. text' => '<p>p{color:"}. text</p>',
103 }, false)
103 }, false)
104 end
104 end
105
105
106 def test_inline_code
106 def test_inline_code
107 assert_html_output(
107 assert_html_output(
108 'this is @some code@' => 'this is <code>some code</code>',
108 'this is @some code@' => 'this is <code>some code</code>',
109 '@<Location /redmine>@' => '<code>&lt;Location /redmine&gt;</code>'
109 '@<Location /redmine>@' => '<code>&lt;Location /redmine&gt;</code>'
110 )
110 )
111 end
111 end
112
112
113 def test_nested_lists
113 def test_nested_lists
114 raw = <<-RAW
114 raw = <<-RAW
115 # Item 1
115 # Item 1
116 # Item 2
116 # Item 2
117 ** Item 2a
117 ** Item 2a
118 ** Item 2b
118 ** Item 2b
119 # Item 3
119 # Item 3
120 ** Item 3a
120 ** Item 3a
121 RAW
121 RAW
122
122
123 expected = <<-EXPECTED
123 expected = <<-EXPECTED
124 <ol>
124 <ol>
125 <li>Item 1</li>
125 <li>Item 1</li>
126 <li>Item 2
126 <li>Item 2
127 <ul>
127 <ul>
128 <li>Item 2a</li>
128 <li>Item 2a</li>
129 <li>Item 2b</li>
129 <li>Item 2b</li>
130 </ul>
130 </ul>
131 </li>
131 </li>
132 <li>Item 3
132 <li>Item 3
133 <ul>
133 <ul>
134 <li>Item 3a</li>
134 <li>Item 3a</li>
135 </ul>
135 </ul>
136 </li>
136 </li>
137 </ol>
137 </ol>
138 EXPECTED
138 EXPECTED
139
139
140 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
140 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
141 end
141 end
142
142
143 def test_escaping
143 def test_escaping
144 assert_html_output(
144 assert_html_output(
145 'this is a <script>' => 'this is a &lt;script&gt;'
145 'this is a <script>' => 'this is a &lt;script&gt;'
146 )
146 )
147 end
147 end
148
148
149 def test_use_of_backslashes_followed_by_numbers_in_headers
149 def test_use_of_backslashes_followed_by_numbers_in_headers
150 assert_html_output({
150 assert_html_output({
151 'h1. 2009\02\09' => '<h1>2009\02\09</h1>'
151 'h1. 2009\02\09' => '<h1>2009\02\09</h1>'
152 }, false)
152 }, false)
153 end
153 end
154
154
155 def test_double_dashes_should_not_strikethrough
155 def test_double_dashes_should_not_strikethrough
156 assert_html_output(
156 assert_html_output(
157 'double -- dashes -- test' => 'double -- dashes -- test',
157 'double -- dashes -- test' => 'double -- dashes -- test',
158 'double -- *dashes* -- test' => 'double -- <strong>dashes</strong> -- test'
158 'double -- *dashes* -- test' => 'double -- <strong>dashes</strong> -- test'
159 )
159 )
160 end
160 end
161
161
162 def test_acronyms
162 def test_acronyms
163 assert_html_output(
163 assert_html_output(
164 'this is an acronym: GPL(General Public License)' => 'this is an acronym: <acronym title="General Public License">GPL</acronym>',
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',
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>'
166 'GPL(This is a double-quoted "title")' => '<acronym title="This is a double-quoted &quot;title&quot;">GPL</acronym>'
167 )
167 )
168 end
168 end
169
169
170 def test_blockquote
170 def test_blockquote
171 # orig raw text
171 # orig raw text
172 raw = <<-RAW
172 raw = <<-RAW
173 John said:
173 John said:
174 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
174 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
175 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
175 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
176 > * Donec odio lorem,
176 > * Donec odio lorem,
177 > * sagittis ac,
177 > * sagittis ac,
178 > * malesuada in,
178 > * malesuada in,
179 > * adipiscing eu, dolor.
179 > * adipiscing eu, dolor.
180 >
180 >
181 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
181 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
182 > Proin a tellus. Nam vel neque.
182 > Proin a tellus. Nam vel neque.
183
183
184 He's right.
184 He's right.
185 RAW
185 RAW
186
186
187 # expected html
187 # expected html
188 expected = <<-EXPECTED
188 expected = <<-EXPECTED
189 <p>John said:</p>
189 <p>John said:</p>
190 <blockquote>
190 <blockquote>
191 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.<br />
191 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.<br />
192 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
192 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
193 <ul>
193 <ul>
194 <li>Donec odio lorem,</li>
194 <li>Donec odio lorem,</li>
195 <li>sagittis ac,</li>
195 <li>sagittis ac,</li>
196 <li>malesuada in,</li>
196 <li>malesuada in,</li>
197 <li>adipiscing eu, dolor.</li>
197 <li>adipiscing eu, dolor.</li>
198 </ul>
198 </ul>
199 <blockquote>
199 <blockquote>
200 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
200 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
201 </blockquote>
201 </blockquote>
202 <p>Proin a tellus. Nam vel neque.</p>
202 <p>Proin a tellus. Nam vel neque.</p>
203 </blockquote>
203 </blockquote>
204 <p>He's right.</p>
204 <p>He's right.</p>
205 EXPECTED
205 EXPECTED
206
206
207 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
207 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
208 end
208 end
209
209
210 def test_table
210 def test_table
211 raw = <<-RAW
211 raw = <<-RAW
212 This is a table with empty cells:
212 This is a table with empty cells:
213
213
214 |cell11|cell12||
214 |cell11|cell12||
215 |cell21||cell23|
215 |cell21||cell23|
216 |cell31|cell32|cell33|
216 |cell31|cell32|cell33|
217 RAW
217 RAW
218
218
219 expected = <<-EXPECTED
219 expected = <<-EXPECTED
220 <p>This is a table with empty cells:</p>
220 <p>This is a table with empty cells:</p>
221
221
222 <table>
222 <table>
223 <tr><td>cell11</td><td>cell12</td><td></td></tr>
223 <tr><td>cell11</td><td>cell12</td><td></td></tr>
224 <tr><td>cell21</td><td></td><td>cell23</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>
225 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
226 </table>
226 </table>
227 EXPECTED
227 EXPECTED
228
228
229 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
229 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
230 end
230 end
231
231
232 def test_table_with_line_breaks
232 def test_table_with_line_breaks
233 raw = <<-RAW
233 raw = <<-RAW
234 This is a table with line breaks:
234 This is a table with line breaks:
235
235
236 |cell11
236 |cell11
237 continued|cell12||
237 continued|cell12||
238 |-cell21-||cell23
238 |-cell21-||cell23
239 cell23 line2
239 cell23 line2
240 cell23 *line3*|
240 cell23 *line3*|
241 |cell31|cell32
241 |cell31|cell32
242 cell32 line2|cell33|
242 cell32 line2|cell33|
243
243
244 RAW
244 RAW
245
245
246 expected = <<-EXPECTED
246 expected = <<-EXPECTED
247 <p>This is a table with line breaks:</p>
247 <p>This is a table with line breaks:</p>
248
248
249 <table>
249 <table>
250 <tr>
250 <tr>
251 <td>cell11<br />continued</td>
251 <td>cell11<br />continued</td>
252 <td>cell12</td>
252 <td>cell12</td>
253 <td></td>
253 <td></td>
254 </tr>
254 </tr>
255 <tr>
255 <tr>
256 <td><del>cell21</del></td>
256 <td><del>cell21</del></td>
257 <td></td>
257 <td></td>
258 <td>cell23<br/>cell23 line2<br/>cell23 <strong>line3</strong></td>
258 <td>cell23<br/>cell23 line2<br/>cell23 <strong>line3</strong></td>
259 </tr>
259 </tr>
260 <tr>
260 <tr>
261 <td>cell31</td>
261 <td>cell31</td>
262 <td>cell32<br/>cell32 line2</td>
262 <td>cell32<br/>cell32 line2</td>
263 <td>cell33</td>
263 <td>cell33</td>
264 </tr>
264 </tr>
265 </table>
265 </table>
266 EXPECTED
266 EXPECTED
267
267
268 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
268 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
269 end
269 end
270
270
271 def test_textile_should_not_mangle_brackets
271 def test_textile_should_not_mangle_brackets
272 assert_equal '<p>[msg1][msg2]</p>', to_html('[msg1][msg2]')
272 assert_equal '<p>[msg1][msg2]</p>', to_html('[msg1][msg2]')
273 end
273 end
274
274
275 def test_textile_should_escape_image_urls
275 def test_textile_should_escape_image_urls
276 # this is onclick="alert('XSS');" in encoded form
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;!'
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>'
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+}, '')
279 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
280 end
280 end
281
281
282
282
283 STR_WITHOUT_PRE = [
283 STR_WITHOUT_PRE = [
284 # 0
284 # 0
285 "h1. Title
285 "h1. Title
286
286
287 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.",
287 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.",
288 # 1
288 # 1
289 "h2. Heading 2
289 "h2. Heading 2
290
290
291 Maecenas sed elit sit amet mi accumsan vestibulum non nec velit. Proin porta tincidunt lorem, consequat rhoncus dolor fermentum in.
291 Maecenas sed elit sit amet mi accumsan vestibulum non nec velit. Proin porta tincidunt lorem, consequat rhoncus dolor fermentum in.
292
292
293 Cras ipsum felis, ultrices at porttitor vel, faucibus eu nunc.",
293 Cras ipsum felis, ultrices at porttitor vel, faucibus eu nunc.",
294 # 2
294 # 2
295 "h2. Heading 2
295 "h2. Heading 2
296
296
297 Morbi facilisis accumsan orci non pharetra.
297 Morbi facilisis accumsan orci non pharetra.
298
298
299 h3. Heading 3
299 h3. Heading 3
300
300
301 Nulla nunc nisi, egestas in ornare vel, posuere ac libero.",
301 Nulla nunc nisi, egestas in ornare vel, posuere ac libero.",
302 # 3
302 # 3
303 "h3. Heading 3
303 "h3. Heading 3
304
304
305 Praesent eget turpis nibh, a lacinia nulla.",
305 Praesent eget turpis nibh, a lacinia nulla.",
306 # 4
306 # 4
307 "h2. Heading 2
307 "h2. Heading 2
308
308
309 Ut rhoncus elementum adipiscing."]
309 Ut rhoncus elementum adipiscing."]
310
310
311 TEXT_WITHOUT_PRE = STR_WITHOUT_PRE.join("\n\n").freeze
311 TEXT_WITHOUT_PRE = STR_WITHOUT_PRE.join("\n\n").freeze
312
312
313 def test_get_section_should_return_the_requested_section_and_its_hash
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
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
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
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
317 assert_section_with_hash STR_WITHOUT_PRE[4], TEXT_WITHOUT_PRE, 6
318
318
319 assert_section_with_hash '', TEXT_WITHOUT_PRE, 0
319 assert_section_with_hash '', TEXT_WITHOUT_PRE, 0
320 assert_section_with_hash '', TEXT_WITHOUT_PRE, 10
320 assert_section_with_hash '', TEXT_WITHOUT_PRE, 10
321 end
321 end
322
322
323 def test_update_section_should_update_the_requested_section
323 def test_update_section_should_update_the_requested_section
324 replacement = "New text"
324 replacement = "New text"
325
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)
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)
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)
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)
329 assert_equal [STR_WITHOUT_PRE[0..3], replacement].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(6, replacement)
330
330
331 assert_equal TEXT_WITHOUT_PRE, @formatter.new(TEXT_WITHOUT_PRE).update_section(0, replacement)
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)
332 assert_equal TEXT_WITHOUT_PRE, @formatter.new(TEXT_WITHOUT_PRE).update_section(10, replacement)
333 end
333 end
334
334
335 def test_update_section_with_hash_should_update_the_requested_section
335 def test_update_section_with_hash_should_update_the_requested_section
336 replacement = "New text"
336 replacement = "New text"
337
337
338 assert_equal [STR_WITHOUT_PRE[0], replacement, STR_WITHOUT_PRE[2..4]].flatten.join("\n\n"),
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]))
339 @formatter.new(TEXT_WITHOUT_PRE).update_section(2, replacement, Digest::MD5.hexdigest(STR_WITHOUT_PRE[1]))
340 end
340 end
341
341
342 def test_update_section_with_wrong_hash_should_raise_an_error
342 def test_update_section_with_wrong_hash_should_raise_an_error
343 assert_raise Redmine::WikiFormatting::StaleSectionError do
343 assert_raise Redmine::WikiFormatting::StaleSectionError do
344 @formatter.new(TEXT_WITHOUT_PRE).update_section(2, "New text", Digest::MD5.hexdigest("Old text"))
344 @formatter.new(TEXT_WITHOUT_PRE).update_section(2, "New text", Digest::MD5.hexdigest("Old text"))
345 end
345 end
346 end
346 end
347
347
348 STR_WITH_PRE = [
348 STR_WITH_PRE = [
349 # 0
349 # 0
350 "h1. Title
350 "h1. Title
351
351
352 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.",
352 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.",
353 # 1
353 # 1
354 "h2. Heading 2
354 "h2. Heading 2
355
355
356 <pre><code class=\"ruby\">
356 <pre><code class=\"ruby\">
357 def foo
357 def foo
358 end
358 end
359 </code></pre>
359 </code></pre>
360
360
361 <pre><code><pre><code class=\"ruby\">
361 <pre><code><pre><code class=\"ruby\">
362 Place your code here.
362 Place your code here.
363 </code></pre>
363 </code></pre>
364 </code></pre>
364 </code></pre>
365
365
366 Morbi facilisis accumsan orci non pharetra.
366 Morbi facilisis accumsan orci non pharetra.
367
367
368 <pre>
368 <pre>
369 Pre Content:
369 Pre Content:
370
370
371 h2. Inside pre
371 h2. Inside pre
372
372
373 <tag> inside pre block
373 <tag> inside pre block
374
374
375 Morbi facilisis accumsan orci non pharetra.
375 Morbi facilisis accumsan orci non pharetra.
376 </pre>",
376 </pre>",
377 # 2
377 # 2
378 "h3. Heading 3
378 "h3. Heading 3
379
379
380 Nulla nunc nisi, egestas in ornare vel, posuere ac libero."]
380 Nulla nunc nisi, egestas in ornare vel, posuere ac libero."]
381
381
382 def test_get_section_should_ignore_pre_content
382 def test_get_section_should_ignore_pre_content
383 text = STR_WITH_PRE.join("\n\n")
383 text = STR_WITH_PRE.join("\n\n")
384
384
385 assert_section_with_hash STR_WITH_PRE[1..2].join("\n\n"), text, 2
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
386 assert_section_with_hash STR_WITH_PRE[2], text, 3
387 end
387 end
388
388
389 def test_update_section_should_not_escape_pre_content_outside_section
389 def test_update_section_should_not_escape_pre_content_outside_section
390 text = STR_WITH_PRE.join("\n\n")
390 text = STR_WITH_PRE.join("\n\n")
391 replacement = "New text"
391 replacement = "New text"
392
392
393 assert_equal [STR_WITH_PRE[0..1], "New text"].flatten.join("\n\n"),
393 assert_equal [STR_WITH_PRE[0..1], "New text"].flatten.join("\n\n"),
394 @formatter.new(text).update_section(3, replacement)
394 @formatter.new(text).update_section(3, replacement)
395 end
395 end
396
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
397 private
422 private
398
423
399 def assert_html_output(to_test, expect_paragraph = true)
424 def assert_html_output(to_test, expect_paragraph = true)
400 to_test.each do |text, expected|
425 to_test.each do |text, expected|
401 assert_equal(( expect_paragraph ? "<p>#{expected}</p>" : expected ), @formatter.new(text).to_html, "Formatting the following text failed:\n===\n#{text}\n===\n")
426 assert_equal(( expect_paragraph ? "<p>#{expected}</p>" : expected ), @formatter.new(text).to_html, "Formatting the following text failed:\n===\n#{text}\n===\n")
402 end
427 end
403 end
428 end
404
429
405 def to_html(text)
430 def to_html(text)
406 @formatter.new(text).to_html
431 @formatter.new(text).to_html
407 end
432 end
408
433
409 def assert_section_with_hash(expected, text, index)
434 def assert_section_with_hash(expected, text, index)
410 result = @formatter.new(text).get_section(index)
435 result = @formatter.new(text).get_section(index)
411
436
412 assert_kind_of Array, result
437 assert_kind_of Array, result
413 assert_equal 2, result.size
438 assert_equal 2, result.size
414 assert_equal expected, result.first, "section content did not match"
439 assert_equal expected, result.first, "section content did not match"
415 assert_equal Digest::MD5.hexdigest(expected), result.last, "section hash did not match"
440 assert_equal Digest::MD5.hexdigest(expected), result.last, "section hash did not match"
416 end
441 end
417 end
442 end
General Comments 0
You need to be logged in to leave comments. Login now